Commit 4bb14e58 authored by Fabio Pelosin's avatar Fabio Pelosin

[WIP][Installer] Adapt for LocalPod refactor

parent d2b90c07
......@@ -16,6 +16,7 @@
- Subspecs now do not inherit the files patterns from the parent spec.
- The workspace is written only if needed greatly reducing the occasions in
which Xcode asks to revert.
- Specification hooks are only called when the specification is installed.
###### Specification DSL
......
......@@ -28,9 +28,11 @@ module Pod
#
class Installer
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :FileReferencesInstaller, 'cocoapods/installer/file_references_installer'
autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
include Config::Mixin
......@@ -53,9 +55,9 @@ module Pod
# @param [Lockfile] lockfile @see lockfile
#
def initialize(sandbox, podfile, lockfile = nil)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
end
# @return [Bool] Whether the installer is in update mode. In update mode
......@@ -80,34 +82,37 @@ module Pod
#
def install!
analyze
generate_local_pods
generate_names_of_pods_to_install
detect_pods_to_install
prepare_for_legacy_compatibility
prepare_sandbox
install_dependencies
UI.section "Downloading dependencies" do
create_file_accessors
install_pod_sources
end
UI.section "Generating Pods Project" do
prepare_pods_project
generate_target_installers
add_file_references_to_pods_project
run_pre_install_hooks
generate_target_support_files
install_file_references
install_targets
run_post_install_hooks
write_pod_project
write_lockfiles
end
integrate_user_project
if config.integrate_targets?
UI.section "Integrating client projects" do
integrate_user_project
end
end
end
#-------------------------------------------------------------------------#
public
# @!group API
#
# This is the tentative API for the podfile and the specification hooks.
# @!group Installation results
# @return [Analyzer] the analyzer which provides the information about what
# needs to be installed.
......@@ -118,20 +123,6 @@ module Pod
#
attr_reader :pods_project
# @return [Array<TargetInstaller>]
#
attr_reader :target_installers
# @return [Hash{TargetDefinition => Array<LocalPod>}] The local pod
# instances grouped by target.
#
attr_reader :local_pods_by_target
# @return [Array<LocalPod>] The list of LocalPod instances for each
# dependency sorted by name.
#
attr_reader :local_pods
# @return [Array<String>] The Pods that should be installed.
#
attr_reader :names_of_pods_to_install
......@@ -141,19 +132,21 @@ module Pod
#
attr_reader :libraries
#--------------------------------------#
# @return [Array<Specification>] The specifications that where installed.
#
attr_accessor :installed_specs
# @!group Hooks compatibility
#-------------------------------------------------------------------------#
alias :project :pods_project
alias :pods :local_pods
private
#-------------------------------------------------------------------------#
# TODO: This is recreating the file accessors
# TODO: the file accessor should be initializable without a path list
# @!group Installation steps
private
# @return [void]
#
def analyze
@analyzer = Analyzer.new(sandbox, podfile, lockfile)
@analyzer.update_mode = update_mode
......@@ -161,46 +154,6 @@ module Pod
@libraries = analyzer.libraries
end
# Converts the specifications produced by the Resolver in local pods.
#
# The LocalPod class is responsible to handle the concrete representation
# of a specification in the {Sandbox}.
#
# @return [void]
#
# @todo [#535] LocalPods should resolve the path of the specifications
# passing the library as arguments.
#
# @todo Why the local pods are generated by the sandbox? I guess because
# some where pre-downloaded? However the sandbox should just store
# the name of those Pods.
#
def generate_local_pods
@local_pods_by_target = {}
analyzer.specs_by_target.each do |target_definition, specs|
libray = libraries.find {|l| l.target_definition == target_definition }
# TODO the sandbox cached the local pods by target definition
# take into account local? in specification#==
locally_sourced_pods = {}
local_pods = {}
libray.local_pods = specs.map do |spec|
if spec.local?
local_pod = (locally_sourced_pods[spec.root.name] ||= LocalPod::LocalSourcedPod.new(spec.root, sandbox, target_definition.platform))
local_pod.add_specification(spec)
else
local_pod = (local_pods[spec.root.name] ||= LocalPod.new(spec.root, sandbox, target_definition.platform))
local_pod.add_specification(spec)
end
local_pod
end.uniq.compact
end
@local_pods = libraries.map(&:local_pods).flatten.uniq.sort_by { |pod| pod.name.downcase }
end
# Computes the list of the Pods that should be installed or reinstalled in
# the {Sandbox}.
#
......@@ -217,17 +170,27 @@ module Pod
# @todo There could be issues with the current implementation regarding
# external specs.
#
def generate_names_of_pods_to_install
changed_pods_names = []
if update_mode
changed_pods_names += pods.select do |pod|
pod.top_specification.version.head? ||
resolver.pods_from_external_sources.include?(pod.name)
end
end
changed_pods_names += analyzer.sandbox_state.added + analyzer.sandbox_state.changed
not_existing_pods = local_pods.reject { |pod| pod.exists? }
@names_of_pods_to_install = (changed_pods_names + not_existing_pods.map(&:name)).uniq
def detect_pods_to_install
names = []
# TODO
# specs_by_root_name.each do |root_name, specs|
# if update_mode
# if specs.any? { |spec| spec.version.head? } #|| resolver.pods_from_external_sources.include?(root_name)
# @names_of_pods_to_install << root_name
# end
# end
# unless pod_installation_exists?(root_name)
# @names_of_pods_to_install << root_name
# end
# end
# TODO user root name.
names += analyzer.sandbox_state.added + analyzer.sandbox_state.changed
names = names.map { |name| Specification.root_name(name) }
names = names.flatten.uniq
@names_of_pods_to_install = names
end
# Prepares the Pods folder in order to be compatible with the most recent
......@@ -258,6 +221,7 @@ module Pod
sandbox.build_headers.implode!
sandbox.public_headers.implode!
# TODO local option
unless analyzer.sandbox_state.deleted.empty?
UI.section "Removing deleted dependencies" do
analyzer.sandbox_state.deleted.each do |pod_name|
......@@ -270,25 +234,6 @@ module Pod
end
end
# @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
local_pods.each do |pod|
if names_of_pods_to_install.include?(pod.name)
UI.section("Installing #{pod}".green, "-> ".green) do
install_local_pod(pod)
end
else
UI.section("Using #{pod}", "-> ".green)
end
end
end
end
# Creates the Pods project from scratch if it doesn't exists.
#
# @return [void]
......@@ -297,70 +242,108 @@ module Pod
#
def prepare_pods_project
UI.message "- Creating Pods project" do
@pods_project = Pod::Project.new(nil)
@pods_project = Pod::Project.new(sandbox.project_path)
if config.podfile_path.exist?
podfile_relative_path = config.podfile_path.relative_path_from(sandbox.project_path.dirname)
@pods_project.add_podfile(podfile_relative_path)
@pods_project.add_podfile(config.podfile_path)
end
sandbox.project = @pods_project
end
end
# Creates a target installer for each definition not empty.
#
# @return [void]
#
def generate_target_installers
@target_installers = libraries.map do |library|
unless library.target_definition.empty?
TargetInstaller.new(sandbox, library)
def create_file_accessors
libraries.each do |library|
library.specs.each do |spec|
if spec.local?
pod_root = Pathname.new(spec.source[:local]).expand_path
else
pod_root = sandbox.pod_dir(spec.root.name)
end
path_list = Sandbox::PathList.new(pod_root)
file_accessor = Sandbox::FileAccessor.new(path_list, spec.consumer(library.platform))
library.file_accessors ||= []
library.file_accessors << file_accessor
end
end.compact
end
end
# Adds 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 have a local source they are added to the `Local Pods` group.
# Downloads, installs the documentation and cleans the sources of the Pods
# which need to be installed.
#
# @return [void]
#
# @todo Clean the groups of the deleted Pods and add only the Pods that
# should be installed.
#
def add_file_references_to_pods_project
UI.message "- Adding Pods files to Pods project" do
local_pods.each do |pod|
pod.add_file_references_to_project(pods_project)
pod.link_headers
unless pod.resources.empty?
resources_group = pods_project.new_group(pod.name, "Resources")
pod.resources.each do |resource|
resources_group.new_file(resource.relative_path_from(sandbox.root))
def install_pod_sources
@installed_specs = []
root_specs = analyzer.specifications.map { |spec| spec.root }
root_specs.each do |spec|
if names_of_pods_to_install.include?(spec.name)
UI.section("Installing #{spec}".green, "-> ".green) do
install_source_of_pod(spec.name)
end
else
UI.section("Using #{spec}", "-> ".green)
end
end
end
end
# Runs the pre install hooks of the installed specs and of the Podfile.
# 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]
#
# @todo Run the hooks only for the installed pods.
def install_source_of_pod(pod_name)
specs_by_platform = {}
libraries.each do |library|
specs = library.specs.select { |spec| spec.root.name == pod_name }
unless specs.empty?
specs_by_platform[library.platform] ||= []
specs_by_platform[library.platform].concat(specs)
end
end
pod_installer = PodSourceInstaller.new(sandbox, specs_by_platform)
root_spec = specs_by_platform.values.flatten.first.root
local_path = root_spec.source[:local]
pod_installer.local_path = Pathname.new(local_path).expand_path if local_path# TODO
pod_installer.clean = config.clean?
pod_installer.aggressive_cache = config.aggressive_cache?
pod_installer.generate_docs = config.generate_docs?
pod_installer.install_docs = config.install_docs?
pod_installer.install!
@installed_specs.concat(specs_by_platform.values.flatten)
end
# Runs the pre install hooks of the installed specs and of the Podfile.
#
# @todo Print a message with the names of the specs.
# @return [void]
#
def run_pre_install_hooks
UI.message "- Running pre install hooks" do
local_pods_by_target.each do |target_definition, pods|
pods.each do |pod|
pod.top_specification.pre_install!(pod, target_definition)
end
installed_specs.each do |spec|
pod_data = Hooks::PodData.new
pod_data.root = sandbox.pod_dir(spec.root.name)
library_data = Hooks::LibraryData.new
executed = spec.pre_install!(pod_data, library_data)
UI.message "- #{spec.name}" if executed
end
@podfile.pre_install!(self)
installer_data = Hooks::InstallerData.new
installer_data.project = pods_project
installer_data.sandbox = sandbox
installer_data.libraries = libraries
installer_data.pods = []
root_specs = analyzer.specifications.map { |spec| spec.root }.uniq
root_specs.each do |spec|
pod_data = Hooks::PodData.new
pod_data.root_spec = spec
pod_data.root = nil #TODO
installer_data.pods = [] << pod_data
end
executed = @podfile.pre_install!(installer_data)
UI.message "- Podfile" if executed
end
end
......@@ -377,21 +360,40 @@ module Pod
#
def run_post_install_hooks
UI.message "- Running post install hooks" do
target_installers.each do |target_installer|
target_installer.library.specs.each do |spec|
spec.post_install!(target_installer)
end
installed_specs.each do |spec|
target_installer_data = Hooks::TargetInstallerData.new
target_installer_data.sandbox = sandbox
target_installer_data.library = libraries.first #TODO
executed = spec.post_install!(target_installer_data)
UI.message "- #{spec.name}" if executed
end
@podfile.post_install!(self)
installer_data = Hooks::InstallerData.new
installer_data.project = pods_project
executed = @podfile.post_install!(installer_data)
UI.message "- Podfile" if executed
end
end
# Installs the file references in the Pods project. This is done once per
# Pod as the same file reference might be shared by multiple targets.
#
# @return [void]
#
def install_file_references
installer = FileReferencesInstaller.new(sandbox, libraries, pods_project)
installer.install!
end
# Installs the targets of the Pods projects and generates their support
# files.
#
def generate_target_support_files
# @return [void]
#
def install_targets
UI.message"- Installing targets" do
target_installers.each do |target_installer|
libraries.each do |library|
next if library.target_definition.empty?
target_installer = TargetInstaller.new(sandbox, library)
target_installer.install!
end
end
......@@ -409,7 +411,7 @@ module Pod
end
end
# Writes the Podfile and the {Sandbox} lock files.
# Writes the Podfile and the lock files.
#
# @return [void]
#
......@@ -425,12 +427,10 @@ module Pod
end
end
# Integrates the user project.
#
# The following actions are performed:
# - libraries are added.
# - the build script are added.
# - the xcconfig files are set.
# Integrates the user projects adding the dependencies on the CocoaPods
# libraries, setting them up to use the xcconfigs and performing other
# actions. This step is also reponsible of creating the workspace if
# needed.
#
# @return [void]
#
......@@ -439,77 +439,10 @@ module Pod
# In any case it appears to be a good idea store target definition
# information in the lockfile.
#
# @todo [#588] The resources should be added through a build phase
# instead of using a script.
#
def integrate_user_project
return unless config.integrate_targets?
UserProjectIntegrator.new(podfile, sandbox, config.project_root, analyzer.libraries).integrate!
end
#-------------------------------------------------------------------------#
private
# @!group Helpers
# 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.
#
# @return [void]
#
def install_local_pod(pod)
unless sandbox.predownloaded_pods.include?(pod.name)
pod.implode
download_pod(pod)
end
generate_docs_if_needed(pod)
pod.clean! if config.clean?
end
# Downloads a Pod forcing the `bleeding edge' version if requested.
#
# @return [void]
#
# @todo Store the source of non specific downloads in the lockfile.
#
def download_pod(pod)
downloader = Downloader.for_target(pod.root, pod.top_specification.source.dup)
downloader.cache_root = "~/Library/Caches/CocoaPods"
downloader.max_cache_size = 500
downloader.agressive_cache = config.agressive_cache?
if pod.top_specification.version.head?
downloader.download_head
specific_source = downloader.checkout_options
else
downloader.download
specific_source = downloader.checkout_options if downloader.specific_options?
end
pod.downloaded = true
if specific_source
# store the specific source
end
end
# Generates the documentation of a Pod unless it exists for a given
# version.
#
# @return [void]
#
def generate_docs_if_needed(pod)
doc_generator = Generator::Documentation.new(pod)
if ( config.generate_docs? && !doc_generator.already_installed? )
UI.section " > Installing documentation"
doc_generator.generate(config.doc_install?)
else
UI.section " > Using existing documentation"
end
installation_root = config.installation_root
libraries = analyzer.libraries
UserProjectIntegrator.new(podfile, sandbox, installation_root, libraries).integrate!
end
#-------------------------------------------------------------------------#
......
require File.expand_path('../../spec_helper', __FILE__)
# TODO add tests for multiple targets!
# @return [Lockfile]
#
def generate_lockfile
......@@ -26,7 +28,7 @@ module Pod
# before do
# @sandbox = temporary_sandbox
# config.repos_dir = fixture('spec-repos')
# config.project_pods_root = @sandbox.root
# config.sandbox_root = @sandbox.root
# FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
# end
#
......@@ -38,7 +40,7 @@ module Pod
describe "Concerning pre-installation computations" do
# @sandbox = temporary_sandbox
# config.project_pods_root = temporary_sandbox.root
# config.sandbox_root = temporary_sandbox.root
# FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
# resolver = Resolver.new(podfile, nil, @sandbox)
......@@ -80,7 +82,7 @@ module Pod
# end
# @sandbox = temporary_sandbox
# config.project_pods_root = temporary_sandbox.root
# config.sandbox_root = temporary_sandbox.root
# FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
# @installer = Installer.new(@sandbox, podfile)
# target_installer = @installer.target_installers.first
......@@ -118,7 +120,7 @@ module Pod
# before do
# sandbox = temporary_sandbox
# Config.instance.project_pods_root = sandbox.root
# Config.instance.sandbox_root = sandbox.root
# Config.instance.integrate_targets = false
# podspec_path = fixture('integration/Reachability/Reachability.podspec')
# podfile = Podfile.new do
......@@ -166,7 +168,7 @@ module Pod
# before do
# sandbox = temporary_sandbox
# Config.instance.project_pods_root = sandbox.root
# Config.instance.sandbox_root = sandbox.root
# Config.instance.integrate_targets = false
# podspec_path = fixture('chameleon')
# podfile = Podfile.new do
......
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