Commit 8c2adafa authored by Eloy Duran's avatar Eloy Duran

No longer cache Specification::Set in the Set class, but in a Context.

parent 9765ffa0
......@@ -3,14 +3,14 @@ module Pod
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
include Config::Mixin
attr_reader :sandbox
def initialize(podfile)
@podfile = podfile
# FIXME: pass this into the installer as a parameter
@sandbox = Sandbox.new(config.project_pods_root)
@resolver = Resolver.new(@podfile, @sandbox)
end
def lock_file
......@@ -22,8 +22,8 @@ module Pod
@project = Pod::Project.for_platform(@podfile.platform)
# First we need to resolve dependencies across *all* targets, so that the
# same correct versions of pods are being used for all targets. This
# happens when we call `build_specifications'.
build_specifications.each do |spec|
# happens when we call `activated_specifications'.
activated_specifications.each do |spec|
# Add all source files to the project grouped by pod
group = @project.add_pod_group(spec.name)
spec.expanded_source_files.each do |path|
......@@ -42,7 +42,7 @@ module Pod
end
def install_dependencies!
build_specifications.map do |spec|
activated_specifications.map do |spec|
LocalPod.new(spec, sandbox).tap do |pod|
if pod.exists? || spec.local?
puts "Using #{pod}" unless config.silent?
......@@ -63,17 +63,17 @@ module Pod
def install!
@sandbox.prepare_for_install
puts "Installing dependencies of: #{@podfile.defined_in_file}" if config.verbose?
pods = install_dependencies!
puts "Generating support files" unless config.silent?
target_installers.each do |target_installer|
target_specs = build_specifications_for_target(target_installer.target_definition)
target_specs = activated_specifications_for_target(target_installer.target_definition)
pods_for_target = pods.select { |pod| target_specs.include?(pod.specification) }
target_installer.install!(pods_for_target, sandbox)
end
generate_lock_file!(pods)
puts "* Running post install hooks" if config.verbose?
......@@ -83,17 +83,16 @@ module Pod
puts "* Writing Xcode project file to `#{@sandbox.project_path}'" if config.verbose?
project.save_as(@sandbox.project_path)
end
def run_post_install_hooks
# we loop over target installers instead of pods, because we yield the target installer
# to the spec post install hook.
target_installers.each do |target_installer|
build_specifications_for_target(target_installer.target_definition).each do |spec|
activated_specifications_for_target(target_installer.target_definition).each do |spec|
spec.post_install(target_installer)
end
end
@podfile.post_install!(self)
end
......@@ -126,27 +125,29 @@ module Pod
end
end
end
def dependent_specifications_for_each_target_definition
@dependent_specifications_for_each_target_definition ||= Resolver.new(@podfile, @sandbox).resolve
@dependent_specifications_for_each_target_definition ||= @resolver.resolve
end
def dependent_specifications
dependent_specifications_for_each_target_definition.values.flatten
end
def build_specifications
def activated_specifications
dependent_specifications.reject do |spec|
spec.wrapper? || spec.defined_in_set.only_part_of_other_pod?
# Don't activate specs which are only wrappers of subspecs, or share
# source with another pod but aren't activated themselves.
spec.wrapper? || @resolver.context.sets[spec.name].only_part_of_other_pod?
end
end
def build_specifications_for_target(target_definition)
def activated_specifications_for_target(target_definition)
dependent_specifications_for_each_target_definition[target_definition]
end
def download_only_specifications
dependent_specifications - build_specifications
dependent_specifications - activated_specifications
end
end
end
......@@ -54,7 +54,7 @@ module Pod
def prefix_header_filename
"#{@target_definition.lib_name}-prefix.pch"
end
def target_support_files
[copy_resources_filename, prefix_header_filename, xcconfig_filename]
end
......@@ -82,7 +82,7 @@ module Pod
configure_build_configurations(xcconfig_file)
create_files(pods, sandbox)
end
def configure_build_configurations(xcconfig_file)
@target.build_configurations.each do |config|
config.base_configuration = xcconfig_file
......
module Pod
class Resolver
# A Resolver::Context caches specification sets and is used by the resolver
# to ensure that extra dependencies on a set are added to the same instance.
#
# In addition, the context is later on used by Specification to lookup other
# specs, like the on they are a part of.
class Context
attr_reader :sources, :sets, :sandbox
def initialize(sandbox)
@sandbox = sandbox
@sets = {}
@sources = Source::Aggregate.new
end
def find_dependency_set(dependency)
@sets[dependency.name] ||= begin
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
specification = external_source.specification_from_sandbox(@sandbox)
Specification::Set::External.new(specification)
else
@sources.search(dependency)
end
end
end
end
attr_reader :podfile, :sandbox
attr_accessor :context
def initialize(podfile, sandbox)
@podfile = podfile
@sandbox = sandbox
@context = Context.new(@sandbox)
end
def resolve
@podfile.target_definitions.values.inject({}) do |result, target_definition|
@sets, @loaded_spec_names, @specs = [], [], []
@specs = {}
result = @podfile.target_definitions.values.inject({}) do |result, target_definition|
@loaded_specs = []
find_dependency_sets(@podfile, target_definition.dependencies)
result[target_definition] = @specs.sort_by(&:name)
result[target_definition] = @specs.values_at(*@loaded_specs).sort_by(&:name)
result
end
# Specification doesn't need to know more about a context, so we assign
# the other specification, of which this pod is a part, to the spec.
@specs.values.sort_by(&:name).each do |spec|
if spec.part_of_other_pod?
spec.part_of_specification = @context.sets[spec.part_of.name].specification
end
end
result
end
private
# this can be called with anything that has dependencies
# e.g. a Specification or a Podfile.
def find_dependency_sets(has_dependencies, dependency_subset = nil)
(dependency_subset || has_dependencies.dependencies).each do |dependency|
set = find_dependency_set(dependency)
set.required_by(has_dependencies)
unless @loaded_spec_names.include?(dependency.name)
def find_dependency_sets(specification, dependencies = nil)
(dependencies || specification.dependencies).each do |dependency|
set = @context.find_dependency_set(dependency)
set.required_by(specification)
unless @loaded_specs.include?(dependency.name)
# Get a reference to the spec that’s actually being loaded.
# If it’s a subspec dependency, e.g. 'RestKit/Network', then
# find that subspec.
......@@ -28,29 +74,18 @@ module Pod
if dependency.subspec_dependency?
spec = spec.subspec_by_name(dependency.name)
end
validate_platform!(spec)
# Ensure we don't resolve the same spec twice
@loaded_spec_names << spec.name
@specs << spec
@sets << set unless @sets.include?(set)
@loaded_specs << spec.name
@specs[spec.name] = spec
# And recursively load the dependencies of the spec.
find_dependency_sets(spec)
end
end
end
def find_dependency_set(dependency)
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
specification = external_source.specification_from_sandbox(@sandbox)
Specification::Set::External.new(specification)
else
Source.search(dependency)
end
end
def validate_platform!(spec)
unless spec.platform.nil? || spec.platform == @podfile.platform
raise Informative, "The platform required by the Podfile (:#{@podfile.platform}) " \
......
module Pod
class Source
def self.all
@sources ||= begin
repos_dir = Config.instance.repos_dir
unless repos_dir.exist?
raise Informative, "No spec repos found in `#{repos_dir}'. " \
"To fetch the `master' repo run: $ pod setup"
class Aggregate
def all
@sources ||= begin
repos_dir = Config.instance.repos_dir
unless repos_dir.exist?
raise Informative, "No spec repos found in `#{repos_dir}'. " \
"To fetch the `master' repo run: $ pod setup"
end
repos_dir.children.select(&:directory?).map { |repo| Source.new(repo) }
end
end
def search(dependency)
all.map { |s| s.search(dependency) }.compact.first ||
raise(Informative, "Unable to find a pod named `#{dependency.name}'")
end
def search_by_name(query, full_text_search)
result = all.map { |s| s.search_by_name(query, full_text_search) }.flatten
if result.empty?
extra = ", summary, or description" if full_text_search
raise(Informative, "Unable to find a pod with name" \
"#{extra} matching `#{query}'")
end
repos_dir.children.select(&:directory?).map { |repo| new(repo) }
result
end
end
def self.all
Aggregate.new.all
end
def self.search(dependency)
all.map { |s| s.search(dependency) }.compact.first ||
raise(Informative, "Unable to find a pod named `#{dependency.name}'")
Aggregate.new.search(dependency)
end
def self.search_by_name(query, full_text_search)
result = all.map { |s| s.search_by_name(query, full_text_search) }.flatten
if result.empty?
extra = ", summary, or description" if full_text_search
raise(Informative, "Unable to find a pod with name" \
"#{extra} matching `#{query}'")
end
result
def self.search_by_name(name, full_text_search)
Aggregate.new.search_by_name(name, full_text_search)
end
attr_reader :repo
......@@ -35,7 +49,7 @@ module Pod
def pod_sets
@repo.children.map do |child|
if child.directory? && child.basename.to_s != '.git'
Specification::Set.by_pod_dir(child)
Specification::Set.new(child)
end
end.compact
end
......
......@@ -150,9 +150,6 @@ module Pod
# Not attributes
# TODO when we move to use a 'ResolveContext' this should happen there.
attr_accessor :defined_in_set
include Config::Mixin
def local?
......@@ -163,6 +160,10 @@ module Pod
Pathname.new(File.expand_path(source[:local]))
end
# This is assigned the other spec, of which this pod's source is a part, by
# a Resolver.
attr_accessor :part_of_specification
def wrapper?
source_files.empty? && !subspecs.empty?
end
......@@ -191,17 +192,6 @@ module Pod
@dependencies.find { |d| d.top_level_spec_name == name }
end
def part_of_specification_set
if part_of
Set.by_specification_name(part_of.name)
end
end
# Returns the specification for the pod that this pod's source is a part of.
def part_of_specification
(set = part_of_specification_set) && set.specification
end
def pod_destroot
if part_of_other_pod?
part_of_specification.pod_destroot
......@@ -368,7 +358,7 @@ module Pod
yield self if block_given?
end
undef_method :name=, :version=, :source=, :defined_in_set=
undef_method :name=, :version=, :source=
def top_level_parent
top_level_parent = @parent
......@@ -388,7 +378,7 @@ module Pod
end
# Override the getters to always return the value of the top level parent spec.
[:version, :summary, :platform, :license, :authors, :requires_arc, :compiler_flags, :defined_in_set].each do |attr|
[:version, :summary, :platform, :license, :authors, :requires_arc, :compiler_flags].each do |attr|
define_method(attr) { top_level_parent.send(attr) }
end
......
module Pod
class Specification
class Set
def self.sets
@sets ||= {}
end
def self.by_specification_name(name)
sets[name]
end
# This keeps an identity map of sets so that you always get the same Set
# instance for the same pod directory.
def self.by_pod_dir(pod_dir)
set = new(pod_dir)
sets[set.name] ||= set
sets[set.name]
end
attr_reader :pod_dir
def initialize(pod_dir)
......@@ -56,7 +40,7 @@ module Pod
end
def specification
@specification ||= Specification.from_file(specification_path).tap { |spec| spec.defined_in_set = self }
@specification ||= Specification.from_file(specification_path)
end
# Return the first version that matches the current dependency.
......@@ -86,7 +70,6 @@ module Pod
class External < Set
def initialize(specification)
@specification = specification
@specification.defined_in_set = self
@required_by = []
end
......
......@@ -244,7 +244,7 @@ else
installer = SpecHelper::Installer.new(spec)
target_definition = installer.target_installers.first.target_definition
installer.build_specifications_for_target(target_definition).first.resources = 'LICEN*', 'Readme.*'
installer.activated_specifications_for_target(target_definition).first.resources = 'LICEN*', 'Readme.*'
installer.install!
contents = (config.project_pods_root + 'Pods-resources.sh').read
......
require File.expand_path('../../spec_helper', __FILE__)
describe "Pod::Installer" do
before do
@config_before = config
Pod::Config.instance = nil
config.silent = true
config.repos_dir = fixture('spec-repos')
config.project_pods_root = fixture('integration')
end
after do
Pod::Config.instance = @config_before
end
describe ", by default," do
before do
@xcconfig = Pod::Installer.new(Pod::Podfile.new do
......@@ -19,18 +31,6 @@ describe "Pod::Installer" do
end
end
before do
@config_before = config
Pod::Config.instance = nil
config.silent = true
config.repos_dir = fixture('spec-repos')
config.project_pods_root = fixture('integration')
end
after do
Pod::Config.instance = @config_before
end
it "generates a BridgeSupport metadata file from all the pod headers" do
podfile = Pod::Podfile.new do
platform :osx
......@@ -39,7 +39,7 @@ describe "Pod::Installer" do
config.rootspec = podfile
expected = []
installer = Pod::Installer.new(podfile)
pods = installer.build_specifications.map do |spec|
pods = installer.activated_specifications.map do |spec|
spec.header_files.each do |header|
expected << config.project_pods_root + header
end
......
......@@ -10,7 +10,7 @@ class StubbedSet < Pod::Specification::Set
end
end
class StubbedResolver < Pod::Resolver
class StubbedContext < Pod::Resolver::Context
attr_accessor :stub_platform
def find_dependency_set(dependency)
......@@ -38,6 +38,16 @@ describe "Pod::Resolver" do
Pod::Config.instance = @config_before
end
it "has a ResolveContext which holds global state, such as cached specification sets" do
resolver = Pod::Resolver.new(@podfile, stub('sandbox'))
resolver.resolve
resolver.context.sets.values.sort_by(&:name).should == [
Pod::Spec::Set.new(config.repos_dir + 'master/ASIHTTPRequest'),
Pod::Spec::Set.new(config.repos_dir + 'master/ASIWebPageRequest'),
Pod::Spec::Set.new(config.repos_dir + 'master/Reachability'),
].sort_by(&:name)
end
it "returns all specs needed for the dependency" do
specs = Pod::Resolver.new(@podfile, stub('sandbox')).resolve.values.flatten
specs.map(&:class).uniq.should == [Pod::Specification]
......@@ -54,18 +64,19 @@ describe "Pod::Resolver" do
end
it "raises once any of the dependencies does not match the platform of the root spec (Podfile)" do
resolver = StubbedResolver.new(config.rootspec, stub('sandbox'))
resolver = Pod::Resolver.new(config.rootspec, stub('sandbox'))
context = resolver.context = StubbedContext.new(resolver.sandbox)
@podfile.platform :ios
resolver.stub_platform = :ios
context.stub_platform = :ios
lambda { resolver.resolve }.should.not.raise
resolver.stub_platform = :osx
context.stub_platform = :osx
lambda { resolver.resolve }.should.raise Pod::Informative
@podfile.platform :osx
resolver.stub_platform = :osx
context.stub_platform = :osx
lambda { resolver.resolve }.should.not.raise
resolver.stub_platform = :ios
context.stub_platform = :ios
lambda { resolver.resolve }.should.raise Pod::Informative
end
......@@ -77,7 +88,13 @@ describe "Pod::Resolver" do
end
config.rootspec = @podfile
resolver = Pod::Resolver.new(@podfile, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{ LibComponentLogging-Core LibComponentLogging-NSLog RestKit RestKit/Network RestKit/ObjectMapping }
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
LibComponentLogging-Core
LibComponentLogging-NSLog
RestKit
RestKit/Network
RestKit/ObjectMapping
}
end
end
......@@ -15,14 +15,14 @@ describe "Pod::Source" do
it "returns a specification set by name from any spec repo" do
set = Pod::Source.search(Pod::Dependency.new('Reachability'))
set.should == Pod::Spec::Set.by_pod_dir(config.repos_dir + 'repo1/Reachability')
set.should == Pod::Spec::Set.new(config.repos_dir + 'repo1/Reachability')
set = Pod::Source.search(Pod::Dependency.new('JSONKit'))
set.should == Pod::Spec::Set.by_pod_dir(config.repos_dir + 'repo2/JSONKit')
set.should == Pod::Spec::Set.new(config.repos_dir + 'repo2/JSONKit')
end
it "returns a specification set by top level spec name" do
set = Pod::Source.search(Pod::Dependency.new('RestKit/Network'))
set.should == Pod::Spec::Set.by_pod_dir(config.repos_dir + 'repo1/RestKit')
set.should == Pod::Spec::Set.new(config.repos_dir + 'repo1/RestKit')
end
it "raises if a specification set can't be found" do
......
......@@ -7,24 +7,11 @@ class Pod::Spec::Set
end
describe "Pod::Specification::Set" do
it "returns nil in case a set hasn't been resolved yet" do
Pod::Spec::Set.reset!
Pod::Spec::Set.by_specification_name('CocoaLumberjack').should == nil
end
before do
@set = Pod::Spec::Set.by_pod_dir(fixture('spec-repos/master/CocoaLumberjack'))
@set = Pod::Spec::Set.new(fixture('spec-repos/master/CocoaLumberjack'))
@set.reset!
end
it "returns a cached set by name once it has been resolved once" do
Pod::Spec::Set.by_specification_name('CocoaLumberjack').should.eql @set
end
it "always returns the same set instance for a pod dir" do
Pod::Spec::Set.by_pod_dir(fixture('spec-repos/master/CocoaLumberjack')).should.eql @set
end
it "returns the name of the pod" do
@set.name.should == 'CocoaLumberjack'
end
......
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