Commit dec60494 authored by Fabio Pelosin's avatar Fabio Pelosin

[WIP] Installer clean up.

parent 80997638
......@@ -41,9 +41,8 @@ module Pod
def run_install_with_update(update)
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(config.podfile, config.lockfile, sandbox)
resolver.update_mode = update
Installer.new(resolver).install!
installer = Installer.new(sandbox, config.podfile, config.lockfile)
installer.install!
end
def run
......
......@@ -5,16 +5,56 @@ module Pod
# integrates the user project so the Pods libraries can be used out of the
# box.
#
# The installer is capable of doing incremental updates to an existing Pod
# installation.
#
# The installer gets the information that it needs mainly from 3 files:
#
# - Podfile: The specification written by the user that contains
# information about targets and Pods.
# - Podfile.lock: Contains information about the pods that were previously
# installed and in concert with the Podfile provides information about
# which specific version of a Pod should be installed. This file is
# ignored in update mode.
# - Pods.lock: A file contained in the Pods folder that keeps track
# of the pods installed in the local machine. This files is used once
# the exact versions of the Pods has been computed to detect if that
# version is already installed. This file is not intended to be kept
# under source control and is a copy of the Podfile.lock.
#
# Once completed the installer should produce the following file structure:
#
# Pods
# |
# +-- Headers
# | +-- Build
# | | +-- [Pod Name]
# | +-- Public
# | +-- [Pod Name]
# |
# +-- Sources
# | +-- [Pod Name]
# |
# +-- Specifications
# |
# +-- Target Support Files
# | +-- [Target Name]
# | +-- Acknowledgements.markdown
# | +-- Acknowledgements.plist
# | +-- Pods.xcconfig
# | +-- Pods-prefix.pch
# | +-- PodsDummy_Pods.m
# |
# +-- Pods.lock
# |
# +-- Pods.xcodeproj
#
class Installer
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
include Config::Mixin
# @return [Resolver] The resolver used by the installer.
#
attr_reader :resolver
# @return [Sandbox] The sandbox where to install the Pods.
#
attr_reader :sandbox
......@@ -29,67 +69,121 @@ module Pod
#
attr_reader :lockfile
# TODO: The installer should receive the Podfile, the Lockfile, and the
# sandbox. It shouldn't get those values from the resolver, but it should
# create the resolver itself.
# @return [Bool] Whether the installer is in update mode. In update
# mode the contents of the Lockfile are not taken into
# account for deciding what Pods to install.
#
def initialize(resolver)
@resolver = resolver
@podfile = resolver.podfile
@sandbox = resolver.sandbox
attr_reader :update_mode
# @param [Sandbox] sandbox @see sandbox
# @param [Podfile] podfile @see podfile
# @param [Lockfile] lockfile @see lockfile
# @param [Bool] update_mode @see update_mode
#
def initialize(sandbox, podfile, lockfile = nil, update_mode = false)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
@update_mode = update_mode
end
# @return [void] The installation process of CocoaPods is mostly linear
# with very few minor exceptions:
# @return [void] The installation process of is mostly linear with few
# minor complications to keep in mind:
#
# - The stored podspecs need to be cleaned before the resolution step
# otherwise the sandbox might return an old podspec and not download
# the new one from an external source.
# - The resolver might trigger the download of Pods from external sources
# necessary to retrieve their podspec (unless it is instructed not to
# do it).
#
# - Pods from external sources might be already downloaded if it is
# necessary to retrieve their podspec.
# @note The order of the steps is very important and should be changed
# carefully.
#
# TODO:
#
def install!
detect_podfile_changes
perform_global_cleaning
perform_pod_specific_cleaning
# TODO: prepare_for_legacy_compatibility
compare_podfile_and_lockfile
clean_global_support_files
clean_removed_pods
clean_pods_to_install
update_repositories_if_needed
generate_locked_dependencies
resolve_dependencies
# TODO: move to perform_pod_specific_cleaning
UI.section "Removing deleted dependencies" do
remove_deleted_dependencies!
end unless resolver.removed_pods.empty?
# TODO: detect_installed_versions
create_local_pods
detect_pods_to_install
install_dependencies
prepare_pods_project
install_dependencies!
generate_support_files
write_lockfile
# TODO: write_sandbox_lockfile
integrate_user_project
end
# @return [void] the
#
def dry_run
end
# @!group Prepare for legacy compatibility
# @return [void] In this step we prepare the Pods folder in order to be
# compatible with the most recent version of CocoaPods.
#
# @note This step should be removed by version 1.0.
#
def prepare_for_legacy_compatibility
# move_target_support_files_if_needed
# copy_lock_file_to_Pods_lock_if_needed
# move_Local_Podspecs_to_Podspecs_if_needed
# move_pods_to_sources_folder_if_needed
end
# @!group Detect Podfile changes step
# @return [Hash{Symbol => Array<Spec>}] The pods grouped by a symbol
# indicating the state (added, changed, removed, unchanged) as identified
# by the {Lockfile}.
# @return [Hash{Symbol => Array<Spec>}] The name of the pods directly
# specified in the Podfile grouped by a symbol representing their state
# (added, changed, removed, unchanged) as identified by the {Lockfile}.
#
attr_reader :pods_by_state
# @return [void] Computes the pods that need to be installed.
# @return [void] In this step the podfile is compared with the lockfile in
# order to detect which dependencies should be locked.
#
# #TODO: If there is not lockfile all the Pods should be marked as added.
# #TODO: This should use the Pods.lock file because they are used by the
# to detect what needs to be installed.
#
def detect_podfile_changes
def compare_podfile_and_lockfile
if lockfile
UI.section "Finding added, modified or removed dependencies:" do
@pods_by_state = @lockfile.detect_changes_with_podfile(podfile)
print_pods_states_list
@unchanged_pods = (lockfile.pods_names - pods_by_state[:added] - pods_by_state[:changed] - pods_by_state[:removed]).uniq
@pods_by_state = lockfile.detect_changes_with_podfile(podfile)
display_dependencies_state
end
else
@pods_by_state = {}
@unchanged_pods = []
end
end
# @return [void] Outputs a lists of the pods by state.
# @return [void] Displays the state of each dependency.
#
def print_pods_states_list
return if config.verbose?
marks = {:added => "A".green, :changed => "M".yellow, :removed => "R".red, :unchanged => "-" }
def display_dependencies_state
return unless config.verbose?
marks = { :added => "A".green,
:changed => "M".yellow,
:removed => "R".red,
:unchanged => "-" }
pods_by_state.each do |symbol, pod_names|
pod_names.each do |pod_name|
UI.message("#{marks[symbol]} #{pod_name}", '',2)
......@@ -97,25 +191,91 @@ module Pod
end
end
# @!group Cleaning steps
def perform_global_cleaning
@sandbox.prepare_for_install
# @return [void] In this step we clean all the folders that will be
# regenerated from scratch and any file which might not be overwritten.
#
# @TODO: Clean the podspecs of all the pods that aren't unchanged so the
# resolution process doesn't get confused by them.
#
def clean_global_support_files
sandbox.prepare_for_install
end
def perform_pod_specific_cleaning
# TODO: clean the headers of only the pods to install
# @return [void] In this step we clean all the files related to the removed
# Pods.
#
# @TODO: Use the local pod implode.
# @TODO: [#534] Clean all the Pods folder that are not unchanged?
#
def clean_removed_pods
UI.section "Removing deleted dependencies" do
pods_by_state[:removed].each do |pod_name|
UI.section("Removing #{pod_name}", "-> ".red) do
path = sandbox.root + pod_name
path.rmtree if path.exist?
end
end
end unless pods_by_state[:removed].empty?
end
# Resolves the dependencies with the resolver
# @return [void] In this step we clean the files of the Pods that will be
# installed. We clean the files that might affect the resolution process
# and the files that might not be overwritten.
#
def resolve_dependencies
#TODO: prepare the resolver
#TODO: lock the dependencies
UI.section "Resolving dependencies of #{UI.path @podfile.defined_in_file}" do
@specs_by_target = @resolver.resolve
# @TODO: [#247] Clean the headers of only the pods to install.
#
def clean_pods_to_install
end
# @!group Generate locked dependencies step
# @return [void] Lazily updates the source repositories. The update is
# triggered if:
# - There are pods that changed in the Podfile.
# - The lockfile is missing.
# - The installer is in update_mode.
#
# TODO: Remove the lockfile condition once compare_podfile_and_lockfile
# is updated.
#
def update_repositories_if_needed
return if config.skip_repo_update?
changed_pods = (pods_by_state[:added] + pods_by_state[:changed])
UI.section 'Updating spec repositories' do
Command::Repo.new(Command::ARGV.new(["update"])).run
end if !lockfile || !changed_pods.empty? || update_mode
end
# @!group Generate locked dependencies step
# @return [Array<Specification>] All dependencies that have been resolved.
#
attr_reader :locked_dependencies
# @return [void] In this step we generate the dependencies of necessary to
# prevent the resolver from updating the pods which are in unchanged
# state. The Podfile is compared to the Podfile.lock to detect what
# version of a dependency should be locked.
#
def generate_locked_dependencies
if update_mode
@locked_dependencies = []
else
@locked_dependencies = pods_by_state[:unchanged].map do |pod_name|
lockfile.dependency_for_installed_pod_named(pod_name)
end
end
end
# @!group Resolution steps
# @return [Hash{Podfile::TargetDefinition => Array<Spec>}]
# The specifications grouped by target as identified in
......@@ -123,109 +283,158 @@ module Pod
#
attr_reader :specs_by_target
# @return [Array<Specification>] All dependencies that have been resolved.
#
attr_reader :specifications
def prepare_pods_project
end
# Install the Pods. If the resolver indicated that a Pod should be installed
# and it exits, it is removed an then reinstalled. In any case if the Pod
# doesn't exits it is installed.
# @return [void] Converts the Podfile in a list of specifications grouped
# by target.
#
# @return [void]
# In update mode the specs from external sources are always downloaded.
#
def install_dependencies!
UI.section "Downloading dependencies" do
pods.sort_by { |pod| pod.top_specification.name.downcase }.each do |pod|
should_install = @resolver.should_install?(pod.top_specification.name) || !pod.exists?
if should_install
UI.section("Installing #{pod}".green, "-> ".green) do
unless pod.downloaded?
pod.implode
download_pod(pod)
def resolve_dependencies
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
@specs_by_target = resolver.resolve
@specifications = specs_by_target.values.flatten
end
# The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
generate_docs(pod)
# Here we clean pod's that just have been downloaded or have been
# pre-downloaded in AbstractExternalSource#specification_from_sandbox.
pod.clean! if config.clean?
end
else
UI.section("Using #{pod}", "-> ".green)
# @!group Detect Pods to install step
# @return [Array<String>] The names of the Pods that should be installed.
#
attr_reader :pods_to_install
# @return [<void>] In this step the pods to install are detected.
# The pods to install are identified as the Pods that don't exist in the
# sandbox or the Pods whose version differs from the one of the lockfile.
#
# In update mode specs originating from external dependencies and or from
# head sources are always reinstalled.
#
# TODO: Decide a how the Lockfile should report versions.
# TODO: [#534] Detect if the folder of a Pod is empty.
#
def detect_pods_to_install
changed_pods_names = []
if lockfile
changed_pods = pods.select do |pod|
pod.top_specification.version != lockfile.pods_versions[pod.name]
end
if update_mode
changed_pods_names += pods.select do |pods|
pod.top_specification.version.head? ||
resolver.pods_from_external_sources.include?(pod.name)
end
end
changed_pods_names += @pods_by_state[:added] + @pods_by_state[:changed]
else
changed_pods = pods
end
def generate_support_files
UI.section "Generating support files" do
UI.message "- Running pre install hooks" do
run_pre_install_hooks
not_existing_pods = pods.reject { |pod| pod.exists? }
@pods_to_install = (changed_pods + not_existing_pods).uniq
end
UI.message"- Installing targets" do
generate_target_support_files
end
UI.message "- Running post install hooks" do
# Post install hooks run _before_ saving of project, so that they can alter it before saving.
run_post_install_hooks
end
UI.message "- Writing Xcode project file to #{UI.path @sandbox.project_path}" do
project.save_as(@sandbox.project_path)
end
# @!group Install step
UI.message "- Writing lockfile in #{UI.path config.project_lockfile}" do
@lockfile = Lockfile.generate(@podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk(config.project_lockfile)
# @return [Hash{Podfile::TargetDefinition => Array<LocalPod>}]
#
attr_reader :pods_by_target
# @return [Array<LocalPod>] A list of LocalPod instances for each
# dependency sorted by name.
# (that is not a download-only one?)
attr_reader :pods
# @return [void] In this step the specifications obtained by the resolver
# are converted in local pods. The LocalPod class is responsible to
# handle the concrete representation of a specification a sandbox.
#
# @TODO: [#535] Pods should be accumulated per Target, also in the Local
# Pod class. The Local Pod class should have a method to add itself
# to a given project so it can use the sources of all the activated
# podspecs across all targets. Also cleaning should take into
# account that.
#
def create_local_pods
@pods_by_target = {}
specs_by_target.each do |target_definition, specs|
@pods_by_target[target_definition] = specs.map do |spec|
if spec.local?
sandbox.locally_sourced_pod_for_spec(spec, target_definition.platform)
else
sandbox.local_pod_for_spec(spec, target_definition.platform)
end
end.uniq.compact
end
@pods = pods_by_target.values.flatten.uniq.sort_by { |pod| pod.name.downcase }
end
def integrate_user_project
UserProjectIntegrator.new(@podfile).integrate! if config.integrate_targets?
end
# @!group Supporting operations
def project
return @project if @project
@project = Pod::Project.new
@project.user_build_configurations = @podfile.user_build_configurations
# @!group Install step
# @return [void] Install the Pods. If the resolver indicated that a Pod
# should be installed and it exits, it is removed an then reinstalled. In
# any case if the Pod doesn't exits it is installed.
#
def install_dependencies
UI.section "Downloading dependencies" do
pods.each do |pod|
# Add all source files to the project grouped by pod
pod.relative_source_files_by_spec.each do |spec, paths|
parent_group = pod.local? ? @project.local_pods : @project.pods
group = @project.add_spec_group(spec.name, parent_group)
paths.each do |path|
group.files.new('path' => path.to_s)
if pods_to_install.include?(pod)
UI.section("Installing #{pod}".green, "-> ".green) do
install_local_pod(pod)
end
else
UI.section("Using #{pod}", "-> ".green)
end
end
# Add a group to hold all the target support files
@project.main_group.groups.new('name' => 'Targets Support Files')
@project
end
def target_installers
@target_installers ||= @podfile.target_definitions.values.map do |definition|
TargetInstaller.new(@podfile, project, definition) unless definition.empty?
end.compact
end
# @return [void] Downloads, clean and generates the documentation of a pod.
#
# @note The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
#
# @note In this step we clean also the Pods that have been pre-downloaded
# in AbstractExternalSource#specification_from_sandbox.
#
# TODO: [#529] Podspecs should not be preserved anymore to prevent user
# confusion. Currently we are copying the ones form external sources
# in `Local Podspecs` and this feature is not needed anymore.
# I think that copying all the used podspecs would be helpful for
# debugging.
#
def install_local_pod(pod)
unless pod.downloaded?
pod.implode
download_pod(pod)
end
generate_docs_if_needed(pod)
pod.clean! if config.clean?
end
# @return [void] Downloads a Pod forcing the `bleeding edge' version if
# requested.
#
def download_pod(pod)
downloader = Downloader.for_pod(pod)
# Force the `bleeding edge' version if necessary.
if pod.top_specification.version.head?
if downloader.respond_to?(:download_head)
downloader.download_head
else
raise Informative, "The downloader of class `#{downloader.class.name}' does not support the `:head' option."
raise Informative,
"The downloader of class `#{downloader.class.name}' does not" \
"support the `:head' option."
end
else
downloader.download
......@@ -233,8 +442,10 @@ module Pod
pod.downloaded = true
end
#TODO: move to generator ?
def generate_docs(pod)
# @return [void] Generates the documentation of a Pod unless it exists
# for a given version.
#
def generate_docs_if_needed(pod)
doc_generator = Generator::Documentation.new(pod)
if ( config.generate_docs? && !doc_generator.already_installed? )
UI.section " > Installing documentation"
......@@ -244,20 +455,77 @@ module Pod
end
end
# @TODO: use the local pod implode
# @!group Generate Pods project and support files step
# @return [void] Creates and populates the targets of the pods project.
#
def remove_deleted_dependencies!
resolver.removed_pods.each do |pod_name|
UI.section("Removing #{pod_name}", "-> ".red) do
path = sandbox.root + pod_name
path.rmtree if path.exist?
# @note Post install hooks run _before_ saving of project, so that they can
# alter it before saving.
#
def generate_support_files
UI.section "Generating support files" do
prepare_pods_project
add_source_files_to_pods_project
run_pre_install_hooks
generate_target_support_files
run_post_install_hooks
write_pod_project
end
end
# @return [Project] The Pods project.
#
attr_reader :pods_project
# @return [void] In this step we create the Pods project from scratch if it
# doesn't exists. If the Pods project exists instead we clean it and
# prepare it for installation.
#
def prepare_pods_project
UI.message "- Creating Pods project" do
@pods_project = Pod::Project.new
pods_project.user_build_configurations = podfile.user_build_configurations
pods_project.main_group.groups.new('name' => 'Targets Support Files')
end
end
# @return [void] In this step we add the source files of the Pods to the
# Pods project. The source files are grouped by Pod and in turn by subspec
# (recursively). Pods are generally added to the Pods group. However, if
# they are local they are added to the Local Pods group.
#
# @TODO [#143] This step is quite slow and should be made incremental by
# modifying only the files of the changed pods. Xcodeproj deletion
# and sorting of folders is required.
#
def add_source_files_to_pods_project
UI.message "- Adding source files to Pods project" do
pods.each do |pod|
pod.relative_source_files_by_spec.each do |spec, paths|
if pod.local?
parent_group = pods_project.local_pods
else
parent_group = pods_project.pods
end
group = pods_project.add_spec_group(spec.name, parent_group)
paths.each do |path|
group.files.new('path' => path.to_s)
end
end
end
end
end
def target_installers
@target_installers ||= podfile.target_definitions.values.map do |definition|
TargetInstaller.new(podfile, pods_project, definition) unless definition.empty?
end.compact
end
def run_pre_install_hooks
UI.message "- Running pre install hooks" do
pods_by_target.each do |target_definition, pods|
pods.each do |pod|
pod.top_specification.pre_install(pod, target_definition)
......@@ -265,10 +533,12 @@ module Pod
end
@podfile.pre_install!(self)
end
end
def run_post_install_hooks
# we loop over target installers instead of pods, because we yield the target installer
# to the spec post install hook.
UI.message "- Running post install hooks" do
# we loop over target installers instead of pods, because we yield the
# target installer to the spec post install hook.
target_installers.each do |target_installer|
specs_by_target[target_installer.target_definition].each do |spec|
spec.post_install(target_installer)
......@@ -276,17 +546,20 @@ module Pod
end
@podfile.post_install!(self)
end
end
def generate_target_support_files
UI.message"- Installing targets" do
target_installers.each do |target_installer|
pods_for_target = pods_by_target[target_installer.target_definition]
target_installer.install!(pods_for_target, @sandbox)
target_installer.install!(pods_for_target, sandbox)
acknowledgements_path = target_installer.target_definition.acknowledgements_path
Generator::Acknowledgements.new(target_installer.target_definition,
pods_for_target).save_as(acknowledgements_path)
generate_dummy_source(target_installer)
end
end
end
def generate_dummy_source(target_installer)
class_name_identifier = target_installer.target_definition.label
......@@ -295,37 +568,46 @@ module Pod
pathname = Pathname.new(sandbox.root + filename)
dummy_source.save_as(pathname)
project_file = project.files.new('path' => filename)
project.group("Targets Support Files") << project_file
project_file = pods_project.files.new('path' => filename)
pods_project.group("Targets Support Files") << project_file
target_installer.target.source_build_phases.first << project_file
end
# @return [Array<Specification>] All dependencies that have been resolved.
def specifications
specs_by_target.values.flatten
def write_pod_project
UI.message "- Writing Xcode project file to #{UI.path @sandbox.project_path}" do
pods_project.save_as(@sandbox.project_path)
end
# @return [Array<LocalPod>] A list of LocalPod instances for each
# dependency that is not a download-only one.
def pods
pods_by_target.values.flatten.uniq
end
def pods_by_target
@pods_by_spec = {}
result = {}
specs_by_target.each do |target_definition, specs|
@pods_by_spec[target_definition.platform] = {}
result[target_definition] = specs.map do |spec|
if spec.local?
@sandbox.locally_sourced_pod_for_spec(spec, target_definition.platform)
else
@sandbox.local_pod_for_spec(spec, target_definition.platform)
# @!group Lockfile related steps
def write_lockfile
UI.message "- Writing Lockfile in #{UI.path config.project_lockfile}" do
@lockfile = Lockfile.generate(podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk(config.project_lockfile)
end
end.uniq.compact
end
result
# @TODO: [#552] Implement
#
def write_sandbox_lockfile
end
# @!group Integrate user project step
# @return [void] In this step the user project is integrated. The Pods
# libraries are added, the build script are added, and the xcconfig files
# are set.
#
# @TODO: [#397] The libraries should be cleaned and the re-added on every
# install. Maybe a clean_user_project phase should be added.
#
def integrate_user_project
UserProjectIntegrator.new(podfile).integrate! if config.integrate_targets?
end
end
end
......@@ -50,8 +50,8 @@ module Pod
end
# @return [Hash{String => Hash}] A hash where the name of the pods are
# the keys and the values are the parameters of an {AbstractExternalSource}
# of the dependency that required the pod.
# the keys and the values are the parameters of an
# {AbstractExternalSource} of the dependency that required the pod.
#
def external_sources
@external_sources ||= to_hash["EXTERNAL SOURCES"] || {}
......@@ -67,8 +67,8 @@ module Pod
end
# @return [Hash{String => Version}] A Hash containing the name
# of the installed Pods as the keys and their corresponding {Version}
# as the values.
# of the installed Pods (top spec name) as the keys and their
# corresponding {Version} as the values.
#
def pods_versions
unless @pods_versions
......@@ -77,6 +77,7 @@ module Pod
pod = pod.keys.first unless pod.is_a?(String)
name, version = name_and_version_for_pod(pod)
@pods_versions[name] = version
@pods_versions[name.split('/').first] = version
end
end
@pods_versions
......
require 'colored'
module Pod
class Resolver
include Config::Mixin
# @return [Bool] Whether the resolver should find the pods to install or
# the pods to update.
# The resolver is responsible of generating a list of specifications grouped
# by target for a given Podfile.
#
attr_accessor :update_mode
# @return [Bool] Whether the resolver should update the external specs
# in the resolution process.
# Its current implementation is naive, in the sense that it can't do full
# automatic resolves like Bundler:
#
attr_accessor :update_external_specs
# @return [Podfile] The Podfile used by the resolver.
# http://patshaughnessy.net/2011/9/24/how-does-bundler-bundle
#
attr_reader :podfile
# @return [Lockfile] The Lockfile used by the resolver.
# Another important aspect to keep in mind of the current implementation
# is that the order of the dependencies matters.
#
attr_reader :lockfile
class Resolver
include Config::Mixin
# @return [Sandbox] The Sandbox used by the resolver to find external
# dependencies.
#
attr_reader :sandbox
# @return [Array<Strings>] The name of the pods that have an
# external source.
# @return [Podfile] The Podfile used by the resolver.
#
attr_reader :pods_from_external_sources
attr_reader :podfile
# @return [Array<Set>] A cache of the sets used to resolve the dependencies.
# @return [Array<Dependency>] The list of dependencies locked to a specific
# version.
#
attr_reader :cached_sets
attr_reader :locked_dependencies
# @return [Source::Aggregate] A cache of the sources needed to find the
# podspecs.
# @return [Bool] Whether the resolver should update the external specs
# in the resolution process. This option is used for detecting changes
# in with the Podfile without affecting the existing Pods installation
# (see `pod outdated`).
#
attr_reader :cached_sources
# @TODO: This implementation is not clean, because if the spec doesn't
# exists the sandbox will actually download it and result modified.
#
attr_accessor :update_external_specs
def initialize(sandbox, podfile, locked_dependencies)
@sandbox = sandbox
@podfile = podfile
@locked_dependencies = locked_dependencies
end
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}]
# Returns the resolved specifications grouped by target.
#
attr_reader :specs_by_target
def initialize(podfile, lockfile, sandbox)
@podfile = podfile
@lockfile = lockfile
@sandbox = sandbox
@update_external_specs = true
@cached_sets = {}
@cached_sources = Source::Aggregate.new
# @return [Array<Specification>] All The specifications loaded by the
# resolver.
#
def specs
@cached_specs.values.uniq
end
# Identifies the specifications that should be installed according whether
# the resolver is in update mode or not.
# @return [Array<Strings>] The name of the pods that have an
# external source.
#
# @TODO: Add an attribute to the specification class?
#
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}] specs_by_target
attr_reader :pods_from_external_sources
# @return [Hash{TargetDefinition => Array<Specification>}] specs_by_target
# Identifies the specifications that should be installed according
# whether the resolver is in update mode or not.
#
def resolve
@cached_sources = Source::Aggregate.new
@cached_sets = {}
@cached_specs = {}
@specs_by_target = {}
@pods_from_external_sources = []
@pods_to_lock = []
if @lockfile
@pods_by_state = @lockfile.detect_changes_with_podfile(podfile)
UI.section "Finding added, modified or removed dependencies:" do
marks = {:added => "A".green, :changed => "M".yellow, :removed => "R".red, :unchanged => "-" }
@pods_by_state.each do |symbol, pod_names|
pod_names.each do |pod_name|
UI.message("#{marks[symbol]} #{pod_name}", '',2)
end
end
end if config.verbose?
@pods_to_lock = (lockfile.pods_names - @pods_by_state[:added] - @pods_by_state[:changed] - @pods_by_state[:removed]).uniq
end
unless config.skip_repo_update?
UI.section 'Updating spec repositories' do
Command::Repo.new(Command::ARGV.new(["update"])).run
end if !@lockfile || !(@pods_by_state[:added] + @pods_by_state[:changed]).empty? || update_mode
end
@podfile.target_definitions.values.each do |target_definition|
podfile.target_definitions.values.each do |target_definition|
UI.section "Resolving dependencies for target `#{target_definition.name}' (#{target_definition.platform})" do
@loaded_specs = []
find_dependency_specs(@podfile, target_definition.dependencies, target_definition)
find_dependency_specs(podfile, target_definition.dependencies, target_definition)
@specs_by_target[target_definition] = @cached_specs.values_at(*@loaded_specs).sort_by(&:name)
end
end
......@@ -98,102 +86,45 @@ module Pod
@specs_by_target
end
# @return [Array<Specification>] The specifications loaded by the resolver.
#
def specs
@cached_specs.values.uniq
end
# @return [Bool] Whether a pod should be installed/reinstalled.
#
def should_install?(name)
pods_to_install.include? name
end
# @return [Array<Strings>] The name of the pods that should be
# installed/reinstalled.
#
def pods_to_install
unless @pods_to_install
if lockfile
@pods_to_install = specs.select do |spec|
spec.version != lockfile.pods_versions[spec.pod_name]
end.map(&:name)
if update_mode
@pods_to_install += specs.select do |spec|
spec.version.head? || pods_from_external_sources.include?(spec.pod_name)
end.map(&:name)
end
@pods_to_install += @pods_by_state[:added] + @pods_by_state[:changed]
else
@pods_to_install = specs.map(&:name)
end
end
@pods_to_install
end
private
# @return [Array<Strings>] The name of the pods that were installed
# but don't have any dependency anymore. The name of the Pods are
# stripped from subspecs.
# @return [Array<Set>] A cache of the sets used to resolve the dependencies.
#
def removed_pods
return [] unless lockfile
unless @removed_pods
previusly_installed = lockfile.pods_names.map { |pod_name| pod_name.split('/').first }
installed = specs.map { |spec| spec.name.split('/').first }
@removed_pods = previusly_installed - installed
end
@removed_pods
end
private
attr_reader :cached_sets
# @return [Set] The cached set for a given dependency.
# @return [Source::Aggregate] A cache of the sources needed to find the
# podspecs.
#
def find_cached_set(dependency, platform)
set_name = dependency.name.split('/').first
@cached_sets[set_name] ||= begin
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
if update_mode && update_external_specs
# Always update external sources in update mode.
specification = external_source.specification_from_external(@sandbox, platform)
else
# Don't update external sources in install mode if not needed.
specification = external_source.specification_from_sandbox(@sandbox, platform)
end
set = Specification::Set::External.new(specification)
if dependency.subspec_dependency?
@cached_sets[dependency.top_level_spec_name] ||= set
end
set
else
@cached_sources.search(dependency)
end
end
end
attr_reader :cached_sources
# Resolves the dependencies of a specification and stores them in @cached_specs
# @return [void] Resolves recursively the dependencies of a specification
# and stores them in @cached_specs
#
# @param [Specification] dependent_specification
# The specification whose dependencies are being resolved.
#
# @param [Array<Dependency>] dependencies
# @param [TargetDefinition] target_definition
# The dependencies of the specification.
#
# @return [void]
# @param [TargetDefinition] target_definition
# The target definition that owns the specification.
#
def find_dependency_specs(dependent_specification, dependencies, target_definition)
dependencies.each do |dependency|
# Replace the dependency with a more specific one if the pod is already installed.
if !update_mode && @pods_to_lock.include?(dependency.name)
dependency = lockfile.dependency_for_installed_pod_named(dependency.name)
end
# Replace the dependency with a more specific one if the pod is already
# installed.
# @TODO: check for compatibility?
locked_dep = locked_dependencies.find { |locked| locked.name == dependency.name }
dependency = locked_dep if locked_dep
UI.message("- #{dependency}", '', 2) do
set = find_cached_set(dependency, target_definition.platform)
set.required_by(dependency, dependent_specification.to_s)
# Ensure we don't resolve the same spec twice for one target
unless @loaded_specs.include?(dependency.name)
if @loaded_specs.include?(dependency.name)
validate_platform(@cached_specs[dependency.name], target_definition)
else
spec = set.specification_by_name(dependency.name)
@pods_from_external_sources << spec.pod_name if dependency.external?
@loaded_specs << spec.name
......@@ -202,20 +133,53 @@ module Pod
spec.activate_platform(target_definition.platform)
spec.version.head = dependency.head?
# And recursively load the dependencies of the spec.
find_dependency_specs(spec, spec.dependencies, target_definition) if spec.dependencies
find_dependency_specs(spec, spec.dependencies, target_definition)
validate_platform(spec, target_definition)
end
end
end
end
validate_platform(spec || @cached_specs[dependency.name], target_definition)
# @return [Set] The cached set for a given dependency.
#
# If the update_external_specs flag is activated the dependencies with
# external sources are always resolved against the remote. Otherwise the
# specification is retrieved from the sanbox that fetches the external
# source only if needed.
#
def find_cached_set(dependency, platform)
set_name = dependency.name.split('/').first
@cached_sets[set_name] ||= begin
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
if update_external_specs
spec = external_source.specification_from_external(sandbox, platform)
else
spec = external_source.specification_from_sandbox(sandbox, platform)
end
set = Specification::Set::External.new(spec)
if dependency.subspec_dependency?
@cached_sets[dependency.top_level_spec_name] ||= set
end
set
else
cached_sources.search(dependency)
end
end
end
# Ensures that a spec is compatible with the platform of a target.
# @return [void] Ensures that a spec is compatible with the platform of a
# target.
#
# @raises If the spec is not supported by the target.
#
def validate_platform(spec, target)
unless spec.available_platforms.any? { |platform| target.platform.supports?(platform) }
raise Informative, "[!] The platform of the target `#{target.name}' (#{target.platform}) is not compatible with `#{spec}' which has a minimun requirement of #{spec.available_platforms.join(' - ')}.".red
raise Informative, "The platform of the target `#{target.name}' "\
"(#{target.platform}) is not compatible with `#{spec}' which " \
"has a minimum requirement of #{spec.available_platforms.join(' - ')}."
end
end
end
......
......@@ -8,30 +8,6 @@ module Pod
end
describe "by default" do
before do
podfile = Podfile.new do
platform :ios
xcodeproj 'MyProject'
pod 'JSONKit'
end
sandbox = Sandbox.new(fixture('integration'))
resolver = Resolver.new(podfile, nil, sandbox)
@xcconfig = Installer.new(resolver).target_installers.first.xcconfig.to_hash
end
it "sets the header search paths where installed Pod headers can be found" do
@xcconfig['ALWAYS_SEARCH_USER_PATHS'].should == 'YES'
end
it "configures the project to load all members that implement Objective-c classes or categories from the static library" do
@xcconfig['OTHER_LDFLAGS'].should == '-ObjC'
end
it "sets the PODS_ROOT build variable" do
@xcconfig['PODS_ROOT'].should.not == nil
end
it "generates a BridgeSupport metadata file from all the pod headers" do
podfile = Podfile.new do
platform :osx
......@@ -39,8 +15,7 @@ module Pod
end
sandbox = Sandbox.new(fixture('integration'))
resolver = Resolver.new(podfile, nil, sandbox)
installer = Installer.new(resolver)
installer = Installer.new(sandbox, podfile)
pods = installer.specifications.map do |spec|
LocalPod.new(spec, installer.sandbox, podfile.target_definitions[:default].platform)
end
......@@ -89,6 +64,34 @@ module Pod
end
end
describe "concerning xcconfig files generation" do
before do
podfile = Podfile.new do
platform :ios
xcodeproj 'MyProject'
pod 'JSONKit'
end
sandbox = Sandbox.new(fixture('integration'))
installer = Installer.new(sandbox, podfile)
@xcconfig = installer.target_installers.first.xcconfig.to_hash
end
it "sets the header search paths where installed Pod headers can be found" do
@xcconfig['ALWAYS_SEARCH_USER_PATHS'].should == 'YES'
end
it "configures the project to load all members that implement Objective-c classes or categories from the static library" do
@xcconfig['OTHER_LDFLAGS'].should == '-ObjC'
end
it "sets the PODS_ROOT build variable" do
@xcconfig['PODS_ROOT'].should.not == nil
end
end
describe "concerning multiple pods originating form the same spec" do
extend SpecHelper::Fixture
......
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