Commit d80a7386 authored by Fabio Pelosin's avatar Fabio Pelosin

[TargetIntegrator] Factor out XCConfig logic

Also includes some small fixes here and there
parent 2cbbbcc5
......@@ -9,6 +9,8 @@ module Pod
#
class TargetIntegrator
autoload :XCConfigIntegrator, 'cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator'
# @return [Target] the target that should be integrated.
#
attr_reader :target
......@@ -26,9 +28,9 @@ module Pod
# @return [void]
#
def integrate!
return if native_targets.empty?
UI.section(integration_message) do
add_xcconfig_base_configuration
XCConfigIntegrator.integrate(target, native_targets)
return if native_targets_to_integrate.empty?
add_pods_library
add_copy_resources_script_phase
add_check_manifest_lock_script_phase
......@@ -36,80 +38,17 @@ module Pod
end
end
# @return [Array<PBXNativeTarget>] the user targets for integration.
#
def native_targets
unless @native_targets
target_uuids = target.user_target_uuids
native_targets = target_uuids.map do |uuid|
native_target = user_project.objects_by_uuid[uuid]
unless native_target
raise Informative, "[Bug] Unable to find the target with " \
"the `#{uuid}` UUID for the `#{target}` integration library"
end
native_target
end
non_integrated = native_targets.reject do |native_target|
native_target.frameworks_build_phase.files.any? do |build_file|
file_ref = build_file.file_ref
file_ref &&
file_ref.isa == 'PBXFileReference' &&
file_ref.display_name == target.product_name
end
end
@native_targets = non_integrated
end
@native_targets
end
# Read the project from the disk to ensure that it is up to date as
# other TargetIntegrators might have modified it.
#
def user_project
@user_project ||= Xcodeproj::Project.open(target.user_project_path)
end
# @return [String] a string representation suitable for debugging.
#
def inspect
"#<#{self.class} for target `#{target.label}'>"
end
#---------------------------------------------------------------------#
# @!group Integration steps
private
# @return [Specification::Consumer] the consumer for the specifications.
#
def spec_consumers
@spec_consumers ||= target.pod_targets.map(&:file_accessors).flatten.map(&:spec_consumer)
end
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
#
# @note It also checks if any build setting of the build
# configurations overrides the `xcconfig` file and warns the
# user.
#
# @todo If the xcconfig is already set don't override it and inform
# the user.
#
# @return [void]
#
def add_xcconfig_base_configuration
native_targets.each do |native_target|
native_target.build_configurations.each do |config|
path = target.xcconfig_relative_path(config.name)
xcconfig = user_project.files.select { |f| f.path == path }.first || user_project.new_file(path)
check_overridden_build_settings(target.xcconfigs[config.name], native_target)
config.base_configuration_reference = xcconfig
end
end
end
# @!group Integration steps
#---------------------------------------------------------------------#
# Adds spec libraries to the frameworks build phase of the
# {TargetDefinition} integration libraries. Adds a file reference to
......@@ -120,7 +59,7 @@ module Pod
#
def add_pods_library
frameworks = user_project.frameworks_group
native_targets.each do |native_target|
native_targets_to_integrate.each do |native_target|
library = frameworks.files.select { |f| f.path == target.product_name }.first ||
frameworks.new_product_ref_for_target(target.name, :static_library)
unless native_target.frameworks_build_phase.files_references.include?(library)
......@@ -137,7 +76,7 @@ module Pod
#
def add_copy_resources_script_phase
phase_name = "Copy Pods Resources"
native_targets.each do |native_target|
native_targets_to_integrate.each do |native_target|
phase = native_target.shell_script_build_phases.select { |bp| bp.name == phase_name }.first ||
native_target.new_shell_script_build_phase(phase_name)
path = target.copy_resources_script_relative_path
......@@ -157,7 +96,7 @@ module Pod
#
def add_check_manifest_lock_script_phase
phase_name = 'Check Pods Manifest.lock'
native_targets.each do |native_target|
native_targets_to_integrate.each do |native_target|
next if native_target.shell_script_build_phases.any? { |phase| phase.name == phase_name }
phase = native_target.project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
native_target.build_phases.unshift(phase)
......@@ -175,42 +114,58 @@ module Pod
end
end
#---------------------------------------------------------------------#
# @!group Private helpers.
private
# Informs the user about any build setting of the target which might
# override the given xcconfig file.
#
# @return [void]
#
def check_overridden_build_settings(xcconfig, native_target)
return unless xcconfig
configs_by_overridden_key = {}
native_target.build_configurations.each do |config|
xcconfig.attributes.keys.each do |key|
target_value = config.build_settings[key]
# @!group Private helpers
#---------------------------------------------------------------------#
if target_value && !target_value.include?('$(inherited)')
configs_by_overridden_key[key] ||= []
configs_by_overridden_key[key] << config.name
# @return [Array<PBXNativeTarget>] The list of all the targets that
# match the given target.
#
def native_targets
unless @native_targets
target_uuids = target.user_target_uuids
@native_targets = target_uuids.map do |uuid|
native_target = user_project.objects_by_uuid[uuid]
unless native_target
raise Informative, "[Bug] Unable to find the target with " \
"the `#{uuid}` UUID for the `#{target}` integration library"
end
native_target
end
end
@native_targets
end
configs_by_overridden_key.each do |key, config_names|
name = "#{native_target.name} [#{config_names.join(' - ')}]"
actions = [
"Use the `$(inherited)` flag, or",
"Remove the build settings from the target."
]
UI.warn("The target `#{name}` overrides the `#{key}` build " \
"setting defined in `#{target.xcconfig_relative_path(config.name)}'.",
actions)
# @return [Array<PBXNativeTarget>] The list of the targets
# that have not been integrated by past installations
# of
#
def native_targets_to_integrate
unless @native_targets_to_integrate
@native_targets_to_integrate = native_targets.reject do |native_target|
native_target.frameworks_build_phase.files.any? do |build_file|
file_ref = build_file.file_ref
file_ref &&
file_ref.isa == 'PBXFileReference' &&
file_ref.display_name == target.product_name
end
end
end
@native_targets_to_integrate
end
# Read the project from the disk to ensure that it is up to date as
# other TargetIntegrators might have modified it.
#
def user_project
@user_project ||= Xcodeproj::Project.open(target.user_project_path)
end
# @return [Specification::Consumer] the consumer for the specifications.
#
def spec_consumers
@spec_consumers ||= target.pod_targets.map(&:file_accessors).flatten.map(&:spec_consumer)
end
# @return [String] the message that should be displayed for the target
......@@ -222,9 +177,6 @@ module Pod
"into aggregate target #{target.name} " \
"of project #{UI.path target.user_project_path}."
end
#---------------------------------------------------------------------#
end
end
end
......
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, config)
check_overrides(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 [[Xcodeproj::XCBuildConfiguration] config
# The build configuration.
#
def self.set_target_xcconfig(pod_bundle, config)
path = pod_bundle.xcconfig_relative_path(config.name)
file_ref = config.project.files.find { |f| f.path == path }
file_ref ||= config.project.new_file(path)
config.base_configuration_reference = file_ref
end
# Checks whether the settings of the CocoaPods generated xcconfig are
# overridden by the build configuration of a target and prints a
# warning to inform the user if needed.
#
# @param [Target::AggregateTarget] pod_bundle
# The Pods bundle.
#
# @param [XcodeProj::PBXNativeTarget] target
# The native target.
#
# @param [Xcodeproj::XCBuildConfiguration] config
# The build configuration.
#
def self.check_overrides(pod_bundle, target, config)
xcconfig = pod_bundle.xcconfigs[config.name]
xcconfig.attributes.keys.each do |key|
target_value = config.build_settings[key]
if target_value && !target_value.include?('$(inherited)')
print_override_warning(pod_bundle, target, config, key)
end
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.xcconfig_relative_path(config.name)}'." \
"This can lead to problems with the CocoaPods installation"
UI.warn(message, actions)
end
end
end
end
end
end
require File.expand_path('../../../../../spec_helper', __FILE__)
module Pod
describe XCConfigIntegrator = Installer::UserProjectIntegrator::TargetIntegrator::XCConfigIntegrator do
before do
project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
@project = Xcodeproj::Project.open(project_path)
Xcodeproj::Project.new(config.sandbox.project_path).save
@target = @project.targets.first
target_definition = Podfile::TargetDefinition.new('Pods', nil)
target_definition.link_with_first_target = true
@pod_bundle = AggregateTarget.new(target_definition, config.sandbox)
@pod_bundle.user_project_path = project_path
@pod_bundle.client_root = project_path.dirname
@pod_bundle.user_target_uuids = [@target.uuid]
configuration = Xcodeproj::Config.new({
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1'
})
@pod_bundle.xcconfigs['Debug'] = configuration
@pod_bundle.xcconfigs['Test'] = configuration
@pod_bundle.xcconfigs['Release'] = configuration
@pod_bundle.xcconfigs['App Store'] = configuration
end
it 'sets the Pods xcconfig as the base config for each build configuration' do
XCConfigIntegrator.integrate(@pod_bundle, [@target])
@target.build_configurations.each do |config|
xcconfig_file = @project.files.find { |f| f.path == @pod_bundle.xcconfig_relative_path(config) }
config.base_configuration_reference.should == xcconfig_file
end
end
it 'does not duplicate the file reference to the CocoaPods xcconfig in the user project' do
path = @pod_bundle.xcconfig_relative_path('Release')
existing = @project.new_file(path)
XCConfigIntegrator.integrate(@pod_bundle, [@target])
config = @target.build_configuration_list['Release']
config.base_configuration_reference.should.equal existing
end
it 'check that the integrated target does not override the CocoaPods build settings' do
UI.warnings = ''
config = @target.build_configuration_list['Release']
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'FLAG=1'
XCConfigIntegrator.integrate(@pod_bundle, [@target])
UI.warnings.should.include 'The `SampleProject [Release]` target ' \
'overrides the `GCC_PREPROCESSOR_DEFINITIONS` build setting'
end
it 'allows build settings which inherit the settings form the CocoaPods xcconfig' do
UI.warnings = ''
config = @target.build_configuration_list['Release']
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited)'
XCConfigIntegrator.integrate(@pod_bundle, [@target])
UI.warnings.should.not.include 'GCC_PREPROCESSOR_DEFINITIONS'
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Installer::UserProjectIntegrator do
describe UserProjectIntegrator = Installer::UserProjectIntegrator do
describe "In general" do
......@@ -23,7 +23,7 @@ module Pod
@library.user_project_path = sample_project_path
@library.user_target_uuids = ['A346496C14F9BE9A0080D870']
empty_library = AggregateTarget.new(@podfile.target_definitions[:empty], config.sandbox)
@integrator = Installer::UserProjectIntegrator.new(@podfile, config.sandbox, temporary_directory, [@library, empty_library])
@integrator = UserProjectIntegrator.new(@podfile, config.sandbox, temporary_directory, [@library, empty_library])
end
#-----------------------------------------------------------------------#
......@@ -31,6 +31,7 @@ module Pod
describe "In general" do
it "adds the Pods project to the workspace" do
UserProjectIntegrator::TargetIntegrator.any_instance.stubs(:integrate!)
@integrator.integrate!
workspace_path = @integrator.send(:workspace_path)
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
......@@ -41,10 +42,8 @@ module Pod
end
it "integrates the user targets" do
UserProjectIntegrator::TargetIntegrator.any_instance.expects(:integrate!)
@integrator.integrate!
user_project = Xcodeproj::Project.open(@sample_project_path)
target = user_project.objects_by_uuid[@library.user_target_uuids.first]
target.frameworks_build_phase.files.map(&:display_name).should.include('libPods.a')
end
it "warns if the podfile does not contain any dependency" do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment