class Pod::DyInstaller
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
@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.
@return [Analyzer] the analyzer which provides the information about what
needs to be installed.
@return [Boolean] Whether it has dependencies. Defaults to true.
@return [Boolean] Whether it has dependencies. Defaults to true.
@return [Array<Specification>] The specifications that where installed.
@return [Lockfile] The Lockfile that stores the information about the
Pods previously installed on any machine.
@return [Array<String>] The Pods that should be installed.
@return [Podfile] The Podfile specification that contains the information
of the Pods that should be installed.
@return [Pod::Project] the `Pods/Pods.xcodeproj` project.
@return [Boolean] Whether the spec repos should be updated.
@return [Boolean] Whether the spec repos should be updated.
@return [Sandbox] The sandbox where the Pods should be installed.
@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.
@return [Boolean] Whether default plugins should be used during
installation. Defaults to true.
@return [Boolean] Whether default plugins should be used during
installation. Defaults to true.
Public Class Methods
Initialize a new instance
@param [Sandbox] sandbox @see sandbox @param [Podfile] podfile @see podfile @param [Lockfile] lockfile @see lockfile
# File lib/pod/installer.rb, line 78 def initialize(sandbox, podfile, lockfile = nil) @sandbox = sandbox @podfile = podfile @lockfile = lockfile @use_default_plugins = true @has_dependencies = true end
Public Instance Methods
@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/pod/installer.rb, line 688 def development_pod_targets pod_targets.select do |pod_target| sandbox.local?(pod_target.pod_name) end end
# File lib/pod/installer.rb, line 188 def download_dependencies UI.section 'Downloading dependencies' do create_file_accessors install_pod_sources run_podfile_pre_install_hooks clean_pod_sources end end
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/pod/installer.rb, line 124 def install! before_prepare = Time.now prepare before_resolve = Time.now resolve_dependencies before_download = Time.now download_dependencies before_validate_targets = Time.now validate_targets before_generate = Time.now generate_pods_project before_integreate = Time.now if installation_options.integrate_targets? integrate_user_project else UI.section 'Skipping User Project Integration' end before_post = Time.now perform_post_install_actions after_post = Time.now puts('Time result:') puts(" 【total: #{after_post - before_prepare}】") puts(" 【prepare: #{before_resolve - before_prepare}】") puts(" 【resolve_dependencies: #{before_download - before_resolve}】") puts(" 【download_dependencies: #{before_validate_targets - before_download}】") puts(" 【validate_targets: #{before_generate - before_validate_targets}】") puts(" 【generate_pods_project: #{before_integreate - before_generate}】") puts(" 【integrate_user_project: #{before_post - before_integreate}】") puts(" 【perform_post_install_actions: #{after_post - before_post}】") end
@return [Array<PodTarget>] The model representations of pod targets
generated as result of the analyzer.
# File lib/pod/installer.rb, line 248 def pod_targets aggregate_target_pod_targets = aggregate_targets.flat_map(&:pod_targets) test_dependent_targets = aggregate_target_pod_targets.flat_map(&:test_dependent_targets) (aggregate_target_pod_targets + test_dependent_targets).uniq end
# File lib/pod/installer.rb, line 155 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
@return [Analyzer] The analyzer used to resolve dependencies
# File lib/pod/installer.rb, line 172 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
Performs the analysis.
@return [void]
# File lib/pod/installer.rb, line 268 def analyze(analyzer = create_analyzer) analyzer.update = update @analysis_result = analyzer.analyze @aggregate_targets = analyzer.result.targets end
Cleans the sources of the Pods if the config instructs to do so.
@todo Why the @pod_installers might be empty?
# File lib/pod/installer.rb, line 409 def clean_pod_sources return unless installation_options.clean? return unless @pod_installers @pod_installers.each(&:clean!) end
@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/pod/installer.rb, line 308 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
# File lib/pod/installer.rb, line 274 def create_analyzer(plugin_sources = nil) Analyzer.new(sandbox, podfile, lockfile, plugin_sources).tap do |analyzer| analyzer.installation_options = installation_options analyzer.has_dependencies = has_dependencies? end end
@return [void] In this step we create the file accessors for the pod
targets.
# File lib/pod/installer.rb, line 335 def create_file_accessors sandbox.create_file_accessors(pod_targets) end
@!group Pods Project Generation
# File lib/pod/installer.rb, line 203 def create_generator Xcode::PodsProjectGenerator.new(aggregate_targets, sandbox, pod_targets, analysis_result, installation_options, config) end
# File lib/pod/installer.rb, line 376 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
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/pod/installer.rb, line 501 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
Ensures that all plugins specified in the {#podfile} are loaded.
@return [void]
# File lib/pod/installer.rb, line 518 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 the 'Pods/Pods.xcodeproj' project.
# File lib/pod/installer.rb, line 209 def generate_pods_project(generator = create_generator) UI.section 'Generating Pods project' do generator.generate! @pods_project = generator.project run_podfile_post_install_hooks generator.write generator.share_development_pod_schemes write_lockfiles end end
Downloads, installs the documentation and cleans the sources of the Pods which need to be installed.
@return [void]
# File lib/pod/installer.rb, line 344 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 != previous_spec_repo) 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 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/pod/installer.rb, line 399 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
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]
@todo [#397] The libraries should be cleaned and the re-added on every
installation. Maybe a clean_user_project phase should be added. In any case it appears to be a good idea store target definition information in the lockfile.
# File lib/pod/installer.rb, line 614 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
Locks the sources of the Pods if the config instructs to do so.
@todo Why the @pod_installers might be empty?
# File lib/pod/installer.rb, line 431 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
Performs any post-installation actions
@return [void]
# File lib/pod/installer.rb, line 458 def perform_post_install_actions unlock_pod_sources run_plugins_post_install_hooks warn_for_deprecations warn_for_installed_script_phases lock_pod_sources print_post_install_message end
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/pod/installer.rb, line 537 def plugins if use_default_plugins? DEFAULT_PLUGINS.merge(podfile.plugins) else podfile.plugins end end
# File lib/pod/installer.rb, line 467 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
@return [Array<Specification>] All the root specifications of the
installation.
# File lib/pod/installer.rb, line 703 def root_specs analysis_result.specifications.map(&:root).uniq end
Runs the registered callbacks for the plugins post install hooks.
# File lib/pod/installer.rb, line 480 def run_plugins_post_install_hooks context = PostInstallHooksContext.generate(sandbox, aggregate_targets) HooksManager.run(:post_install, context, plugins) end
Runs the registered callbacks for the plugins pre install hooks.
@return [void]
# File lib/pod/installer.rb, line 449 def run_plugins_pre_install_hooks context = PreInstallHooksContext.generate(sandbox, podfile, lockfile) HooksManager.run(:pre_install, context, plugins) end
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/pod/installer.rb, line 673 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
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/pod/installer.rb, line 660 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
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/pod/installer.rb, line 645 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
Runs the pre install hooks of the installed specs and of the Podfile.
@return [void]
# File lib/pod/installer.rb, line 632 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
Runs the registered callbacks for the source provider plugin hooks.
@return [void]
# File lib/pod/installer.rb, line 489 def run_source_provider_hooks context = SourceProviderHooksContext.generate HooksManager.run(:source_provider, context, plugins) context.sources end
@return [SpecsState] The state of the sandbox returned by the analyzer.
# File lib/pod/installer.rb, line 709 def sandbox_state analysis_result.sandbox_state end
Unlocks the sources of the Pods.
@todo Why the @pod_installers might be empty?
# File lib/pod/installer.rb, line 419 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
Ensures that the white-listed build configurations are known to prevent silent typos.
@raise If an unknown user configuration is found.
# File lib/pod/installer.rb, line 286 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
# File lib/pod/installer.rb, line 440 def validate_targets validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets) validator.validate! end
Prints a warning for any pods that are deprecated
@return [void]
# File lib/pod/installer.rb, line 549 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
Prints a warning for any pods that included script phases
@return [void]
# File lib/pod/installer.rb, line 567 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
Writes the Podfile and the lock files.
@todo Pass the checkout options to the Lockfile.
@return [void]
# File lib/pod/installer.rb, line 586 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