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
end
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
title_options = { :verbose_prefix => '-> '.green }
UI.titled_section('Pod installation complete! ' \
......@@ -548,7 +548,7 @@ module Pod
# @return [void]
#
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 }
@lockfile = Lockfile.generate(podfile, analysis_result.specifications, checkout_options, analysis_result.specs_by_source)
......
......@@ -10,11 +10,12 @@ module Pod
delegate_installation_options { podfile }
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 :PodfileDependencyCache, 'cocoapods/installer/analyzer/podfile_dependency_cache'
autoload :PodVariant, 'cocoapods/installer/analyzer/pod_variant'
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 :TargetInspector, 'cocoapods/installer/analyzer/target_inspector'
......@@ -54,6 +55,7 @@ module Pod
@has_dependencies = true
@test_pod_target_analyzer_cache = {}
@test_pod_target_key = Struct.new(:name, :pod_targets)
@podfile_dependency_cache = PodfileDependencyCache.from_podfile(podfile)
end
# Performs the analysis.
......@@ -71,6 +73,7 @@ module Pod
validate_podfile!
validate_lockfile_version!
@result = AnalysisResult.new
@result.podfile_dependency_cache = @podfile_dependency_cache
if installation_options.integrate_targets?
@result.target_inspections = inspect_targets_to_integrate
else
......@@ -186,7 +189,7 @@ module Pod
alias_method :specs_updated?, :specs_updated
def validate_podfile!
validator = Installer::PodfileValidator.new(podfile)
validator = Installer::PodfileValidator.new(podfile, @podfile_dependency_cache)
validator.validate
unless validator.valid?
......@@ -234,7 +237,7 @@ module Pod
pods_state
else
state = SpecsState.new
state.added.merge(podfile.dependencies.map(&:root_name))
state.added.merge(@podfile_dependency_cache.podfile_dependencies.map(&:root_name))
state
end
end
......@@ -395,8 +398,7 @@ module Pod
embedded_targets = aggregate_targets.select(&:requires_host_target?)
analyze_host_targets_in_podfile(aggregate_targets, embedded_targets)
use_frameworks_embedded_targets = embedded_targets.select(&:requires_frameworks?)
non_use_frameworks_embedded_targets = embedded_targets.reject(&:requires_frameworks?)
use_frameworks_embedded_targets, non_use_frameworks_embedded_targets = embedded_targets.partition(&:requires_frameworks?)
aggregate_targets.each do |target|
# 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
......@@ -408,9 +410,9 @@ module Pod
end
end
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)
end
end).freeze
end
end
......@@ -634,7 +636,7 @@ module Pod
else
pods_to_update = result.podfile_state.changed + result.podfile_state.deleted
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|
sandbox.specification(pod_name).checksum == lockfile.checksum(pod_name)
end
......@@ -675,7 +677,7 @@ module Pod
end
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 }
deps_with_different_sources.each do |root_name, dependencies|
raise Informative, 'There are multiple dependencies with different ' \
......@@ -698,7 +700,7 @@ module Pod
def dependencies_to_fetch
@deps_to_fetch ||= begin
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
deps_to_fetch = deps_with_external_source
......@@ -731,7 +733,7 @@ module Pod
elsif update_mode == :all
pods_to_fetch += result.podfile_state.unchanged + result.podfile_state.deleted
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? }.
map(&:root_name)
pods_to_fetch
......@@ -739,7 +741,7 @@ module Pod
end
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)
sandbox.store_checkout_source(dep.root_name, checkout_options)
end
......@@ -766,7 +768,7 @@ module Pod
# grouped by target.
#
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 }
duplicate_dependencies.each do |name, dependencies|
UI.warn "There are duplicate dependencies on `#{name}` in #{UI.path podfile.defined_in_file}:\n\n" \
......@@ -857,8 +859,8 @@ module Pod
plugin_sources = @plugin_sources || []
# 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
dependency_sources = @podfile_dependency_cache.podfile_dependencies.map(&:podspec_repo).compact
all_dependencies_have_sources = dependency_sources.count == @podfile_dependency_cache.podfile_dependencies.count
if all_dependencies_have_sources
sources = dependency_sources
......@@ -890,7 +892,7 @@ module Pod
#
def verify_platforms_specified!
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?
raise Informative, 'It is necessary to specify the platform in the Podfile if not integrating.'
end
......@@ -909,7 +911,7 @@ module Pod
def inspect_targets_to_integrate
inspection_result = {}
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?
TargetInspector.new(target_definition, config.installation_root)
end.compact
......
......@@ -33,8 +33,14 @@ module Pod
# @return [Hash{TargetDefinition => Array<TargetInspectionResult>}] the
# results of inspecting the user targets
#
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
# configurations across all integration targets. Each key
# 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
# @param [Podfile] podfile
# The podfile to validate
#
def initialize(podfile)
def initialize(podfile, podfile_dependency_cache = Analyzer::PodfileDependencyCache.from_podfile(podfile))
@podfile = podfile
@podfile_dependency_cache = podfile_dependency_cache
@errors = []
@warnings = []
@validated = false
......@@ -67,11 +68,7 @@ module Pod
end
def validate_pod_directives
dependencies = podfile.target_definitions.flat_map do |_, target|
target.dependencies
end.uniq
dependencies.each do |dependency|
@podfile_dependency_cache.podfile_dependencies.each do |dependency|
validate_conflicting_external_sources!(dependency)
end
end
......@@ -106,7 +103,7 @@ module Pod
# @return [void]
#
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.'
end
end
......@@ -116,8 +113,8 @@ module Pod
# target, or be inherited by a target where `inheritance == complete`.
#
def validate_no_abstract_only_pods!
all_dependencies = podfile.dependencies
concrete_dependencies = podfile.target_definition_list.reject(&:abstract?).flat_map(&:dependencies).uniq
all_dependencies = @podfile_dependency_cache.podfile_dependencies
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.each do |dep|
add_error "The dependency `#{dep}` is not used in any concrete target."
......@@ -125,7 +122,7 @@ module Pod
end
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|
next unless definitions.size > 1
error = "The target `#{name}` is declared twice"
......
......@@ -598,8 +598,11 @@ module Pod
it 'fetches the dependencies with external sources' do
podfile_state = Installer::Analyzer::SpecsState.new
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))
@podfile.stubs(:dependencies).returns([Dependency.new('BananaLib', :git => 'example.com')])
ExternalSources::DownloaderSource.any_instance.expects(:fetch)
@analyzer.send(:fetch_external_sources)
end
......@@ -607,11 +610,12 @@ module Pod
it 'does not download the same source multiple times for different subspecs' do
podfile_state = Installer::Analyzer::SpecsState.new
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))
@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
@analyzer.send(:fetch_external_sources)
end
......@@ -984,7 +988,7 @@ module Pod
describe 'podfile validation' do
before do
@sandbox = stub('Sandbox')
@podfile = stub('Podfile')
@podfile = Podfile.new
@analyzer = Installer::Analyzer.new(@sandbox, @podfile)
end
......
......@@ -371,6 +371,7 @@ module Pod
describe '#clean_sandbox' do
before do
@analysis_result = Installer::Analyzer::AnalysisResult.new
@analysis_result.podfile_dependency_cache = Installer::Analyzer::PodfileDependencyCache.from_podfile(@installer.podfile)
@analysis_result.specifications = []
@analysis_result.sandbox_state = Installer::Analyzer::SpecsState.new
@spec = stub(:name => 'Spec', :test_specification? => false)
......@@ -563,6 +564,7 @@ module Pod
describe '#write_lockfiles' do
before do
@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.specs_by_source = {}
@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