Commit ccfde782 authored by Eric Firestone's avatar Eric Firestone

Add ability to specify source with each dependency

Enables specifying a :source flag with each pod dependency
parent 484a0dbc
......@@ -8,6 +8,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
##### Enhancements
* Add support for specifying :source with a pod dependency.
[Eric Firestone](https://github.com/efirestone)
[#4486](https://github.com/CocoaPods/CocoaPods/pull/4486)
* Ask user to run `pod install` when a resource not found during in copy resources script.
[Muhammed Yavuz Nuzumlalı](https://github.com/manuyavuz)
......
......@@ -7,7 +7,7 @@ GIT
GIT
remote: https://github.com/CocoaPods/Core.git
revision: ce26e1eb797a6ad1f1d94b0304a50c491bc41237
revision: 6c2544496ed201104e712dd95793ec7bc6a171e8
branch: master
specs:
cocoapods-core (0.39.0)
......
......@@ -614,13 +614,21 @@ module Pod
def sources
@sources ||= begin
sources = podfile.sources
if sources.empty?
url = 'https://github.com/CocoaPods/Specs.git'
[SourcesManager.find_or_create_source_with_url(url)]
# Add any sources specified using the :source flag on individual dependencies.
dependency_sources = podfile.dependencies.map(&:podspec_repo).compact
all_dependencies_have_sources = dependency_sources.count == podfile.dependencies.count
if all_dependencies_have_sources
sources = dependency_sources
elsif sources.empty?
sources = ['https://github.com/CocoaPods/Specs.git']
else
sources.map do |source_url|
SourcesManager.find_or_create_source_with_url(source_url)
end
sources += dependency_sources
end
sources.uniq.map do |source_url|
SourcesManager.find_or_create_source_with_url(source_url)
end
end
end
......
......@@ -335,13 +335,17 @@ module Pod
# The dependency for which the set is needed.
#
def create_set_from_sources(dependency)
aggregate.search(dependency)
aggregate_for_dependency(dependency).search(dependency)
end
# @return [Source::Aggregate] The aggregate of the {#sources}.
#
def aggregate
@aggregate ||= Source::Aggregate.new(sources.map(&:repo))
def aggregate_for_dependency(dependency)
if dependency && dependency.podspec_repo
return SourcesManager.aggregate_for_dependency(dependency)
else
@aggregate ||= Source::Aggregate.new(sources.map(&:repo))
end
end
# Ensures that a specification is compatible with the platform of a target.
......
......@@ -11,7 +11,27 @@ module Pod
def aggregate
return Source::Aggregate.new([]) unless config.repos_dir.exist?
dirs = config.repos_dir.children.select(&:directory?)
Source::Aggregate.new(dirs)
aggregate_with_repos(dirs)
end
# @return [Source::Aggregate] The aggregate of the sources from repos.
#
# @param [Dependency] dependency
# The dependency for which to find or build the appropriate.
# aggregate. If the dependency specifies a source podspec repo
# then only that source will be used, otherwise all sources
# will be used.
#
def aggregate_for_dependency(dependency)
if dependency.podspec_repo
source = source_with_url(dependency.podspec_repo)
raise StandardError, '[Bug] Failed to find known source with the URL ' \
"#{dependency.podspec_repo.inspect}" if source.nil?
aggregate_with_repos([source_dir(source.name)])
else
aggregate
end
end
# @return [Array<Source>] The list of the sources with the given names.
......@@ -86,16 +106,16 @@ module Pod
sources(['master'])
end
# Search all the sources to match the set for the given dependency.
# Search the appropriate sources to match the set for the given dependency.
#
# @return [Set, nil] a set for a given dependency including all the
# {Source} that contain the Pod. If no sources containing the
# Pod where found it returns nil.
#
# @raise If no source including the set can be found.
# @raise If no source can be found that includes the dependency.
#
def search(dependency)
aggregate.search(dependency)
aggregate_for_dependency(dependency).search(dependency)
end
# Search all the sources with the given search term.
......@@ -448,6 +468,16 @@ module Pod
private
# @return [Source::Aggregate] The aggregate of the sources from repos.
#
# @param [Array<Pathname>] repos
# The local file paths to one or more podspec repo caches.
#
def aggregate_with_repos(repos)
@aggregates_by_repos ||= {}
@aggregates_by_repos[repos] ||= Source::Aggregate.new(repos)
end
# @return [Bool] Whether the given path is writable by the current user.
#
# @param [#to_s] path
......
Pod::Spec.new do |s|
s.name = 'CrossRepoDependent'
s.version = '1.0'
s.authors = 'Ned Needy', { 'Mr. Needy' => 'needy@example.local' }
s.homepage = 'http://example.local/cross-repo-dependent.html'
s.summary = 'I\'m dependent upon another spec repo to resolve my dependencies.'
s.description = 'I\'m dependent upon another spec repo to resolve my dependencies.'
s.platform = :ios
s.source = { :git => 'http://example.local/cross-repo-dependent.git', :tag => 'v1.0' }
s.source_files = 'Classes/*.{h,m}', 'Vendor'
s.dependency 'AFNetworking', '2.4.0'
s.license = {
:type => 'MIT',
:file => 'LICENSE',
:text => 'Permission is hereby granted ...'
}
end
......@@ -50,10 +50,11 @@ module Pod
end
it "updates the Podfile's sources by default" do
config.stubs(:podfile).returns Podfile.new do
podfile = Podfile.new do
source 'https://github.com/CocoaPods/Specs.git'
pod 'AFNetworking'
end
config.stubs(:podfile).returns(podfile)
config.stubs(:skip_repo_update?).returns(false)
lockfile = mock
lockfile.stubs(:version).returns(Version.new('1.0'))
......
......@@ -62,6 +62,19 @@ module Pod
@analyzer.update_repositories
end
it 'does not update sources if there are no dependencies' do
podfile = Podfile.new do
source 'https://github.com/CocoaPods/Specs.git'
# No dependencies specified
end
config.skip_repo_update = false
config.verbose = true
SourcesManager.expects(:update).never
analyzer = Pod::Installer::Analyzer.new(config.sandbox, podfile, nil)
analyzer.update_repositories
end
it 'does not update non-git repositories' do
tmp_directory = Pathname(Dir.tmpdir) + 'CocoaPods'
FileUtils.mkdir_p(tmp_directory)
......@@ -88,6 +101,28 @@ module Pod
FileUtils.rm_rf(non_git_repo)
end
it 'updates sources specified with dependencies' do
repo_url = 'https://url/to/specs.git'
podfile = Podfile.new do
source 'repo_1'
pod 'BananaLib', '1.0', :source => repo_url
pod 'JSONKit', :source => repo_url
end
config.skip_repo_update = false
config.verbose = true
# Note that we are explicitly ignoring 'repo_1' since it isn't used.
source = mock
source.stubs(:name).returns('repo_2')
source.stubs(:repo).returns('/repo/cache/path')
SourcesManager.expects(:find_or_create_source_with_url).with(repo_url).returns(source)
SourcesManager.stubs(:git_repo?).with(source.repo).returns(true)
SourcesManager.expects(:update).once.with(source.name)
analyzer = Pod::Installer::Analyzer.new(config.sandbox, podfile, nil)
analyzer.update_repositories
end
#--------------------------------------#
it 'generates the model to represent the target definitions' do
......@@ -370,7 +405,7 @@ module Pod
@analyzer.send(:fetch_external_sources)
end
xit 'it fetches the specification from either the sandbox or from the remote be default' do
xit 'it fetches the specification from either the sandbox or from the remote by default' do
dependency = Dependency.new('Name', :git => 'www.example.com')
ExternalSources::DownloaderSource.any_instance.expects(:specification_from_external).returns(Specification.new).once
@resolver.send(:set_from_external_source, dependency)
......@@ -474,6 +509,7 @@ module Pod
it 'raises if no specs repo with that URL could be added' do
podfile = Podfile.new do
source 'not-a-git-repo'
pod 'JSONKit', '1.4'
end
@analyzer.instance_variable_set(:@podfile, podfile)
should.raise Informative do
......@@ -484,6 +520,7 @@ module Pod
it 'fetches a specs repo that is specified by the podfile' do
podfile = Podfile.new do
source 'https://github.com/artsy/Specs.git'
pod 'JSONKit', '1.4'
end
@analyzer.instance_variable_set(:@podfile, podfile)
SourcesManager.expects(:find_or_create_source_with_url).once
......
......@@ -70,7 +70,7 @@ module Pod
]
end
it 'it resolves specifications from external sources' do
it 'resolves specifications from external sources' do
podspec = fixture('integration/Reachability/Reachability.podspec')
spec = Specification.from_file(podspec)
config.sandbox.expects(:specification).with('Reachability').returns(spec)
......@@ -441,46 +441,6 @@ module Pod
e.message.should.match(/`pod update CocoaLumberjack`/)
end
it 'consults all sources when finding a matching spec' do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '> 2'
end
file = fixture('spec-repos/test_repo/JSONKit/999.999.999/JSONKit.podspec')
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999'
spec.defined_in_file.should == file
sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999'
resolver.resolve.values.flatten.first.defined_in_file.should == file
end
it 'warns and chooses the first source when multiple sources contain ' \
'a pod' do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.4'
end
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '1.4'
spec.defined_in_file.should == fixture('spec-repos/master/Specs/JSONKit/1.4/JSONKit.podspec.json')
sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
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')
UI.warnings.should.match /multiple specifications/
end
describe 'concerning dependencies that are scoped by consumer platform' do
def resolve
Resolver.new(config.sandbox, @podfile, empty_graph, SourcesManager.all).resolve
......@@ -550,6 +510,152 @@ module Pod
#-------------------------------------------------------------------------#
describe 'Multiple sources' do
it 'consults all sources when finding a matching spec' do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '> 2'
end
file = fixture('spec-repos/test_repo/JSONKit/999.999.999/JSONKit.podspec')
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999'
spec.defined_in_file.should == file
sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '999.999.999'
resolver.resolve.values.flatten.first.defined_in_file.should == file
end
it 'warns and chooses the first source when multiple sources contain ' \
'a pod' do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.4'
end
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '1.4'
spec.defined_in_file.should == fixture('spec-repos/master/Specs/JSONKit/1.4/JSONKit.podspec.json')
sources = SourcesManager.sources(%w(test_repo master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
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')
UI.warnings.should.match /multiple specifications/
end
it 'does not warn when multiple sources contain a pod but a dependency ' \
'has an explicit source specified' do
test_repo_url = SourcesManager.source_with_name_or_url('test_repo').url
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.4', :source => test_repo_url
end
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
resolver.resolve
UI.warnings.should.not.match /multiple specifications/
end
it 'fails to resolve a dependency with an explicit source even if it can be ' \
'resolved using the global sources' do
test_repo_url = SourcesManager.source_with_name_or_url('test_repo').url
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.5pre', :source => test_repo_url
end
sources = SourcesManager.sources(%w(master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
e = lambda { resolver.resolve }.should.raise Informative
e.message.should.match(/None of the spec sources contain a spec satisfying/)
e.message.should.match(/JSONKit/)
e.message.should.match(/\= 1.5pre/)
end
it 'resolves a dependency with an explicit source even if it can\'t be ' \
'resolved using the global sources' do
master_repo_url = SourcesManager.source_with_name_or_url('master').url
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.5pre', :source => master_repo_url
end
sources = SourcesManager.sources(%w(test_repo))
sources.map(&:url).should.not.include(master_repo_url)
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
spec = resolver.resolve.values.flatten.first
spec.version.to_s.should == '1.5pre'
spec.defined_in_file.should == fixture('spec-repos/master/Specs/JSONKit/1.5pre/JSONKit.podspec.json')
end
it 'uses explicit source repos for a dependency even when it\'s transitive' do
master_repo_url = SourcesManager.source_with_name_or_url('master').url
test_repo_url = SourcesManager.source_with_name_or_url('test_repo').url
podfile = Podfile.new do
platform :ios
# KeenClient has a dependency on JSONKit 1.4
pod 'KeenClient', '3.2.2', :source => master_repo_url
pod 'JSONKit', '1.4', :source => test_repo_url
end
sources = SourcesManager.sources(%w(master test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
resolver.resolve
possible_specs = resolver.search_for(Dependency.new('JSONKit', '1.4'))
# JSONKit, v1.4 exists in both repos, but we should only ever be offered the test_repo version.
possible_specs.count.should == 1
possible_specs.first.version.to_s.should == '1.4'
possible_specs.first.defined_in_file.should == fixture('spec-repos/test_repo/JSONKit/1.4/JSONKit.podspec')
end
it 'uses global source repos for resolving a transitive dependency even ' \
'if the root dependency has an explicit source' do
test_repo_url = SourcesManager.source_with_name_or_url('test_repo').url
podfile = Podfile.new do
platform :ios, '6.0'
pod 'CrossRepoDependent', '1.0', :source => test_repo_url
end
# CrossRepoDependent depends on AFNetworking which is only available in the master repo.
sources = SourcesManager.sources(%w(master))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
resolver.resolve
specs = resolver.resolve.values.flatten
specs.map(&:name).should ==
%w(AFNetworking AFNetworking/NSURLConnection AFNetworking/NSURLSession AFNetworking/Reachability) +
%w(AFNetworking/Security AFNetworking/Serialization AFNetworking/UIKit CrossRepoDependent)
afnetworking_spec = specs.find { |s| s.name == 'AFNetworking' }
afnetworking_spec.should.not.be.nil
afnetworking_spec.defined_in_file.should == fixture('spec-repos/master/Specs/AFNetworking/2.4.0/AFNetworking.podspec.json')
# Check that if the master source is not available the dependency cannot be resolved.
sources = SourcesManager.sources(%w(test_repo))
resolver = Resolver.new(config.sandbox, podfile, empty_graph, sources)
e = lambda { resolver.resolve }.should.raise Informative
e.message.should.match(/Unable to find a specification for/)
e.message.should.match(/`AFNetworking \(= 2.4.0\)`/)
end
end
#-------------------------------------------------------------------------#
describe 'Pre-release versions' do
it 'resolves explicitly requested pre-release versions' do
@podfile = Podfile.new do
......
......@@ -53,6 +53,25 @@ module Pod
SourcesManager.all.map(&:name).should == %w(master test_repo)
end
it 'includes all sources in an aggregate for a dependency if no source is specified' do
dependency = Dependency.new('JSONKit', '1.4')
aggregate = SourcesManager.aggregate_for_dependency(dependency)
aggregate.sources.map(&:name).should == %w(master test_repo)
end
it 'includes only the one source in an aggregate for a dependency if a source is specified' do
repo_url = 'https://url/to/specs.git'
dependency = Dependency.new('JSONKit', '1.4', :source => repo_url)
source = mock
source.stubs(:name).returns('repo')
SourcesManager.expects(:source_with_url).with(repo_url).returns(source)
aggregate = SourcesManager.aggregate_for_dependency(dependency)
aggregate.sources.map(&:name).should == [source.name]
end
it 'searches for the set of a dependency' do
set = SourcesManager.search(Dependency.new('BananaLib'))
set.class.should == Specification::Set
......@@ -135,7 +154,7 @@ module Pod
end
end
it "generates the search index before performing a search if it doesn't exits" do
it "generates the search index before performing a search if it doesn't exist" do
SourcesManager.stubs(:all).returns([@test_source])
Source::Aggregate.any_instance.expects(:generate_search_index_for_source).with(@test_source).returns('BananaLib' => ['BananaLib'])
SourcesManager.updated_search_index = nil
......
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