CocoaPods/CocoaPods

View on GitHub
lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
93%
module Pod
  class Installer
    class UserProjectIntegrator
      class TargetIntegrator
        # Configures an user target to use the CocoaPods xcconfigs which allow
        # lo link against the Pods.
        #
        class XCConfigIntegrator
          # Integrates the user target.
          #
          # @param  [Target::AggregateTarget] pod_bundle
          #         The Pods bundle.
          #
          # @param  [Array<PBXNativeTarget>] targets
          #         The native targets associated which should be integrated
          #         with the Pod bundle.
          #
          def self.integrate(pod_bundle, targets)
            targets.each do |target|
              target.build_configurations.each do |config|
                set_target_xcconfig(pod_bundle, target, config)
              end
            end
          end

          private

          # @!group Integration steps
          #-------------------------------------------------------------------#

          # Creates a file reference to the xcconfig generated by
          # CocoaPods (if needed) and sets it as the base configuration of
          # build configuration of the user target.
          #
          # @param  [Target::AggregateTarget] pod_bundle
          #         The Pods bundle.
          #
          # @param  [PBXNativeTarget] target
          #         The native target.
          #
          # @param  [Xcodeproj::XCBuildConfiguration] config
          #         The build configuration.
          #
          def self.set_target_xcconfig(pod_bundle, target, config)
            file_ref = create_xcconfig_ref(pod_bundle, config)
            path = file_ref.path

            existing = config.base_configuration_reference

            if existing && existing != file_ref
              if existing.real_path.to_path.start_with?(pod_bundle.sandbox.root.to_path << '/')
                config.base_configuration_reference = file_ref
              elsif !xcconfig_includes_target_xcconfig?(config.base_configuration_reference, path)
                unless existing_config_is_identical_to_pod_config?(existing.real_path, pod_bundle.xcconfig_path(config.name))
                  UI.warn 'CocoaPods did not set the base configuration of your ' \
                  'project because your project already has a custom ' \
                  'config set. In order for CocoaPods integration to work at ' \
                  'all, please either set the base configurations of the target ' \
                  "`#{target.name}` to `#{path}` or include the `#{path}` in your " \
                  "build configuration (#{UI.path(existing.real_path)})."
                end
              end
            elsif config.base_configuration_reference.nil? || file_ref.nil?
              config.base_configuration_reference = file_ref
            end
          end

          private

          # @!group Private helpers
          #-------------------------------------------------------------------#

          # Prints a warning informing the user that a build configuration of
          # the integrated target is overriding the CocoaPods build settings.
          #
          # @param  [Target::AggregateTarget] pod_bundle
          #         The Pods bundle.
          #
          # @param  [XcodeProj::PBXNativeTarget] target
          #         The native target.
          #
          # @param  [Xcodeproj::XCBuildConfiguration] config
          #         The build configuration.
          #
          # @param  [String] key
          #         The key of the overridden build setting.
          #
          def self.print_override_warning(pod_bundle, target, config, key)
            actions = [
              'Use the `$(inherited)` flag, or',
              'Remove the build settings from the target.',
            ]
            message = "The `#{target.name} [#{config.name}]` " \
              "target overrides the `#{key}` build setting defined in " \
              "`#{pod_bundle.pod_bundle.xcconfig_relative_path(config.name)}'. " \
              'This can lead to problems with the CocoaPods installation'
            UI.warn(message, actions)
          end

          # Naively checks to see if a given PBXFileReference imports a given
          # path.
          #
          # @param  [PBXFileReference] base_config_ref
          #         A file reference to an `.xcconfig` file.
          #
          # @param  [String] target_config_path
          #         The path to check for.
          #
          SILENCE_WARNINGS_STRING = '// @COCOAPODS_SILENCE_WARNINGS@ //'
          def self.xcconfig_includes_target_xcconfig?(base_config_ref, target_config_path)
            return unless base_config_ref && base_config_ref.real_path.file?
            regex = %r{
              ^(
                (\s*                                  # Possible, but unlikely, space before include statement
                  \#include(\?)?\s+                   # Include statement
                  ['"]                                # Open quote
                  (.*\/)?                             # Possible prefix to path
                  #{Regexp.quote(target_config_path)} # The path should end in the target_config_path
                  ['"]                                # Close quote
                )
                |
                (#{Regexp.quote(SILENCE_WARNINGS_STRING)}) # Token to treat xcconfig as good and silence pod install warnings
              )
            }x
            base_config_ref.real_path.readlines.find { |line| line =~ regex }
          end

          # Checks to see if the config files at two paths exist and are identical
          #
          # @param  The existing config path
          #
          # @param  The pod config path
          #
          def self.existing_config_is_identical_to_pod_config?(existing_config_path, pod_config_path)
            existing_config_path.file? && (!pod_config_path.file? || FileUtils.compare_file(existing_config_path, pod_config_path))
          end

          # Creates a file reference to the xcconfig generated by
          # CocoaPods (if needed).
          # If the Pods group not exists, create the group and set
          # the location to the `Pods` directory.
          # If the file reference exists, the location is different
          # with the xcconfig's path and the symlink target paths
          # are different, we will update the location.
          #
          # @param  [Target::AggregateTarget] pod_bundle
          #         The Pods bundle.
          #
          # @param  [Xcodeproj::XCBuildConfiguration] config
          #         The build configuration.
          #
          # @return [PBXFileReference] the xcconfig reference.
          #
          def self.create_xcconfig_ref(pod_bundle, config)
            # Xcode root group's path is absolute, we must get the relative path of the sandbox to the user project
            group_path = pod_bundle.relative_pods_root_path
            group = config.project['Pods'] || config.project.new_group('Pods', group_path)

            # support user custom paths of Pods group and xcconfigs files.
            group_path = Pathname.new(group.real_path)
            xcconfig_path = Pathname.new(pod_bundle.xcconfig_path(config.name))
            path = xcconfig_path.relative_path_from(group_path)

            filename = path.basename.to_s
            file_ref = group.files.find { |f| f.display_name == filename }
            if file_ref && file_ref.path != path
              file_ref_path = Pathname.new(file_ref.real_path)
              if !file_ref_path.exist? || !xcconfig_path.exist? || file_ref_path.realpath != xcconfig_path.realpath
                file_ref.path = path.to_s
              end
            end

            file_ref || group.new_file(path.to_s)
          end
        end
      end
    end
  end
end