lib/cocoapods/target/pod_target.rb
require 'cocoapods/xcode/framework_paths'
require 'cocoapods/xcode/xcframework'
module Pod
# Stores the information relative to the target used to compile a single Pod.
# A pod can have one or more activated spec, subspecs and test specs.
#
class PodTarget < Target
# @return [Array<Specification>] the spec, subspecs and test specs of the target.
#
attr_reader :specs
# @return [Array<Specification>] All of the test specs within this target.
# Subset of #specs.
#
attr_reader :test_specs
# @return [Array<Specification>] All of the specs within this target that are library specs.
# Subset of #specs.
#
attr_reader :library_specs
# @return [Array<Specification>] All of the specs within this target that are app specs.
# Subset of #specs.
#
attr_reader :app_specs
# @return [Array<TargetDefinition>] the target definitions of the Podfile
# that generated this target.
#
attr_reader :target_definitions
# @return [Array<Sandbox::FileAccessor>] the file accessors for the
# specifications of this target.
#
attr_reader :file_accessors
# @return [String] the suffix used for this target when deduplicated. May be `nil`.
#
# @note This affects the value returned by #configuration_build_dir
# and accessors relying on this as #build_product_path.
#
attr_reader :scope_suffix
# @return [HeadersStore] the header directory for the target.
#
attr_reader :build_headers
# @return [Array<PodTarget>] the targets that this target has a dependency
# upon.
#
attr_reader :dependent_targets
attr_reader :dependent_targets_by_config
# @deprecated
def dependent_targets=(dependent_targets)
@dependent_targets = dependent_targets
@dependent_targets_by_config = { :debug => dependent_targets, :release => dependent_targets }
end
def dependent_targets_by_config=(dependent_targets_by_config)
@dependent_targets_by_config = dependent_targets_by_config
@dependent_targets = dependent_targets_by_config.each_value.reduce([], &:|)
end
# @return [Hash{String=>Array<PodTarget>}] all target dependencies by test spec name.
#
attr_reader :test_dependent_targets_by_spec_name
attr_reader :test_dependent_targets_by_spec_name_by_config
# @deprecated
def test_dependent_targets_by_spec_name=(test_dependent_targets_by_spec_name)
@test_dependent_targets_by_spec_name = test_dependent_targets_by_spec_name
@test_dependent_targets_by_spec_name_by_config = Hash[test_dependent_targets_by_spec_name.map do |k, v|
[k, { :debug => v, :release => v }]
end]
end
def test_dependent_targets_by_spec_name_by_config=(test_dependent_targets_by_spec_name_by_config)
@test_dependent_targets_by_spec_name_by_config = test_dependent_targets_by_spec_name_by_config
@test_dependent_targets_by_spec_name = Hash[test_dependent_targets_by_spec_name_by_config.map do |k, v|
[k, v.each_value.reduce(Set.new, &:|).to_a]
end]
end
# @return [Hash{String=>Array<PodTarget>}] all target dependencies by app spec name.
#
attr_reader :app_dependent_targets_by_spec_name
attr_reader :app_dependent_targets_by_spec_name_by_config
# @deprecated
def app_dependent_targets_by_spec_name=(app_dependent_targets_by_spec_name)
@app_dependent_targets_by_spec_name = app_dependent_targets_by_spec_name
@app_dependent_targets_by_spec_name_by_config = Hash[app_dependent_targets_by_spec_name.map do |k, v|
[k, { :debug => v, :release => v }]
end]
end
def app_dependent_targets_by_spec_name_by_config=(app_dependent_targets_by_spec_name_by_config)
@app_dependent_targets_by_spec_name_by_config = app_dependent_targets_by_spec_name_by_config
@app_dependent_targets_by_spec_name = Hash[app_dependent_targets_by_spec_name_by_config.map do |k, v|
[k, v.each_value.reduce(Set.new, &:|).to_a]
end]
end
# @return [Hash{Specification => (Specification,PodTarget)}] tuples of app specs and pod targets by test spec.
#
attr_accessor :test_app_hosts_by_spec
# @return [Hash{String => BuildSettings}] the test spec build settings for this target.
#
attr_reader :test_spec_build_settings
attr_reader :test_spec_build_settings_by_config
# @return [Hash{String => BuildSettings}] the app spec build settings for this target.
#
attr_reader :app_spec_build_settings
attr_reader :app_spec_build_settings_by_config
# @return [String] the Swift version for this target.
#
attr_reader :swift_version
# Initialize a new instance
#
# @param [Sandbox] sandbox @see Target#sandbox
# @param [BuildType] build_type @see Target#build_type
# @param [Hash{String=>Symbol}] user_build_configurations @see Target#user_build_configurations
# @param [Array<String>] archs @see Target#archs
# @param [Platform] platform @see Target#platform
# @param [Array<Specification>] specs @see #specs
# @param [Array<TargetDefinition>] target_definitions @see #target_definitions
# @param [Array<Sandbox::FileAccessor>] file_accessors @see #file_accessors
# @param [String] scope_suffix @see #scope_suffix
# @param [String] swift_version @see #swift_version
#
def initialize(sandbox, build_type, user_build_configurations, archs, platform, specs, target_definitions,
file_accessors = [], scope_suffix = nil, swift_version = nil)
super(sandbox, build_type, user_build_configurations, archs, platform)
raise "Can't initialize a PodTarget without specs!" if specs.nil? || specs.empty?
raise "Can't initialize a PodTarget without TargetDefinition!" if target_definitions.nil? || target_definitions.empty?
raise "Can't initialize a PodTarget with an empty string scope suffix!" if scope_suffix == ''
@specs = specs.dup.freeze
@target_definitions = target_definitions
@file_accessors = file_accessors
@scope_suffix = scope_suffix
@swift_version = swift_version
all_specs_by_type = @specs.group_by(&:spec_type)
@library_specs = all_specs_by_type[:library] || []
@test_specs = all_specs_by_type[:test] || []
@app_specs = all_specs_by_type[:app] || []
@build_headers = Sandbox::HeadersStore.new(sandbox, 'Private', :private)
self.dependent_targets = []
self.test_dependent_targets_by_spec_name = Hash[test_specs.map { |ts| [ts.name, []] }]
self.app_dependent_targets_by_spec_name = Hash[app_specs.map { |as| [as.name, []] }]
@test_app_hosts_by_spec = {}
@build_config_cache = {}
@test_spec_build_settings_by_config = create_test_build_settings_by_config
@app_spec_build_settings_by_config = create_app_build_settings_by_config
end
# Scopes the current target based on the existing pod targets within the cache.
#
# @param [Hash{Array => PodTarget}] cache
# the cached target for a previously scoped target.
#
# @return [Array<PodTarget>] a scoped copy for each target definition.
#
def scoped(cache = {})
target_definitions.map do |target_definition|
cache_key = [specs, target_definition]
cache[cache_key] ||= begin
target = PodTarget.new(sandbox, build_type, user_build_configurations, archs, platform, specs,
[target_definition], file_accessors, target_definition.label, swift_version)
scope_dependent_targets = ->(dependent_targets) do
dependent_targets.flat_map do |pod_target|
pod_target.scoped(cache).select { |pt| pt.target_definitions == [target_definition] }
end
end
target.dependent_targets_by_config = Hash[dependent_targets_by_config.map { |k, v| [k, scope_dependent_targets[v]] }]
target.test_dependent_targets_by_spec_name_by_config = Hash[test_dependent_targets_by_spec_name_by_config.map do |spec_name, test_pod_targets_by_config|
[spec_name, Hash[test_pod_targets_by_config.map { |k, v| [k, scope_dependent_targets[v]] }]]
end]
target.app_dependent_targets_by_spec_name_by_config = Hash[app_dependent_targets_by_spec_name_by_config.map do |spec_name, app_pod_targets_by_config|
[spec_name, Hash[app_pod_targets_by_config.map { |k, v| [k, scope_dependent_targets[v]] }]]
end]
target.test_app_hosts_by_spec = Hash[test_app_hosts_by_spec.map do |spec, (app_host_spec, app_pod_target)|
[spec, [app_host_spec, app_pod_target.scoped(cache).find { |pt| pt.target_definitions == [target_definition] }]]
end]
target
end
end
end
# @return [String] the label for the target.
#
def label
if scope_suffix.nil? || scope_suffix[0] == '.'
"#{root_spec.name}#{scope_suffix}"
else
"#{root_spec.name}-#{scope_suffix}"
end
end
# @return [Array<Pathname>] The list of all files tracked.
#
def all_files
Sandbox::FileAccessor.all_files(file_accessors)
end
# @return [Pathname] the pathname for headers in the sandbox.
#
def headers_sandbox
Pathname.new(pod_name)
end
# @return [Hash{FileAccessor => Hash}] Hash of file accessors by header mappings.
#
def header_mappings_by_file_accessor
valid_accessors = file_accessors.reject { |fa| fa.spec.non_library_specification? }
Hash[valid_accessors.map do |file_accessor|
# Private headers will always end up in Pods/Headers/Private/PodA/*.h
# This will allow for `""` imports to work.
[file_accessor, header_mappings(file_accessor, file_accessor.headers)]
end]
end
# @return [Hash{FileAccessor => Hash}] Hash of file accessors by public header mappings.
#
def public_header_mappings_by_file_accessor
valid_accessors = file_accessors.reject { |fa| fa.spec.non_library_specification? }
Hash[valid_accessors.map do |file_accessor|
# Public headers on the other hand will be added in Pods/Headers/Public/PodA/PodA/*.h
# The extra folder is intentional in order for `<>` imports to work.
[file_accessor, header_mappings(file_accessor, file_accessor.public_headers)]
end]
end
# @return [Array<Version>] the Swift versions supported. Might be empty if the author has not
# specified any versions, most likely due to legacy reasons.
#
def spec_swift_versions
root_spec.swift_versions
end
# @return [Podfile] The podfile which declares the dependency.
#
def podfile
target_definitions.first.podfile
end
# @return [String] the project name derived from the target definitions that integrate this pod. If none is
# specified then the name of the pod is used by default.
#
# @note The name is guaranteed to be the same across all target definitions and is validated by the target
# validator during installation.
#
def project_name
target_definitions.map { |td| td.project_name_for_pod(pod_name) }.compact.first || pod_name
end
# @return [String] The name to use for the source code module constructed
# for this target, and which will be used to import the module in
# implementation source files.
#
def product_module_name
root_spec.module_name
end
# @param [Specification] spec the specification
#
# @return [String] the product basename of the specification's target
def product_basename_for_spec(spec)
user_specified = build_settings_by_config_for_spec(spec).
each_value.
map { |settings| settings.merged_pod_target_xcconfigs['PRODUCT_NAME'] }.
compact.
uniq
if user_specified.size == 1
user_specified.first
else
spec_label(spec)
end
end
# @return [Boolean] Whether or not this target should be built.
#
# A target should not be built if it has no source files.
#
def should_build?
return @should_build if defined? @should_build
accessors = file_accessors.select { |fa| fa.spec.library_specification? }
source_files = accessors.flat_map(&:source_files)
source_files -= accessors.flat_map(&:headers)
@should_build = !source_files.empty?
end
# @return [Array<Specification::Consumer>] the specification consumers for
# the target.
#
def spec_consumers
specs.map { |spec| spec.consumer(platform) }
end
# @return [Array<Specification::Consumer>] the test specification consumers for
# the target.
#
def test_spec_consumers
test_specs.map { |test_spec| test_spec.consumer(platform) }
end
# @return [Array<Specification::Consumer>] the app specification consumers for
# the target.
#
def app_spec_consumers
app_specs.map { |app_spec| app_spec.consumer(platform) }
end
# Checks whether the target itself plus its specs uses Swift code.
# This check excludes source files from non library specs.
# Note that if a target does not need to be built (no source code),
# we fallback to check whether it indicates a swift version.
#
# @return [Boolean] Whether the target uses Swift code.
#
def uses_swift?
return @uses_swift if defined? @uses_swift
@uses_swift = (!should_build? && !spec_swift_versions.empty?) ||
file_accessors.select { |a| a.spec.library_specification? }.any? do |file_accessor|
uses_swift_for_spec?(file_accessor.spec)
end
end
# Checks whether a specification uses Swift or not.
#
# @param [Specification] spec
# The spec to query against.
#
# @return [Boolean] Whether the target uses Swift code within the requested non library spec.
#
def uses_swift_for_spec?(spec)
@uses_swift_for_spec_cache ||= {}
return @uses_swift_for_spec_cache[spec.name] if @uses_swift_for_spec_cache.key?(spec.name)
@uses_swift_for_spec_cache[spec.name] = begin
file_accessor = file_accessors.find { |fa| fa.spec == spec }
raise "[Bug] Unable to find file accessor for spec `#{spec.inspect}` in pod target `#{label}`" unless file_accessor
file_accessor.source_files.any? { |sf| sf.extname == '.swift' }
end
end
# @return [Boolean] Whether the target defines a "module"
# (and thus will need a module map and umbrella header).
#
# @note Static library targets can temporarily opt in to this behavior by setting
# `DEFINES_MODULE = YES` in their specification's `pod_target_xcconfig`.
#
def defines_module?
return @defines_module if defined?(@defines_module)
return @defines_module = true if uses_swift? || build_as_framework?
explicit_target_definitions = target_definitions.select { |td| td.dependencies.any? { |d| d.root_name == pod_name } }
tds_by_answer = explicit_target_definitions.group_by { |td| td.build_pod_as_module?(pod_name) }
if tds_by_answer.size > 1
UI.warn "Unable to determine whether to build `#{label}` as a module due to a conflict " \
"between the following target definitions:\n\t- #{tds_by_answer.map do |a, td|
"`#{td.to_sentence}` #{a ? "requires `#{label}` as a module" : "does not require `#{label}` as a module"}"
end.join("\n\t- ")}\n\n" \
"Defaulting to skip building `#{label}` as a module."
elsif tds_by_answer.keys.first == true || target_definitions.all? { |td| td.build_pod_as_module?(pod_name) }
return @defines_module = true
end
@defines_module = library_specs.any? { |s| s.consumer(platform).pod_target_xcconfig['DEFINES_MODULE'] == 'YES' }
end
# @return [Array<Hash{Symbol=>String}>] An array of hashes where each hash represents a single script phase.
#
def script_phases
spec_consumers.flat_map(&:script_phases)
end
# @return [Boolean] Whether the target contains any script phases.
#
def contains_script_phases?
!script_phases.empty?
end
# @return [Boolean] Whether the target has any tests specifications.
#
def contains_test_specifications?
!test_specs.empty?
end
# @return [Boolean] Whether the target has any app specifications.
#
def contains_app_specifications?
!app_specs.empty?
end
# @return [Hash{String=>Array<Xcode::FrameworkPaths>}] The vendored and non vendored framework paths this target
# depends upon keyed by spec name. For the root spec and subspecs the framework path of the target itself
# is included.
#
def framework_paths
@framework_paths ||= begin
file_accessors.each_with_object({}) do |file_accessor, hash|
frameworks = file_accessor.vendored_dynamic_artifacts.map do |framework_path|
relative_path_to_sandbox = framework_path.relative_path_from(sandbox.root)
framework_source = "${PODS_ROOT}/#{relative_path_to_sandbox}"
# Until this can be configured, assume the dSYM file uses the file name as the framework.
# See https://github.com/CocoaPods/CocoaPods/issues/1698
dsym_name = "#{framework_path.basename}.dSYM"
dsym_path = Pathname.new("#{framework_path.dirname}/#{dsym_name}")
dsym_source = if dsym_path.exist?
"${PODS_ROOT}/#{relative_path_to_sandbox}.dSYM"
end
dirname = framework_path.dirname
bcsymbolmap_paths = if dirname.exist?
Dir.chdir(dirname) do
Dir.glob('*.bcsymbolmap').map do |bcsymbolmap_file_name|
bcsymbolmap_path = dirname + bcsymbolmap_file_name
"${PODS_ROOT}/#{bcsymbolmap_path.relative_path_from(sandbox.root)}"
end
end
end
Xcode::FrameworkPaths.new(framework_source, dsym_source, bcsymbolmap_paths)
end
if file_accessor.spec.library_specification? && should_build? && build_as_dynamic_framework?
frameworks << Xcode::FrameworkPaths.new(build_product_path('${BUILT_PRODUCTS_DIR}'))
end
hash[file_accessor.spec.name] = frameworks
end
end
end
# @return [Hash{String=>Array<Xcode::XCFramework>}] The vendored xcframeworks this target
# depends upon keyed by spec name.
#
def xcframeworks
@xcframeworks ||= begin
file_accessors.each_with_object({}) do |file_accessor, hash|
frameworks = file_accessor.vendored_xcframeworks.map do |framework_path|
Xcode::XCFramework.new(file_accessor.spec.name, framework_path)
end
hash[file_accessor.spec.name] = frameworks
end
end
end
# @return [Hash{String=>Array<String>}] The resource and resource bundle paths this target depends upon keyed by
# spec name. Resource (not resource bundles) paths can vary depending on the type of spec:
# - App specs _always_ get their resource paths added to "Copy Bundle Resources" phase from
# [PodTargetInstaller] therefore their resource paths are never included here.
# - Test specs may have their resource paths added to "Copy Bundle Resources" if the target itself is
# built as a framework, which is again checked and handled by PodTargetInstaller. If that is true then
# the resource paths are not included, otherwise they are included and handled via the CocoaPods copy
# resources script phase.
# - Library specs _do not_ have per-configuration CocoaPods copy resources script phase and their resource
# paths will be added to "Copy Bundle Resources" phase if the target is built as a framework because
# it supports it. We always include the resource paths for library specs because they are also
# integrated to the user target.
#
def resource_paths
@resource_paths ||= begin
file_accessors.each_with_object({}) do |file_accessor, hash|
resource_paths = if file_accessor.spec.app_specification? || (file_accessor.spec.test_specification? && build_as_framework?)
[]
else
file_accessor.resources.map do |res|
"${PODS_ROOT}/#{res.relative_path_from(sandbox.project_path.dirname)}"
end
end
prefix = Pod::Target::BuildSettings::CONFIGURATION_BUILD_DIR_VARIABLE
prefix = configuration_build_dir unless file_accessor.spec.test_specification?
resource_bundle_paths = file_accessor.resource_bundles.keys.map { |name| "#{prefix}/#{name.shellescape}.bundle" }
hash[file_accessor.spec.name] = (resource_paths + resource_bundle_paths).map(&:to_s)
end
end
end
# @param [Specification] spec The non library spec to calculate the deployment target for.
#
# @return [String] The deployment target to use for the non library spec. If the non library spec explicitly
# specifies one then this is the one used otherwise the one that was determined by the analyzer is used.
#
def deployment_target_for_non_library_spec(spec)
raise ArgumentError, 'Must be a non library spec.' unless spec.non_library_specification?
spec.deployment_target(platform.name.to_s) || platform.deployment_target.to_s
end
# Returns the corresponding native product type to use given the test type.
# This is primarily used when creating the native targets in order to produce the correct test bundle target
# based on the type of tests included.
#
# @param [Symbol] test_type
# The test type to map to provided by the test specification DSL.
#
# @return [Symbol] The native product type to use.
#
def product_type_for_test_type(test_type)
case test_type
when :unit
:unit_test_bundle
when :ui
:ui_test_bundle
else
raise ArgumentError, "Unknown test type `#{test_type}`."
end
end
# Returns the label to use for the given test type.
# This is used to generate native target names for test specs.
#
# @param [Symbol] test_type
# The test type to map to provided by the test specification DSL.
#
# @return [String] The native product type to use.
#
def label_for_test_type(test_type)
case test_type
when :unit
'Unit'
when :ui
'UI'
else
raise ArgumentError, "Unknown test type `#{test_type}`."
end
end
# @return [Specification] The root specification for the target.
#
def root_spec
@root_spec ||= specs.first.root
end
# @return [String] The name of the Pod that this target refers to.
#
def pod_name
root_spec.name
end
# @return [Pathname] the absolute path of the LLVM module map file that
# defines the module structure for the compiler.
#
def module_map_path
basename = "#{label}.modulemap"
if build_as_framework?
super
elsif file_accessors.any?(&:module_map)
build_headers.root + product_module_name + basename
else
sandbox.public_headers.root + product_module_name + basename
end
end
# @return [Pathname] the absolute path of the prefix header file.
#
def prefix_header_path
support_files_dir + "#{label}-prefix.pch"
end
# @return [Hash] the additional entries to add to the generated Info.plist
#
def info_plist_entries
root_spec.info_plist
end
# @param [String] bundle_name
# The name of the bundle product, which is given by the +spec+.
#
# @return [String] The derived name of the resource bundle target.
#
def resources_bundle_target_label(bundle_name)
"#{label}-#{bundle_name}"
end
# @param [Specification] subspec
# The subspec to use for producing the label.
#
# @return [String] The derived name of the target.
#
def subspec_label(subspec)
raise ArgumentError, 'Must not be a root spec' if subspec.root?
subspec.name.split('/')[1..-1].join('-').to_s
end
# @param [Specification] test_spec
# The test spec to use for producing the test label.
#
# @return [String] The derived name of the test target.
#
def test_target_label(test_spec)
"#{label}-#{label_for_test_type(test_spec.test_type)}-#{subspec_label(test_spec)}"
end
# @param [Specification] app_spec
# The app spec to use for producing the app label.
#
# @return [String] The derived name of the app target.
#
def app_target_label(app_spec)
"#{label}-#{subspec_label(app_spec)}"
end
# @param [Specification] test_spec
# the test spec to use for producing the app host target label.
#
# @return [(String,String)] a tuple, where the first item is the PodTarget#label of the pod target that defines the
# app host, and the second item is the target name of the app host
#
def app_host_target_label(test_spec)
app_spec, app_target = test_app_hosts_by_spec[test_spec]
if app_spec
[app_target.name, app_target.app_target_label(app_spec)]
elsif test_spec.consumer(platform).requires_app_host?
[name, "AppHost-#{label}-#{label_for_test_type(test_spec.test_type)}-Tests"]
end
end
# @param [Specification] spec
# the spec to return app host dependencies for
#
# @param [String] configuration
# the configuration to retrieve the app host dependent targets for.
#
# @return [Array<PodTarget>] the app host dependent targets for the given spec.
#
def app_host_dependent_targets_for_spec(spec, configuration: nil)
return [] unless spec.test_specification? && spec.consumer(platform).test_type == :unit
app_host_info = test_app_hosts_by_spec[spec]
if app_host_info.nil?
[]
else
app_spec, app_target = *app_host_info
app_target.dependent_targets_for_app_spec(app_spec, :configuration => configuration)
end
end
def spec_label(spec)
case spec.spec_type
when :library then label
when :test then test_target_label(spec)
when :app then app_target_label(spec)
else raise ArgumentError, "Unhandled spec type #{spec.spec_type.inspect} for #{spec.inspect}"
end
end
# for backwards compatibility
alias non_library_spec_label spec_label
# @param [Specification] spec
# The spec to return scheme configuration for.
#
# @return [Hash] The scheme configuration used or empty if none is specified.
#
def scheme_for_spec(spec)
return {} if (spec.library_specification? && !spec.root?) || spec.available_platforms.none? do |p|
p.name == platform.name
end
spec.consumer(platform).scheme
end
# @param [Specification] spec
# The spec this copy resources script path is for.
#
# @return [Pathname] The absolute path of the copy resources script for the given spec.
#
def copy_resources_script_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-resources.sh"
end
# @param [Specification] spec
# The spec this copy resources script path is for.
#
# @return [Pathname] The absolute path of the copy resources script input file list for the given spec.
#
def copy_resources_script_input_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-resources-input-files.xcfilelist"
end
# @param [Specification] spec
# The spec this copy resources script path is for.
#
# @return [Pathname] The absolute path of the copy resources script output file list for the given spec.
#
def copy_resources_script_output_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-resources-output-files.xcfilelist"
end
# @param [Specification] spec
# The spec this embed frameworks script path is for.
#
# @return [Pathname] The absolute path of the embed frameworks script for the given spec.
#
def embed_frameworks_script_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-frameworks.sh"
end
# @param [Specification] spec
# The spec this embed frameworks script path is for.
#
# @return [Pathname] The absolute path of the embed frameworks script input file list for the given spec.
#
def embed_frameworks_script_input_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-frameworks-input-files.xcfilelist"
end
# @param [Specification] spec
# The spec this embed frameworks script path is for.
#
# @return [Pathname] The absolute path of the embed frameworks script output file list for the given spec.
#
def embed_frameworks_script_output_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-frameworks-output-files.xcfilelist"
end
# @return [Pathname] The absolute path of the copy xcframeworks script.
#
def copy_xcframeworks_script_path
support_files_dir + "#{label}-xcframeworks.sh"
end
# @return [String] The path of the copy xcframeworks input files file list
#
def copy_xcframeworks_script_input_files_path
support_files_dir + "#{label}-xcframeworks-input-files.xcfilelist"
end
# @return [String] The path of the copy xcframeworks output files file list
#
def copy_xcframeworks_script_output_files_path
support_files_dir + "#{label}-xcframeworks-output-files.xcfilelist"
end
# @param [Specification] spec
# The spec this script path is for.
#
# @return [Pathname] The absolute path of the prepare artifacts script for the given spec.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-artifacts.sh"
end
# @param [Specification] spec
# The spec this script path is for.
#
# @return [Pathname] The absolute path of the prepare artifacts script input file list for the given spec.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_input_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-artifacts-input-files.xcfilelist"
end
# @param [Specification] spec
# The spec this script path is for.
#
# @return [Pathname] The absolute path of the prepare artifacts script output file list for the given spec.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_output_files_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-artifacts-output-files.xcfilelist"
end
# @return [Pathname] The absolute path of the copy dSYMs script.
#
def copy_dsyms_script_path
support_files_dir + "#{label}-copy-dsyms.sh"
end
# @return [Pathname] The absolute path of the copy dSYM script phase input file list.
#
def copy_dsyms_script_input_files_path
support_files_dir + "#{label}-copy-dsyms-input-files.xcfilelist"
end
# @return [Pathname] The absolute path of the copy dSYM script phase output file list.
#
def copy_dsyms_script_output_files_path
support_files_dir + "#{label}-copy-dsyms-output-files.xcfilelist"
end
# @param [Specification] spec
# The spec this Info.plist path is for.
#
# @return [Pathname] The absolute path of the Info.plist for the given spec.
#
def info_plist_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-Info.plist"
end
# @param [Specification] spec
# The spec this prefix header path is for.
#
# @return [Pathname] the absolute path of the prefix header file for the given spec.
#
def prefix_header_path_for_spec(spec)
support_files_dir + "#{spec_label(spec)}-prefix.pch"
end
# @return [Array<String>] The names of the Pods on which this target
# depends.
#
def dependencies
spec_consumers.flat_map do |consumer|
consumer.dependencies.map { |dep| Specification.root_name(dep.name) }
end.uniq
end
# Returns all dependent targets of this target. If a configuration is passed then the list can be scoped to a given
# configuration.
#
# @param [String] configuration
# The configuration to return the dependent targets for or `nil` if all configurations should be included.
#
# @return [Array<PodTarget>] the recursive targets that this target has a dependency upon.
#
def recursive_dependent_targets(configuration: nil)
@recursive_dependent_targets ||= begin
hash = Hash[config_variants.map do |config|
[config, _add_recursive_dependent_targets(Set.new, :configuration => config).delete(self).to_a.freeze]
end]
hash[nil] = hash.each_value.reduce(Set.new, &:|).to_a
hash
end
@recursive_dependent_targets.fetch(configuration) { raise ArgumentError, "No configuration #{configuration} for #{self}, known configurations are #{@recursive_dependent_targets.keys}" }
end
def _add_recursive_dependent_targets(set, configuration: nil)
if defined?(@recursive_dependent_targets)
return set.merge(@recursive_dependent_targets[configuration])
end
dependent_targets = configuration ? dependent_targets_by_config[configuration] : self.dependent_targets
dependent_targets.each do |target|
target._add_recursive_dependent_targets(set, :configuration => configuration) if set.add?(target)
end
set
end
protected :_add_recursive_dependent_targets
# @param [Specification] test_spec
# the test spec to scope dependencies for
#
# @param [String] configuration
# the configuration to retrieve the test dependent targets for.
#
# @return [Array<PodTarget>] the recursive targets that this target has a
# test dependency upon.
#
def recursive_test_dependent_targets(test_spec, configuration: nil)
@recursive_test_dependent_targets ||= {}
@recursive_test_dependent_targets[test_spec] ||= begin
hash = Hash[config_variants.map do |config|
[config, _add_recursive_test_dependent_targets(test_spec, Set.new, :configuration => config).to_a.freeze]
end]
hash[nil] = hash.each_value.reduce(Set.new, &:|).to_a.freeze
hash
end
@recursive_test_dependent_targets[test_spec][configuration]
end
def _add_recursive_test_dependent_targets(test_spec, set, configuration: nil)
raise ArgumentError, 'Must give a test spec' unless test_spec
dependent_targets = configuration ? test_dependent_targets_by_spec_name_by_config[test_spec.name][configuration] : test_dependent_targets_by_spec_name[test_spec.name]
raise ArgumentError, "Unable to find deps for #{test_spec} for config #{configuration.inspect} (out of #{test_dependent_targets_by_spec_name_by_config.inspect})" unless dependent_targets
dependent_targets.each do |target|
target._add_recursive_dependent_targets(set, :configuration => configuration) if set.add?(target)
end
set
end
private :_add_recursive_test_dependent_targets
# @param [Specification] test_spec
# the test spec to scope dependencies for
#
# @param [String] configuration
# the configuration to retrieve the test dependent targets for.
#
# @return [Array<PodTarget>] the canonical list of dependent targets this target has a dependency upon.
# This list includes the target itself as well as its recursive dependent and test dependent targets.
#
def dependent_targets_for_test_spec(test_spec, configuration: nil)
[self, *recursive_dependent_targets(:configuration => configuration), *recursive_test_dependent_targets(test_spec, :configuration => configuration)].uniq
end
# @param [Specification] app_spec
# the app spec to scope dependencies for
#
# @param [String] configuration
# the configuration to retrieve the app dependent targets for.
#
# @return [Array<PodTarget>] the recursive targets that this target has a
# app dependency upon.
#
def recursive_app_dependent_targets(app_spec, configuration: nil)
@recursive_app_dependent_targets ||= {}
@recursive_app_dependent_targets[app_spec] ||= begin
hash = Hash[config_variants.map do |config|
[config, _add_recursive_app_dependent_targets(app_spec, Set.new, :configuration => config).to_a.freeze]
end]
hash[nil] = hash.each_value.reduce(Set.new, &:|).to_a.freeze
hash
end
@recursive_app_dependent_targets[app_spec][configuration]
end
def _add_recursive_app_dependent_targets(app_spec, set, configuration: nil)
raise ArgumentError, 'Must give a app spec' unless app_spec
dependent_targets = configuration ? app_dependent_targets_by_spec_name_by_config[app_spec.name][configuration] : app_dependent_targets_by_spec_name[app_spec.name]
raise ArgumentError, "Unable to find deps for #{app_spec} for config #{configuration.inspect} #{app_dependent_targets_by_spec_name_by_config.inspect}" unless dependent_targets
dependent_targets.each do |target|
target._add_recursive_dependent_targets(set, :configuration => configuration) if set.add?(target)
end
set
end
private :_add_recursive_app_dependent_targets
# @param [Specification] app_spec
# the app spec to scope dependencies for
#
# @param [String] configuration
# the configuration to retrieve the app dependent targets for.
#
# @return [Array<PodTarget>] the canonical list of dependent targets this target has a dependency upon.
# This list includes the target itself as well as its recursive dependent and app dependent targets.
#
def dependent_targets_for_app_spec(app_spec, configuration: nil)
[self, *recursive_dependent_targets(:configuration => configuration), *recursive_app_dependent_targets(app_spec, :configuration => configuration)].uniq
end
# Checks if warnings should be inhibited for this pod.
#
# @return [Boolean]
#
def inhibit_warnings?
return @inhibit_warnings if defined? @inhibit_warnings
whitelists = target_definitions.map do |target_definition|
target_definition.inhibits_warnings_for_pod?(root_spec.name)
end.uniq
if whitelists.empty?
@inhibit_warnings = false
false
elsif whitelists.count == 1
@inhibit_warnings = whitelists.first
whitelists.first
else
UI.warn "The pod `#{pod_name}` is linked to different targets " \
"(#{target_definitions.map { |td| "`#{td.name}`" }.to_sentence}), which contain different " \
'settings to inhibit warnings. CocoaPods does not currently ' \
'support different settings and will fall back to your preference ' \
'set in the root target definition.'
@inhibit_warnings = podfile.root_target_definitions.first.inhibits_warnings_for_pod?(root_spec.name)
end
end
# @param [String] dir
# The directory (which might be a variable) relative to which
# the returned path should be. This must be used if the
# $CONFIGURATION_BUILD_DIR is modified.
#
# @return [String] The absolute path to the configuration build dir
#
def configuration_build_dir(dir = BuildSettings::CONFIGURATION_BUILD_DIR_VARIABLE)
"#{dir}/#{label}"
end
# @param [String] dir
# @see #configuration_build_dir
#
# @return [String] The absolute path to the build product
#
def build_product_path(dir = BuildSettings::CONFIGURATION_BUILD_DIR_VARIABLE)
"#{configuration_build_dir(dir)}/#{product_name}"
end
# @return [String] The source path of the root for this target relative to `$(PODS_ROOT)`
#
def pod_target_srcroot
"${PODS_ROOT}/#{sandbox.pod_dir(pod_name).relative_path_from(sandbox.root)}"
end
# @return [String] The version associated with this target
#
def version
version = root_spec.version
[version.major, version.minor, version.patch].join('.')
end
# @param [Boolean] include_dependent_targets_for_test_spec
# whether to include header search paths for test dependent targets
#
# @param [Boolean] include_dependent_targets_for_app_spec
# whether to include header search paths for app dependent targets
#
# @param [Boolean] include_private_headers
# whether to include header search paths for private headers of this
# target
#
# @param [String] configuration
# the configuration to return header search paths for or `nil` for all configurations.
#
# @return [Array<String>] The set of header search paths this target uses.
#
def header_search_paths(include_dependent_targets_for_test_spec: nil, include_dependent_targets_for_app_spec: nil,
include_private_headers: true, configuration: nil)
header_search_paths = []
header_search_paths.concat(build_headers.search_paths(platform, nil, false)) if include_private_headers
header_search_paths.concat(sandbox.public_headers.search_paths(platform, pod_name, uses_modular_headers?))
dependent_targets = recursive_dependent_targets(:configuration => configuration)
if include_dependent_targets_for_test_spec
dependent_targets += recursive_test_dependent_targets(include_dependent_targets_for_test_spec, :configuration => configuration)
end
if include_dependent_targets_for_app_spec
dependent_targets += recursive_app_dependent_targets(include_dependent_targets_for_app_spec, :configuration => configuration)
end
dependent_targets.uniq.each do |dependent_target|
header_search_paths.concat(sandbox.public_headers.search_paths(platform, dependent_target.pod_name, defines_module? && dependent_target.uses_modular_headers?(false)))
end
header_search_paths.uniq
end
# @param [Specification] spec the specification to return build settings for.
#
# @param [String] configuration the configuration to scope the build settings.
#
# @return [BuildSettings::PodTargetSettings] The build settings for the given spec
#
def build_settings_for_spec(spec, configuration: nil)
raise ArgumentError, 'Must give configuration' unless configuration
configuration = user_build_configurations[configuration] if user_build_configurations.key?(configuration)
build_settings_by_config_for_spec(spec)[configuration] || raise(ArgumentError, "No build settings for #{spec} (configuration #{configuration.inspect}) (known configurations #{config_variants})")
end
def build_settings_by_config_for_spec(spec)
case spec.spec_type
when :test then test_spec_build_settings_by_config[spec.name]
when :app then app_spec_build_settings_by_config[spec.name]
else build_settings
end || raise(ArgumentError, "No build settings for #{spec}")
end
def user_config_names_by_config_type
user_build_configurations.each_with_object({}) do |(user, type), hash|
hash[type] ||= []
hash[type] << user
end.each_value(&:freeze).freeze
end
protected
# Returns whether the pod target should use modular headers.
#
# @param [Boolean] only_if_defines_modules
# whether the use of modular headers should require the target to define a module
#
# @note This must return false when a pod has a `header_mappings_dir` or `header_dir`,
# as that allows the spec to customize the header structure, and
# therefore it might not be expecting the module name to be prepended
# to imports at all.
#
def uses_modular_headers?(only_if_defines_modules = true)
return false if only_if_defines_modules && !defines_module?
return @uses_modular_headers if defined? @uses_modular_headers
@uses_modular_headers = spec_consumers.none?(&:header_mappings_dir) && spec_consumers.none?(&:header_dir)
end
private
def config_variants
if user_build_configurations.empty?
%i(debug release)
else
user_build_configurations.values.uniq
end
end
def create_build_settings
Hash[config_variants.map do |config|
[config, BuildSettings::PodTargetSettings.new(self, nil, :configuration => config)]
end]
end
def create_test_build_settings_by_config
Hash[test_specs.map do |test_spec|
[test_spec.name, Hash[config_variants.map do |config|
[config, BuildSettings::PodTargetSettings.new(self, test_spec, :configuration => config)]
end]]
end]
end
def create_app_build_settings_by_config
Hash[app_specs.map do |app_spec|
[app_spec.name, Hash[config_variants.map do |config|
[config, BuildSettings::PodTargetSettings.new(self, app_spec, :configuration => config)]
end]]
end]
end
# Computes the destination sub-directory in the sandbox
#
# @param [Sandbox::FileAccessor] file_accessor
# The consumer file accessor for which the headers need to be
# linked.
#
# @param [Array<Pathname>] headers
# The absolute paths of the headers which need to be mapped.
#
# @return [Hash{Pathname => Array<Pathname>}] A hash containing the
# headers folders as the keys and the absolute paths of the
# header files as the values.
#
def header_mappings(file_accessor, headers)
consumer = file_accessor.spec_consumer
header_mappings_dir = consumer.header_mappings_dir
dir = headers_sandbox
dir += consumer.header_dir if consumer.header_dir
mappings = {}
headers.each do |header|
next if header.to_s.include?('.framework/')
sub_dir = dir
if header_mappings_dir
relative_path = header.relative_path_from(file_accessor.path_list.root + header_mappings_dir)
sub_dir += relative_path.dirname
end
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
mappings
end
# @!group Deprecated APIs
# ----------------------------------------------------------------------- #
public
# @deprecated Use `test_app_hosts_by_spec` instead.
#
# @todo Remove in 2.0
#
# @return [Hash{String => (Specification,PodTarget)}] tuples of app specs and pod targets by test spec name.
#
def test_app_hosts_by_spec_name
Hash[test_app_hosts_by_spec.map do |spec, value|
[spec.name, value]
end]
end
end
end