class Pod::Installer

The Installer is responsible of taking a Podfile and transform it in the Pods libraries. It also 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.
- Manifest.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.

The Installer is designed to work in environments where the Podfile folder is under source control and environments where it is not. The rest of the files, like the user project and the workspace are assumed to be under source control.

Constants

DEFAULT_PLUGINS

Attributes

aggregate_targets[R]

@return [Array<AggregateTarget>] The model representations of an

aggregation of pod targets generated for a target definition
in the Podfile as result of the analyzer.
analysis_result[R]

@return [Analyzer] the analyzer which provides the information about what

needs to be installed.
has_dependencies[RW]

@return [Boolean] Whether it has dependencies. Defaults to true.

has_dependencies?[RW]

@return [Boolean] Whether it has dependencies. Defaults to true.

installed_specs[RW]

@return [Array<Specification>] The specifications that were installed.

lockfile[R]

@return [Lockfile] The Lockfile that stores the information about the

Pods previously installed on any machine.
pod_targets[R]

@return [Array<PodTarget>] The model representations of pod targets

generated as result of the analyzer.
podfile[R]

@return [Podfile] The Podfile specification that contains the information

of the Pods that should be installed.
pods_project[R]

@return [Pod::Project] the `Pods/Pods.xcodeproj` project.

repo_update[RW]

@return [Boolean] Whether the spec repos should be updated.

repo_update?[RW]

@return [Boolean] Whether the spec repos should be updated.

sandbox[R]

@return [Sandbox] The sandbox where the Pods should be installed.

target_installation_results[R]

@return [Array<Hash{String, TargetInstallationResult}>] the installation results produced by the pods project

generator
update[RW]

@return [Hash, Boolean, nil] Pods that have been requested to be

updated or true if all Pods should be updated.
If all Pods should been updated the contents of the Lockfile are
not taken into account for deciding what Pods to install.
use_default_plugins[RW]

@return [Boolean] Whether default plugins should be used during

installation. Defaults to true.
use_default_plugins?[RW]

@return [Boolean] Whether default plugins should be used during

installation. Defaults to true.

Public Class Methods

new(sandbox, podfile, lockfile = nil) click to toggle source

Initialize a new instance

@param [Sandbox] sandbox @see sandbox @param [Podfile] podfile @see podfile @param [Lockfile] lockfile @see lockfile

# File lib/cocoapods/installer.rb, line 68
def initialize(sandbox, podfile, lockfile = nil)
  @sandbox  = sandbox || raise(ArgumentError, 'Missing required argument `sandbox`')
  @podfile  = podfile || raise(ArgumentError, 'Missing required argument `podfile`')
  @lockfile = lockfile

  @use_default_plugins = true
  @has_dependencies = true
end
targets_from_sandbox(sandbox, podfile, lockfile) click to toggle source

@!group Convenience Methods

# File lib/cocoapods/installer.rb, line 684
def self.targets_from_sandbox(sandbox, podfile, lockfile)
  raise Informative, 'You must run `pod install` to be able to generate target information' unless lockfile

  new(sandbox, podfile, lockfile).instance_exec do
    plugin_sources = run_source_provider_hooks
    analyzer = create_analyzer(plugin_sources)
    analyze(analyzer)
    if analysis_result.podfile_needs_install?
      raise Pod::Informative, 'The Podfile has changed, you must run `pod install`'
    elsif analysis_result.sandbox_needs_install?
      raise Pod::Informative, 'The `Pods` directory is out-of-date, you must run `pod install`'
    end

    aggregate_targets
  end
end

Public Instance Methods

development_pod_targets() click to toggle source

@return [Array<PodTarget>] The targets of the development pods generated by

the installation process. This can be used as a convenience method for external scripts.
# File lib/cocoapods/installer.rb, line 653
def development_pod_targets
  pod_targets.select do |pod_target|
    sandbox.local?(pod_target.pod_name)
  end
end
download_dependencies() click to toggle source
# File lib/cocoapods/installer.rb, line 161
def download_dependencies
  UI.section 'Downloading dependencies' do
    install_pod_sources
    run_podfile_pre_install_hooks
    clean_pod_sources
  end
end
install!() click to toggle source

Installs the Pods.

The installation process is mostly linear with a 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).

@return [void]

# File lib/cocoapods/installer.rb, line 114
def install!
  prepare
  resolve_dependencies
  download_dependencies
  validate_targets
  generate_pods_project
  if installation_options.integrate_targets?
    integrate_user_project
  else
    UI.section 'Skipping User Project Integration'
  end
  perform_post_install_actions
end
prepare() click to toggle source
# File lib/cocoapods/installer.rb, line 128
def prepare
  # Raise if pwd is inside Pods
  if Dir.pwd.start_with?(sandbox.root.to_path)
    message = 'Command should be run from a directory outside Pods directory.'
    message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
    raise Informative, message
  end
  UI.message 'Preparing' do
    deintegrate_if_different_major_version
    sandbox.prepare
    ensure_plugins_are_installed!
    run_plugins_pre_install_hooks
  end
end
resolve_dependencies() click to toggle source

@return [Analyzer] The analyzer used to resolve dependencies

# File lib/cocoapods/installer.rb, line 145
def resolve_dependencies
  plugin_sources = run_source_provider_hooks
  analyzer = create_analyzer(plugin_sources)

  UI.section 'Updating local specs repositories' do
    analyzer.update_repositories
  end if repo_update?

  UI.section 'Analyzing dependencies' do
    analyze(analyzer)
    validate_build_configurations
    clean_sandbox
  end
  analyzer
end

Private Instance Methods

analyze(analyzer = create_analyzer) click to toggle source

Performs the analysis.

@return [void]

# File lib/cocoapods/installer.rb, line 237
def analyze(analyzer = create_analyzer)
  @analysis_result = analyzer.analyze
  @aggregate_targets = @analysis_result.targets
  @pod_targets = @analysis_result.pod_targets
end
any_plugin_post_install_hooks?() click to toggle source

@return [Boolean] whether there are any plugin post-install hooks to run

# File lib/cocoapods/installer.rb, line 453
def any_plugin_post_install_hooks?
  HooksManager.hooks_to_run(:post_install, plugins).any?
end
clean_pod_sources() click to toggle source

Cleans the sources of the Pods if the config instructs to do so.

@todo Why the @pod_installers might be empty?

# File lib/cocoapods/installer.rb, line 370
def clean_pod_sources
  return unless installation_options.clean?
  return unless @pod_installers
  @pod_installers.each(&:clean!)
end
clean_sandbox() click to toggle source

@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 [#247] Clean the headers of only the pods to install.

# File lib/cocoapods/installer.rb, line 276
def clean_sandbox
  sandbox.public_headers.implode!
  target_support_dirs = sandbox.target_support_files_root.children.select(&:directory?)
  pod_targets.each do |pod_target|
    pod_target.build_headers.implode!
    target_support_dirs.delete(pod_target.support_files_dir)
  end

  aggregate_targets.each do |aggregate_target|
    target_support_dirs.delete(aggregate_target.support_files_dir)
  end

  target_support_dirs.each { |dir| FileUtils.rm_rf(dir) }

  unless sandbox_state.deleted.empty?
    title_options = { :verbose_prefix => '-> '.red }
    sandbox_state.deleted.each do |pod_name|
      UI.titled_section("Removing #{pod_name}".red, title_options) do
        sandbox.clean_pod(pod_name)
      end
    end
  end
end
create_analyzer(plugin_sources = nil) click to toggle source
# File lib/cocoapods/installer.rb, line 243
def create_analyzer(plugin_sources = nil)
  Analyzer.new(sandbox, podfile, lockfile, plugin_sources, has_dependencies?, update).tap do |analyzer|
    analyzer.installation_options = installation_options
  end
end
create_generator() click to toggle source

@!group Pods Project Generation

# File lib/cocoapods/installer.rb, line 175
def create_generator
  Xcode::PodsProjectGenerator.new(sandbox, aggregate_targets, pod_targets, analysis_result, installation_options, config)
end
create_pod_installer(pod_name) click to toggle source
# File lib/cocoapods/installer.rb, line 337
def create_pod_installer(pod_name)
  specs_by_platform = {}
  pod_targets.each do |pod_target|
    if pod_target.root_spec.name == pod_name
      specs_by_platform[pod_target.platform] ||= []
      specs_by_platform[pod_target.platform].concat(pod_target.specs)
    end
  end

  raise Informative, "Could not install '#{pod_name}' pod. There is no target that supports it." if specs_by_platform.empty?

  @pod_installers ||= []
  pod_installer = PodSourceInstaller.new(sandbox, specs_by_platform, :can_cache => installation_options.clean?)
  @pod_installers << pod_installer
  pod_installer
end
deintegrate_if_different_major_version() click to toggle source

Run the deintegrator against all projects in the installation root if the current CocoaPods major version part is different than the one in the lockfile.

@return [void]

# File lib/cocoapods/installer.rb, line 473
def deintegrate_if_different_major_version
  return unless lockfile
  return if lockfile.cocoapods_version.major == Version.create(VERSION).major
  UI.section('Re-creating CocoaPods due to major version update.') do
    projects = Pathname.glob(config.installation_root + '*.xcodeproj').map { |path| Xcodeproj::Project.open(path) }
    deintegrator = Deintegrator.new
    projects.each do |project|
      config.with_changes(:silent => true) { deintegrator.deintegrate_project(project) }
      project.save if project.dirty?
    end
  end
end
ensure_plugins_are_installed!() click to toggle source

Ensures that all plugins specified in the {#podfile} are loaded.

@return [void]

# File lib/cocoapods/installer.rb, line 490
def ensure_plugins_are_installed!
  require 'claide/command/plugin_manager'

  loaded_plugins = Command::PluginManager.specifications.map(&:name)

  podfile.plugins.keys.each do |plugin|
    unless loaded_plugins.include? plugin
      raise Informative, "Your Podfile requires that the plugin `#{plugin}` be installed. Please install it and try installation again."
    end
  end
end
generate_pods_project(generator = create_generator) click to toggle source

Generate the 'Pods/Pods.xcodeproj' project.

# File lib/cocoapods/installer.rb, line 181
def generate_pods_project(generator = create_generator)
  UI.section 'Generating Pods project' do
    @target_installation_results = generator.generate!
    @pods_project = generator.project
    run_podfile_post_install_hooks
    generator.write
    generator.share_development_pod_schemes
    write_lockfiles
  end
end
install_pod_sources() click to toggle source

Downloads, installs the documentation and cleans the sources of the Pods which need to be installed.

@return [void]

# File lib/cocoapods/installer.rb, line 305
def install_pod_sources
  @installed_specs = []
  pods_to_install = sandbox_state.added | sandbox_state.changed
  title_options = { :verbose_prefix => '-> '.green }
  root_specs.sort_by(&:name).each do |spec|
    if pods_to_install.include?(spec.name)
      if sandbox_state.changed.include?(spec.name) && sandbox.manifest
        current_version = spec.version
        previous_version = sandbox.manifest.version(spec.name)
        has_changed_version = current_version != previous_version
        current_repo = analysis_result.specs_by_source.detect { |key, values| break key if values.map(&:name).include?(spec.name) }
        current_repo &&= current_repo.url || current_repo.name
        previous_spec_repo = sandbox.manifest.spec_repo(spec.name)
        has_changed_repo = !previous_spec_repo.nil? && current_repo && !current_repo.casecmp(previous_spec_repo).zero?
        title = "Installing #{spec.name} #{spec.version}"
        title << " (was #{previous_version} and source changed to `#{current_repo}` from `#{previous_spec_repo}`)" if has_changed_version && has_changed_repo
        title << " (was #{previous_version})" if has_changed_version && !has_changed_repo
        title << " (source changed to `#{current_repo}` from `#{previous_spec_repo}`)" if !has_changed_version && has_changed_repo
      else
        title = "Installing #{spec}"
      end
      UI.titled_section(title.green, title_options) do
        install_source_of_pod(spec.name)
      end
    else
      UI.titled_section("Using #{spec}", title_options) do
        create_pod_installer(spec.name)
      end
    end
  end
end
install_source_of_pod(pod_name) click to toggle source

Install the Pods. If the resolver indicated that a Pod should be installed and it exits, it is removed and then reinstalled. In any case if the Pod doesn't exits it is installed.

@return [void]

# File lib/cocoapods/installer.rb, line 360
def install_source_of_pod(pod_name)
  pod_installer = create_pod_installer(pod_name)
  pod_installer.install!
  @installed_specs.concat(pod_installer.specs_by_platform.values.flatten.uniq)
end
integrate_user_project() click to toggle source

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 responsible of creating the workspace if needed.

@return [void]

# File lib/cocoapods/installer.rb, line 579
def integrate_user_project
  UI.section "Integrating client #{'project'.pluralize(aggregate_targets.map(&:user_project_path).uniq.count)}" do
    installation_root = config.installation_root
    integrator = UserProjectIntegrator.new(podfile, sandbox, installation_root, aggregate_targets)
    integrator.integrate!
  end
end
lock_pod_sources() click to toggle source

Locks the sources of the Pods if the config instructs to do so.

@todo Why the @pod_installers might be empty?

# File lib/cocoapods/installer.rb, line 392
def lock_pod_sources
  return unless installation_options.lock_pod_sources?
  return unless @pod_installers
  @pod_installers.each do |installer|
    pod_target = pod_targets.find { |target| target.pod_name == installer.name }
    installer.lock_files!(pod_target.file_accessors)
  end
end
perform_post_install_actions() click to toggle source

Performs any post-installation actions

@return [void]

# File lib/cocoapods/installer.rb, line 419
def perform_post_install_actions
  run_plugins_post_install_hooks
  warn_for_deprecations
  warn_for_installed_script_phases
  print_post_install_message
end
plugins() click to toggle source

Returns the plugins that should be run, as indicated by the default plugins and the podfile's plugins

@return [Hash<String, Hash>] The plugins to be used

# File lib/cocoapods/installer.rb, line 509
def plugins
  if use_default_plugins?
    DEFAULT_PLUGINS.merge(podfile.plugins)
  else
    podfile.plugins
  end
end
print_post_install_message() click to toggle source
# File lib/cocoapods/installer.rb, line 426
def print_post_install_message
  podfile_dependencies = analysis_result.podfile_dependency_cache.podfile_dependencies.size
  pods_installed = root_specs.size
  title_options = { :verbose_prefix => '-> '.green }
  UI.titled_section('Pod installation complete! ' \
                    "There #{podfile_dependencies == 1 ? 'is' : 'are'} #{podfile_dependencies} " \
                    "#{'dependency'.pluralize(podfile_dependencies)} from the Podfile " \
                    "and #{pods_installed} total #{'pod'.pluralize(pods_installed)} installed.".green,
                    title_options)
end
root_specs() click to toggle source

@return [Array<Specification>] All the root specifications of the

installation.
# File lib/cocoapods/installer.rb, line 668
def root_specs
  analysis_result.specifications.map(&:root).uniq
end
run_plugins_post_install_hooks() click to toggle source

Runs the registered callbacks for the plugins post install hooks.

# File lib/cocoapods/installer.rb, line 439
def run_plugins_post_install_hooks
  # This short-circuits because unlocking pod sources is expensive
  if any_plugin_post_install_hooks?
    unlock_pod_sources

    context = PostInstallHooksContext.generate(sandbox, aggregate_targets)
    HooksManager.run(:post_install, context, plugins)
  end

  lock_pod_sources
end
run_plugins_pre_install_hooks() click to toggle source

Runs the registered callbacks for the plugins pre install hooks.

@return [void]

# File lib/cocoapods/installer.rb, line 410
def run_plugins_pre_install_hooks
  context = PreInstallHooksContext.generate(sandbox, podfile, lockfile)
  HooksManager.run(:pre_install, context, plugins)
end
run_podfile_post_install_hook() click to toggle source

Runs the post install hook of the Podfile

@raise Raises an informative if the hooks raises.

@return [Boolean] Whether the hook was run.

# File lib/cocoapods/installer.rb, line 638
def run_podfile_post_install_hook
  podfile.post_install!(self)
rescue => e
  raise Informative, 'An error occurred while processing the post-install ' \
    'hook of the Podfile.' \
    "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
end
run_podfile_post_install_hooks() click to toggle source

Runs the post install hooks of the installed specs and of the Podfile.

@note Post install hooks run before saving of project, so that they

can alter it before it is written to the disk.

@return [void]

# File lib/cocoapods/installer.rb, line 625
def run_podfile_post_install_hooks
  UI.message '- Running post install hooks' do
    executed = run_podfile_post_install_hook
    UI.message '- Podfile' if executed
  end
end
run_podfile_pre_install_hook() click to toggle source

Runs the pre install hook of the Podfile

@raise Raises an informative if the hooks raises.

@return [Boolean] Whether the hook was run.

# File lib/cocoapods/installer.rb, line 610
def run_podfile_pre_install_hook
  podfile.pre_install!(self)
rescue => e
  raise Informative, 'An error occurred while processing the pre-install ' \
    'hook of the Podfile.' \
    "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
end
run_podfile_pre_install_hooks() click to toggle source

Runs the pre install hooks of the installed specs and of the Podfile.

@return [void]

# File lib/cocoapods/installer.rb, line 597
def run_podfile_pre_install_hooks
  UI.message '- Running pre install hooks' do
    executed = run_podfile_pre_install_hook
    UI.message '- Podfile' if executed
  end
end
run_source_provider_hooks() click to toggle source

Runs the registered callbacks for the source provider plugin hooks.

@return [void]

# File lib/cocoapods/installer.rb, line 461
def run_source_provider_hooks
  context = SourceProviderHooksContext.generate
  HooksManager.run(:source_provider, context, plugins)
  context.sources
end
sandbox_state() click to toggle source

@return [SpecsState] The state of the sandbox returned by the analyzer.

# File lib/cocoapods/installer.rb, line 674
def sandbox_state
  analysis_result.sandbox_state
end
unlock_pod_sources() click to toggle source

Unlocks the sources of the Pods.

@todo Why the @pod_installers might be empty?

# File lib/cocoapods/installer.rb, line 380
def unlock_pod_sources
  return unless @pod_installers
  @pod_installers.each do |installer|
    pod_target = pod_targets.find { |target| target.pod_name == installer.name }
    installer.unlock_files!(pod_target.file_accessors)
  end
end
validate_build_configurations() click to toggle source

Ensures that the white-listed build configurations are known to prevent silent typos.

@raise If an unknown user configuration is found.

# File lib/cocoapods/installer.rb, line 254
def validate_build_configurations
  whitelisted_configs = pod_targets.
    flat_map(&:target_definitions).
    flat_map(&:all_whitelisted_configurations).
    map(&:downcase).
    uniq
  all_user_configurations = analysis_result.all_user_build_configurations.keys.map(&:downcase)

  remainder = whitelisted_configs - all_user_configurations
  unless remainder.empty?
    raise Informative,
          "Unknown #{'configuration'.pluralize(remainder.size)} whitelisted: #{remainder.sort.to_sentence}. " \
          "CocoaPods found #{all_user_configurations.sort.to_sentence}, did you mean one of these?"
  end
end
validate_targets() click to toggle source
# File lib/cocoapods/installer.rb, line 401
def validate_targets
  validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets)
  validator.validate!
end
warn_for_deprecations() click to toggle source

Prints a warning for any pods that are deprecated

@return [void]

# File lib/cocoapods/installer.rb, line 521
def warn_for_deprecations
  deprecated_pods = root_specs.select do |spec|
    spec.deprecated || spec.deprecated_in_favor_of
  end
  deprecated_pods.each do |spec|
    if spec.deprecated_in_favor_of
      UI.warn "#{spec.name} has been deprecated in " \
        "favor of #{spec.deprecated_in_favor_of}"
    else
      UI.warn "#{spec.name} has been deprecated"
    end
  end
end
warn_for_installed_script_phases() click to toggle source

Prints a warning for any pods that included script phases

@return [void]

# File lib/cocoapods/installer.rb, line 539
def warn_for_installed_script_phases
  pods_to_install = sandbox_state.added | sandbox_state.changed
  pod_targets.group_by(&:pod_name).each do |name, pod_targets|
    if pods_to_install.include?(name)
      script_phase_count = pod_targets.inject(0) { |sum, target| sum + target.script_phases.count }
      unless script_phase_count.zero?
        UI.warn "#{name} has added #{script_phase_count} #{'script phase'.pluralize(script_phase_count)}. " \
          'Please inspect before executing a build. See `https://guides.cocoapods.org/syntax/podspec.html#script_phases` for more information.'
      end
    end
  end
end
write_lockfiles() click to toggle source

Writes the Podfile and the lock files.

@return [void]

# File lib/cocoapods/installer.rb, line 556
def write_lockfiles
  external_source_pods = analysis_result.podfile_dependency_cache.podfile_dependencies.select(&:external_source).map(&:root_name).uniq
  checkout_options = sandbox.checkout_sources.select { |root_name, _| external_source_pods.include? root_name }
  @lockfile = Lockfile.generate(podfile, analysis_result.specifications, checkout_options, analysis_result.specs_by_source)

  UI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do
    @lockfile.write_to_disk(config.lockfile_path)
  end

  UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
    sandbox.manifest_path.open('w') do |f|
      f.write config.lockfile_path.read
    end
  end
end