class Pod::Resolver

The resolver is responsible of generating a list of specifications grouped by target for a given Podfile.

Attributes

cached_sets[R]

@return [Hash<String => Set>] A cache that keeps tracks of the sets

loaded by the resolution process.

@note Sets store the resolved dependencies and return the highest

available specification found in the sources. This is done
globally and not per target definition because there can be just
one Pod installation, so different version of the same Pods for
target definitions are not allowed.
locked_dependencies[R]

@return [Array<Dependency>] the list of dependencies locked to a specific

version.
podfile[R]

@return [Podfile] the Podfile used by the resolver.

sandbox[R]

@return [Sandbox] the Sandbox used by the resolver to find external

dependencies.
sources[R]

@return [Array<Source>] The list of the sources which will be used for

the resolution.
sources_manager[R]

@return [Source::Manager] the manager to use for dependency resolution

specs_updated[R]

@return [Boolean] Whether the resolver has sources repositories up-to-date.

specs_updated?[R]

@return [Boolean] Whether the resolver has sources repositories up-to-date.

Public Class Methods

new(sandbox, podfile, locked_dependencies, sources, specs_updated, podfile_dependency_cache: Installer::Analyzer::PodfileDependencyCache.from_podfile(podfile), sources_manager: Config.instance.sources_manager) click to toggle source

Init a new Resolver

@param [Sandbox] sandbox @see sandbox @param [Podfile] podfile @see podfile @param [Array<Dependency>] locked_dependencies @see locked_dependencies @param [Array<Source>, Source] sources @see sources @param [Boolean] specs_updated @see specs_updated @param [PodfileDependencyCache] podfile_dependency_cache the podfile dependency cache to use

within this Resolver.
# File lib/cocoapods/resolver.rb, line 56
def initialize(sandbox, podfile, locked_dependencies, sources, specs_updated,
               podfile_dependency_cache: Installer::Analyzer::PodfileDependencyCache.from_podfile(podfile),
               sources_manager: Config.instance.sources_manager)
  @sandbox = sandbox
  @podfile = podfile
  @locked_dependencies = locked_dependencies
  @sources = Array(sources)
  @specs_updated = specs_updated
  @podfile_dependency_cache = podfile_dependency_cache
  @sources_manager = sources_manager
  @platforms_by_dependency = Hash.new { |h, k| h[k] = [] }

  @cached_sets = {}
  @podfile_requirements_by_root_name = @podfile_dependency_cache.podfile_dependencies.group_by(&:root_name).each_value { |a| a.map!(&:requirement).freeze }.freeze
  @search = {}
  @validated_platforms = Set.new
end

Public Instance Methods

after_resolution() click to toggle source

Called after resolution ends.

Completely silence this, as we show nothing.

@return [Void]

# File lib/cocoapods/resolver.rb, line 310
def after_resolution
end
before_resolution() click to toggle source

Called before resolution starts.

Completely silence this, as we show nothing.

@return [Void]

# File lib/cocoapods/resolver.rb, line 301
def before_resolution
end
dependencies_for(specification) click to toggle source

Returns the dependencies of ‘specification`.

@return [Array<Specification>] all dependencies of ‘specification`.

@param [Specification] specification the specification whose own

dependencies are being asked for.
# File lib/cocoapods/resolver.rb, line 176
def dependencies_for(specification)
  root_name = Specification.root_name(specification.name)
  specification.all_dependencies.map do |dependency|
    if dependency.root_name == root_name
      dependency.dup.tap { |d| d.specific_version = specification.version }
    else
      dependency
    end
  end
end
indicate_progress() click to toggle source

Called during resolution to indicate progress.

Completely silence this, as we show nothing.

@return [Void]

# File lib/cocoapods/resolver.rb, line 319
def indicate_progress
end
name_for(dependency) click to toggle source

Returns the name for the given ‘dependency`.

@return [String] the name for the given ‘dependency`.

@param [Dependency] dependency the dependency whose name is being

queried.
# File lib/cocoapods/resolver.rb, line 194
def name_for(dependency)
  dependency.name
end
name_for_explicit_dependency_source() click to toggle source

@return [String] the user-facing name for a {Podfile}.

# File lib/cocoapods/resolver.rb, line 200
def name_for_explicit_dependency_source
  'Podfile'
end
name_for_locking_dependency_source() click to toggle source

@return [String] the user-facing name for a {Lockfile}.

# File lib/cocoapods/resolver.rb, line 206
def name_for_locking_dependency_source
  'Podfile.lock'
end
output() click to toggle source

The UI object the resolver should use for displaying user-facing output.

@return [UserInterface] the normal CocoaPods UI object.

# File lib/cocoapods/resolver.rb, line 291
def output
  UI
end
requirement_satisfied_by?(requirement, activated, spec) click to toggle source

Determines whether the given ‘requirement` is satisfied by the given `spec`, in the context of the current `activated` dependency graph.

@return [Boolean] whether ‘requirement` is satisfied by `spec` in the

context of the current `activated` dependency graph.

@param [Dependency] requirement the dependency in question.

@param [Molinillo::DependencyGraph] activated the current dependency

graph in the resolution process.

@param [Specification] spec the specification in question.

# File lib/cocoapods/resolver.rb, line 223
def requirement_satisfied_by?(requirement, activated, spec)
  version = spec.version
  return false unless requirement.requirement.satisfied_by?(version)
  return false unless valid_possibility_version_for_root_name?(requirement, activated, spec)
  return false unless spec_is_platform_compatible?(activated, requirement, spec)
  true
end
resolve() click to toggle source

Identifies the specifications that should be installed.

@return [Hash{TargetDefinition => Array<ResolverSpecification>}] resolver_specs_by_target

the resolved specifications that need to be installed grouped by target
definition.
# File lib/cocoapods/resolver.rb, line 86
def resolve
  dependencies = @podfile_dependency_cache.target_definition_list.flat_map do |target|
    @podfile_dependency_cache.target_definition_dependencies(target).each do |dep|
      next unless target.platform
      @platforms_by_dependency[dep].push(target.platform)
    end
  end.uniq
  @platforms_by_dependency.each_value(&:uniq!)
  @activated = Molinillo::Resolver.new(self, self).resolve(dependencies, locked_dependencies)
  resolver_specs_by_target
rescue Molinillo::ResolverError => e
  handle_resolver_error(e)
end
resolver_specs_by_target() click to toggle source

@return [Hash{Podfile::TargetDefinition => Array<ResolverSpecification>}]

returns the resolved specifications grouped by target.

@note The returned specifications can be subspecs.

# File lib/cocoapods/resolver.rb, line 105
def resolver_specs_by_target
  @resolver_specs_by_target ||= {}.tap do |resolver_specs_by_target|
    @podfile_dependency_cache.target_definition_list.each do |target|
      next if target.abstract? && !target.platform

      # can't use vertex.root? since that considers _all_ targets
      explicit_dependencies = @podfile_dependency_cache.target_definition_dependencies(target).map(&:name).to_set

      used_by_aggregate_target_by_spec_name = {}
      used_vertices_by_spec_name = {}

      # it's safe to make a single pass here since we iterate in topological order,
      # so all of the predecessors have been visited before we get to a node.
      # #tsort returns no-children vertices first, and we want them last (i.e. we want no-parent vertices first)
      @activated.tsort.reverse_each do |vertex|
        spec_name = vertex.name
        explicitly_included = explicit_dependencies.include?(spec_name)
        if explicitly_included || vertex.incoming_edges.any? { |edge| used_vertices_by_spec_name.key?(edge.origin.name) && edge_is_valid_for_target_platform?(edge, target.platform) }
          validate_platform(vertex.payload, target)
          used_vertices_by_spec_name[spec_name] = vertex
          used_by_aggregate_target_by_spec_name[spec_name] = vertex.payload.library_specification? &&
            (explicitly_included || vertex.predecessors.any? { |predecessor| used_by_aggregate_target_by_spec_name.fetch(predecessor.name, false) })
        end
      end

      resolver_specs_by_target[target] = used_vertices_by_spec_name.each_value.
        map do |vertex|
          payload = vertex.payload
          non_library = !used_by_aggregate_target_by_spec_name.fetch(vertex.name)
          spec_source = payload.respond_to?(:spec_source) && payload.spec_source
          ResolverSpecification.new(payload, non_library, spec_source)
        end.
        sort_by(&:name)
    end
  end
end
search_for(dependency) click to toggle source

Returns (and caches) the specification that satisfy the given dependency.

@return [Array<Specification>] the specifications that satisfy the given

`dependency`.

@param [Dependency] dependency the dependency that is being searched for.

# File lib/cocoapods/resolver.rb, line 157
def search_for(dependency)
  @search[dependency] ||= begin
    additional_requirements = if locked_requirement = requirement_for_locked_pod_named(dependency.name)
                                [locked_requirement]
                              else
                                Array(@podfile_requirements_by_root_name[dependency.root_name])
                              end

    specifications_for_dependency(dependency, additional_requirements).freeze
  end
end
sort_dependencies(dependencies, activated, conflicts) click to toggle source

Sort dependencies so that the ones that are easiest to resolve are first. Easiest to resolve is (usually) defined by:

1) Is this dependency already activated?
2) How relaxed are the requirements?
3) Are there any conflicts for this dependency?
4) How many possibilities are there to satisfy this dependency?

@return [Array<Dependency>] the sorted dependencies.

@param [Array<Dependency>] dependencies the unsorted dependencies.

@param [Molinillo::DependencyGraph] activated the dependency graph of

currently activated specs.

@param [{String => Array<Conflict>}] conflicts the current conflicts.

# File lib/cocoapods/resolver.rb, line 266
def sort_dependencies(dependencies, activated, conflicts)
  dependencies.sort_by! do |dependency|
    name = name_for(dependency)
    [
      activated.vertex_named(name).payload ? 0 : 1,
      dependency.external_source ? 0 : 1,
      dependency.prerelease? ? 0 : 1,
      conflicts[name] ? 0 : 1,
      search_for(dependency).count,
    ]
  end
end

Private Instance Methods

aggregate_for_dependency(dependency) click to toggle source

@return [Source::Aggregate] The aggregate of the {#sources}.

# File lib/cocoapods/resolver.rb, line 421
def aggregate_for_dependency(dependency)
  if dependency && dependency.podspec_repo
    sources_manager.aggregate_for_dependency(dependency)
  elsif (locked_vertex = @locked_dependencies.vertex_named(dependency.name)) && (locked_dependency = locked_vertex.payload) && locked_dependency.podspec_repo
    sources_manager.aggregate_for_dependency(locked_dependency)
  else
    @aggregate ||= Source::Aggregate.new(sources)
  end
end
create_set_from_sources(dependency) click to toggle source

@return [Set] Creates a set for the Pod of the given dependency from the

sources. The set will contain all versions from all sources that
include the Pod.

@param [Dependency] dependency

The dependency for which the set is needed.
# File lib/cocoapods/resolver.rb, line 415
def create_set_from_sources(dependency)
  aggregate_for_dependency(dependency).search(dependency)
end
edge_is_valid_for_target_platform?(edge, target_platform) click to toggle source

Whether the given ‘edge` should be followed to find dependencies for the given `target_platform`.

@return [Boolean]

# File lib/cocoapods/resolver.rb, line 580
def edge_is_valid_for_target_platform?(edge, target_platform)
  @edge_validity ||= Hash.new do |hash, edge_and_platform|
    e = edge_and_platform.edge
    platform = edge_and_platform.target_platform
    requirement_name = e.requirement.name

    hash[edge_and_platform] = e.origin.payload.all_dependencies(platform).any? do |dep|
      dep.name == requirement_name
    end
  end

  @edge_validity[EdgeAndPlatform.new(edge, target_platform)]
end
find_cached_set(dependency) click to toggle source

@return [Set] Loads or returns a previously initialized set for the Pod

of the given dependency.

@param [Dependency] dependency

The dependency for which the set is needed.

@return [Set] the cached set for a given dependency.

# File lib/cocoapods/resolver.rb, line 374
def find_cached_set(dependency)
  name = dependency.root_name
  cached_sets[name] ||= begin
    if dependency.external_source
      spec = sandbox.specification(name)
      unless spec
        raise StandardError, '[Bug] Unable to find the specification ' \
          "for `#{dependency}`."
      end
      set = Specification::Set::External.new(spec)
    else
      set = create_set_from_sources(dependency)
    end

    unless set
      raise Molinillo::NoSuchDependencyError.new(dependency) # rubocop:disable Style/RaiseArgs
    end

    set
  end
end
handle_resolver_error(error) click to toggle source

Handles errors that come out of a {Molinillo::Resolver}.

@return [void]

@param [Molinillo::ResolverError] error

# File lib/cocoapods/resolver.rb, line 453
    def handle_resolver_error(error)
      message = error.message
      type = Informative
      unless specs_updated?
        specs_update_message = "\n * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`."
      end
      case error
      when Molinillo::VersionConflict
        message = error.message_with_trees(
          :solver_name => 'CocoaPods',
          :possibility_type => 'pod',
          :version_for_spec => lambda(&:version),
          :additional_message_for_conflict => lambda do |o, name, conflict|
            local_pod_parent = conflict.requirement_trees.flatten.reverse.find(&:local?)
            if local_pod_parent && !specifications_for_dependency(conflict.requirement).empty? && !conflict.possibility && conflict.locked_requirement
              # Conflict was caused by a requirement from a local dependency.
              # Tell user to use `pod update`.
              o << "\n\nYou have either:#{specs_update_message}" \
                   "\n * changed the constraints of dependency `#{name}` inside your development pod `#{local_pod_parent.name}`." \
                   "\n   You should run `pod update #{name}` to apply changes you've made."
            elsif !conflict.possibility && conflict.locked_requirement && conflict.locked_requirement.external_source && conflict.locked_requirement.external_source[:podspec] &&
                                           conflict.requirement && conflict.requirement.external_source && conflict.requirement.external_source[:podspec]
              # The internal version of the Podspec doesn't match the external definition of a podspec
              o << "\nIt seems like you've changed the version of the dependency `#{name}` " \
              "and it differs from the version stored in `Pods/Local Podspecs`.\nYou should run `pod update #{name} --no-repo-update` to apply " \
              'changes made locally.'
            elsif (conflict.possibility && conflict.possibility.version.prerelease?) &&
                (conflict.requirement && !(
                conflict.requirement.prerelease? ||
                conflict.requirement.external_source)
                )
              # Conflict was caused by not specifying an explicit version for the requirement #[name],
              # and there is no available stable version satisfying constraints for the requirement.
              o << "\nThere are only pre-release versions available satisfying the following requirements:\n"
              conflict.requirements.values.flatten.uniq.each do |r|
                unless search_for(r).empty?
                  o << "\n\t'#{name}', '#{r.requirement}'\n"
                end
              end
              o << "\nYou should explicitly specify the version in order to install a pre-release version"
            elsif !conflict.existing
              conflicts = conflict.requirements.values.flatten.uniq
              found_conflicted_specs = conflicts.reject { |c| search_for(c).empty? }
              if found_conflicted_specs.empty?
                # There are no existing specification inside any of the spec repos with given requirements.
                type = NoSpecFoundError
                dependencies = conflicts.count == 1 ? 'dependency' : 'dependencies'
                o << "\nNone of your spec sources contain a spec satisfying "\
                  "the #{dependencies}: `#{conflicts.join(', ')}`." \
                  "\n\nYou have either:#{specs_update_message}" \
                  "\n * mistyped the name or version." \
                  "\n * not added the source repo that hosts the Podspec to your Podfile."

              else
                o << "\nSpecs satisfying the `#{conflicts.join(', ')}` dependency were found, " \
                  'but they required a higher minimum deployment target.'
              end
            end
          end,
        )
      when Molinillo::NoSuchDependencyError
        message += <<-EOS


You have either:#{specs_update_message}
 * mistyped the name or version.
 * not added the source repo that hosts the Podspec to your Podfile.
        EOS
      end
      raise type.new(message).tap { |e| e.set_backtrace(error.backtrace) }
    end
requirement_for_locked_pod_named(name) click to toggle source

@return [Requirement, Nil]

The {Requirement} that locks the dependency with name `name` in
{#locked_dependencies}.
# File lib/cocoapods/resolver.rb, line 400
def requirement_for_locked_pod_named(name)
  if vertex = locked_dependencies.vertex_named(name)
    if dependency = vertex.payload
      dependency.requirement
    end
  end
end
spec_is_platform_compatible?(dependency_graph, dependency, spec) click to toggle source

Returns whether the given spec is platform-compatible with the dependency graph, taking into account the dependency that has required the spec.

@param [Molinillo::DependencyGraph] dependency_graph

@param [Dependency] dependency

@param [Specification] spec

@return [Boolean]

# File lib/cocoapods/resolver.rb, line 536
def spec_is_platform_compatible?(dependency_graph, dependency, spec)
  # This is safe since a pod will only be in locked dependencies if we're
  # using the same exact version
  return true if locked_dependencies.vertex_named(spec.name)

  vertex = dependency_graph.vertex_named(dependency.name)
  predecessors = vertex.recursive_predecessors.select(&:root?)
  predecessors << vertex if vertex.root?
  platforms_to_satisfy = predecessors.flat_map(&:explicit_requirements).flat_map { |r| @platforms_by_dependency[r] }.uniq

  available_platforms = spec.available_platforms

  platforms_to_satisfy.all? do |platform_to_satisfy|
    available_platforms.all? do |spec_platform|
      next true unless spec_platform.name == platform_to_satisfy.name
      # For non library specs all we care is to match by the platform name, not to satisfy the version.
      next true if spec.non_library_specification?
      platform_to_satisfy.supports?(spec_platform)
    end
  end
end
specifications_for_dependency(dependency, additional_requirements = []) click to toggle source

Returns available specifications which satisfy requirements of given dependency and additional requirements.

@param [Dependency] dependency

The dependency whose requirements will be satisfied.

@param [Array<Requirement>] additional_requirements

List of additional requirements which should also be satisfied.

@return [Array<Specification>] List of specifications satisfying given requirements.

# File lib/cocoapods/resolver.rb, line 356
def specifications_for_dependency(dependency, additional_requirements = [])
  requirement_list = dependency.requirement.as_list + additional_requirements.flat_map(&:as_list)
  requirement_list.uniq!
  requirement = Requirement.new(requirement_list)
  find_cached_set(dependency).
    all_specifications(warn_for_multiple_pod_sources, requirement).
    map { |s| s.subspec_by_name(dependency.name, false, true) }.
    compact
end
valid_possibility_version_for_root_name?(requirement, activated, spec) click to toggle source
# File lib/cocoapods/resolver.rb, line 231
def valid_possibility_version_for_root_name?(requirement, activated, spec)
  return true if prerelease_requirement = requirement.prerelease? || requirement.external_source || !spec.version.prerelease?

  activated.each do |vertex|
    next unless vertex.payload
    next unless Specification.root_name(vertex.name) == requirement.root_name

    prerelease_requirement ||= vertex.requirements.any? { |r| r.prerelease? || r.external_source }

    if vertex.payload.respond_to?(:version)
      return true if vertex.payload.version == spec.version
      break
    end
  end

  prerelease_requirement
end
validate_platform(spec, target) click to toggle source

Ensures that a specification is compatible with the platform of a target.

@raise If the specification is not supported by the target.

@return [void]

# File lib/cocoapods/resolver.rb, line 437
def validate_platform(spec, target)
  return unless target_platform = target.platform
  return unless @validated_platforms.add?([spec.object_id, target_platform])
  unless spec.available_platforms.any? { |p| target_platform.to_sym == p.to_sym }
    raise Informative, "The platform of the target `#{target.name}` "     \
      "(#{target.platform}) is not compatible with `#{spec}`, which does "  \
      "not support `#{target.platform.string_name}`."
  end
end
warn_for_multiple_pod_sources() click to toggle source

@return [Boolean] whether to emit a warning when a pod is found in multiple sources

# File lib/cocoapods/resolver.rb, line 596
def warn_for_multiple_pod_sources
  podfile.installation_options.warn_for_multiple_pod_sources
end