Class: Pod::Resolver

Inherits:
Object
  • Object
show all
Includes:
Molinillo::SpecificationProvider, Molinillo::UI
Defined in:
lib/cocoapods/resolver.rb

Overview

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

Resolution collapse

Instance Attribute Summary collapse

Resolution collapse

Private helpers collapse

Instance Method Summary collapse

Constructor Details

#initialize(sandbox, podfile, locked_dependencies, sources) ⇒ Resolver

Init a new Resolver

Parameters:

  • sandbox (Sandbox)

    @see sandbox

  • podfile (Podfile)

    @see podfile

  • locked_dependencies (Array<Dependency>)

    @see locked_dependencies

  • sources (Array<Source>, Source)

    @see sources



35
36
37
38
39
40
41
42
# File 'lib/cocoapods/resolver.rb', line 35

def initialize(sandbox, podfile, locked_dependencies, sources)
  @sandbox = sandbox
  @podfile = podfile
  @locked_dependencies = locked_dependencies
  @sources = Array(sources)
  @platforms_by_dependency = Hash.new { |h, k| h[k] = [] }
  @cached_sets = {}
end

Instance Attribute Details

#cached_setsHash<String => Set> (private)

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.

Returns A cache that keeps tracks of the sets loaded by the resolution process.

Returns:

  • (Hash<String => Set>)

    A cache that keeps tracks of the sets loaded by the resolution process.



268
269
270
# File 'lib/cocoapods/resolver.rb', line 268

def cached_sets
  @cached_sets
end

#locked_dependenciesArray<Dependency> (readonly)

Returns the list of dependencies locked to a specific version.

Returns:

  • (Array<Dependency>)

    the list of dependencies locked to a specific version.



21
22
23
# File 'lib/cocoapods/resolver.rb', line 21

def locked_dependencies
  @locked_dependencies
end

#podfilePodfile (readonly)

Returns the Podfile used by the resolver.

Returns:

  • (Podfile)

    the Podfile used by the resolver.



16
17
18
# File 'lib/cocoapods/resolver.rb', line 16

def podfile
  @podfile
end

#sandboxSandbox (readonly)

Returns the Sandbox used by the resolver to find external dependencies.

Returns:

  • (Sandbox)

    the Sandbox used by the resolver to find external dependencies.



12
13
14
# File 'lib/cocoapods/resolver.rb', line 12

def sandbox
  @sandbox
end

#sourcesArray<Source>

Returns The list of the sources which will be used for the resolution.

Returns:

  • (Array<Source>)

    The list of the sources which will be used for the resolution.



26
27
28
# File 'lib/cocoapods/resolver.rb', line 26

def sources
  @sources
end

Instance Method Details

#after_resolutionVoid

Called after resolution ends.

Completely silence this, as we show nothing.

Returns:

  • (Void)


241
242
# File 'lib/cocoapods/resolver.rb', line 241

def after_resolution
end

#aggregate_for_dependency(dependency) ⇒ Source::Aggregate (private)

Returns The aggregate of the #sources.

Returns:

  • (Source::Aggregate)

    The aggregate of the #sources.



351
352
353
354
355
356
357
# File 'lib/cocoapods/resolver.rb', line 351

def aggregate_for_dependency(dependency)
  if dependency && dependency.podspec_repo
    return Config.instance.sources_manager.aggregate_for_dependency(dependency)
  else
    @aggregate ||= Source::Aggregate.new(sources)
  end
end

#before_resolutionVoid

Called before resolution starts.

Completely silence this, as we show nothing.

Returns:

  • (Void)


232
233
# File 'lib/cocoapods/resolver.rb', line 232

def before_resolution
end

#create_set_from_sources(dependency) ⇒ Set (private)

Returns 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.

Parameters:

  • dependency (Dependency)

    The dependency for which the set is needed.

Returns:

  • (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.



345
346
347
# File 'lib/cocoapods/resolver.rb', line 345

def create_set_from_sources(dependency)
  aggregate_for_dependency(dependency).search(dependency)
end

#dependencies_for(specification) ⇒ Array<Specification>

Returns the dependencies of specification.

Parameters:

  • specification (Specification)

    the specification whose own dependencies are being asked for.

Returns:



119
120
121
122
123
124
125
126
127
# File 'lib/cocoapods/resolver.rb', line 119

def dependencies_for(specification)
  specification.all_dependencies.map do |dependency|
    if dependency.root_name == Specification.root_name(specification.name)
      dependency.dup.tap { |d| d.specific_version = specification.version }
    else
      dependency
    end
  end
end

#edge_is_valid_for_target?(edge, target) ⇒ Bool (private)

Whether the given edge should be followed to find dependencies for the given target.

Returns:

  • (Bool)


489
490
491
492
493
# File 'lib/cocoapods/resolver.rb', line 489

def edge_is_valid_for_target?(edge, target)
  dependencies_for_target_platform =
    edge.origin.payload.all_dependencies(target.platform).map(&:name)
  dependencies_for_target_platform.include?(edge.requirement.name)
end

#find_cached_set(dependency) ⇒ Set (private)

Parameters:

  • dependency (Dependency)

    The dependency for which the set is needed.

Returns:

  • (Set)

    Loads or returns a previously initialized set for the Pod of the given dependency.

  • (Set)

    the cached set for a given dependency.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/cocoapods/resolver.rb', line 305

def find_cached_set(dependency)
  name = dependency.root_name
  unless cached_sets[name]
    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
    cached_sets[name] = set
    unless set
      raise Molinillo::NoSuchDependencyError.new(dependency) # rubocop:disable Style/RaiseArgs
    end
  end
  cached_sets[name]
end

#handle_resolver_error(error) ⇒ void (private)

TODO:

The check for version conflicts coming from the Lockfile requiring a pre-release version can be deleted for version 1.0, as it is a migration step for Lockfiles coming from CocoaPods versions before 0.35.0.

This method returns an undefined value.

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

Parameters:

Raises:



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/cocoapods/resolver.rb', line 385

def handle_resolver_error(error)
  message = error.message.dup
  case error
  when Molinillo::VersionConflict
    error.conflicts.each do |name, conflict|
      local_pod_parent = conflict.requirement_trees.flatten.reverse.find(&:local?)
      lockfile_reqs = conflict.requirements[name_for_locking_dependency_source]

      if lockfile_reqs && lockfile_reqs.last && lockfile_reqs.last.prerelease? && !conflict.existing
        message = 'Due to the previous naïve CocoaPods resolver, ' \
          "you were using a pre-release version of `#{name}`, " \
          'without explicitly asking for a pre-release version, which now leads to a conflict. ' \
          'Please decide to either use that pre-release version by adding the ' \
          'version requirement to your Podfile ' \
          "(e.g. `pod '#{name}', '#{lockfile_reqs.map(&:requirement).join("', '")}'`) " \
          "or revert to a stable version by running `pod update #{name}`."
      elsif local_pod_parent && !specifications_for_dependency(conflict.requirement).empty?
        # Conflict was caused by a requirement from a local dependency.
        # Tell user to use `pod update`.
        message << "\n\nIt seems like you've changed the constraints of dependency `#{name}` " \
        "inside your development pod `#{local_pod_parent.name}`.\nYou should run `pod update #{name}` to apply " \
        "changes you've made."
      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.
        message = "There are only pre-release versions available satisfying the following requirements:\n"
        conflict.requirements.values.flatten.each do |r|
          unless search_for(r).empty?
            message << "\n\t'#{name}', '#{r.requirement}'\n"
          end
        end
        message << "\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.
          dependencies = conflicts.count == 1 ? 'dependency' : 'dependencies'
          message << "\n\nNone of your spec sources contain a spec satisfying "\
            "the #{dependencies}: `#{conflicts.join(', ')}`." \
            "\n\nYou have either:" \
            "\n * out-of-date source repos which you can update with `pod repo update`." \
            "\n * mistyped the name or version." \
            "\n * not added the source repo that hosts the Podspec to your Podfile." \
            "\n\nNote: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default."

        else
          message << "\n\nSpecs satisfying the `#{conflicts.join(', ')}` dependency were found, " \
            'but they required a higher minimum deployment target.'
        end
      end
    end
  end
  raise Informative, message
end

#indicate_progressVoid

Called during resolution to indicate progress.

Completely silence this, as we show nothing.

Returns:

  • (Void)


250
251
# File 'lib/cocoapods/resolver.rb', line 250

def indicate_progress
end

#name_for(dependency) ⇒ String

Returns the name for the given dependency.

Parameters:

  • dependency (Dependency)

    the dependency whose name is being queried.

Returns:

  • (String)

    the name for the given dependency.



136
137
138
# File 'lib/cocoapods/resolver.rb', line 136

def name_for(dependency)
  dependency.name
end

#name_for_explicit_dependency_sourceString

Returns the user-facing name for a Podfile.

Returns:

  • (String)

    the user-facing name for a Podfile.



142
143
144
# File 'lib/cocoapods/resolver.rb', line 142

def name_for_explicit_dependency_source
  'Podfile'
end

#name_for_locking_dependency_sourceString

Returns the user-facing name for a Lockfile.

Returns:

  • (String)

    the user-facing name for a Lockfile.



148
149
150
# File 'lib/cocoapods/resolver.rb', line 148

def name_for_locking_dependency_source
  'Podfile.lock'
end

#outputUserInterface

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

Returns:



222
223
224
# File 'lib/cocoapods/resolver.rb', line 222

def output
  UI
end

#requirement_for_locked_pod_named(name) ⇒ Requirement, Nil (private)

Returns The Requirement that locks the dependency with name name in #locked_dependencies.

Returns:

  • (Requirement, Nil)

    The Requirement that locks the dependency with name name in #locked_dependencies.



330
331
332
333
334
335
336
# File 'lib/cocoapods/resolver.rb', line 330

def requirement_for_locked_pod_named(name)
  if vertex = locked_dependencies.vertex_named(name)
    if dependency = vertex.payload
      dependency.requirement
    end
  end
end

#requirement_satisfied_by?(requirement, activated, spec) ⇒ Boolean

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

Parameters:

  • requirement (Dependency)

    the dependency in question.

  • activated (Molinillo::DependencyGraph)

    the current dependency graph in the resolution process.

  • spec (Specification)

    the specification in question.

Returns:

  • (Boolean)

    whether requirement is satisfied by spec in the context of the current activated dependency graph.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/cocoapods/resolver.rb', line 165

def requirement_satisfied_by?(requirement, activated, spec)
  existing_vertices = activated.vertices.values.select do |v|
    Specification.root_name(v.name) == requirement.root_name
  end
  existing = existing_vertices.map(&:payload).compact.first
  requirement_satisfied =
    if existing
      existing.version == spec.version && requirement.requirement.satisfied_by?(spec.version)
    else
      requirement.requirement.satisfied_by? spec.version
    end
  requirement_satisfied && !(
    spec.version.prerelease? &&
    existing_vertices.flat_map(&:requirements).none? { |r| r.prerelease? || r.external_source }
  ) && spec_is_platform_compatible?(activated, requirement, spec)
end

#resolveHash{TargetDefinition => Array<Specification>}

Identifies the specifications that should be installed.

Returns:

  • (Hash{TargetDefinition => Array<Specification>})

    specs_by_target the specifications that need to be installed grouped by target definition.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/cocoapods/resolver.rb', line 56

def resolve
  dependencies = podfile.target_definition_list.flat_map do |target|
    target.dependencies.each do |dep|
      @platforms_by_dependency[dep].push(target.platform).uniq! if target.platform
    end
  end
  @activated = Molinillo::Resolver.new(self, self).resolve(dependencies, locked_dependencies)
  specs_by_target
rescue Molinillo::ResolverError => e
  handle_resolver_error(e)
end

#search_for(dependency) ⇒ Array<Specification>

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

Parameters:

  • dependency (Dependency)

    the dependency that is being searched for.

Returns:

  • (Array<Specification>)

    the specifications that satisfy the given dependency.



104
105
106
107
108
109
110
# File 'lib/cocoapods/resolver.rb', line 104

def search_for(dependency)
  @search ||= {}
  @search[dependency] ||= begin
    specifications_for_dependency(dependency, [requirement_for_locked_pod_named(dependency.name)])
  end
  @search[dependency].dup
end

#sort_dependencies(dependencies, activated, conflicts) ⇒ Array<Dependency>

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?

Parameters:

  • dependencies (Array<Dependency>)

    the unsorted dependencies.

  • activated (Molinillo::DependencyGraph)

    the dependency graph of currently activated specs.

  • conflicts ({String => Array<Conflict>})

    the current conflicts.

Returns:

  • (Array<Dependency>)

    the sorted dependencies.



198
199
200
201
202
203
204
205
206
207
208
# File 'lib/cocoapods/resolver.rb', line 198

def sort_dependencies(dependencies, activated, conflicts)
  dependencies.sort_by do |dependency|
    name = name_for(dependency)
    [
      activated.vertex_named(name).payload ? 0 : 1,
      dependency.prerelease? ? 0 : 1,
      conflicts[name] ? 0 : 1,
      search_for(dependency).count,
    ]
  end
end

#spec_is_platform_compatible?(dependency_graph, dependency, spec) ⇒ Bool (private)

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

Parameters:

  • dependency_graph (Molinillo::DependencyGraph)
  • dependency (Dependency)
  • specification (Specification)

Returns:

  • (Bool)


455
456
457
458
459
460
461
462
463
464
465
# File 'lib/cocoapods/resolver.rb', line 455

def spec_is_platform_compatible?(dependency_graph, dependency, spec)
  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] }

  platforms_to_satisfy.all? do |platform_to_satisfy|
    spec.available_platforms.select { |spec_platform| spec_platform.name == platform_to_satisfy.name }.
      all? { |spec_platform| platform_to_satisfy.supports?(spec_platform) }
  end
end

#specifications_for_dependency(dependency, additional_requirements = []) ⇒ Array<Specification> (private)

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

Parameters:

  • dependency (Dependency)

    The dependency whose requirements will be satisfied.

  • additional_requirements (Array<Requirement>) (defaults to: [])

    List of additional requirements which should also be satisfied.

Returns:

  • (Array<Specification>)

    List of specifications satisfying given requirements.



287
288
289
290
291
292
293
294
295
# File 'lib/cocoapods/resolver.rb', line 287

def specifications_for_dependency(dependency, additional_requirements = [])
  requirement = Requirement.new(dependency.requirement.as_list + additional_requirements)
  find_cached_set(dependency).
    all_specifications.
    select { |s| requirement.satisfied_by? s.version }.
    map { |s| s.subspec_by_name(dependency.name, false) }.
    compact.
    reverse
end

#specs_by_targetHash{Podfile::TargetDefinition => Array<Specification>}

Note:

The returned specifications can be subspecs.

Returns the resolved specifications grouped by target.

Returns:

  • (Hash{Podfile::TargetDefinition => Array<Specification>})

    returns the resolved specifications grouped by target.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/cocoapods/resolver.rb', line 73

def specs_by_target
  @specs_by_target ||= {}.tap do |specs_by_target|
    podfile.target_definition_list.each do |target|
      specs = target.dependencies.map(&:name).flat_map do |name|
        node = @activated.vertex_named(name)
        valid_dependencies_for_target_from_node(target, node) << node
      end

      specs_by_target[target] = specs.
        map(&:payload).
        uniq.
        sort_by(&:name)
    end
  end
end

#valid_dependencies_for_target_from_node(target, node) ⇒ Array<Molinillo::DependencyGraph::Vertex> (private)

Returns the target-appropriate nodes that are successors of node, rejecting those that are scoped by target platform and have incompatible targets.

Returns:

  • (Array<Molinillo::DependencyGraph::Vertex>)

    An array of target-appropriate nodes whose payloads are dependencies for target.



475
476
477
478
479
480
481
482
# File 'lib/cocoapods/resolver.rb', line 475

def valid_dependencies_for_target_from_node(target, node)
  validate_platform(node.payload, target)
  dependency_nodes = node.outgoing_edges.select do |edge|
    edge_is_valid_for_target?(edge, target)
  end.map(&:destination)

  dependency_nodes + dependency_nodes.flat_map { |n| valid_dependencies_for_target_from_node(target, n) }
end

#validate_platform(spec, target) ⇒ void (private)

This method returns an undefined value.

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

Raises:

  • If the specification is not supported by the target.



365
366
367
368
369
370
371
372
# File 'lib/cocoapods/resolver.rb', line 365

def validate_platform(spec, target)
  return unless target_platform = 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.name}`."
  end
end