Commit 0a6e8a17 authored by Samuel E. Giddins's avatar Samuel E. Giddins

Merge pull request #2637 from CocoaPods/resolver

Molinillo
parents 28b95e88 d0d72ea7
...@@ -4,7 +4,13 @@ To install or update CocoaPods see this [guide](http://docs.cocoapods.org/guides ...@@ -4,7 +4,13 @@ To install or update CocoaPods see this [guide](http://docs.cocoapods.org/guides
## Master ## Master
##### Enhacements ##### Breaking
* Attempts to resolve circular dependencies will now raise an exception.
[Samuel Giddins](https://github.com/segiddins)
[Molinillo#6](https://github.com/CocoaPods/Molinillo/issues/6)
##### Enhancements
* The `pod push` has been removed as it has been deprecated in favour of `pod * The `pod push` has been removed as it has been deprecated in favour of `pod
repo push` in CocoaPods 0.33. repo push` in CocoaPods 0.33.
...@@ -15,6 +21,26 @@ To install or update CocoaPods see this [guide](http://docs.cocoapods.org/guides ...@@ -15,6 +21,26 @@ To install or update CocoaPods see this [guide](http://docs.cocoapods.org/guides
[Marius Rackwitz](https://github.com/mrackwitz) [Marius Rackwitz](https://github.com/mrackwitz)
[#2461](https://github.com/CocoaPods/CocoaPods/issues/2461) [#2461](https://github.com/CocoaPods/CocoaPods/issues/2461)
* The `Resolver` has been completely rewritten to use
[Molinillo](https://github.com/CocoaPods/Molinillo), an iterative dependency
resolution algorithm that automatically resolves version conflicts.
The order in which dependencies are declared in the `Podfile` no longer has
any effect on the resolution process.
[Samuel Giddins](https://github.com/segiddins)
[#978](https://github.com/CocoaPods/CocoaPods/issues/978)
[#2002](https://github.com/CocoaPods/CocoaPods/issues/2002)
* Implicit dependencies are now locked, so simply running `pod install` will not
cause them to be updated when they shouldn't be.
[Samuel Giddins](https://github.com/segiddins)
[#2318](https://github.com/CocoaPods/CocoaPods/issues/2318)
[#2506](https://github.com/CocoaPods/CocoaPods/issues/2506)
* Pre-release versions are only considered in the resolution process when there
are dependencies that explicitly reference pre-release requirements.
[Samuel Giddins](https://github.com/segiddins)
[#1489](https://github.com/CocoaPods/CocoaPods/issues/1489)
##### Bug Fixes ##### Bug Fixes
* Do not try to clone spec-repos in `/`. * Do not try to clone spec-repos in `/`.
......
...@@ -12,12 +12,13 @@ gemspec ...@@ -12,12 +12,13 @@ gemspec
group :development do group :development do
cp_gem 'claide', 'CLAide' cp_gem 'claide', 'CLAide'
cp_gem 'cocoapods-core', 'Core' cp_gem 'cocoapods-core', 'Core', 'resolver'
cp_gem 'cocoapods-downloader', 'cocoapods-downloader' cp_gem 'cocoapods-downloader', 'cocoapods-downloader'
cp_gem 'cocoapods-plugins', 'cocoapods-plugins' cp_gem 'cocoapods-plugins', 'cocoapods-plugins'
cp_gem 'cocoapods-trunk', 'cocoapods-trunk' cp_gem 'cocoapods-trunk', 'cocoapods-trunk'
cp_gem 'cocoapods-try', 'cocoapods-try' cp_gem 'cocoapods-try', 'cocoapods-try'
cp_gem 'xcodeproj', 'Xcodeproj' cp_gem 'xcodeproj', 'Xcodeproj'
cp_gem 'molinillo', 'Molinillo'
gem 'bacon' gem 'bacon'
gem 'mocha' gem 'mocha'
......
...@@ -7,8 +7,8 @@ GIT ...@@ -7,8 +7,8 @@ GIT
GIT GIT
remote: https://github.com/CocoaPods/Core.git remote: https://github.com/CocoaPods/Core.git
revision: 99748b5b9fa8fe6541218f1752c0379589c7a6ee revision: 802401562948abf9f4f417ace4cd8e9bb8b08859
branch: master branch: resolver
specs: specs:
cocoapods-core (0.34.4) cocoapods-core (0.34.4)
activesupport (>= 3.2.15) activesupport (>= 3.2.15)
...@@ -16,6 +16,13 @@ GIT ...@@ -16,6 +16,13 @@ GIT
json_pure (~> 1.8) json_pure (~> 1.8)
nap (~> 0.8.0) nap (~> 0.8.0)
GIT
remote: https://github.com/CocoaPods/Molinillo.git
revision: 3e27234148022ba1455a9228e7b710314204d4c5
branch: master
specs:
molinillo (0.0.1)
GIT GIT
remote: https://github.com/CocoaPods/Xcodeproj.git remote: https://github.com/CocoaPods/Xcodeproj.git
revision: ff7c70da5f5082eef47bb248012b69c76f6c6e54 revision: ff7c70da5f5082eef47bb248012b69c76f6c6e54
...@@ -71,6 +78,7 @@ PATH ...@@ -71,6 +78,7 @@ PATH
colored (~> 1.2) colored (~> 1.2)
escape (~> 0.0.4) escape (~> 0.0.4)
json_pure (~> 1.8) json_pure (~> 1.8)
molinillo (~> 0)
nap (~> 0.8) nap (~> 0.8)
open4 (~> 1.3) open4 (~> 1.3)
xcodeproj (~> 0.19.4) xcodeproj (~> 0.19.4)
...@@ -174,6 +182,7 @@ DEPENDENCIES ...@@ -174,6 +182,7 @@ DEPENDENCIES
mime-types (< 2.0) mime-types (< 2.0)
mocha mocha
mocha-on-bacon mocha-on-bacon
molinillo!
prettybacon prettybacon
pry pry
rake rake
......
...@@ -34,6 +34,7 @@ Gem::Specification.new do |s| ...@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'cocoapods-plugins', '~> 0.3.1' s.add_runtime_dependency 'cocoapods-plugins', '~> 0.3.1'
s.add_runtime_dependency 'cocoapods-try', '~> 0.4.1' s.add_runtime_dependency 'cocoapods-try', '~> 0.4.1'
s.add_runtime_dependency 'cocoapods-trunk', '~> 0.3.1' s.add_runtime_dependency 'cocoapods-trunk', '~> 0.3.1'
s.add_runtime_dependency 'molinillo', '~> 0'
s.add_runtime_dependency 'colored', '~> 1.2' s.add_runtime_dependency 'colored', '~> 1.2'
s.add_runtime_dependency 'escape', '~> 0.0.4' s.add_runtime_dependency 'escape', '~> 0.0.4'
......
require 'colored' require 'colored'
require 'claide' require 'claide'
require 'molinillo/errors'
module Molinillo
class ResolverError
include CLAide::InformativeError
end
end
module Pod module Pod
class PlainInformative class PlainInformative
......
...@@ -8,6 +8,8 @@ module Pod ...@@ -8,6 +8,8 @@ module Pod
autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer' autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
autoload :LockingDependencyAnalyzer, 'cocoapods/installer/analyzer/locking_dependency_analyzer'
# @return [Sandbox] The sandbox where the Pods should be installed. # @return [Sandbox] The sandbox where the Pods should be installed.
# #
attr_reader :sandbox attr_reader :sandbox
...@@ -149,17 +151,13 @@ module Pod ...@@ -149,17 +151,13 @@ module Pod
pods_state = nil pods_state = nil
UI.section 'Finding Podfile changes' do UI.section 'Finding Podfile changes' do
pods_by_state = lockfile.detect_changes_with_podfile(podfile) pods_by_state = lockfile.detect_changes_with_podfile(podfile)
pods_by_state.dup.each do |state, full_names|
pods = full_names.map { |fn| Specification.root_name(fn) }.uniq
pods_by_state[state] = pods
end
pods_state = SpecsState.new(pods_by_state) pods_state = SpecsState.new(pods_by_state)
pods_state.print pods_state.print
end end
pods_state pods_state
else else
state = SpecsState.new state = SpecsState.new
state.added.concat(podfile.dependencies.map(&:root_name).uniq) state.added.concat(podfile.dependencies.map(&:name).uniq)
state state
end end
end end
...@@ -231,21 +229,17 @@ module Pod ...@@ -231,21 +229,17 @@ module Pod
# is in update mode, to prevent it from upgrading the Pods that weren't # is in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}. # changed in the {Podfile}.
# #
# @return [Array<Dependency>] the dependencies generate by the lockfile # @return [Molinillo::DependencyGraph<Dependency>] the dependencies
# that prevent the resolver to update a Pod. # generated by the lockfile that prevent the resolver to update
# a Pod.
# #
def generate_version_locking_dependencies def generate_version_locking_dependencies
if update_mode == :all if update_mode == :all || !lockfile
[] LockingDependencyAnalyzer.unlocked_dependency_graph
else else
locking_pods = result.podfile_state.unchanged pods_to_update = result.podfile_state.changed + result.podfile_state.deleted
if update_mode == :selected pods_to_update += update[:pods] if update_mode == :selected
# If selected Pods should been updated, filter them out of the list LockingDependencyAnalyzer.generate_version_locking_dependencies(lockfile, pods_to_update)
locking_pods = locking_pods.select { |pod| !update[:pods].include?(pod) }
end
locking_pods.map do |pod|
lockfile.dependencies_to_lock_pod_named(pod)
end.flatten
end end
end end
...@@ -281,9 +275,9 @@ module Pod ...@@ -281,9 +275,9 @@ module Pod
if update_mode == :selected if update_mode == :selected
pods_to_fetch += update[:pods] pods_to_fetch += update[:pods]
end end
deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch.include?(dep.root_name) } deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch.include?(dep.name) }
deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.root_name) } deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.name) }
deps_to_fetch += deps_to_fetch_if_needed.select { |dep| sandbox.specification(dep.root_name).nil? || !dep.external_source[:local].nil? || !dep.external_source[:path].nil? || !sandbox.pod_dir(dep.root_name).directory? } deps_to_fetch += deps_to_fetch_if_needed.select { |dep| sandbox.specification(dep.name).nil? || !dep.external_source[:local].nil? || !dep.external_source[:path].nil? || !sandbox.pod_dir(dep.name).directory? }
end end
unless deps_to_fetch.empty? unless deps_to_fetch.empty?
...@@ -352,7 +346,7 @@ module Pod ...@@ -352,7 +346,7 @@ module Pod
# @!group Analysis internal products # @!group Analysis internal products
# @return [Array<Dependency>] the dependencies generate by the lockfile # @return [Molinillo::DependencyGraph<Dependency>] the dependencies generated by the lockfile
# that prevent the resolver to update a Pod. # that prevent the resolver to update a Pod.
# #
attr_reader :locked_dependencies attr_reader :locked_dependencies
...@@ -604,7 +598,7 @@ module Pod ...@@ -604,7 +598,7 @@ module Pod
# #
class SpecsState class SpecsState
# @param [Hash{Symbol=>String}] pods_by_state # @param [Hash{Symbol=>String}] pods_by_state
# The **root** name of the pods grouped by their state # The name of the pods grouped by their state
# (`:added`, `:removed`, `:changed` or `:unchanged`). # (`:added`, `:removed`, `:changed` or `:unchanged`).
# #
def initialize(pods_by_state = nil) def initialize(pods_by_state = nil)
...@@ -656,12 +650,9 @@ module Pod ...@@ -656,12 +650,9 @@ module Pod
# @param [Symbol] # @param [Symbol]
# the state of the Pod. # the state of the Pod.
# #
# @raise If there is an attempt to add the name of a subspec.
#
# @return [void] # @return [void]
# #
def add_name(name, state) def add_name(name, state)
raise '[Bug] Attempt to add subspec to the pods state' if name.include?('/')
send(state) << name send(state) << name
end end
end end
......
require 'molinillo/dependency_graph'
module Pod
class Installer
class Analyzer
# Generates dependencies that require the specific version of the Pods
# that haven't changed in the {Lockfile}.
module LockingDependencyAnalyzer
# Generates dependencies that require the specific version of the Pods
# that haven't changed in the {Lockfile}.
#
# These dependencies are passed to the {Resolver}, unless the installer
# is in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}.
#
# @return [Molinillo::DependencyGraph<Dependency>] the dependencies
# generated by the lockfile that prevent the resolver to update
# a Pod.
#
def self.generate_version_locking_dependencies(lockfile, pods_to_update)
dependency_graph = Molinillo::DependencyGraph.new
if lockfile
explicit_dependencies = lockfile.to_hash['DEPENDENCIES'] || []
explicit_dependencies.each do |string|
dependency = Dependency.new(string)
dependency_graph.add_root_vertex(dependency.name, nil)
end
pods = lockfile.to_hash['PODS'] || []
pods.each do |pod|
add_to_dependency_graph(pod, [], dependency_graph)
end
pods_to_update.each { |u| dependency_graph.detach_vertex_named(u) }
end
dependency_graph
end
# Generates a completely 'unlocked' dependency graph.
#
# @return [Molinillo::DependencyGraph<Dependency>] an empty dependency
# graph
#
def self.unlocked_dependency_graph
Molinillo::DependencyGraph.new
end
private
def self.add_child_vertex_to_graph(dependency_string, parents, dependency_graph)
dependency = Dependency.from_string(dependency_string)
dependency_graph.add_child_vertex(dependency.name, parents.empty? ? dependency : nil, parents, nil)
dependency
end
def self.add_to_dependency_graph(object, parents, dependency_graph)
case object
when String
add_child_vertex_to_graph(object, parents, dependency_graph)
when Hash
object.each do |key, value|
dependency = add_child_vertex_to_graph(key, parents, dependency_graph)
value.each { |v| add_to_dependency_graph(v, [dependency.name], dependency_graph) }
end
end
end
end
end
end
end
require 'molinillo'
require 'cocoapods/resolver/lazy_specification'
module Pod module Pod
# The resolver is responsible of generating a list of specifications grouped # The resolver is responsible of generating a list of specifications grouped
# by target for a given Podfile. # by target for a given Podfile.
# #
# @todo Its current implementation is naive, in the sense that it can't do full
# automatic resolves like Bundler:
# [how-does-bundler-bundle](http://patshaughnessy.net/2011/9/24/how-does-bundler-bundle)
#
# @todo Another limitation is that the order of the dependencies matter. The
# current implementation could create issues, for example, if a
# specification is loaded for a target definition and later for another
# target is set in head mode. The first specification will not be in head
# mode.
#
#
class Resolver class Resolver
# @return [Sandbox] the Sandbox used by the resolver to find external # @return [Sandbox] the Sandbox used by the resolver to find external
# dependencies. # dependencies.
...@@ -58,23 +50,12 @@ module Pod ...@@ -58,23 +50,12 @@ module Pod
# definition. # definition.
# #
def resolve def resolve
@cached_sets = {} dependencies = @podfile.target_definition_list.map(&:dependencies).flatten
@cached_specs = {} @cached_sets = {}
@specs_by_target = {} @activated = Molinillo::Resolver.new(self, self).resolve(dependencies, locked_dependencies)
target_definitions = podfile.target_definition_list
target_definitions.each do |target|
title = "Resolving dependencies for target `#{target.name}' " \
"(#{target.platform})"
UI.section(title) do
@loaded_specs = []
find_dependency_specs(podfile, target.dependencies, target)
specs = cached_specs.values_at(*@loaded_specs).sort_by(&:name)
specs_by_target[target] = specs
end
end
specs_by_target specs_by_target
rescue Molinillo::ResolverError => e
raise Informative, e.message
end end
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}] # @return [Hash{Podfile::TargetDefinition => Array<Specification>}]
...@@ -82,113 +63,235 @@ module Pod ...@@ -82,113 +63,235 @@ module Pod
# #
# @note The returned specifications can be subspecs. # @note The returned specifications can be subspecs.
# #
attr_reader :specs_by_target def specs_by_target
@specs_by_target ||= begin
specs_by_target = {}
podfile.target_definition_list.each do |target|
specs = target.dependencies.map(&:name).map do |name|
node = @activated.vertex_named(name)
(node.recursive_successors << node).to_a
end
specs_by_target[target] = specs.
flatten.
map(&:payload).
uniq.
sort_by(&:name).
each do |spec|
validate_platform(spec, target)
sandbox.store_head_pod(spec.name) if spec.version.head
end
end
specs_by_target
end
end
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
private public
# !@ Resolution context # @!group Specification Provider
# @return [Hash<String => Set>] A cache that keeps tracks of the sets include Molinillo::SpecificationProvider
# loaded by the resolution process.
# Returns (and caches) the specification that satisfy the given dependency.
# #
# @note Sets store the resolved dependencies and return the highest # @return [Array<Specification>] the specifications that satisfy the given
# available specification found in the sources. This is done # `dependency`.
# 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.
# #
attr_accessor :cached_sets # @param [Dependency] dependency the dependency that is being searched for.
#
def search_for(dependency)
@search ||= {}
@search[dependency] ||= begin
specs = find_cached_set(dependency).
all_specifications.
select { |s| dependency.requirement.satisfied_by? s.version }.
map { |s| s.subspec_by_name(dependency.name, false) }
# @return [Hash<String => Specification>] The loaded specifications grouped specs.
# by name. reverse.
each { |s| s.version.head = dependency.head? }
end
@search[dependency].dup
end
# Returns the dependencies of `specification`.
#
# @return [Array<Specification>] all dependencies of `specification`.
#
# @param [Specification] specification the specification whose own
# dependencies are being asked for.
# #
attr_accessor :cached_specs def dependencies_for(specification)
specification.all_dependencies.map do |dependency|
if dependency.root_name == Specification.root_name(specification.name)
Dependency.new(dependency.name, specification.version)
else
dependency
end
end
end
#-------------------------------------------------------------------------# # 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.
#
def name_for(dependency)
dependency.name
end
private # @return [String] the user-facing name for a {Podfile}.
#
def name_for_explicit_dependency_source
'Podfile'
end
# @!group Private helpers # @return [String] the user-facing name for a {Lockfile}.
#
def name_for_locking_dependency_source
'Podfile.lock'
end
# Resolves recursively the dependencies of a specification and stores them # Determines whether the given `requirement` is satisfied by the given
# in the @cached_specs ivar. # `spec`, in the context of the current `activated` dependency graph.
# #
# @param [Podfile, Specification, #to_s] dependent_spec # @return [Boolean] whether `requirement` is satisfied by `spec` in the
# the specification whose dependencies are being resolved. Used # context of the current `activated` dependency graph.
# only for UI purposes.
# #
# @param [Array<Dependency>] dependencies # @param [Dependency] requirement the dependency in question.
# the dependencies of the specification.
# #
# @param [TargetDefinition] target_definition # @param [Molinillo::DependencyGraph] activated the current dependency
# the target definition that owns the specification. # graph in the resolution process.
# #
# @note If there is a locked dependency with the same name of a # @param [Specification] spec the specification in question.
# given dependency the locked one is used in place of the
# dependency of the specification. In this way it is possible to
# prevent the update of the version of installed pods not changed
# in the Podfile.
# #
# @note The recursive process checks if a dependency has already been def requirement_satisfied_by?(requirement, activated, spec)
# loaded to prevent an infinite loop. 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?(&:prerelease?))
end
# 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?
# #
# @note The set class merges all (of all the target definitions) the # @return [Array<Dependency>] the sorted dependencies.
# dependencies and thus it keeps track of whether it is in head
# mode or from an external source because {Dependency#merge}
# preserves this information.
# #
# @return [void] # @param [Array<Dependency>] dependencies the unsorted dependencies.
# #
def find_dependency_specs(dependent_spec, dependencies, target_definition) # @param [Molinillo::DependencyGraph] activated the dependency graph of
dependencies.each do |dependency| # currently activated specs.
locked_dep = locked_dependencies.find { |ld| ld.name == dependency.name } #
dependency = locked_dep if locked_dep # @param [{String => Array<Conflict>}] conflicts the current conflicts.
#
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
UI.message("- #{dependency}", '', 2) do #-------------------------------------------------------------------------#
set = find_cached_set(dependency, dependent_spec)
set.required_by(dependency, dependent_spec.to_s)
if (paths = set.specification_paths_for_version(set.required_version)).length > 1 public
UI.warn "Found multiple specifications for #{dependency}:\n" \
"- #{paths.join("\n")}"
end
unless @loaded_specs.include?(dependency.name) # @!group Resolver UI
spec = set.specification.subspec_by_name(dependency.name)
@loaded_specs << spec.name
cached_specs[spec.name] = spec
validate_platform(spec, target_definition)
if dependency.head? || sandbox.head_pod?(spec.name)
spec.version.head = true
sandbox.store_head_pod(spec.name)
end
spec_dependencies = spec.all_dependencies(target_definition.platform) include Molinillo::UI
find_dependency_specs(spec, spec_dependencies, target_definition)
end include Config::Mixin
end
end # The UI object the resolver should use for displaying user-facing output.
#
# @return [UserInterface] the normal CocoaPods UI object.
#
def output
UI
end end
# Called before resolution starts. We print out `Resolving dependencies` in
# the analyzer, so here we just want to print out a starting `.` in verbose
# mode.
#
# @return [Void]
#
def before_resolution
UI.print '.' if config.verbose
end
# Called after resolution ends. We don't want to {#indicate_progress}
# unless in verbose mode, so we only use the default implementation then.
#
# @return [Void]
#
def after_resolution
super if config.verbose
end
# Called during resolution to indicate progress.
# We only use the default implementation in verbose mode.
#
# @return [Void]
#
def indicate_progress
super if config.verbose
end
#-------------------------------------------------------------------------#
private
# !@ Resolution context
# @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.
#
attr_accessor :cached_sets
#-------------------------------------------------------------------------#
private
# @!group Private helpers
# @return [Set] Loads or returns a previously initialized set for the Pod # @return [Set] Loads or returns a previously initialized set for the Pod
# of the given dependency. # of the given dependency.
# #
# @param [Dependency] dependency # @param [Dependency] dependency
# The dependency for which the set is needed. # The dependency for which the set is needed.
# #
# @param [#to_s] dependent_spec
# the specification whose dependencies are being resolved. Used
# only for UI purposes.
#
# @return [Set] the cached set for a given dependency. # @return [Set] the cached set for a given dependency.
# #
def find_cached_set(dependency, dependent_spec) def find_cached_set(dependency)
name = dependency.root_name name = dependency.root_name
unless cached_sets[name] unless cached_sets[name]
if dependency.external_source if dependency.external_source
spec = sandbox.specification(dependency.root_name) spec = sandbox.specification(name)
unless spec unless spec
raise StandardError, '[Bug] Unable to find the specification ' \ raise StandardError, '[Bug] Unable to find the specification ' \
"for `#{dependency}`." "for `#{dependency}`."
...@@ -199,8 +302,7 @@ module Pod ...@@ -199,8 +302,7 @@ module Pod
end end
cached_sets[name] = set cached_sets[name] = set
unless set unless set
raise Informative, 'Unable to find a specification for ' \ raise Molinillo::NoSuchDependencyError.new(dependency) # rubocop:disable Style/RaiseArgs
"`#{dependency}` depended upon by #{dependent_spec}."
end end
end end
cached_sets[name] cached_sets[name]
......
module Pod
class Specification
class Set
class LazySpecification < BasicObject
attr_reader :name, :version, :source
def initialize(name, version, source)
@name = name
@version = version
@source = source
end
def method_missing(method, *args, &block)
specification.send(method, *args, &block)
end
def subspec_by_name(name = nil, raise_if_missing = true)
if !name || name == self.name
self
else
specification.subspec_by_name(name, raise_if_missing)
end
end
def specification
@specification ||= source.specification(name, version)
end
end
class External
def all_specifications
[specification]
end
end
def all_specifications
@all_specifications ||= begin
sources_by_version = {}
versions_by_source.each do |source, versions|
versions.each { |v| (sources_by_version[v] ||= []) << source }
sources_by_version
end
duplicate_versions = sources_by_version.select { |_version, sources| sources.count > 1 }
duplicate_versions.each do |version, sources|
UI.warn "Found multiple specifications for `#{name} (#{version})`:\n" +
sources.
map { |s| s.specification_path(name, version) }.
map { |v| "- #{v}" }.join("\n")
end
versions_by_source.map do |source, versions|
versions.map { |version| LazySpecification.new(name, version, source) }
end.flatten
end
end
end
end
end
...@@ -219,6 +219,7 @@ module Pod ...@@ -219,6 +219,7 @@ module Pod
# @return [Nil] if the podspec is not stored. # @return [Nil] if the podspec is not stored.
# #
def specification_path(name) def specification_path(name)
name = Specification.root_name(name)
path = specifications_root + "#{name}.podspec" path = specifications_root + "#{name}.podspec"
if path.exist? if path.exist?
path path
...@@ -378,7 +379,7 @@ module Pod ...@@ -378,7 +379,7 @@ module Pod
end end
# @return [Hash{String=>String}] The path of the Pods with a local source # @return [Hash{String=>String}] The path of the Pods with a local source
# grouped by their name. # grouped by their root name.
# #
# @todo Rename (e.g. `pods_with_local_path`) # @todo Rename (e.g. `pods_with_local_path`)
# #
......
Subproject commit ae87cdaa7f0c96ad246ddd804a08bc4260018548 Subproject commit afc3bdb824e79fe2f99361aa9b4525cda4cc95d1
Subproject commit b0f4e85738f1f911e32e2914b39e9acdce7c7ed9 Subproject commit b6e1c2429964b4e47b344958572930e15ffbbcea
...@@ -226,13 +226,13 @@ module Pod ...@@ -226,13 +226,13 @@ module Pod
it 'cats the given podspec' do it 'cats the given podspec' do
lambda { command('spec', 'cat', 'AFNetworking').run }.should.not.raise lambda { command('spec', 'cat', 'AFNetworking').run }.should.not.raise
UI.output.should.include fixture('spec-repos/master/Specs/AFNetworking/2.3.1/AFNetworking.podspec.json').read UI.output.should.include fixture('spec-repos/master/Specs/AFNetworking/2.4.1/AFNetworking.podspec.json').read
end end
it 'cats the first podspec from all podspecs' do it 'cats the first podspec from all podspecs' do
UI.next_input = "1\n" UI.next_input = "1\n"
run_command('spec', 'cat', '--show-all', 'AFNetworking') run_command('spec', 'cat', '--show-all', 'AFNetworking')
UI.output.should.include fixture('spec-repos/master/Specs/AFNetworking/2.3.1/AFNetworking.podspec.json').read UI.output.should.include fixture('spec-repos/master/Specs/AFNetworking/2.4.1/AFNetworking.podspec.json').read
end end
end end
...@@ -292,7 +292,7 @@ module Pod ...@@ -292,7 +292,7 @@ module Pod
it 'returns the path of the specification with the given name' do it 'returns the path of the specification with the given name' do
path = @sut.send(:get_path_of_spec, 'AFNetworking') path = @sut.send(:get_path_of_spec, 'AFNetworking')
path.should == fixture('spec-repos') + 'master/Specs/AFNetworking/2.3.1/AFNetworking.podspec.json' path.should == fixture('spec-repos') + 'master/Specs/AFNetworking/2.4.1/AFNetworking.podspec.json'
end end
end end
......
...@@ -48,7 +48,7 @@ module Pod ...@@ -48,7 +48,7 @@ module Pod
output = UI.output output = UI.output
output.should.include? 'Author: Robbie Hanson' output.should.include? 'Author: Robbie Hanson'
output.should.include? 'License: BSD' output.should.include? 'License: BSD'
output.should.include? 'Platform: iOS - OS X' output.should.include? 'Platform: iOS 5.0 - OS X 10.7'
output.should.include? 'Watchers: 318' output.should.include? 'Watchers: 318'
output.should.include? 'Forks: 42' output.should.include? 'Forks: 42'
output.should.include? 'Pushed: more than a year ago' output.should.include? 'Pushed: more than a year ago'
......
...@@ -13,7 +13,7 @@ def create_analyzer ...@@ -13,7 +13,7 @@ def create_analyzer
end end
hash = {} hash = {}
hash['PODS'] = ['JSONKit (1.4)', 'NUI (0.2.0)', 'SVPullToRefresh (0.4)'] hash['PODS'] = ['JSONKit (1.5pre)', 'NUI (0.2.0)', 'SVPullToRefresh (0.4)']
hash['DEPENDENCIES'] = %w(JSONKit NUI SVPullToRefresh) hash['DEPENDENCIES'] = %w(JSONKit NUI SVPullToRefresh)
hash['SPEC CHECKSUMS'] = {} hash['SPEC CHECKSUMS'] = {}
hash['COCOAPODS'] = Pod::VERSION hash['COCOAPODS'] = Pod::VERSION
...@@ -52,10 +52,10 @@ module Pod ...@@ -52,10 +52,10 @@ module Pod
it 'computes the state of the Podfile respect to the Lockfile' do it 'computes the state of the Podfile respect to the Lockfile' do
state = @analyzer.analyze.podfile_state state = @analyzer.analyze.podfile_state
state.added.should == %w(AFNetworking libextobjc) state.added.should == %w(AFNetworking libextobjc/EXTKeyPathCoding)
state.changed.should == ['JSONKit'] state.changed.should == %w()
state.unchanged.should == ['SVPullToRefresh'] state.unchanged.should == %w(JSONKit SVPullToRefresh)
state.deleted.should == ['NUI'] state.deleted.should == %w(NUI)
end end
#--------------------------------------# #--------------------------------------#
...@@ -131,13 +131,35 @@ module Pod ...@@ -131,13 +131,35 @@ module Pod
it 'locks the version of the dependencies which did not change in the Podfile' do it 'locks the version of the dependencies which did not change in the Podfile' do
@analyzer.analyze @analyzer.analyze
@analyzer.send(:locked_dependencies).map(&:to_s).should == ['SVPullToRefresh (= 0.4)'] @analyzer.send(:locked_dependencies).map(&:payload).map(&:to_s).
should == ['JSONKit (= 1.5pre)', 'SVPullToRefresh (= 0.4)']
end end
it 'does not lock the dependencies in update mode' do it 'does not lock the dependencies in update mode' do
@analyzer.update = true @analyzer.update = true
@analyzer.analyze @analyzer.analyze
@analyzer.send(:locked_dependencies).map(&:to_s).should == [] @analyzer.send(:locked_dependencies).to_a.map(&:payload).should == []
end
#--------------------------------------#
it 'takes into account locked implicit dependencies' do
podfile = Podfile.new do
platform :ios, '8.0'
xcodeproj 'SampleProject/SampleProject'
pod 'ARAnalytics/Mixpanel'
end
hash = {}
hash['PODS'] = ['ARAnalytics/CoreIOS (2.8.0)', {'ARAnalytics/Mixpanel (2.8.0)' => ['ARAnlytics/CoreIOS', 'Mixpanel']}, 'Mixpanel (2.5.1)']
hash['DEPENDENCIES'] = %w(ARAnalytics/Mixpanel)
hash['SPEC CHECKSUMS'] = {}
hash['COCOAPODS'] = Pod::VERSION
lockfile = Pod::Lockfile.new(hash)
analyzer = Installer::Analyzer.new(config.sandbox, podfile, lockfile)
analyzer.analyze.specifications.
find { |s| s.name == 'Mixpanel' }.
version.to_s.should == '2.5.1'
end end
#--------------------------------------# #--------------------------------------#
......
require File.expand_path('../../spec_helper', __FILE__) require File.expand_path('../../spec_helper', __FILE__)
def dependency_graph_from_array(locked_dependencies)
locked_dependencies.reduce(Molinillo::DependencyGraph.new) do |graph, dep|
graph.add_root_vertex(dep.name, dep)
graph
end
end
def empty_graph
Molinillo::DependencyGraph.new
end
module Pod module Pod
describe Resolver do describe Resolver do
describe 'In general' do describe 'In general' do
...@@ -8,7 +19,7 @@ module Pod ...@@ -8,7 +19,7 @@ module Pod
platform :ios platform :ios
pod 'BlocksKit', '1.5.2' pod 'BlocksKit', '1.5.2'
end end
locked_deps = [Dependency.new('BlocksKit', '1.5.2')] locked_deps = dependency_graph_from_array([Dependency.new('BlocksKit', '1.5.2')])
@resolver = Resolver.new(config.sandbox, @podfile, locked_deps, SourcesManager.all) @resolver = Resolver.new(config.sandbox, @podfile, locked_deps, SourcesManager.all)
end end
...@@ -21,7 +32,8 @@ module Pod ...@@ -21,7 +32,8 @@ module Pod
end end
it 'returns the locked dependencies' do it 'returns the locked dependencies' do
@resolver.locked_dependencies.should == [Dependency.new('BlocksKit', '1.5.2')] @resolver.locked_dependencies.
should == dependency_graph_from_array([Dependency.new('BlocksKit', '1.5.2')])
end end
#--------------------------------------# #--------------------------------------#
...@@ -55,11 +67,20 @@ module Pod ...@@ -55,11 +67,20 @@ module Pod
platform :ios platform :ios
pod 'Reachability', :podspec => podspec pod 'Reachability', :podspec => podspec
end end
resolver = Resolver.new(config.sandbox, podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
resolver.resolve resolver.resolve
specs = resolver.specs_by_target.values.flatten specs = resolver.specs_by_target.values.flatten
specs.map(&:to_s).should == ['Reachability (3.0.0)'] specs.map(&:to_s).should == ['Reachability (3.0.0)']
end end
it 'resolves an empty podfile' do
@podfile = Podfile.new do
platform :ios
end
resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should == []
end
end end
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
...@@ -70,7 +91,7 @@ module Pod ...@@ -70,7 +91,7 @@ module Pod
platform :ios, '6.0' platform :ios, '6.0'
pod 'BlocksKit', '1.5.2' pod 'BlocksKit', '1.5.2'
end end
@resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) @resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
end end
it 'cross resolves dependencies' do it 'cross resolves dependencies' do
...@@ -80,11 +101,53 @@ module Pod ...@@ -80,11 +101,53 @@ module Pod
pod 'AFQuickLookView', '= 0.1.0' # requires 'AFNetworking', '>= 0.9.0' pod 'AFQuickLookView', '= 0.1.0' # requires 'AFNetworking', '>= 0.9.0'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should == ['AFNetworking (0.9.1)', 'AFQuickLookView (0.1.0)'] specs.should == ['AFNetworking (0.9.1)', 'AFQuickLookView (0.1.0)']
end end
it 'resolves basic conflicts' do
@podfile = Podfile.new do
platform :ios, '7.0'
pod 'RestKit' # latest version (0.23.3) requires 'AFNetworking', '~> 1.3.0'
pod 'AFNetworking', '~> 1.2.0'
end
resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should == ['AFNetworking (1.2.1)', 'RestKit (0.20.1)',
'RestKit/Core (0.20.1)', 'RestKit/CoreData (0.20.1)',
'RestKit/Network (0.20.1)', 'RestKit/ObjectMapping (0.20.1)',
'RestKit/Support (0.20.1)', 'SOCKit (1.1)', 'TransitionKit (1.1.0)']
end
it 'resolves three-way conflicts' do
@podfile = Podfile.new do
platform :ios, '7.0'
pod 'AFAmazonS3Client' # latest version (2.0.0) requires 'AFNetworking', '~> 2.0'
pod 'CargoBay' # latest version (2.1.0) requires 'AFNetworking', '~> 2.2'
pod 'AFOAuth2Client' # latest version (0.1.2) requires 'AFNetworking', '~> 1.3'
end
resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should == ['AFAmazonS3Client (1.0.1)', 'AFNetworking (1.3.4)',
'AFOAuth2Client (0.1.2)', 'CargoBay (1.0.0)']
end
it 'uses a Podfile requirement even when a previously declared ' \
'dependency has a different requirement' do
@podfile = Podfile.new do
platform :ios, '7.0'
pod 'InstagramKit' # latest version (3.5.0) requires 'AFNetworking', '~> 2.0'
pod 'AFNetworking', '2.0.1'
end
resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:root).map(&:to_s).uniq.sort
specs.should == ['AFNetworking (2.0.1)', 'InstagramKit (3.5.0)']
end
it 'holds the context state, such as cached specification sets' do it 'holds the context state, such as cached specification sets' do
@resolver.resolve @resolver.resolve
cached_sets = @resolver.send(:cached_sets) cached_sets = @resolver.send(:cached_sets)
...@@ -107,7 +170,7 @@ module Pod ...@@ -107,7 +170,7 @@ module Pod
@resolver.resolve @resolver.resolve
end.message end.message
message.should.match /Unable to find a specification/ message.should.match /Unable to find a specification/
message.should.match /`Windows` depended upon by BlocksKit/ message.should.match /`Windows` depended upon by `BlocksKit`/
end end
it 'does not raise if all dependencies are supported by the platform of the target definition' do it 'does not raise if all dependencies are supported by the platform of the target definition' do
...@@ -119,7 +182,7 @@ module Pod ...@@ -119,7 +182,7 @@ module Pod
platform :ios, '7.0' platform :ios, '7.0'
pod 'RestKit', '0.10.3' pod 'RestKit', '0.10.3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
resolver.resolve.values.flatten.map(&:name).sort.should == %w( resolver.resolve.values.flatten.map(&:name).sort.should == %w(
FileMD5Hash FileMD5Hash
ISO8601DateFormatter ISO8601DateFormatter
...@@ -142,6 +205,24 @@ module Pod ...@@ -142,6 +205,24 @@ module Pod
) )
end end
it 'handles pre-release dependencies with subspecs' do
@podfile = Podfile.new do
platform :ios, '7.0'
pod 'RestKit', '0.20.0-rc1'
end
resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
resolver.resolve.values.flatten.map(&:to_s).sort.should == [
'AFNetworking (1.1.0)',
'RestKit (0.20.0-rc1)',
'RestKit/Core (0.20.0-rc1)',
'RestKit/CoreData (0.20.0-rc1)',
'RestKit/Network (0.20.0-rc1)',
'RestKit/ObjectMapping (0.20.0-rc1)',
'RestKit/Support (0.20.0-rc1)',
'SOCKit (1.1)',
]
end
it 'handles correctly subspecs from external sources' do it 'handles correctly subspecs from external sources' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios platform :ios
...@@ -158,7 +239,7 @@ module Pod ...@@ -158,7 +239,7 @@ module Pod
end end
end end
config.sandbox.expects(:specification).with('MainSpec').returns(spec) config.sandbox.expects(:specification).with('MainSpec').returns(spec)
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:name).sort specs = resolver.resolve.values.flatten.map(&:name).sort
specs.should == %w( specs.should == %w(
MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec
...@@ -171,7 +252,7 @@ module Pod ...@@ -171,7 +252,7 @@ module Pod
pod 'FileMD5Hash' pod 'FileMD5Hash'
pod 'JSONKit', :head pod 'JSONKit', :head
end end
resolver = Resolver.new(config.sandbox, podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name) filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name)
filemd5hash.version.should.not.be.head filemd5hash.version.should.not.be.head
jsonkit.version.should.be.head jsonkit.version.should.be.head
...@@ -179,15 +260,41 @@ module Pod ...@@ -179,15 +260,41 @@ module Pod
config.sandbox.head_pod?('JSONKit').should.be.true config.sandbox.head_pod?('JSONKit').should.be.true
end end
it 'raises if it finds two conflicting dependencies' do it 'raises if it finds two conflicting explicit dependencies' do
podfile = Podfile.new do podfile = Podfile.new do
platform :ios platform :ios
pod 'JSONKit', '1.4' pod 'JSONKit', '1.4'
pod 'JSONKit', '1.5pre' pod 'JSONKit', '1.5pre'
end end
resolver = Resolver.new(config.sandbox, podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
e = lambda { resolver.resolve }.should.raise Pod::Informative e = lambda { resolver.resolve }.should.raise Informative
e.message.should.match(/Unable to satisfy the following requirements/)
e.message.should.match(/`JSONKit \(= 1.4\)` required by `Podfile`/)
e.message.should.match(/`JSONKit \(= 1.5pre\)` required by `Podfile`/)
end
it 'raises if it finds two conflicting dependencies' do
podfile = Podfile.new do
platform :ios
pod 'RestKit', '0.23.3' # dependends on AFNetworking ~> 1.3.0
pod 'AFNetworking', '> 2'
end
resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
e = lambda { resolver.resolve }.should.raise Informative
e.message.should.match(/Unable to satisfy the following requirements/) e.message.should.match(/Unable to satisfy the following requirements/)
e.message.should.match(/`AFNetworking \(~> 1.3.0\)` required by `RestKit\/Network \(.*\)`/)
e.message.should.match(/`AFNetworking \(> 2\)` required by `Podfile`/)
end
it 'raises if no such version of a dependency exists' do
podfile = Podfile.new do
platform :ios
pod 'AFNetworking', '3.0.1'
end
resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
e = lambda { resolver.resolve }.should.raise Informative
e.message.should.match(/Unable to satisfy the following requirements/)
e.message.should.match(/`AFNetworking \(= 3.0.1\)` required by `Podfile`/)
end end
it 'takes into account locked dependencies' do it 'takes into account locked dependencies' do
...@@ -195,45 +302,16 @@ module Pod ...@@ -195,45 +302,16 @@ module Pod
platform :ios platform :ios
pod 'JSONKit', '<= 1.5pre' pod 'JSONKit', '<= 1.5pre'
end end
resolver = Resolver.new(config.sandbox, podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, podfile, empty_graph, SourcesManager.all)
version = resolver.resolve.values.flatten.first.version version = resolver.resolve.values.flatten.first.version
version.to_s.should == '1.5pre' version.to_s.should == '1.5pre'
locked_deps = [Dependency.new('JSONKit', '= 1.4')] locked_deps = dependency_graph_from_array([Dependency.new('JSONKit', '= 1.4')])
resolver = Resolver.new(config.sandbox, podfile, locked_deps, SourcesManager.all) resolver = Resolver.new(config.sandbox, podfile, locked_deps, SourcesManager.all)
version = resolver.resolve.values.flatten.first.version version = resolver.resolve.values.flatten.first.version
version.to_s.should == '1.4' version.to_s.should == '1.4'
end end
xit 'takes into account locked implicit dependencies' do
podfile = Podfile.new do
platform :ios, '8.0'
pod 'ARAnalytics/Mixpanel'
end
lockfile_yaml = <<-EOS
PODS:
- ARAnalytics/CoreIOS (2.8.0)
- ARAnalytics/Mixpanel (2.8.0):
- ARAnalytics/CoreIOS
- Mixpanel
- Mixpanel (2.5.1)
DEPENDENCIES:
- ARAnalytics/Mixpanel
SPEC CHECKSUMS:
ARAnalytics: 93c5b65989145f88f4d45e262612eac277b0c219
Mixpanel: 0115466ba70fd12e67ac4d3d071408dd1d489657
COCOAPODS: 0.33.1
EOS
lockfile = Lockfile.new(YAMLHelper.load_string(lockfile_yaml))
resolver = Resolver.new(config.sandbox, podfile, lockfile.dependencies, SourcesManager.all)
resolver.resolve.values.first.
find { |s| s.name == 'Mixpanel' }.
version.to_s.should == '2.5.1'
end
it 'consults all sources when finding a matching spec' do it 'consults all sources when finding a matching spec' do
podfile = Podfile.new do podfile = Podfile.new do
platform :ios platform :ios
...@@ -241,13 +319,13 @@ COCOAPODS: 0.33.1 ...@@ -241,13 +319,13 @@ COCOAPODS: 0.33.1
end end
file = fixture('spec-repos/test_repo/JSONKit/999.999.999/JSONKit.podspec') file = fixture('spec-repos/test_repo/JSONKit/999.999.999/JSONKit.podspec')
sources = SourcesManager.sources(%w(master test_repo)) sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, [], sources) resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999' spec.version.to_s.should == '999.999.999'
spec.defined_in_file.should == file spec.defined_in_file.should == file
sources = SourcesManager.sources(%w(test_repo master)) sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, [], sources) resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999' spec.version.to_s.should == '999.999.999'
resolver.resolve.values.flatten.first.defined_in_file.should == file resolver.resolve.values.flatten.first.defined_in_file.should == file
...@@ -260,13 +338,13 @@ COCOAPODS: 0.33.1 ...@@ -260,13 +338,13 @@ COCOAPODS: 0.33.1
pod 'JSONKit', '1.4' pod 'JSONKit', '1.4'
end end
sources = SourcesManager.sources(%w(master test_repo)) sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, [], sources) resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '1.4' spec.version.to_s.should == '1.4'
spec.defined_in_file.should == fixture('spec-repos/master/Specs/JSONKit/1.4/JSONKit.podspec.json') spec.defined_in_file.should == fixture('spec-repos/master/Specs/JSONKit/1.4/JSONKit.podspec.json')
sources = SourcesManager.sources(%w(test_repo master)) sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, [], sources) resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '1.4' spec.version.to_s.should == '1.4'
resolver.resolve.values.flatten.first.defined_in_file.should == fixture('spec-repos/test_repo/JSONKit/1.4/JSONKit.podspec') resolver.resolve.values.flatten.first.defined_in_file.should == fixture('spec-repos/test_repo/JSONKit/1.4/JSONKit.podspec')
...@@ -285,90 +363,90 @@ COCOAPODS: 0.33.1 ...@@ -285,90 +363,90 @@ COCOAPODS: 0.33.1
pod 'AFNetworking', '1.0RC3' pod 'AFNetworking', '1.0RC3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should == ['AFNetworking (1.0RC3)'] specs.should == ['AFNetworking (1.0RC3)']
end end
xit 'resolves to latest minor version even when explicitly requesting pre-release versions when using ~>' do it 'resolves to latest minor version even when explicitly requesting pre-release versions when using ~>' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '~> 1.0RC3' pod 'AFNetworking', '~> 1.0RC3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.2.0)'] specs.should == ['AFNetworking (1.3.4)']
end end
xit 'does not resolve to a pre-release version implicitly when matching exact version' do it 'does not resolve to a pre-release version implicitly when matching exact version' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '1.0' pod 'AFNetworking', '1.0'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.0)'] specs.should == ['AFNetworking (1.0)']
end end
xit 'does not resolve to a pre-release version implicitly when using <' do it 'does not resolve to a pre-release version implicitly when using <' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '< 1.0' pod 'AFNetworking', '< 1.0'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (0.10.1)'] specs.should == ['AFNetworking (0.10.1)']
end end
xit 'does not resolve to a pre-release version implicitly when using <=' do it 'does not resolve to a pre-release version implicitly when using <=' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '<= 1.0' pod 'AFNetworking', '<= 1.0'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.0)'] specs.should == ['AFNetworking (1.0)']
end end
xit 'does not resolve to a pre-release version implicitly when using >' do it 'does not resolve to a pre-release version implicitly when using >' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '> 1.0', '< 1.3' pod 'AFNetworking', '> 1.0', '< 1.3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.2.1)'] specs.should == ['AFNetworking (1.2.1)']
end end
xit 'does not resolve to a pre-release version implicitly when using >=' do it 'does not resolve to a pre-release version implicitly when using >=' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '>= 1.0', '< 1.3' pod 'AFNetworking', '>= 1.0', '< 1.3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.2.1)'] specs.should == ['AFNetworking (1.2.1)']
end end
xit 'does not resolve to a pre-release version implicitly when using ~>' do it 'does not resolve to a pre-release version implicitly when using ~>' do
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios, '6.0' platform :ios, '6.0'
pod 'AFNetworking', '~> 1.0', '< 1.3' pod 'AFNetworking', '~> 1.0', '< 1.3'
end end
resolver = Resolver.new(config.sandbox, @podfile, [], SourcesManager.all) resolver = Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all)
specs = resolver.resolve.values.flatten.map(&:to_s).sort specs = resolver.resolve.values.flatten.map(&:to_s).sort
specs.should != ['AFNetworking (1.0RC3)'] specs.should != ['AFNetworking (1.0RC3)']
specs.should == ['AFNetworking (1.2.1)'] specs.should == ['AFNetworking (1.2.1)']
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment