Commit 2849fbd8 authored by Samuel Giddins's avatar Samuel Giddins

Add Podfile dependency caching

Since Podfile#dependencies and TargetDefinition#dependencies require both recursing and creating new Dependency objects. Doing this over and over is slow, so now we cache those results.
parent ea311b8c
...@@ -429,7 +429,7 @@ module Pod ...@@ -429,7 +429,7 @@ module Pod
end end
def print_post_install_message def print_post_install_message
podfile_dependencies = podfile.dependencies.uniq.size podfile_dependencies = analysis_result.podfile_dependency_cache.podfile_dependencies.size
pods_installed = root_specs.size pods_installed = root_specs.size
title_options = { :verbose_prefix => '-> '.green } title_options = { :verbose_prefix => '-> '.green }
UI.titled_section('Pod installation complete! ' \ UI.titled_section('Pod installation complete! ' \
...@@ -548,7 +548,7 @@ module Pod ...@@ -548,7 +548,7 @@ module Pod
# @return [void] # @return [void]
# #
def write_lockfiles def write_lockfiles
external_source_pods = podfile.dependencies.select(&:external_source).map(&:root_name).uniq external_source_pods = analysis_result.podfile_dependency_cache.podfile_dependencies.select(&:external_source).map(&:root_name).uniq
checkout_options = sandbox.checkout_sources.select { |root_name, _| external_source_pods.include? root_name } checkout_options = sandbox.checkout_sources.select { |root_name, _| external_source_pods.include? root_name }
@lockfile = Lockfile.generate(podfile, analysis_result.specifications, checkout_options, analysis_result.specs_by_source) @lockfile = Lockfile.generate(podfile, analysis_result.specifications, checkout_options, analysis_result.specs_by_source)
......
...@@ -10,11 +10,12 @@ module Pod ...@@ -10,11 +10,12 @@ module Pod
delegate_installation_options { podfile } delegate_installation_options { podfile }
autoload :AnalysisResult, 'cocoapods/installer/analyzer/analysis_result' autoload :AnalysisResult, 'cocoapods/installer/analyzer/analysis_result'
autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
autoload :SpecsState, 'cocoapods/installer/analyzer/specs_state'
autoload :LockingDependencyAnalyzer, 'cocoapods/installer/analyzer/locking_dependency_analyzer' autoload :LockingDependencyAnalyzer, 'cocoapods/installer/analyzer/locking_dependency_analyzer'
autoload :PodfileDependencyCache, 'cocoapods/installer/analyzer/podfile_dependency_cache'
autoload :PodVariant, 'cocoapods/installer/analyzer/pod_variant' autoload :PodVariant, 'cocoapods/installer/analyzer/pod_variant'
autoload :PodVariantSet, 'cocoapods/installer/analyzer/pod_variant_set' autoload :PodVariantSet, 'cocoapods/installer/analyzer/pod_variant_set'
autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
autoload :SpecsState, 'cocoapods/installer/analyzer/specs_state'
autoload :TargetInspectionResult, 'cocoapods/installer/analyzer/target_inspection_result' autoload :TargetInspectionResult, 'cocoapods/installer/analyzer/target_inspection_result'
autoload :TargetInspector, 'cocoapods/installer/analyzer/target_inspector' autoload :TargetInspector, 'cocoapods/installer/analyzer/target_inspector'
...@@ -54,6 +55,7 @@ module Pod ...@@ -54,6 +55,7 @@ module Pod
@has_dependencies = true @has_dependencies = true
@test_pod_target_analyzer_cache = {} @test_pod_target_analyzer_cache = {}
@test_pod_target_key = Struct.new(:name, :pod_targets) @test_pod_target_key = Struct.new(:name, :pod_targets)
@podfile_dependency_cache = PodfileDependencyCache.from_podfile(podfile)
end end
# Performs the analysis. # Performs the analysis.
...@@ -71,6 +73,7 @@ module Pod ...@@ -71,6 +73,7 @@ module Pod
validate_podfile! validate_podfile!
validate_lockfile_version! validate_lockfile_version!
@result = AnalysisResult.new @result = AnalysisResult.new
@result.podfile_dependency_cache = @podfile_dependency_cache
if installation_options.integrate_targets? if installation_options.integrate_targets?
@result.target_inspections = inspect_targets_to_integrate @result.target_inspections = inspect_targets_to_integrate
else else
...@@ -186,7 +189,7 @@ module Pod ...@@ -186,7 +189,7 @@ module Pod
alias_method :specs_updated?, :specs_updated alias_method :specs_updated?, :specs_updated
def validate_podfile! def validate_podfile!
validator = Installer::PodfileValidator.new(podfile) validator = Installer::PodfileValidator.new(podfile, @podfile_dependency_cache)
validator.validate validator.validate
unless validator.valid? unless validator.valid?
...@@ -234,7 +237,7 @@ module Pod ...@@ -234,7 +237,7 @@ module Pod
pods_state pods_state
else else
state = SpecsState.new state = SpecsState.new
state.added.merge(podfile.dependencies.map(&:root_name)) state.added.merge(@podfile_dependency_cache.podfile_dependencies.map(&:root_name))
state state
end end
end end
...@@ -395,8 +398,7 @@ module Pod ...@@ -395,8 +398,7 @@ module Pod
embedded_targets = aggregate_targets.select(&:requires_host_target?) embedded_targets = aggregate_targets.select(&:requires_host_target?)
analyze_host_targets_in_podfile(aggregate_targets, embedded_targets) analyze_host_targets_in_podfile(aggregate_targets, embedded_targets)
use_frameworks_embedded_targets = embedded_targets.select(&:requires_frameworks?) use_frameworks_embedded_targets, non_use_frameworks_embedded_targets = embedded_targets.partition(&:requires_frameworks?)
non_use_frameworks_embedded_targets = embedded_targets.reject(&:requires_frameworks?)
aggregate_targets.each do |target| aggregate_targets.each do |target|
# For targets that require frameworks, we always have to copy their pods to their # For targets that require frameworks, we always have to copy their pods to their
# host targets because those frameworks will all be loaded from the host target's bundle # host targets because those frameworks will all be loaded from the host target's bundle
...@@ -408,9 +410,9 @@ module Pod ...@@ -408,9 +410,9 @@ module Pod
end end
end end
aggregate_targets.each do |target| aggregate_targets.each do |target|
target.search_paths_aggregate_targets = aggregate_targets.select do |aggregate_target| target.search_paths_aggregate_targets.concat(aggregate_targets.select do |aggregate_target|
target.target_definition.targets_to_inherit_search_paths.include?(aggregate_target.target_definition) target.target_definition.targets_to_inherit_search_paths.include?(aggregate_target.target_definition)
end end).freeze
end end
end end
...@@ -634,7 +636,7 @@ module Pod ...@@ -634,7 +636,7 @@ module Pod
else else
pods_to_update = result.podfile_state.changed + result.podfile_state.deleted pods_to_update = result.podfile_state.changed + result.podfile_state.deleted
pods_to_update += update[:pods] if update_mode == :selected pods_to_update += update[:pods] if update_mode == :selected
local_pod_names = podfile.dependencies.select(&:local?).map(&:root_name) local_pod_names = @podfile_dependency_cache.podfile_dependencies.select(&:local?).map(&:root_name)
pods_to_unlock = local_pod_names.reject do |pod_name| pods_to_unlock = local_pod_names.reject do |pod_name|
sandbox.specification(pod_name).checksum == lockfile.checksum(pod_name) sandbox.specification(pod_name).checksum == lockfile.checksum(pod_name)
end end
...@@ -675,7 +677,7 @@ module Pod ...@@ -675,7 +677,7 @@ module Pod
end end
def verify_no_pods_with_different_sources! def verify_no_pods_with_different_sources!
deps_with_different_sources = podfile.dependencies.group_by(&:root_name). deps_with_different_sources = @podfile_dependency_cache.podfile_dependencies.group_by(&:root_name).
select { |_root_name, dependencies| dependencies.map(&:external_source).uniq.count > 1 } select { |_root_name, dependencies| dependencies.map(&:external_source).uniq.count > 1 }
deps_with_different_sources.each do |root_name, dependencies| deps_with_different_sources.each do |root_name, dependencies|
raise Informative, 'There are multiple dependencies with different ' \ raise Informative, 'There are multiple dependencies with different ' \
...@@ -698,7 +700,7 @@ module Pod ...@@ -698,7 +700,7 @@ module Pod
def dependencies_to_fetch def dependencies_to_fetch
@deps_to_fetch ||= begin @deps_to_fetch ||= begin
deps_to_fetch = [] deps_to_fetch = []
deps_with_external_source = podfile.dependencies.select(&:external_source) deps_with_external_source = @podfile_dependency_cache.podfile_dependencies.select(&:external_source)
if update_mode == :all if update_mode == :all
deps_to_fetch = deps_with_external_source deps_to_fetch = deps_with_external_source
...@@ -731,7 +733,7 @@ module Pod ...@@ -731,7 +733,7 @@ module Pod
elsif update_mode == :all elsif update_mode == :all
pods_to_fetch += result.podfile_state.unchanged + result.podfile_state.deleted pods_to_fetch += result.podfile_state.unchanged + result.podfile_state.deleted
end end
pods_to_fetch += podfile.dependencies. pods_to_fetch += @podfile_dependency_cache.podfile_dependencies.
select { |dep| Hash(dep.external_source).key?(:podspec) && sandbox.specification_path(dep.root_name).nil? }. select { |dep| Hash(dep.external_source).key?(:podspec) && sandbox.specification_path(dep.root_name).nil? }.
map(&:root_name) map(&:root_name)
pods_to_fetch pods_to_fetch
...@@ -739,7 +741,7 @@ module Pod ...@@ -739,7 +741,7 @@ module Pod
end end
def store_existing_checkout_options def store_existing_checkout_options
podfile.dependencies.select(&:external_source).each do |dep| @podfile_dependency_cache.podfile_dependencies.select(&:external_source).each do |dep|
if checkout_options = lockfile && lockfile.checkout_options_for_pod_named(dep.root_name) if checkout_options = lockfile && lockfile.checkout_options_for_pod_named(dep.root_name)
sandbox.store_checkout_source(dep.root_name, checkout_options) sandbox.store_checkout_source(dep.root_name, checkout_options)
end end
...@@ -766,7 +768,7 @@ module Pod ...@@ -766,7 +768,7 @@ module Pod
# grouped by target. # grouped by target.
# #
def resolve_dependencies def resolve_dependencies
duplicate_dependencies = podfile.dependencies.group_by(&:name). duplicate_dependencies = @podfile_dependency_cache.podfile_dependencies.group_by(&:name).
select { |_name, dependencies| dependencies.count > 1 } select { |_name, dependencies| dependencies.count > 1 }
duplicate_dependencies.each do |name, dependencies| duplicate_dependencies.each do |name, dependencies|
UI.warn "There are duplicate dependencies on `#{name}` in #{UI.path podfile.defined_in_file}:\n\n" \ UI.warn "There are duplicate dependencies on `#{name}` in #{UI.path podfile.defined_in_file}:\n\n" \
...@@ -857,8 +859,8 @@ module Pod ...@@ -857,8 +859,8 @@ module Pod
plugin_sources = @plugin_sources || [] plugin_sources = @plugin_sources || []
# Add any sources specified using the :source flag on individual dependencies. # Add any sources specified using the :source flag on individual dependencies.
dependency_sources = podfile.dependencies.map(&:podspec_repo).compact dependency_sources = @podfile_dependency_cache.podfile_dependencies.map(&:podspec_repo).compact
all_dependencies_have_sources = dependency_sources.count == podfile.dependencies.count all_dependencies_have_sources = dependency_sources.count == @podfile_dependency_cache.podfile_dependencies.count
if all_dependencies_have_sources if all_dependencies_have_sources
sources = dependency_sources sources = dependency_sources
...@@ -890,7 +892,7 @@ module Pod ...@@ -890,7 +892,7 @@ module Pod
# #
def verify_platforms_specified! def verify_platforms_specified!
unless installation_options.integrate_targets? unless installation_options.integrate_targets?
podfile.target_definition_list.each do |target_definition| @podfile_dependency_cache.target_definition_list.each do |target_definition|
if !target_definition.empty? && target_definition.platform.nil? if !target_definition.empty? && target_definition.platform.nil?
raise Informative, 'It is necessary to specify the platform in the Podfile if not integrating.' raise Informative, 'It is necessary to specify the platform in the Podfile if not integrating.'
end end
...@@ -909,7 +911,7 @@ module Pod ...@@ -909,7 +911,7 @@ module Pod
def inspect_targets_to_integrate def inspect_targets_to_integrate
inspection_result = {} inspection_result = {}
UI.section 'Inspecting targets to integrate' do UI.section 'Inspecting targets to integrate' do
inspectors = podfile.target_definition_list.map do |target_definition| inspectors = @podfile_dependency_cache.target_definition_list.map do |target_definition|
next if target_definition.abstract? next if target_definition.abstract?
TargetInspector.new(target_definition, config.installation_root) TargetInspector.new(target_definition, config.installation_root)
end.compact end.compact
......
...@@ -33,8 +33,14 @@ module Pod ...@@ -33,8 +33,14 @@ module Pod
# @return [Hash{TargetDefinition => Array<TargetInspectionResult>}] the # @return [Hash{TargetDefinition => Array<TargetInspectionResult>}] the
# results of inspecting the user targets # results of inspecting the user targets
#
attr_accessor :target_inspections attr_accessor :target_inspections
# @return [PodfileDependencyCache] the cache of all dependencies in the
# podfile.
#
attr_accessor :podfile_dependency_cache
# @return [Hash{String=>Symbol}] A hash representing all the user build # @return [Hash{String=>Symbol}] A hash representing all the user build
# configurations across all integration targets. Each key # configurations across all integration targets. Each key
# corresponds to the name of a configuration and its value to # corresponds to the name of a configuration and its value to
......
module Pod
class Installer
class Analyzer
# Caches podfile & target definition dependencies, so they do not need to be re-computed
# from the internal hash on each access
class PodfileDependencyCache
# @return [Array<Pod::Dependency>]
# All the dependencies in the podfile
#
attr_reader :podfile_dependencies
def initialize(podfile_dependencies, dependencies_by_target_definition)
@podfile_dependencies = podfile_dependencies
@dependencies_by_target_definition = dependencies_by_target_definition
end
# Returns the dependencies for the given target definition
#
def target_definition_dependencies(target_definition)
@dependencies_by_target_definition[target_definition] || raise("dependencies for #{target_definition.inspect} do not exist in the cache")
end
# Returns a list of all of the target definitions in the Podfile
#
def target_definition_list
@dependencies_by_target_definition.keys
end
# Creates a {PodfileDependencyCache} from the given {Podfile}
#
def self.from_podfile(podfile)
podfile_dependencies = []
dependencies_by_target_definition = {}
podfile.target_definition_list.each do |target_definition|
deps = target_definition.dependencies.freeze
podfile_dependencies.concat deps
dependencies_by_target_definition[target_definition] = deps
end
podfile_dependencies.uniq!
new(podfile_dependencies.freeze, dependencies_by_target_definition.freeze)
end
end
end
end
end
...@@ -20,8 +20,9 @@ module Pod ...@@ -20,8 +20,9 @@ module Pod
# @param [Podfile] podfile # @param [Podfile] podfile
# The podfile to validate # The podfile to validate
# #
def initialize(podfile) def initialize(podfile, podfile_dependency_cache = Analyzer::PodfileDependencyCache.from_podfile(podfile))
@podfile = podfile @podfile = podfile
@podfile_dependency_cache = podfile_dependency_cache
@errors = [] @errors = []
@warnings = [] @warnings = []
@validated = false @validated = false
...@@ -67,11 +68,7 @@ module Pod ...@@ -67,11 +68,7 @@ module Pod
end end
def validate_pod_directives def validate_pod_directives
dependencies = podfile.target_definitions.flat_map do |_, target| @podfile_dependency_cache.podfile_dependencies.each do |dependency|
target.dependencies
end.uniq
dependencies.each do |dependency|
validate_conflicting_external_sources!(dependency) validate_conflicting_external_sources!(dependency)
end end
end end
...@@ -106,7 +103,7 @@ module Pod ...@@ -106,7 +103,7 @@ module Pod
# @return [void] # @return [void]
# #
def validate_dependencies_are_present! def validate_dependencies_are_present!
if podfile.target_definitions.values.all?(&:empty?) if @podfile_dependency_cache.target_definition_list.all?(&:empty?)
add_warning 'The Podfile does not contain any dependencies.' add_warning 'The Podfile does not contain any dependencies.'
end end
end end
...@@ -116,8 +113,8 @@ module Pod ...@@ -116,8 +113,8 @@ module Pod
# target, or be inherited by a target where `inheritance == complete`. # target, or be inherited by a target where `inheritance == complete`.
# #
def validate_no_abstract_only_pods! def validate_no_abstract_only_pods!
all_dependencies = podfile.dependencies all_dependencies = @podfile_dependency_cache.podfile_dependencies
concrete_dependencies = podfile.target_definition_list.reject(&:abstract?).flat_map(&:dependencies).uniq concrete_dependencies = @podfile_dependency_cache.target_definition_list.reject(&:abstract?).flat_map { |td| @podfile_dependency_cache.target_definition_dependencies(td) }
abstract_only_dependencies = all_dependencies - concrete_dependencies abstract_only_dependencies = all_dependencies - concrete_dependencies
abstract_only_dependencies.each do |dep| abstract_only_dependencies.each do |dep|
add_error "The dependency `#{dep}` is not used in any concrete target." add_error "The dependency `#{dep}` is not used in any concrete target."
...@@ -125,7 +122,7 @@ module Pod ...@@ -125,7 +122,7 @@ module Pod
end end
def validate_no_duplicate_targets! def validate_no_duplicate_targets!
podfile.target_definition_list.group_by { |td| [td.name, td.user_project_path] }. @podfile_dependency_cache.target_definition_list.group_by { |td| [td.name, td.user_project_path] }.
each do |(name, project), definitions| each do |(name, project), definitions|
next unless definitions.size > 1 next unless definitions.size > 1
error = "The target `#{name}` is declared twice" error = "The target `#{name}` is declared twice"
......
...@@ -598,8 +598,11 @@ module Pod ...@@ -598,8 +598,11 @@ module Pod
it 'fetches the dependencies with external sources' do it 'fetches the dependencies with external sources' do
podfile_state = Installer::Analyzer::SpecsState.new podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.added << 'BananaLib' podfile_state.added << 'BananaLib'
@podfile = Podfile.new do
pod 'BananaLib', :git => 'example.com'
end
@analyzer = Installer::Analyzer.new(@sandbox, @podfile)
@analyzer.stubs(:result).returns(stub(:podfile_state => podfile_state)) @analyzer.stubs(:result).returns(stub(:podfile_state => podfile_state))
@podfile.stubs(:dependencies).returns([Dependency.new('BananaLib', :git => 'example.com')])
ExternalSources::DownloaderSource.any_instance.expects(:fetch) ExternalSources::DownloaderSource.any_instance.expects(:fetch)
@analyzer.send(:fetch_external_sources) @analyzer.send(:fetch_external_sources)
end end
...@@ -607,11 +610,12 @@ module Pod ...@@ -607,11 +610,12 @@ module Pod
it 'does not download the same source multiple times for different subspecs' do it 'does not download the same source multiple times for different subspecs' do
podfile_state = Installer::Analyzer::SpecsState.new podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.added << 'ARAnalytics' podfile_state.added << 'ARAnalytics'
@podfile = Podfile.new do
pod 'ARAnalytics/Mixpanel', :git => 'https://github.com/orta/ARAnalytics', :commit => '6f1a1c314894437e7e5c09572c276e644dbfb64b'
pod 'ARAnalytics/HockeyApp', :git => 'https://github.com/orta/ARAnalytics', :commit => '6f1a1c314894437e7e5c09572c276e644dbfb64b'
end
@analyzer = Installer::Analyzer.new(@sandbox, @podfile)
@analyzer.stubs(:result).returns(stub(:podfile_state => podfile_state)) @analyzer.stubs(:result).returns(stub(:podfile_state => podfile_state))
@podfile.stubs(:dependencies).returns([
Dependency.new('ARAnalytics/Mixpanel', :git => 'https://github.com/orta/ARAnalytics', :commit => '6f1a1c314894437e7e5c09572c276e644dbfb64b'),
Dependency.new('ARAnalytics/HockeyApp', :git => 'https://github.com/orta/ARAnalytics', :commit => '6f1a1c314894437e7e5c09572c276e644dbfb64b'),
])
ExternalSources::DownloaderSource.any_instance.expects(:fetch).once ExternalSources::DownloaderSource.any_instance.expects(:fetch).once
@analyzer.send(:fetch_external_sources) @analyzer.send(:fetch_external_sources)
end end
...@@ -984,7 +988,7 @@ module Pod ...@@ -984,7 +988,7 @@ module Pod
describe 'podfile validation' do describe 'podfile validation' do
before do before do
@sandbox = stub('Sandbox') @sandbox = stub('Sandbox')
@podfile = stub('Podfile') @podfile = Podfile.new
@analyzer = Installer::Analyzer.new(@sandbox, @podfile) @analyzer = Installer::Analyzer.new(@sandbox, @podfile)
end end
......
...@@ -371,6 +371,7 @@ module Pod ...@@ -371,6 +371,7 @@ module Pod
describe '#clean_sandbox' do describe '#clean_sandbox' do
before do before do
@analysis_result = Installer::Analyzer::AnalysisResult.new @analysis_result = Installer::Analyzer::AnalysisResult.new
@analysis_result.podfile_dependency_cache = Installer::Analyzer::PodfileDependencyCache.from_podfile(@installer.podfile)
@analysis_result.specifications = [] @analysis_result.specifications = []
@analysis_result.sandbox_state = Installer::Analyzer::SpecsState.new @analysis_result.sandbox_state = Installer::Analyzer::SpecsState.new
@spec = stub(:name => 'Spec', :test_specification? => false) @spec = stub(:name => 'Spec', :test_specification? => false)
...@@ -563,6 +564,7 @@ module Pod ...@@ -563,6 +564,7 @@ module Pod
describe '#write_lockfiles' do describe '#write_lockfiles' do
before do before do
@analysis_result = Installer::Analyzer::AnalysisResult.new @analysis_result = Installer::Analyzer::AnalysisResult.new
@analysis_result.podfile_dependency_cache = Installer::Analyzer::PodfileDependencyCache.from_podfile(@installer.podfile)
@analysis_result.specifications = [fixture_spec('banana-lib/BananaLib.podspec')] @analysis_result.specifications = [fixture_spec('banana-lib/BananaLib.podspec')]
@analysis_result.specs_by_source = {} @analysis_result.specs_by_source = {}
@installer.stubs(:analysis_result).returns(@analysis_result) @installer.stubs(:analysis_result).returns(@analysis_result)
......
This diff is collapsed.
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