Commit eb272e33 authored by Dimitris Koutsogiorgas's avatar Dimitris Koutsogiorgas Committed by Dimitris Koutsogiorgas

Remove analyzer `attr_accessor` properties

parent 4f8ffc7b
......@@ -241,15 +241,13 @@ module Pod
# @return [void]
#
def analyze(analyzer = create_analyzer)
analyzer.update = update
@analysis_result = analyzer.analyze
@aggregate_targets = analyzer.result.targets
@aggregate_targets = @analysis_result.targets
end
def create_analyzer(plugin_sources = nil)
Analyzer.new(sandbox, podfile, lockfile, plugin_sources).tap do |analyzer|
Analyzer.new(sandbox, podfile, lockfile, plugin_sources, has_dependencies?, update).tap do |analyzer|
analyzer.installation_options = installation_options
analyzer.has_dependencies = has_dependencies?
end
end
......@@ -684,9 +682,9 @@ module Pod
plugin_sources = run_source_provider_hooks
analyzer = create_analyzer(plugin_sources)
analyze(analyzer)
if analyzer.podfile_needs_install?(analyzer.result)
if analysis_result.podfile_needs_install?
raise Pod::Informative, 'The Podfile has changed, you must run `pod install`'
elsif analyzer.sandbox_needs_install?(analyzer.result)
elsif analysis_result.sandbox_needs_install?
raise Pod::Informative, 'The `Pods` directory is out-of-date, you must run `pod install`'
end
......
......@@ -19,42 +19,51 @@ module Pod
autoload :TargetInspectionResult, 'cocoapods/installer/analyzer/target_inspection_result'
autoload :TargetInspector, 'cocoapods/installer/analyzer/target_inspector'
# @return [Sandbox] The sandbox where the Pods should be installed.
# @return [Sandbox] The sandbox to use for this analysis.
#
attr_reader :sandbox
# @return [Podfile] The Podfile specification that contains the
# information of the Pods that should be installed.
# @return [Podfile] The Podfile specification that contains the information of the Pods that should be installed.
#
attr_reader :podfile
# @return [Lockfile] The Lockfile that stores the information about the
# Pods previously installed on any machine.
# @return [Lockfile] The Lockfile, if available, that stores the information about the Pods previously installed.
#
attr_reader :lockfile
# @return [Array<Source>] Sources provided by plugins
# @return [Array<Source>] Sources provided by plugins or `nil`.
#
attr_reader :plugin_sources
# @return [Bool] Whether the analysis has dependencies and thus sources must be configured.
#
# @note This is used by the `pod lib lint` command to prevent update of specs when not needed.
#
attr_reader :has_dependencies
alias_method :has_dependencies?, :has_dependencies
# @return [Hash, Boolean, nil] Pods that have been requested to be updated or true if all Pods should be updated.
# This can be false if no pods should be updated.
#
attr_reader :pods_to_update
# Initialize a new instance
#
# @param [Sandbox] sandbox @see #sandbox
# @param [Podfile] podfile @see #podfile
# @param [Lockfile] lockfile @see #lockfile
# @param [Array<Source>] plugin_sources @see #plugin_sources
# @param [Sandbox] sandbox @see #sandbox
# @param [Podfile] podfile @see #podfile
# @param [Lockfile] lockfile @see #lockfile
# @param [Array<Source>] plugin_sources @see #plugin_sources
# @param [Boolean] has_dependencies @see #has_dependencies
# @param [Hash, Boolean, nil] pods_to_update @see #pods_to_update
#
def initialize(sandbox, podfile, lockfile = nil, plugin_sources = nil)
def initialize(sandbox, podfile, lockfile = nil, plugin_sources = nil, has_dependencies = true,
pods_to_update = false)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
@plugin_sources = plugin_sources
@update = false
@allow_pre_downloads = true
@has_dependencies = true
@test_pod_target_analyzer_cache = {}
@test_pod_target_key = Struct.new(:name, :pod_targets)
@has_dependencies = has_dependencies
@pods_to_update = pods_to_update
@podfile_dependency_cache = PodfileDependencyCache.from_podfile(podfile)
@result = nil
end
......@@ -74,122 +83,109 @@ module Pod
return @result if @result
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
target_inspections = inspect_targets_to_integrate
else
verify_platforms_specified!
target_inspections = {}
end
@result.podfile_state = generate_podfile_state
podfile_state = generate_podfile_state
store_existing_checkout_options
fetch_external_sources if allow_fetches
fetch_external_sources(podfile_state) if allow_fetches
@locked_dependencies = generate_version_locking_dependencies
resolver_specs_by_target = resolve_dependencies
locked_dependencies = generate_version_locking_dependencies(podfile_state)
resolver_specs_by_target = resolve_dependencies(locked_dependencies)
validate_platforms(resolver_specs_by_target)
@result.specifications = generate_specifications(resolver_specs_by_target)
@result.targets = generate_targets(resolver_specs_by_target)
@result.sandbox_state = generate_sandbox_state
@result.specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
specifications = generate_specifications(resolver_specs_by_target)
targets = generate_targets(resolver_specs_by_target, target_inspections)
sandbox_state = generate_sandbox_state(specifications)
specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
hash[rspecs_by_target[0]] = rspecs_by_target[1].map(&:spec)
end
@result.specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map { |source, specs| [source, specs.map(&:spec).uniq] }]
sources.each { |s| @result.specs_by_source[s] ||= [] }
@result
specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map do |source, specs|
[source, specs.map(&:spec).uniq]
end]
sources.each { |s| specs_by_source[s] ||= [] }
@result = AnalysisResult.new(podfile_state, specs_by_target, specs_by_source, specifications, sandbox_state,
targets, @podfile_dependency_cache)
end
attr_accessor :result
# @return [Bool] Whether an installation should be performed or this
# CocoaPods project is already up to date.
# Updates the git source repositories.
#
def needs_install?
analysis_result = analyze(false)
podfile_needs_install?(analysis_result) || sandbox_needs_install?(analysis_result)
def update_repositories
sources.each do |source|
if source.git?
config.sources_manager.update(source.name, true)
else
UI.message "Skipping `#{source.name}` update because the repository is not a git source repository."
end
end
@specs_updated = true
end
# @param [AnalysisResult] analysis_result
# the analysis result to check for changes
#
# @return [Bool] Whether the podfile has changes respect to the lockfile.
# Returns the sources used to query for specifications.
#
def podfile_needs_install?(analysis_result)
state = analysis_result.podfile_state
needing_install = state.added + state.changed + state.deleted
!needing_install.empty?
end
# @param [AnalysisResult] analysis_result
# the analysis result to check for changes
# When no explicit Podfile sources or plugin sources are defined, this defaults to the master spec repository.
#
# @return [Bool] Whether the sandbox is in synch with the lockfile.
# @return [Array<Source>] the sources to be used in finding specifications, as specified by the podfile or all
# sources.
#
def sandbox_needs_install?(analysis_result)
state = analysis_result.sandbox_state
needing_install = state.added + state.changed + state.deleted
!needing_install.empty?
def sources
@sources ||= begin
sources = podfile.sources
plugin_sources = @plugin_sources || []
# Add any sources specified using the :source flag on individual dependencies.
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
elsif has_dependencies? && sources.empty? && plugin_sources.empty?
sources = ['https://github.com/CocoaPods/Specs.git']
else
sources += dependency_sources
end
result = sources.uniq.map do |source_url|
config.sources_manager.find_or_create_source_with_url(source_url)
end
unless plugin_sources.empty?
result.insert(0, *plugin_sources)
end
result
end
end
#-----------------------------------------------------------------------#
# @!group Configuration
private
# @return [Hash, Boolean, nil] Pods that have been requested to be
# updated or true if all Pods should be updated
#
attr_accessor :update
# @!group Configuration
# @return [Bool] Whether the version of the dependencies which did not
# change in the Podfile should be locked.
#
def update_mode?
update != nil
pods_to_update != nil
end
# @return [Symbol] Whether and how the dependencies in the Podfile
# should be updated.
#
def update_mode
if !update
if !pods_to_update
:none
elsif update == true
elsif pods_to_update == true
:all
elsif !update[:pods].nil?
elsif !pods_to_update[:pods].nil?
:selected
end
end
# @return [Bool] Whether the analysis allows pre-downloads and thus
# modifications to the sandbox.
#
# @note This flag should not be used in installations.
#
# @note This is used by the `pod outdated` command to prevent
# modification of the sandbox in the resolution process.
#
attr_accessor :allow_pre_downloads
alias_method :allow_pre_downloads?, :allow_pre_downloads
# @return [Bool] Whether the analysis has dependencies and thus
# sources must be configured.
#
# @note This is used by the `pod lib lint` command to prevent
# update of specs when not needed.
#
attr_accessor :has_dependencies
alias_method :has_dependencies?, :has_dependencies
#-----------------------------------------------------------------------#
private
# @return [Bool] Whether the analysis has updated sources repositories.
#
attr_accessor :specs_updated
alias_method :specs_updated?, :specs_updated
def validate_podfile!
validator = Installer::PodfileValidator.new(podfile, @podfile_dependency_cache)
validator.validate
......@@ -225,6 +221,8 @@ module Pod
# the name of the Pod (root name of the dependencies) and doesn't
# group them by target definition.
#
# @return [SpecState]
#
def generate_podfile_state
if lockfile
pods_state = nil
......@@ -241,23 +239,6 @@ module Pod
end
end
public
# Updates the git source repositories.
#
def update_repositories
sources.each do |source|
if source.git?
config.sources_manager.update(source.name, true)
else
UI.message "Skipping `#{source.name}` update because the repository is not a git source repository."
end
end
@specs_updated = true
end
private
# Copies the pod_targets of any of the app embedded aggregate targets into
# their potential host aggregate target, if that potential host aggregate target's
# user_target hosts any of the app embedded aggregate targets' user_targets
......@@ -383,13 +364,16 @@ module Pod
# mapping of targets to resolved specs (containing information about test usage)
# aggregate targets
#
# @param [Array<TargetInspection>] target_inspections
# the user target inspections used to construct the aggregate and pod targets.
#
# @return [Array<AggregateTarget>] the list of aggregate targets generated.
#
def generate_targets(resolver_specs_by_target)
def generate_targets(resolver_specs_by_target, target_inspections)
resolver_specs_by_target = resolver_specs_by_target.reject { |td, _| td.abstract? }
pod_targets = generate_pod_targets(resolver_specs_by_target)
pod_targets = generate_pod_targets(resolver_specs_by_target, target_inspections)
aggregate_targets = resolver_specs_by_target.keys.map do |target_definition|
generate_target(target_definition, pod_targets, resolver_specs_by_target)
generate_target(target_definition, target_inspections, pod_targets, resolver_specs_by_target)
end
if installation_options.integrate_targets?
# Copy embedded target pods that cannot have their pods embedded as frameworks to
......@@ -420,6 +404,9 @@ module Pod
# @param [TargetDefinition] target_definition
# the target definition for the user target.
#
# @param [Array<TargetInspection>] target_inspections
# the user target inspections used to construct the aggregate and pod targets.
#
# @param [Array<PodTarget>] pod_targets
# the pod targets, which were generated.
#
......@@ -428,9 +415,9 @@ module Pod
#
# @return [AggregateTarget]
#
def generate_target(target_definition, pod_targets, resolver_specs_by_target)
def generate_target(target_definition, target_inspections, pod_targets, resolver_specs_by_target)
if installation_options.integrate_targets?
target_inspection = result.target_inspections[target_definition]
target_inspection = target_inspections[target_definition]
raise "missing inspection: #{target_definition.name}" unless target_inspection
user_project = target_inspection.project
client_root = user_project.path.dirname.realpath
......@@ -478,7 +465,9 @@ module Pod
pod_targets.each do |pod_target|
next unless pod_target.target_definitions.include?(target_definition)
next unless resolver_specs_by_target[target_definition].any? { |resolver_spec| !resolver_spec.used_by_tests_only? && pod_target.specs.include?(resolver_spec.spec) }
next unless resolver_specs_by_target[target_definition].any? do |resolver_spec|
!resolver_spec.used_by_tests_only? && pod_target.specs.include?(resolver_spec.spec)
end
pod_name = pod_target.pod_name
......@@ -515,9 +504,12 @@ module Pod
# @param [Hash{Podfile::TargetDefinition => Array<ResolvedSpecification>}] resolver_specs_by_target
# the resolved specifications grouped by target.
#
# @param [Array<TargetInspection>] target_inspections
# the user target inspections used to construct the aggregate and pod targets.
#
# @return [Array<PodTarget>]
#
def generate_pod_targets(resolver_specs_by_target)
def generate_pod_targets(resolver_specs_by_target, target_inspections)
if installation_options.deduplicate_targets?
distinct_targets = resolver_specs_by_target.each_with_object({}) do |dependency, hash|
target_definition, dependent_specs = *dependency
......@@ -534,7 +526,7 @@ module Pod
pod_targets = distinct_targets.flat_map do |_root, target_definitions_by_variant|
suffixes = PodVariantSet.new(target_definitions_by_variant.keys).scope_suffixes
target_definitions_by_variant.flat_map do |variant, target_definitions|
generate_pod_target(target_definitions, variant.specs + variant.test_specs, :scope_suffix => suffixes[variant])
generate_pod_target(target_definitions, target_inspections, variant.specs + variant.test_specs, :scope_suffix => suffixes[variant])
end
end
......@@ -559,7 +551,7 @@ module Pod
resolver_specs_by_target.flat_map do |target_definition, specs|
grouped_specs = specs.group_by(&:root).values.uniq
pod_targets = grouped_specs.flat_map do |pod_specs|
generate_pod_target([target_definition], pod_specs.map(&:spec)).scoped(dedupe_cache)
generate_pod_target([target_definition], target_inspections, pod_specs.map(&:spec)).scoped(dedupe_cache)
end
pod_targets.each do |target|
......@@ -628,6 +620,9 @@ module Pod
# @param [TargetDefinitions] target_definitions
# the aggregate target
#
# @param [Array<TargetInspection>] target_inspections
# the user target inspections used to construct the aggregate and pod targets.
#
# @param [Array<Specification>] specs
# the specifications of an equal root.
#
......@@ -636,9 +631,9 @@ module Pod
#
# @return [PodTarget]
#
def generate_pod_target(target_definitions, specs, scope_suffix: nil)
def generate_pod_target(target_definitions, target_inspections, specs, scope_suffix: nil)
if installation_options.integrate_targets?
target_inspections = result.target_inspections.select { |t, _| target_definitions.include?(t) }.values
target_inspections = target_inspections.select { |t, _| target_definitions.include?(t) }.values
user_build_configurations = target_inspections.map(&:build_configurations).reduce({}, &:merge)
archs = target_inspections.flat_map(&:archs).compact.uniq.sort
else
......@@ -707,21 +702,24 @@ module Pod
# is in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}.
#
# @param [SpecState] podfile_state
# the state of the podfile for which dependencies have or have not changed, added, deleted or updated.
#
# @return [Molinillo::DependencyGraph<Dependency>] the dependencies
# generated by the lockfile that prevent the resolver to update
# a Pod.
#
def generate_version_locking_dependencies
def generate_version_locking_dependencies(podfile_state)
if update_mode == :all || !lockfile
LockingDependencyAnalyzer.unlocked_dependency_graph
else
pods_to_update = result.podfile_state.changed + result.podfile_state.deleted
pods_to_update += update[:pods] if update_mode == :selected
deleted_and_changed = podfile_state.changed + podfile_state.deleted
deleted_and_changed += pods_to_update[:pods] if update_mode == :selected
local_pod_names = @podfile_dependency_cache.podfile_dependencies.select(&:local?).map(&:root_name)
pods_to_unlock = local_pod_names.to_set.delete_if do |pod_name|
sandbox.specification(pod_name).checksum == lockfile.checksum(pod_name)
end
LockingDependencyAnalyzer.generate_version_locking_dependencies(lockfile, pods_to_update, pods_to_unlock)
LockingDependencyAnalyzer.generate_version_locking_dependencies(lockfile, deleted_and_changed, pods_to_unlock)
end
end
......@@ -740,16 +738,17 @@ module Pod
# compatible with the version reported by the podspec of the
# external source the resolver will raise.
#
# @param [SpecState] podfile_state
# the state of the podfile for which dependencies have or have not changed, added, deleted or updated.
#
# @return [void]
#
def fetch_external_sources
return unless allow_pre_downloads?
def fetch_external_sources(podfile_state)
verify_no_pods_with_different_sources!
unless dependencies_to_fetch.empty?
unless dependencies_to_fetch(podfile_state).empty?
UI.section 'Fetching external sources' do
dependencies_to_fetch.sort.each do |dependency|
fetch_external_source(dependency, !pods_to_fetch.include?(dependency.root_name))
dependencies_to_fetch(podfile_state).sort.each do |dependency|
fetch_external_source(dependency, !pods_to_fetch(podfile_state).include?(dependency.root_name))
end
end
end
......@@ -775,7 +774,7 @@ module Pod
source.fetch(sandbox)
end
def dependencies_to_fetch
def dependencies_to_fetch(podfile_state)
@deps_to_fetch ||= begin
deps_to_fetch = []
deps_with_external_source = @podfile_dependency_cache.podfile_dependencies.select(&:external_source)
......@@ -783,8 +782,8 @@ module Pod
if update_mode == :all
deps_to_fetch = deps_with_external_source
else
deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch.include?(dep.root_name) }
deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.root_name) }
deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch(podfile_state).include?(dep.root_name) }
deps_to_fetch_if_needed = deps_with_external_source.select { |dep| podfile_state.unchanged.include?(dep.root_name) }
deps_to_fetch += deps_to_fetch_if_needed.select do |dep|
sandbox.specification_path(dep.root_name).nil? ||
!dep.external_source[:path].nil? ||
......@@ -803,13 +802,13 @@ module Pod
locked_checkout_options != sandbox_checkout_options
end
def pods_to_fetch
def pods_to_fetch(podfile_state)
@pods_to_fetch ||= begin
pods_to_fetch = result.podfile_state.added + result.podfile_state.changed
pods_to_fetch = podfile_state.added + podfile_state.changed
if update_mode == :selected
pods_to_fetch += update[:pods]
pods_to_fetch += pods_to_update[:pods]
elsif update_mode == :all
pods_to_fetch += result.podfile_state.unchanged + result.podfile_state.deleted
pods_to_fetch += podfile_state.unchanged + podfile_state.deleted
end
pods_to_fetch += @podfile_dependency_cache.podfile_dependencies.
select { |dep| Hash(dep.external_source).key?(:podspec) && sandbox.specification_path(dep.root_name).nil? }.
......@@ -845,7 +844,7 @@ module Pod
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
#
def resolve_dependencies
def resolve_dependencies(locked_dependencies)
duplicate_dependencies = @podfile_dependency_cache.podfile_dependencies.group_by(&:name).
select { |_name, dependencies| dependencies.count > 1 }
duplicate_dependencies.each do |name, dependencies|
......@@ -855,7 +854,7 @@ module Pod
resolver_specs_by_target = nil
UI.section "Resolving dependencies of #{UI.path(podfile.defined_in_file) || 'Podfile'}" do
resolver = Resolver.new(sandbox, podfile, locked_dependencies, sources, specs_updated?)
resolver = Pod::Resolver.new(sandbox, podfile, locked_dependencies, sources, @specs_updated)
resolver_specs_by_target = resolver.resolve
resolver_specs_by_target.values.flatten(1).map(&:spec).each(&:validate_cocoapods_version)
end
......@@ -865,7 +864,7 @@ module Pod
# Warns for any specification that is incompatible with its target.
#
# @param [Hash{TargetDefinition => Array<Spec>}] resolver_specs_by_target
# the specifications grouped by target.
# the resolved specifications grouped by target.
#
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
......@@ -885,6 +884,9 @@ module Pod
# Returns the list of all the resolved specifications.
#
# @param [Hash{TargetDefinition => Array<Spec>}] resolver_specs_by_target
# the resolved specifications grouped by target.
#
# @return [Array<Specification>] the list of the specifications.
#
def generate_specifications(resolver_specs_by_target)
......@@ -897,10 +899,10 @@ module Pod
# @return [SpecsState] the representation of the state of the manifest
# specifications.
#
def generate_sandbox_state
def generate_sandbox_state(specifications)
sandbox_state = nil
UI.section 'Comparing resolved specification to the sandbox manifest' do
sandbox_analyzer = SandboxAnalyzer.new(sandbox, result.specifications, update_mode?)
sandbox_analyzer = SandboxAnalyzer.new(sandbox, specifications, update_mode?)
sandbox_state = sandbox_analyzer.analyze
sandbox_state.print
end
......@@ -909,58 +911,6 @@ module Pod
#-----------------------------------------------------------------------#
# @!group Analysis internal products
# @return [Molinillo::DependencyGraph<Dependency>] the dependencies
# generated by the lockfile that prevent the resolver to update a
# Pod.
#
attr_reader :locked_dependencies
#-----------------------------------------------------------------------#
public
# Returns the sources used to query for specifications
#
# When no explicit Podfile sources or plugin sources are defined, this
# defaults to the master spec repository.
# available sources ({config.sources_manager.all}).
#
# @return [Array<Source>] the sources to be used in finding
# specifications, as specified by the {#podfile} or all sources.
#
def sources
@sources ||= begin
sources = podfile.sources
plugin_sources = @plugin_sources || []
# Add any sources specified using the :source flag on individual dependencies.
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
elsif has_dependencies? && sources.empty? && plugin_sources.empty?
sources = ['https://github.com/CocoaPods/Specs.git']
else
sources += dependency_sources
end
result = sources.uniq.map do |source_url|
config.sources_manager.find_or_create_source_with_url(source_url)
end
unless plugin_sources.empty?
result.insert(0, *plugin_sources)
end
result
end
end
#-----------------------------------------------------------------------#
private
# @!group Analysis sub-steps
# Checks whether the platform is specified if not integrating
......
module Pod
class Installer
class Analyzer
# A simple container produced after a analysis is completed by the {Analyzer}.
#
class AnalysisResult
# @return [SpecsState] the states of the Podfile specs.
#
attr_accessor :podfile_state
attr_reader :podfile_state
# @return [Hash{TargetDefinition => Array<Specification>}] the
# specifications grouped by target.
# @return [Hash{TargetDefinition => Array<Specification>}] the specifications grouped by target.
#
attr_accessor :specs_by_target
attr_reader :specs_by_target
# @return [Hash{Source => Array<Specification>}] the
# specifications grouped by spec repo source.
# @return [Hash{Source => Array<Specification>}] the specifications grouped by spec repo source.
#
attr_accessor :specs_by_source
attr_reader :specs_by_source
# @return [Array<Specification>] the specifications of the resolved
# version of Pods that should be installed.
# @return [Array<Specification>] the specifications of the resolved version of Pods that should be installed.
#
attr_accessor :specifications
attr_reader :specifications
# @return [SpecsState] the states of the {Sandbox} respect the resolved
# specifications.
# @return [SpecsState] the states of the {Sandbox} respect the resolved specifications.
#
attr_accessor :sandbox_state
# @return [Array<AggregateTarget>] The aggregate targets created for each
# {TargetDefinition} from the {Podfile}.
# @return [Array<AggregateTarget>] The aggregate targets created for each {TargetDefinition} from the {Podfile}.
#
attr_accessor :targets
attr_reader :targets
# @return [Hash{TargetDefinition => Array<TargetInspectionResult>}] the
# results of inspecting the user targets
# @return [PodfileDependencyCache] the cache of all dependencies in the podfile.
#
attr_accessor :target_inspections
attr_reader :podfile_dependency_cache
# @return [PodfileDependencyCache] the cache of all dependencies in the
# podfile.
#
attr_accessor :podfile_dependency_cache
def initialize(podfile_state, specs_by_target, specs_by_source, specifications, sandbox_state, targets,
podfile_dependency_cache)
@podfile_state = podfile_state
@specs_by_target = specs_by_target
@specs_by_source = specs_by_source
@specifications = specifications
@sandbox_state = sandbox_state
@targets = targets
@podfile_dependency_cache = podfile_dependency_cache
end
# @return [Hash{String=>Symbol}] A hash representing all the user build
# configurations across all integration targets. Each key
......@@ -51,6 +53,29 @@ module Pod
result.merge(target.user_build_configurations)
end
end
# @return [Bool] Whether an installation should be performed or this
# CocoaPods project is already up to date.
#
def needs_install?
podfile_needs_install? || sandbox_needs_install?
end
# @return [Bool] Whether the podfile has changes respect to the lockfile.
#
def podfile_needs_install?
state = podfile_state
needing_install = state.added.length + state.changed.length + state.deleted.length
needing_install > 0
end
# @return [Bool] Whether the sandbox is in synch with the lockfile.
#
def sandbox_needs_install?
state = sandbox_state
needing_install = state.added.length + state.changed.length + state.deleted.length
needing_install > 0
end
end
end
end
......
......@@ -20,7 +20,9 @@ module Pod
@sources = []
end
# @param [Source] Source object to be added to the installer
# @param [Source] source object to be added to the installer
#
# @return [void]
#
def add_source(source)
unless source.nil?
......
......@@ -40,17 +40,15 @@ module Pod
end
it 'returns whether an installation should be performed' do
@analyzer.needs_install?.should.be.true
@analyzer.analyze.needs_install?.should.be.true
end
it 'returns whether the Podfile has changes' do
analysis_result = @analyzer.analyze(false)
@analyzer.podfile_needs_install?(analysis_result).should.be.true
@analyzer.analyze(false).podfile_needs_install?.should.be.true
end
it 'returns whether the sandbox is not in sync with the lockfile' do
analysis_result = @analyzer.analyze(false)
@analyzer.sandbox_needs_install?(analysis_result).should.be.true
@analyzer.analyze(false).sandbox_needs_install?.should.be.true
end
#--------------------------------------#
......@@ -469,22 +467,22 @@ module Pod
#--------------------------------------#
it 'locks the version of the dependencies which did not change in the Podfile' do
@analyzer.analyze
@analyzer.send(:locked_dependencies).map(&:payload).map(&:to_s).
should == ['JSONKit (= 1.5pre)', 'SVPullToRefresh (= 0.4)']
podfile_state = @analyzer.send(:generate_podfile_state)
@analyzer.send(:generate_version_locking_dependencies, podfile_state).map(&:payload).map(&:to_s).should ==
['JSONKit (= 1.5pre)', 'SVPullToRefresh (= 0.4)']
end
it 'does not lock the dependencies in update mode' do
@analyzer.update = true
@analyzer.analyze
@analyzer.send(:locked_dependencies).to_a.map(&:payload).should == []
@analyzer.stubs(:pods_to_update).returns(true)
podfile_state = @analyzer.send(:generate_podfile_state)
@analyzer.send(:generate_version_locking_dependencies, podfile_state).to_a.map(&:payload).should == []
end
it 'unlocks dependencies in a case-insensitive manner' do
@analyzer.update = { :pods => %w(JSONKit) }
@analyzer.analyze
@analyzer.send(:locked_dependencies).map(&:payload).map(&:to_s).
should == ['SVPullToRefresh (= 0.4)']
@analyzer.stubs(:pods_to_update).returns(:pods => %w(JSONKit))
podfile_state = @analyzer.send(:generate_podfile_state)
@analyzer.send(:generate_version_locking_dependencies, podfile_state).map(&:payload).map(&:to_s).should ==
['SVPullToRefresh (= 0.4)']
end
it 'unlocks all dependencies with the same root name in update mode' do
......@@ -511,9 +509,8 @@ module Pod
hash['SPEC CHECKSUMS'] = {}
hash['COCOAPODS'] = Pod::VERSION
lockfile = Pod::Lockfile.new(hash)
analyzer = Installer::Analyzer.new(config.sandbox, podfile, lockfile)
analyzer = Installer::Analyzer.new(config.sandbox, podfile, lockfile, nil, true, :pods => %w(AFNetworking))
analyzer.update = { :pods => %w(AFNetworking) }
analyzer.analyze.specifications.
find { |s| s.name == 'AFNetworking' }.
version.to_s.should == '2.6.3'
......@@ -602,9 +599,8 @@ module Pod
pod 'BananaLib', :git => 'example.com'
end
@analyzer = Installer::Analyzer.new(@sandbox, @podfile)
@analyzer.stubs(:result).returns(stub(:podfile_state => podfile_state))
ExternalSources::DownloaderSource.any_instance.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'does not download the same source multiple times for different subspecs' do
......@@ -615,9 +611,8 @@ module Pod
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))
ExternalSources::DownloaderSource.any_instance.expects(:fetch).once
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
xit 'it fetches the specification from either the sandbox or from the remote by default' do
......@@ -1103,82 +1098,95 @@ module Pod
@analyzer.send(:checkout_requires_update?, @dependency).should == true
end
before do
@analyzer.result = Installer::Analyzer::AnalysisResult.new
@analyzer.result.podfile_state = Installer::Analyzer::SpecsState.new
end
it 'uses lockfile checkout options when no source exists in the sandbox' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
@sandbox_manifest.send(:checkout_options_data).delete('BananaLib')
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file, true).returns(downloader)
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file,
true).returns(downloader)
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
downloader.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'uses lockfile checkout options when a different checkout exists in the sandbox' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
@sandbox_manifest.send(:checkout_options_data)['BananaLib'] = @lockfile_checkout_options.merge(:commit => 'other commit')
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file, true).returns(downloader)
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file,
true).returns(downloader)
downloader.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'ignores lockfile checkout options when the podfile state has changed' do
@analyzer.result.podfile_state.changed << 'BananaLib'
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.changed << 'BananaLib'
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file, true).returns(downloader)
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file,
true).returns(downloader)
downloader.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'ignores lockfile checkout options when updating selected pods' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
@analyzer.stubs(:update).returns(:pods => %w(BananaLib))
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
@analyzer.stubs(:pods_to_update).returns(:pods => %w(BananaLib))
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file, true).returns(downloader)
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file,
true).returns(downloader)
downloader.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'ignores lockfile checkout options when updating all pods' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
@analyzer.stubs(:update).returns(true)
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
@analyzer.stubs(:pods_to_update).returns(true)
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file, true).returns(downloader)
ExternalSources.stubs(:from_params).with(@dependency.external_source, @dependency, @podfile.defined_in_file,
true).returns(downloader)
downloader.expects(:fetch)
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'does not use the cache when the podfile instructs not to clean' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
@sandbox_manifest.send(:checkout_options_data).delete('BananaLib')
downloader = stub('DownloaderSource')
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file, false).returns(downloader)
ExternalSources.stubs(:from_params).with(@lockfile_checkout_options, @dependency, @podfile.defined_in_file,
false).returns(downloader)
downloader.expects(:fetch)
@analyzer.installation_options.clean = false
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
it 'does not re-fetch the external source when the sandbox has the correct revision of the source' do
@analyzer.result.podfile_state.unchanged << 'BananaLib'
podfile_state = Installer::Analyzer::SpecsState.new
podfile_state.unchanged << 'BananaLib'
@analyzer.expects(:fetch_external_source).never
@analyzer.send(:fetch_external_sources)
@analyzer.send(:fetch_external_sources, podfile_state)
end
end
end
......
......@@ -63,8 +63,10 @@ module Pod
aggregate_targets = [@ios_target, @osx_target]
@analysis_result = Pod::Installer::Analyzer::AnalysisResult.new
@analysis_result.targets = aggregate_targets
@analysis_result = Pod::Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new,
{}, {}, [],
Pod::Installer::Analyzer::SpecsState.new,
aggregate_targets, nil)
@installation_options = Pod::Installer::InstallationOptions.new
......
......@@ -192,7 +192,6 @@ module Pod
analyzer = Installer::Analyzer.new(config.sandbox, @installer.podfile, @installer.lockfile)
analyzer.stubs(:analyze).returns(result)
analyzer.stubs(:result).returns(result)
@installer.stubs(:create_analyzer).returns(analyzer)
@installer.send(:analyze)
......@@ -336,10 +335,8 @@ module Pod
it 'configures the analyzer to use update mode if appropriate' do
@installer.update = true
Installer::Analyzer.any_instance.expects(:update=).with(true)
@installer.send(:analyze)
@installer.aggregate_targets.map(&:name).sort.should == ['Pods-SampleProject', 'Pods-SampleProjectTests']
@installer.pod_targets.map(&:name).sort.should == ['JSONKit']
analyzer = @installer.send(:create_analyzer)
analyzer.pods_to_update.should.be.true
end
end
......@@ -369,13 +366,13 @@ 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
@analysis_result = Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new, {}, {},
[], Pod::Installer::Analyzer::SpecsState.new, [],
Installer::Analyzer::PodfileDependencyCache.from_podfile(@installer.podfile))
@spec = stub(:name => 'Spec', :test_specification? => false)
@spec.stubs(:root => @spec)
@pod_targets = [PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [@spec], [fixture_target_definition], nil)]
@pod_targets = [PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [@spec],
[fixture_target_definition], nil)]
@installer.stubs(:analysis_result).returns(@analysis_result)
@installer.stubs(:pod_targets).returns(@pod_targets)
@installer.stubs(:aggregate_targets).returns([])
......@@ -421,7 +418,9 @@ module Pod
end
it 'deletes the target support file dirs of the removed aggregate targets' do
aggregate_target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios, fixture_target_definition('MyApp'), config.sandbox.root.dirname, nil, nil, {})
aggregate_target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios,
fixture_target_definition('MyApp'), config.sandbox.root.dirname, nil,
nil, {})
@installer.stubs(:aggregate_targets).returns([aggregate_target])
FileUtils.mkdir_p(config.sandbox.target_support_files_root)
FileUtils.mkdir_p(@installer.aggregate_targets.first.support_files_dir)
......@@ -434,7 +433,9 @@ module Pod
end
it 'does not delete the target support file dirs for non removed aggregate targets' do
aggregate_target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios, fixture_target_definition('MyApp'), config.sandbox.root.dirname, nil, nil, {})
aggregate_target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios,
fixture_target_definition('MyApp'), config.sandbox.root.dirname, nil,
nil, {})
@installer.stubs(:aggregate_targets).returns([aggregate_target])
FileUtils.mkdir_p(config.sandbox.target_support_files_root)
FileUtils.mkdir_p(@installer.aggregate_targets.first.support_files_dir)
......@@ -469,7 +470,8 @@ module Pod
it 'correctly configures the Pod source installer' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition], nil)
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition],
nil)
pod_target.stubs(:platform).returns(:ios)
@installer.stubs(:pod_targets).returns([pod_target])
@installer.instance_variable_set(:@installed_specs, [])
......@@ -479,7 +481,8 @@ module Pod
it 'maintains the list of the installed specs' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition], nil)
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition],
nil)
pod_target.stubs(:platform).returns(:ios)
@installer.stubs(:pod_targets).returns([pod_target, pod_target])
@installer.instance_variable_set(:@installed_specs, [])
......@@ -494,9 +497,9 @@ module Pod
spec.version = Version.new('2.0')
manifest = Lockfile.new('SPEC REPOS' => { 'source1' => ['RestKit'] })
manifest.stubs(:version).with('RestKit').returns(Version.new('1.0'))
analysis_result = Installer::Analyzer::AnalysisResult.new
analysis_result.specifications = [spec]
analysis_result.specs_by_source = { Source.new('source1') => [spec] }
analysis_result = Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new, {},
{ Source.new('source1') => [spec] }, [spec],
Pod::Installer::Analyzer::SpecsState.new, [], nil)
@installer.stubs(:analysis_result).returns(analysis_result)
@installer.sandbox.stubs(:manifest).returns(manifest)
@installer.stubs(:root_specs).returns([spec])
......@@ -515,9 +518,9 @@ module Pod
spec.version = Version.new('1.0')
manifest = Lockfile.new('SPEC REPOS' => { 'source1' => ['RestKit'] })
manifest.stubs(:version).with('RestKit').returns(Version.new('1.0'))
analysis_result = Installer::Analyzer::AnalysisResult.new
analysis_result.specifications = [spec]
analysis_result.specs_by_source = { Source.new('source2') => [spec] }
analysis_result = Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new, {},
{ Source.new('source2') => [spec] }, [spec],
Pod::Installer::Analyzer::SpecsState.new, [], nil)
@installer.stubs(:analysis_result).returns(analysis_result)
@installer.sandbox.stubs(:manifest).returns(manifest)
@installer.stubs(:root_specs).returns([spec])
......@@ -536,9 +539,9 @@ module Pod
spec.version = Version.new('3.0')
manifest = Lockfile.new('SPEC REPOS' => { 'source1' => ['RestKit'] })
manifest.stubs(:version).with('RestKit').returns(Version.new('2.0'))
analysis_result = Installer::Analyzer::AnalysisResult.new
analysis_result.specifications = [spec]
analysis_result.specs_by_source = { Source.new('source2') => [spec] }
analysis_result = Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new, {},
{ Source.new('source2') => [spec] }, [spec],
Pod::Installer::Analyzer::SpecsState.new, [], nil)
@installer.stubs(:analysis_result).returns(analysis_result)
@installer.sandbox.stubs(:manifest).returns(manifest)
@installer.stubs(:root_specs).returns([spec])
......@@ -552,7 +555,8 @@ module Pod
it 'raises when it attempts to install pod source with no target supporting it' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition], nil)
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec], [fixture_target_definition],
nil)
pod_target.stubs(:platform).returns(:ios)
@installer.stubs(:pod_targets).returns([pod_target])
should.raise Informative do
......@@ -563,7 +567,8 @@ module Pod
it 'prints a warning for installed pods that included script phases' do
spec = fixture_spec('coconut-lib/CoconutLib.podspec')
spec.test_specs.first.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"' }
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec, *spec.test_specs], [fixture_target_definition], nil)
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec, *spec.test_specs],
[fixture_target_definition], nil)
pod_target.stubs(:platform).returns(:ios)
sandbox_state = Installer::Analyzer::SpecsState.new
sandbox_state.added << 'CoconutLib'
......@@ -578,7 +583,8 @@ module Pod
it 'does not print a warning for already installed pods that include script phases' do
spec = fixture_spec('coconut-lib/CoconutLib.podspec')
spec.test_specs.first.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"' }
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec, *spec.test_specs], [fixture_target_definition], nil)
pod_target = PodTarget.new(config.sandbox, false, {}, [], Platform.ios, [spec, *spec.test_specs],
[fixture_target_definition], nil)
pod_target.stubs(:platform).returns(:ios)
sandbox_state = Installer::Analyzer::SpecsState.new
sandbox_state.unchanged << 'CoconutLib'
......@@ -608,10 +614,11 @@ module Pod
describe 'Generating pods project' do
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 = {}
podfile_dependency_cache = Installer::Analyzer::PodfileDependencyCache.from_podfile(@installer.podfile)
@analysis_result = Installer::Analyzer::AnalysisResult.new(Pod::Installer::Analyzer::SpecsState.new, {}, {},
[fixture_spec('banana-lib/BananaLib.podspec')],
Pod::Installer::Analyzer::SpecsState.new, [],
podfile_dependency_cache)
@installer.stubs(:analysis_result).returns(@analysis_result)
end
......@@ -638,7 +645,8 @@ module Pod
describe 'Integrating client projects' do
it 'integrates the client projects' do
target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios, fixture_target_definition, config.sandbox.root.dirname, nil, nil, {})
target = AggregateTarget.new(config.sandbox, false, {}, [], Platform.ios, fixture_target_definition,
config.sandbox.root.dirname, nil, nil, {})
@installer.stubs(:aggregate_targets).returns([target])
Installer::UserProjectIntegrator.any_instance.expects(:integrate!)
@installer.send(:integrate_user_project)
......
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