| 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. |
| # |
| class PodTarget < Target |
| # @return [Array<Specification>] the spec and subspecs for the target. |
| # |
| attr_reader :specs |
| |
| # @return [Array<TargetDefinition>] the target definitions of the Podfile |
| # that generated this target. |
| # |
| attr_reader :target_definitions |
| |
| # @return [HeadersStore] the header directory for the target. |
| # |
| attr_reader :build_headers |
| |
| # @return [String] used as suffix in the label |
| # |
| # @note This affects the value returned by #configuration_build_dir |
| # and accessors relying on this as #build_product_path. |
| # |
| attr_reader :scope_suffix |
| |
| # @return [Array<PodTarget>] the targets that this target has a dependency |
| # upon. |
| # |
| attr_accessor :dependent_targets |
| |
| # @return [Array<PodTarget>] the targets that this target has a test dependency |
| # upon. |
| # |
| attr_accessor :test_dependent_targets |
| |
| # return [Array<PBXNativeTarget>] the test target generated in the Pods project for |
| # this library or `nil` if there is no test target created. |
| # |
| attr_accessor :test_native_targets |
| |
| # @param [Array<Specification>] specs @see #specs |
| # @param [Array<TargetDefinition>] target_definitions @see #target_definitions |
| # @param [Sandbox] sandbox @see #sandbox |
| # @param [String] scope_suffix @see #scope_suffix |
| # |
| def initialize(specs, target_definitions, sandbox, scope_suffix = nil) |
| 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 only abstract TargetDefinitions" if target_definitions.all?(&:abstract?) |
| raise "Can't initialize a PodTarget with an empty string scope suffix!" if scope_suffix == '' |
| super() |
| @specs = specs |
| @target_definitions = target_definitions |
| @sandbox = sandbox |
| @scope_suffix = scope_suffix |
| @build_headers = Sandbox::HeadersStore.new(sandbox, 'Private') |
| @file_accessors = [] |
| @resource_bundle_targets = [] |
| @test_resource_bundle_targets = [] |
| @test_native_targets = [] |
| @dependent_targets = [] |
| @test_dependent_targets = [] |
| @build_config_cache = {} |
| end |
| |
| # @param [Hash{Array => PodTarget}] cache |
| # the cached PodTarget for a previously scoped (specs, target_definition) |
| # @return [Array<PodTarget>] a scoped copy for each target definition. |
| # |
| def scoped(cache = {}) |
| target_definitions.map do |target_definition| |
| cache_key = [specs, target_definition] |
| if cache[cache_key] |
| cache[cache_key] |
| else |
| target = PodTarget.new(specs, [target_definition], sandbox, target_definition.label) |
| target.file_accessors = file_accessors |
| target.user_build_configurations = user_build_configurations |
| target.native_target = native_target |
| target.archs = archs |
| target.dependent_targets = dependent_targets.flat_map { |pt| pt.scoped(cache) }.select { |pt| pt.target_definitions == [target_definition] } |
| target.host_requires_frameworks = host_requires_frameworks |
| cache[cache_key] = 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 [String] the Swift version for the target. If the pod author has provided a swift version |
| # then that is the one returned, otherwise the Swift version is determined by the user |
| # targets that include this pod target. |
| # |
| def swift_version |
| root_spec.swift_version || target_definitions.map(&:swift_version).compact.uniq.first |
| end |
| |
| # @note The deployment target for the pod target is the maximum of all |
| # the deployment targets for the current platform of the target |
| # (or the minimum required to support the current installation |
| # strategy, if higher). |
| # |
| # @return [Platform] the platform for this target. |
| # |
| def platform |
| @platform ||= begin |
| platform_name = target_definitions.first.platform.name |
| default = Podfile::TargetDefinition::PLATFORM_DEFAULTS[platform_name] |
| deployment_target = specs.map do |spec| |
| Version.new(spec.deployment_target(platform_name) || default) |
| end.max |
| if platform_name == :ios && requires_frameworks? |
| minimum = Version.new('8.0') |
| deployment_target = [deployment_target, minimum].max |
| end |
| Platform.new(platform_name, deployment_target) |
| end |
| end |
| |
| # @visibility private |
| # |
| attr_writer :platform |
| |
| # @return [Podfile] The podfile which declares the dependency. |
| # |
| def podfile |
| target_definitions.first.podfile |
| 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 |
| |
| # @return [Array<Sandbox::FileAccessor>] the file accessors for the |
| # specifications of this target. |
| # |
| attr_accessor :file_accessors |
| |
| # @return [Array<PBXNativeTarget>] the resource bundle targets belonging |
| # to this target. |
| attr_reader :resource_bundle_targets |
| |
| # @return [Array<PBXNativeTarget>] the resource bundle test targets belonging |
| # to this target. |
| attr_reader :test_resource_bundle_targets |
| |
| # @return [Bool] Whether or not this target should be build. |
| # |
| # A target should not be build if it has no source files. |
| # |
| def should_build? |
| return @should_build if defined? @should_build |
| @should_build = begin |
| source_files = file_accessors.flat_map(&:source_files) |
| source_files -= file_accessors.flat_map(&:headers) |
| !source_files.empty? || contains_script_phases? |
| end |
| end |
| |
| # @return [Array<Specification::Consumer>] the specification consumers for |
| # the target. |
| # |
| def spec_consumers |
| specs.map { |spec| spec.consumer(platform) } |
| end |
| |
| # @return [Boolean] Whether the target uses Swift code. |
| # |
| def uses_swift? |
| return @uses_swift if defined? @uses_swift |
| @uses_swift = begin |
| file_accessors.any? do |file_accessor| |
| file_accessor.source_files.any? { |sf| sf.extname == '.swift' } |
| end |
| end |
| end |
| |
| # @return [Array<Hash{Symbol=>String}>] An array of hashes where each hash represents a single script phase. |
| # |
| def script_phases |
| spec_consumers.map(&:script_phases).flatten |
| end |
| |
| # @return [Boolean] Whether the target contains any script phases. |
| # |
| def contains_script_phases? |
| !script_phases.empty? |
| end |
| |
| # @return [Hash{Array => Specification}] a hash where the keys are the test native targets and the value |
| # an array of all the test specs associated with this native target. |
| # |
| def test_specs_by_native_target |
| test_specs.group_by { |test_spec| native_target_for_spec(test_spec) } |
| end |
| |
| # @return [Boolean] Whether the target has any tests specifications. |
| # |
| def contains_test_specifications? |
| specs.any?(&:test_specification?) |
| end |
| |
| # @return [Array<Specification>] All of the test specs within this target. |
| # |
| def test_specs |
| specs.select(&:test_specification?) |
| end |
| |
| # @return [Array<Specification>] All of the non test specs within this target. |
| # |
| def non_test_specs |
| specs.reject(&:test_specification?) |
| end |
| |
| # @return [Array<Symbol>] All of the test supported types within this target. |
| # |
| def supported_test_types |
| test_specs.map(&:test_type).uniq |
| end |
| |
| # Returns the framework paths associated with this target. By default all paths include the framework paths |
| # that are part of test specifications. |
| # |
| # @param [Boolean] include_test_spec_paths |
| # Whether to include framework paths from test specifications or not. |
| # |
| # @return [Array<Hash{Symbol => [String]}>] The vendored and non vendored framework paths |
| # this target depends upon. |
| # |
| def framework_paths(include_test_spec_paths = true) |
| @framework_paths ||= {} |
| return @framework_paths[include_test_spec_paths] if @framework_paths.key?(include_test_spec_paths) |
| @framework_paths[include_test_spec_paths] = begin |
| accessors = file_accessors |
| accessors = accessors.reject { |a| a.spec.test_specification? } unless include_test_spec_paths |
| frameworks = [] |
| accessors.flat_map(&:vendored_dynamic_artifacts).map do |framework_path| |
| relative_path_to_sandbox = framework_path.relative_path_from(sandbox.root) |
| framework = { :name => framework_path.basename.to_s, |
| :input_path => "${PODS_ROOT}/#{relative_path_to_sandbox}", |
| :output_path => "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/#{framework_path.basename}" } |
| # 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}") |
| if dsym_path.exist? |
| framework[:dsym_name] = dsym_name |
| framework[:dsym_input_path] = "${PODS_ROOT}/#{relative_path_to_sandbox}.dSYM" |
| framework[:dsym_output_path] = "${DWARF_DSYM_FOLDER_PATH}/#{dsym_name}" |
| end |
| frameworks << framework |
| end |
| if should_build? && requires_frameworks? && !static_framework? |
| frameworks << { :name => product_name, |
| :input_path => build_product_path('${BUILT_PRODUCTS_DIR}'), |
| :output_path => "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/#{product_name}" } |
| end |
| frameworks |
| end |
| end |
| |
| # Returns the resource paths associated with this target. By default all paths include the resource paths |
| # that are part of test specifications. |
| # |
| # @param [Boolean] include_test_spec_paths |
| # Whether to include resource paths from test specifications or not. |
| # |
| # @return [Array<String>] The resource and resource bundle paths this target depends upon. |
| # |
| def resource_paths(include_test_spec_paths = true) |
| @resource_paths ||= {} |
| return @resource_paths[include_test_spec_paths] if @resource_paths.key?(include_test_spec_paths) |
| @resource_paths[include_test_spec_paths] = begin |
| accessors = file_accessors |
| accessors = accessors.reject { |a| a.spec.test_specification? } unless include_test_spec_paths |
| resource_paths = accessors.flat_map do |accessor| |
| accessor.resources.flat_map { |res| "${PODS_ROOT}/#{res.relative_path_from(sandbox.project.path.dirname)}" } |
| end |
| resource_bundles = accessors.flat_map do |accessor| |
| prefix = Generator::XCConfig::XCConfigHelper::CONFIGURATION_BUILD_DIR_VARIABLE |
| prefix = configuration_build_dir unless accessor.spec.test_specification? |
| accessor.resource_bundles.keys.map { |name| "#{prefix}/#{name.shellescape}.bundle" } |
| end |
| resource_paths + resource_bundles |
| end |
| end |
| |
| # Returns the corresponding native target to use based on the provided specification. |
| # This is used to figure out whether to add a source file into the library native target or any of the |
| # test native targets. |
| # |
| # @param [Specification] spec |
| # The specification to base from in order to find the native target. |
| # |
| # @return [PBXNativeTarget] the native target to use or `nil` if none is found. |
| # |
| def native_target_for_spec(spec) |
| return native_target unless spec.test_specification? |
| test_native_targets.find do |native_target| |
| native_target.symbol_type == product_type_for_test_type(spec.test_type) |
| end |
| 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 |
| else |
| raise Informative, "Unknown test type `#{test_type}`." |
| end |
| end |
| |
| # Returns the corresponding test type given the product type. |
| # |
| # @param [Symbol] product_type |
| # The product type to map to a test type. |
| # |
| # @return [Symbol] The native product type to use. |
| # |
| def test_type_for_product_type(product_type) |
| case product_type |
| when :unit_test_bundle |
| :unit |
| else |
| raise Informative, "Unknown product type `#{product_type}`." |
| end |
| end |
| |
| # @return [Specification] The root specification for the target. |
| # |
| def 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 |
| |
| # @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 [Symbol] test_type |
| # The test type to use for producing the test label. |
| # |
| # @return [String] The derived name of the test target. |
| # |
| def test_target_label(test_type) |
| "#{label}-#{test_type.capitalize}-Tests" |
| end |
| |
| # @param [Symbol] test_type |
| # The test type to use for producing the test label. |
| # |
| # @return [String] The label of the app host label to use given the platform and test type. |
| # |
| def app_host_label(test_type) |
| "AppHost-#{Platform.string_name(platform.symbolic_name)}-#{test_type.capitalize}-Tests" |
| end |
| |
| # @param [Symbol] test_type |
| # The test type this embed frameworks script path is for. |
| # |
| # @return [Pathname] The absolute path of the copy resources script for the given test type. |
| # |
| def copy_resources_script_path_for_test_type(test_type) |
| support_files_dir + "#{test_target_label(test_type)}-resources.sh" |
| end |
| |
| # @param [Symbol] test_type |
| # The test type this embed frameworks script path is for. |
| # |
| # @return [Pathname] The absolute path of the embed frameworks script for the given test type. |
| # |
| def embed_frameworks_script_path_for_test_type(test_type) |
| support_files_dir + "#{test_target_label(test_type)}-frameworks.sh" |
| end |
| |
| # @return [Pathname] the absolute path of the prefix header file. |
| # |
| def prefix_header_path |
| support_files_dir + "#{label}-prefix.pch" |
| end |
| |
| # @param [Symbol] test_type |
| # The test type prefix header path is for. |
| # |
| # @return [Pathname] the absolute path of the prefix header file for the given test type. |
| # |
| def prefix_header_path_for_test_type(test_type) |
| support_files_dir + "#{test_target_label(test_type)}-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 |
| |
| # @return [Array<PodTarget>] the recursive targets that this target has a |
| # dependency upon. |
| # |
| def recursive_dependent_targets |
| @recursive_dependent_targets ||= begin |
| targets = dependent_targets.clone |
| |
| targets.each do |target| |
| target.dependent_targets.each do |t| |
| targets.push(t) unless t == self || targets.include?(t) |
| end |
| end |
| |
| targets |
| end |
| end |
| |
| # @return [Array<PodTarget>] the recursive targets that this target has a |
| # test dependency upon. |
| # |
| def recursive_test_dependent_targets |
| @recursive_test_dependent_targets ||= begin |
| targets = test_dependent_targets.clone |
| |
| targets.each do |target| |
| target.test_dependent_targets.each do |t| |
| targets.push(t) unless t == self || targets.include?(t) |
| end |
| end |
| |
| targets |
| end |
| end |
| |
| # @return [Array<PodTarget>] the canonical list of test dependent targets this target has a dependency upon. |
| # This includes the parent target as well as its transitive dependencies. |
| # |
| def all_test_dependent_targets |
| [self, *recursive_dependent_targets, *recursive_test_dependent_targets].uniq |
| end |
| |
| # Checks if the target should be included in the build configuration with |
| # the given name of a given target definition. |
| # |
| # @param [TargetDefinition] target_definition |
| # The target definition to check. |
| # |
| # @param [String] configuration_name |
| # The name of the build configuration. |
| # |
| def include_in_build_config?(target_definition, configuration_name) |
| key = [target_definition.label, configuration_name] |
| if @build_config_cache.key?(key) |
| return @build_config_cache[key] |
| end |
| |
| whitelists = target_definition_dependencies(target_definition).map do |dependency| |
| target_definition.pod_whitelisted_for_configuration?(dependency.name, configuration_name) |
| end.uniq |
| |
| if whitelists.empty? |
| @build_config_cache[key] = true |
| true |
| elsif whitelists.count == 1 |
| @build_config_cache[key] = whitelists.first |
| whitelists.first |
| else |
| raise Informative, "The subspecs of `#{pod_name}` are linked to " \ |
| "different build configurations for the `#{target_definition}` " \ |
| 'target. CocoaPods does not currently support subspecs across ' \ |
| 'different build configurations.' |
| end |
| end |
| |
| # Checks if warnings should be inhibited for this pod. |
| # |
| # @return [Bool] |
| # |
| 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(&:label)}), 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.' |
| 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 = Generator::XCConfig::XCConfigHelper::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 = Generator::XCConfig::XCConfigHelper::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 |
| |
| private |
| |
| # @param [TargetDefinition] target_definition |
| # The target definition to check. |
| # |
| # @return [Array<Dependency>] The dependency of the target definition for |
| # this Pod. Return an empty array if the Pod is not a direct |
| # dependency of the target definition but the dependency of one or |
| # more Pods. |
| # |
| def target_definition_dependencies(target_definition) |
| target_definition.dependencies.select do |dependency| |
| Specification.root_name(dependency.name) == pod_name |
| end |
| end |
| end |
| end |