Commit ee5eb989 authored by Fabio Pelosin's avatar Fabio Pelosin

[Analyzer] moved to Installer::Analizer.

parent a583e50a
......@@ -19,7 +19,6 @@ module Pod
end
end
autoload :Analyzer, 'cocoapods/analyzer'
autoload :Command, 'cocoapods/command'
autoload :Executable, 'cocoapods/executable'
autoload :ExternalSources, 'cocoapods/external_sources'
......
module Pod
# Analyzes the Podfile, the Lockfile, and the sandbox manifest to generate
# the information relative to a CocoaPods installation.
#
class Analyzer
include Config::Mixin
# @return [Sandbox] The sandbox where the Pods should be installed.
#
attr_reader :sandbox
# @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.
#
attr_reader :lockfile
# @param [Sandbox] sandbox @see sandbox
# @param [Podfile] podfile @see podfile
# @param [Lockfile] lockfile @see lockfile
#
def initialize(sandbox, podfile, lockfile = nil)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
@update_mode = false
@allow_pre_downloads = true
end
# Performs the analysis.
#
# The Podfile and the Lockfile provide the information necessary to compute
# which specification should be installed. The manifest of the sandbox
# returns which specifications are installed.
#
# @return [void]
#
def analyze
@podfile_state = generate_podfile_state
update_repositories_if_needed
@libraries = generated_libraries
@locked_dependencies = generate_version_locking_dependencies
@specs_by_target = resolve_dependencies
@specifications = generate_specifications
@sandbox_state = generate_sandbox_state
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 = generate_podfile_state
needing_install = state.added + state.changed + state.deleted
!needing_install.empty?
end
# @return [Bool] Whether the sandbox is in synch with the lockfile.
#
def sandbox_needs_install?
lockfile =! sandbox.manifest
end
#-------------------------------------------------------------------------#
# @!group Configuration
# @return [Bool] Whether the version of the dependencies which did non
# change in the Podfile should be locked.
#
attr_accessor :update_mode
alias_method :update_mode?, :update_mode
# @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
#-------------------------------------------------------------------------#
# @!group Analysis products
public
# @return [SpecsState] the states of the Podfile specs.
#
attr_reader :podfile_state
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
#
attr_reader :specs_by_target
# @return [Array<Specification>] the specifications of the resolved version
# of Pods that should be installed.
#
attr_reader :specifications
# @return [SpecsState] the states of the {Sandbox} respect the resolved
# specifications.
#
attr_reader :sandbox_state
# @return [Array<Library>] the libraries generated by the target
# definitions.
#
attr_reader :libraries
#-------------------------------------------------------------------------#
# @!group Analysis steps
private
# Compares the {Podfile} with the {Lockfile} in order to detect which
# dependencies should be locked.
#
# @return [SpecsState] the states of the Podfile specs.
#
# @note As the target definitions share the same sandbox they should have
# the same version of a Pod. For this reason this method returns
# the name of the Pod (root name of the dependencies) and doesn't
# group them by target definition.
#
# @todo [CocoaPods > 0.18] If there isn't a Lockfile all the Pods should
# be marked as added.
#
def generate_podfile_state
if lockfile
pods_state = nil
UI.section "Finding Podfile changes:" do
pods_by_state = lockfile.detect_changes_with_podfile(podfile)
pods_by_state.dup.each do |state, full_names|
pods_by_state[state] = full_names.map { |fn| Specification.root_name(fn) }
end
pods_state = SpecsState.new(pods_by_state)
pods_state.print
end
pods_state
else
SpecsState.new({})
end
end
# Updates the source repositories unless the config indicates to skip it.
#
# @return [void]
#
def update_repositories_if_needed
unless config.skip_repo_update?
UI.section 'Updating spec repositories' do
SourcesManager.update
end
end
end
# Creates the models that represent the libraries generated by CocoaPods.
#
# @note The libraries are generated before the resolution process because
# it might be necessary to infer the platform from the user
# targets, which in turns requires to identify the user project.
#
# @note The specification of the libraries are added in the
# {#resolve_dependencies} step.
#
# @return [Array<Libraries>] the generated libraries.
#
def generated_libraries
libraries = []
podfile.target_definitions.values.each do |target_definition|
lib = Library.new(target_definition)
lib.support_files_root = config.sandbox.library_support_files_dir(lib.name)
if config.integrate_targets?
lib.user_project_path = compute_user_project_path(target_definition)
lib.user_project = Xcodeproj::Project.new(lib.user_project_path)
lib.user_targets = compute_user_project_targets(target_definition, lib.user_project)
lib.user_build_configurations = compute_user_build_configurations(target_definition, lib.user_targets)
lib.platform = compute_platform_for_target_definition(target_definition, lib.user_targets)
else
lib.user_project_path = config.project_root
lib.user_project = nil
lib.user_targets = []
lib.user_build_configurations = {}
lib.platform = target_definition.platform
raise Informative "It is necessary to specify the platform in the Podfile if not integrating." unless target_definition.platform
end
libraries << lib
end
libraries
end
# Generates dependencies that require the specific version of the Pods that
# haven't changed in the {Lockfile}.
#
# These dependencies are passed to the {Resolver}, unless the installer is
# in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}.
#
# @return [Array<Dependency>] the dependencies generate by the lockfile
# that prevent the resolver to update a Pod.
#
def generate_version_locking_dependencies
return [] if update_mode?
podfile_state.unchanged.map do |pod|
lockfile.dependency_to_lock_pod_named(pod)
end
end
# Converts the Podfile in a list of specifications grouped by target.
#
# @note In this step the specs are added to the libraries.
#
# @note As some dependencies might have external sources the resolver is
# aware of the {Sandbox} and interacts with it to download the
# podspecs of the external sources. This is necessary because the
# resolver needs their specifications to analyze their
# dependencies.
#
# @note The specifications of the external sources which are added,
# modified or removed need to deleted from the sandbox before the
# resolution process. Otherwise the resolver might use an incorrect
# specification instead of pre-downloading it.
#
# @note In update mode the resolver is set to always update the specs
# from external sources.
#
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
#
def resolve_dependencies
specs_by_target = nil
if allow_pre_downloads?
changed_pods = podfile_state.added + podfile_state.changed + podfile_state.deleted
changed_pods.each do |pod_name|
podspec = sandbox.specification_path(pod_name)
podspec.delete if podspec
end
end
UI.section "Resolving dependencies of #{UI.path podfile.defined_in_file}" do
resolver = Resolver.new(sandbox, podfile, locked_dependencies)
resolver.update_external_specs = update_mode?
resolver.allow_pre_downloads = allow_pre_downloads?
specs_by_target = resolver.resolve
end
specs_by_target.each do |target_definition, specs|
lib = libraries.find { |l| l.target_definition == target_definition}
lib.specs = specs
end
specs_by_target
end
# Returns the list of all the resolved the resolved specifications.
#
# @return [Array<Specification>] the list of the specifications.
#
def generate_specifications
specs_by_target.values.flatten.uniq
end
# Computes the state of the sandbox respect to the resolved specifications.
#
# The logic is the following:
#
# Added
# - If not present in the sandbox lockfile.
#
# Changed
# - The version of the Pod changed.
# - The specific installed (sub)specs of the same Pod changed.
# - The SHA of the specification file changed.
#
# Removed
# - If a specification is present in the lockfile but not in the resolved
# specs.
#
# Unchanged
# - If none of the above conditions match.
#
# @todo [CocoaPods > 0.18] Version 0.17 falls back to the lockfile of the
# Podfile for the sandbox manifest to prevent the full
# re-installation for upgrading users (this was the old behaviour
# pre sandbox manifest) of all the pods. Drop in 0.18.
#
# @return [SpecsState] the representation of the state of the manifest
# specifications.
#
def generate_sandbox_state
sandbox_lockfile = sandbox.manifest || lockfile
sandbox_state = SpecsState.new
UI.section "Comparing resolved specification to the sandbox manifest:" do
resolved_subspecs_names = specifications.group_by { |s| s.root.name }
resolved_names = resolved_subspecs_names.keys
if sandbox_lockfile
sandbox_subspecs_names = sandbox_lockfile.pod_names.group_by { |name| Specification.root_name(name) }
sandbox_names = sandbox_subspecs_names.keys
all_names = (resolved_names + sandbox_names).uniq
root_specs = specifications.map(&:root).uniq
is_changed = lambda do |name|
spec = root_specs.find { |spec| spec.name == name }
spec.version != sandbox_lockfile.version(name) \
|| spec.checksum != sandbox_lockfile.checksum(name) \
|| resolved_subspecs_names[name] =! sandbox_subspecs_names[name] \
end
all_names.each do |name|
state = case
when resolved_names.include?(name) && !sandbox_names.include?(name) then :added
when !resolved_names.include?(name) && sandbox_names.include?(name) then :deleted
when is_changed.call(name) then :changed
else :unchanged
end
sandbox_state.add_name(name, state)
end
else
sandbox_state.added.concat(resolved_names)
end
sandbox_state.print
end
sandbox_state
end
#-------------------------------------------------------------------------#
# @!group Analysis internal products
# @return [Array<Dependency>] the dependencies generate by the lockfile
# that prevent the resolver to update a Pod.
#
attr_reader :locked_dependencies
#-------------------------------------------------------------------------#
private
# @!group Analysis sub-steps
# Returns the path of the user project that the {TargetDefinition}
# should integrate.
#
# @raise If the project is implicit and there are multiple projects.
#
# @raise If the path doesn't exits.
#
# @return [Pathname] the path of the user project.
#
def compute_user_project_path(target_definition)
if target_definition.user_project_path
user_project_path = Pathname.new(config.project_root + target_definition.user_project_path)
user_project_path = user_project_path.sub_ext '.xcodeproj'
unless user_project_path.exist?
raise Informative, "Unable to find the Xcode project " \
"`#{user_project_path}` for the target `#{target_definition.label}`."
end
else
xcodeprojs = Pathname.glob(config.project_root + '*.xcodeproj')
if xcodeprojs.size == 1
user_project_path = xcodeprojs.first
else
raise Informative, "Could not automatically select an Xcode project. " \
"Specify one in your Podfile like so:\n\n" \
" xcodeproj 'path/to/Project.xcodeproj'\n"
end
end
user_project_path
end
# Returns a list of the targets from the project of {TargetDefinition}
# that needs to be integrated.
#
# @note The method first looks if there is a target specified with
# the `link_with` option of the {TargetDefinition}. Otherwise
# it looks for the target that has the same name of the target
# definition. Finally if no target was found the first
# encountered target is returned (it is assumed to be the one
# to integrate in simple projects).
#
# @note This will only return targets that do **not** already have
# the Pods library in their frameworks build phase.
#
#
def compute_user_project_targets(target_definition, user_project)
if link_with = target_definition.link_with
targets = user_project.targets.select { |t| link_with.include? t.name }
raise Informative, "Unable to find the targets named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty?
elsif target_definition.name != :default
target = user_project.targets.find { |t| t.name == target_definition.name.to_s }
targets = [ target ].compact
raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" if targets.empty?
else
targets = [ user_project.targets.first ].compact
raise Informative, "Unable to find a target" if targets.empty?
end
targets
end
# @return [Hash{String=>Symbol}] A hash representing the user build
# configurations where each key corresponds to the name of a
# configuration and its value to its type (`:debug` or `:release`).
#
def compute_user_build_configurations(target_definition, user_targets)
if user_targets
user_targets.map { |t| t.build_configurations.map(&:name) }.flatten.inject({}) do |hash, name|
unless name == 'Debug' || name == 'Release'
hash[name] = :release
end
hash
end.merge(target_definition.build_configurations || {})
else
target_definition.build_configurations || {}
end
end
# @return [Platform] The platform for the library.
#
# @note This resolves to the lowest deployment target across the user
# targets.
#
# @todo Is assigning the platform to the target definition the best way
# to go?
#
def compute_platform_for_target_definition(target_definition, user_targets)
return target_definition.platform if target_definition.platform
name = nil
deployment_target = nil
user_targets.each do |target|
name ||= target.platform_name
raise Informative, "Targets with different platforms" unless name == target.platform_name
if !deployment_target || deployment_target > Version.new(target.deployment_target)
deployment_target = Version.new(target.deployment_target)
end
end
platform = Platform.new(name, deployment_target)
target_definition.platform = platform
platform
end
#-------------------------------------------------------------------------#
# This class represents the state of a collection of Pods.
#
# @note The names of the pods stored by this class are always the **root**
# name of the specification.
#
class SpecsState
# @param [Hash{Symbol=>String}] pods_by_state
# The **root** name of the pods grouped by their state (`:added`,
# `:removed`, `:changed` or `:unchanged`).
#
def initialize(pods_by_state = nil)
@added = []
@deleted = []
@changed = []
@unchanged = []
if pods_by_state
@added = pods_by_state[:added] || []
@deleted = pods_by_state[:removed] || []
@changed = pods_by_state[:changed] || []
@unchanged = pods_by_state[:unchanged] || []
end
end
# @return [Array<String>] the names of the pods that were added.
#
attr_accessor :added
# @return [Array<String>] the names of the pods that were changed.
#
attr_accessor :changed
# @return [Array<String>] the names of the pods that were deleted.
#
attr_accessor :deleted
# @return [Array<String>] the names of the pods that were unchanged.
#
attr_accessor :unchanged
# Displays the state of each pod.
#
# @return [void]
#
def print
added .each { |pod| UI.message("A".green + " #{pod}", '', 2) }
deleted .each { |pod| UI.message("R".red + " #{pod}", '', 2) }
changed .each { |pod| UI.message("M".yellow + " #{pod}", '', 2) }
unchanged.each { |pod| UI.message("-" + " #{pod}", '', 2) }
end
# Adds the name of a Pod to the give state.
#
# @parm [String]
# the name of the Pod.
#
# @parm [Symbol]
# the state of the Pod.
#
# @raise If there is an attempt to add the name of a subspec.
#
# @return [void]
#
def add_name(name, state)
raise "[Bug] Attempt to add subspec to the pods state" if name.include?('/')
self.send(state) << name
end
end
end
end
......@@ -28,6 +28,7 @@ module Pod
#
class Installer
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
......
module Pod
class Installer
# Analyzes the Podfile, the Lockfile, and the sandbox manifest to generate
# the information relative to a CocoaPods installation.
#
class Analyzer
include Config::Mixin
# @return [Sandbox] The sandbox where the Pods should be installed.
#
attr_reader :sandbox
# @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.
#
attr_reader :lockfile
# @param [Sandbox] sandbox @see sandbox
# @param [Podfile] podfile @see podfile
# @param [Lockfile] lockfile @see lockfile
#
def initialize(sandbox, podfile, lockfile = nil)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
@update_mode = false
@allow_pre_downloads = true
end
# Performs the analysis.
#
# The Podfile and the Lockfile provide the information necessary to compute
# which specification should be installed. The manifest of the sandbox
# returns which specifications are installed.
#
# @return [void]
#
def analyze
@podfile_state = generate_podfile_state
update_repositories_if_needed
@libraries = generated_libraries
@locked_dependencies = generate_version_locking_dependencies
@specs_by_target = resolve_dependencies
@specifications = generate_specifications
@sandbox_state = generate_sandbox_state
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 = generate_podfile_state
needing_install = state.added + state.changed + state.deleted
!needing_install.empty?
end
# @return [Bool] Whether the sandbox is in synch with the lockfile.
#
def sandbox_needs_install?
lockfile =! sandbox.manifest
end
#-------------------------------------------------------------------------#
# @!group Configuration
# @return [Bool] Whether the version of the dependencies which did non
# change in the Podfile should be locked.
#
attr_accessor :update_mode
alias_method :update_mode?, :update_mode
# @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
#-------------------------------------------------------------------------#
# @!group Analysis products
public
# @return [SpecsState] the states of the Podfile specs.
#
attr_reader :podfile_state
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
#
attr_reader :specs_by_target
# @return [Array<Specification>] the specifications of the resolved version
# of Pods that should be installed.
#
attr_reader :specifications
# @return [SpecsState] the states of the {Sandbox} respect the resolved
# specifications.
#
attr_reader :sandbox_state
# @return [Array<Library>] the libraries generated by the target
# definitions.
#
attr_reader :libraries
#-------------------------------------------------------------------------#
# @!group Analysis steps
private
# Compares the {Podfile} with the {Lockfile} in order to detect which
# dependencies should be locked.
#
# @return [SpecsState] the states of the Podfile specs.
#
# @note As the target definitions share the same sandbox they should have
# the same version of a Pod. For this reason this method returns
# the name of the Pod (root name of the dependencies) and doesn't
# group them by target definition.
#
# @todo [CocoaPods > 0.18] If there isn't a Lockfile all the Pods should
# be marked as added.
#
def generate_podfile_state
if lockfile
pods_state = nil
UI.section "Finding Podfile changes:" do
pods_by_state = lockfile.detect_changes_with_podfile(podfile)
pods_by_state.dup.each do |state, full_names|
pods_by_state[state] = full_names.map { |fn| Specification.root_name(fn) }
end
pods_state = SpecsState.new(pods_by_state)
pods_state.print
end
pods_state
else
SpecsState.new({})
end
end
# Updates the source repositories unless the config indicates to skip it.
#
# @return [void]
#
def update_repositories_if_needed
unless config.skip_repo_update?
UI.section 'Updating spec repositories' do
SourcesManager.update
end
end
end
# Creates the models that represent the libraries generated by CocoaPods.
#
# @note The libraries are generated before the resolution process because
# it might be necessary to infer the platform from the user
# targets, which in turns requires to identify the user project.
#
# @note The specification of the libraries are added in the
# {#resolve_dependencies} step.
#
# @return [Array<Libraries>] the generated libraries.
#
def generated_libraries
libraries = []
podfile.target_definitions.values.each do |target_definition|
lib = Library.new(target_definition)
lib.support_files_root = config.sandbox.library_support_files_dir(lib.name)
if config.integrate_targets?
lib.user_project_path = compute_user_project_path(target_definition)
lib.user_project = Xcodeproj::Project.new(lib.user_project_path)
lib.user_targets = compute_user_project_targets(target_definition, lib.user_project)
lib.user_build_configurations = compute_user_build_configurations(target_definition, lib.user_targets)
lib.platform = compute_platform_for_target_definition(target_definition, lib.user_targets)
else
lib.user_project_path = config.project_root
lib.user_project = nil
lib.user_targets = []
lib.user_build_configurations = {}
lib.platform = target_definition.platform
raise Informative "It is necessary to specify the platform in the Podfile if not integrating." unless target_definition.platform
end
libraries << lib
end
libraries
end
# Generates dependencies that require the specific version of the Pods that
# haven't changed in the {Lockfile}.
#
# These dependencies are passed to the {Resolver}, unless the installer is
# in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}.
#
# @return [Array<Dependency>] the dependencies generate by the lockfile
# that prevent the resolver to update a Pod.
#
def generate_version_locking_dependencies
return [] if update_mode?
podfile_state.unchanged.map do |pod|
lockfile.dependency_to_lock_pod_named(pod)
end
end
# Converts the Podfile in a list of specifications grouped by target.
#
# @note In this step the specs are added to the libraries.
#
# @note As some dependencies might have external sources the resolver is
# aware of the {Sandbox} and interacts with it to download the
# podspecs of the external sources. This is necessary because the
# resolver needs their specifications to analyze their
# dependencies.
#
# @note The specifications of the external sources which are added,
# modified or removed need to deleted from the sandbox before the
# resolution process. Otherwise the resolver might use an incorrect
# specification instead of pre-downloading it.
#
# @note In update mode the resolver is set to always update the specs
# from external sources.
#
# @return [Hash{TargetDefinition => Array<Spec>}] the specifications
# grouped by target.
#
def resolve_dependencies
specs_by_target = nil
if allow_pre_downloads?
changed_pods = podfile_state.added + podfile_state.changed + podfile_state.deleted
changed_pods.each do |pod_name|
podspec = sandbox.specification_path(pod_name)
podspec.delete if podspec
end
end
UI.section "Resolving dependencies of #{UI.path podfile.defined_in_file}" do
resolver = Resolver.new(sandbox, podfile, locked_dependencies)
resolver.update_external_specs = update_mode?
resolver.allow_pre_downloads = allow_pre_downloads?
specs_by_target = resolver.resolve
end
specs_by_target.each do |target_definition, specs|
lib = libraries.find { |l| l.target_definition == target_definition}
lib.specs = specs
end
specs_by_target
end
# Returns the list of all the resolved the resolved specifications.
#
# @return [Array<Specification>] the list of the specifications.
#
def generate_specifications
specs_by_target.values.flatten.uniq
end
# Computes the state of the sandbox respect to the resolved specifications.
#
# The logic is the following:
#
# Added
# - If not present in the sandbox lockfile.
#
# Changed
# - The version of the Pod changed.
# - The specific installed (sub)specs of the same Pod changed.
# - The SHA of the specification file changed.
#
# Removed
# - If a specification is present in the lockfile but not in the resolved
# specs.
#
# Unchanged
# - If none of the above conditions match.
#
# @todo [CocoaPods > 0.18] Version 0.17 falls back to the lockfile of the
# Podfile for the sandbox manifest to prevent the full
# re-installation for upgrading users (this was the old behaviour
# pre sandbox manifest) of all the pods. Drop in 0.18.
#
# @return [SpecsState] the representation of the state of the manifest
# specifications.
#
def generate_sandbox_state
sandbox_lockfile = sandbox.manifest || lockfile
sandbox_state = SpecsState.new
UI.section "Comparing resolved specification to the sandbox manifest:" do
resolved_subspecs_names = specifications.group_by { |s| s.root.name }
resolved_names = resolved_subspecs_names.keys
if sandbox_lockfile
sandbox_subspecs_names = sandbox_lockfile.pod_names.group_by { |name| Specification.root_name(name) }
sandbox_names = sandbox_subspecs_names.keys
all_names = (resolved_names + sandbox_names).uniq
root_specs = specifications.map(&:root).uniq
is_changed = lambda do |name|
spec = root_specs.find { |spec| spec.name == name }
spec.version != sandbox_lockfile.version(name) \
|| spec.checksum != sandbox_lockfile.checksum(name) \
|| resolved_subspecs_names[name] =! sandbox_subspecs_names[name] \
end
all_names.each do |name|
state = case
when resolved_names.include?(name) && !sandbox_names.include?(name) then :added
when !resolved_names.include?(name) && sandbox_names.include?(name) then :deleted
when is_changed.call(name) then :changed
else :unchanged
end
sandbox_state.add_name(name, state)
end
else
sandbox_state.added.concat(resolved_names)
end
sandbox_state.print
end
sandbox_state
end
#-------------------------------------------------------------------------#
# @!group Analysis internal products
# @return [Array<Dependency>] the dependencies generate by the lockfile
# that prevent the resolver to update a Pod.
#
attr_reader :locked_dependencies
#-------------------------------------------------------------------------#
private
# @!group Analysis sub-steps
# Returns the path of the user project that the {TargetDefinition}
# should integrate.
#
# @raise If the project is implicit and there are multiple projects.
#
# @raise If the path doesn't exits.
#
# @return [Pathname] the path of the user project.
#
def compute_user_project_path(target_definition)
if target_definition.user_project_path
user_project_path = Pathname.new(config.project_root + target_definition.user_project_path)
user_project_path = user_project_path.sub_ext '.xcodeproj'
unless user_project_path.exist?
raise Informative, "Unable to find the Xcode project " \
"`#{user_project_path}` for the target `#{target_definition.label}`."
end
else
xcodeprojs = Pathname.glob(config.project_root + '*.xcodeproj')
if xcodeprojs.size == 1
user_project_path = xcodeprojs.first
else
raise Informative, "Could not automatically select an Xcode project. " \
"Specify one in your Podfile like so:\n\n" \
" xcodeproj 'path/to/Project.xcodeproj'\n"
end
end
user_project_path
end
# Returns a list of the targets from the project of {TargetDefinition}
# that needs to be integrated.
#
# @note The method first looks if there is a target specified with
# the `link_with` option of the {TargetDefinition}. Otherwise
# it looks for the target that has the same name of the target
# definition. Finally if no target was found the first
# encountered target is returned (it is assumed to be the one
# to integrate in simple projects).
#
# @note This will only return targets that do **not** already have
# the Pods library in their frameworks build phase.
#
#
def compute_user_project_targets(target_definition, user_project)
if link_with = target_definition.link_with
targets = user_project.targets.select { |t| link_with.include? t.name }
raise Informative, "Unable to find the targets named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty?
elsif target_definition.name != :default
target = user_project.targets.find { |t| t.name == target_definition.name.to_s }
targets = [ target ].compact
raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" if targets.empty?
else
targets = [ user_project.targets.first ].compact
raise Informative, "Unable to find a target" if targets.empty?
end
targets
end
# @return [Hash{String=>Symbol}] A hash representing the user build
# configurations where each key corresponds to the name of a
# configuration and its value to its type (`:debug` or `:release`).
#
def compute_user_build_configurations(target_definition, user_targets)
if user_targets
user_targets.map { |t| t.build_configurations.map(&:name) }.flatten.inject({}) do |hash, name|
unless name == 'Debug' || name == 'Release'
hash[name] = :release
end
hash
end.merge(target_definition.build_configurations || {})
else
target_definition.build_configurations || {}
end
end
# @return [Platform] The platform for the library.
#
# @note This resolves to the lowest deployment target across the user
# targets.
#
# @todo Is assigning the platform to the target definition the best way
# to go?
#
def compute_platform_for_target_definition(target_definition, user_targets)
return target_definition.platform if target_definition.platform
name = nil
deployment_target = nil
user_targets.each do |target|
name ||= target.platform_name
raise Informative, "Targets with different platforms" unless name == target.platform_name
if !deployment_target || deployment_target > Version.new(target.deployment_target)
deployment_target = Version.new(target.deployment_target)
end
end
platform = Platform.new(name, deployment_target)
target_definition.platform = platform
platform
end
#-------------------------------------------------------------------------#
# This class represents the state of a collection of Pods.
#
# @note The names of the pods stored by this class are always the **root**
# name of the specification.
#
class SpecsState
# @param [Hash{Symbol=>String}] pods_by_state
# The **root** name of the pods grouped by their state (`:added`,
# `:removed`, `:changed` or `:unchanged`).
#
def initialize(pods_by_state = nil)
@added = []
@deleted = []
@changed = []
@unchanged = []
if pods_by_state
@added = pods_by_state[:added] || []
@deleted = pods_by_state[:removed] || []
@changed = pods_by_state[:changed] || []
@unchanged = pods_by_state[:unchanged] || []
end
end
# @return [Array<String>] the names of the pods that were added.
#
attr_accessor :added
# @return [Array<String>] the names of the pods that were changed.
#
attr_accessor :changed
# @return [Array<String>] the names of the pods that were deleted.
#
attr_accessor :deleted
# @return [Array<String>] the names of the pods that were unchanged.
#
attr_accessor :unchanged
# Displays the state of each pod.
#
# @return [void]
#
def print
added .each { |pod| UI.message("A".green + " #{pod}", '', 2) }
deleted .each { |pod| UI.message("R".red + " #{pod}", '', 2) }
changed .each { |pod| UI.message("M".yellow + " #{pod}", '', 2) }
unchanged.each { |pod| UI.message("-" + " #{pod}", '', 2) }
end
# Adds the name of a Pod to the give state.
#
# @parm [String]
# the name of the Pod.
#
# @parm [Symbol]
# the state of the Pod.
#
# @raise If there is an attempt to add the name of a subspec.
#
# @return [void]
#
def add_name(name, state)
raise "[Bug] Attempt to add subspec to the pods state" if name.include?('/')
self.send(state) << name
end
end
end
end
end
......@@ -20,13 +20,13 @@ def create_analyzer
lockfile = Pod::Lockfile.new(hash)
SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
analyzer = Pod::Analyzer.new(config.sandbox, podfile, lockfile)
analyzer = Pod::Installer::Analyzer.new(config.sandbox, podfile, lockfile)
end
#-----------------------------------------------------------------------------#
module Pod
describe Analyzer do
describe Installer::Analyzer do
before do
@analyzer = create_analyzer
......
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