diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8eb68e8..3bbc216be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ##### Enhancements -* None. +* Resolve variable substitution for xcconfig declared build settings + [Ruenzuo](https://github.com/Ruenzuo) + [#501](https://github.com/CocoaPods/CocoaPods/issues/501) ##### Bug Fixes diff --git a/lib/xcodeproj/project/object/build_configuration.rb b/lib/xcodeproj/project/object/build_configuration.rb index d6db4ddc9..796f37dfd 100644 --- a/lib/xcodeproj/project/object/build_configuration.rb +++ b/lib/xcodeproj/project/object/build_configuration.rb @@ -77,15 +77,20 @@ def type # @param [String] key # the key of the build setting. # + # @param [PBXNativeTarget] root_target + # use this to resolve complete recursion between project and targets + # # @return [String] The value of the build setting # - def resolve_build_setting(key) + def resolve_build_setting(key, root_target = nil) setting = build_settings[key] + setting = resolve_variable_substitution(setting, root_target) if setting.is_a?(String) config_setting = base_configuration_reference && config[key] + config_setting = resolve_variable_substitution(config_setting, root_target) if config_setting.is_a?(String) project_setting = project.build_configuration_list[name] project_setting = nil if project_setting == self - project_setting &&= project_setting.resolve_build_setting(key) + project_setting &&= project_setting.resolve_build_setting(key, root_target) [project_setting, config_setting, setting].compact.reduce do |inherited, value| expand_build_setting(value, inherited) @@ -96,6 +101,16 @@ def resolve_build_setting(key) private + CAPTURE_VARIABLE_IN_BUILD_CONFIG = / + \$ # matches dollar sign literally + [{(] # matches a single caracter on this list + ( # capture block + [^inherited] # ignore if match characters in this list + [$(){}_a-zA-Z0-9]*? # non-greedy lookup for everything that contains this list + ) + [})] # matches a single caracter on this list + /x + def expand_build_setting(build_setting_value, config_value) default = build_setting_value.is_a?(String) ? '' : [] inherited = config_value || default @@ -103,6 +118,25 @@ def expand_build_setting(build_setting_value, config_value) build_setting_value.map { |value| Constants::INHERITED_KEYWORDS.include?(value) ? inherited : value }.flatten end + def resolve_variable_substitution(config_setting, root_target) + variable = match_variable(config_setting) + if variable.nil? + return name if config_setting.eql?('CONFIGURATION') + if root_target + return root_target.build_configuration_list[name].resolve_build_setting(config_setting, root_target) || config_setting + else + return resolve_build_setting(config_setting, root_target) || config_setting + end + end + resolve_variable_substitution(config_setting.sub(CAPTURE_VARIABLE_IN_BUILD_CONFIG, resolve_variable_substitution(variable, root_target)), root_target) + end + + def match_variable(config_setting) + match_data = config_setting.match(CAPTURE_VARIABLE_IN_BUILD_CONFIG) + return match_data.captures.first unless match_data.nil? + match_data + end + def sorted_build_settings sorted = {} build_settings.keys.sort.each do |key| diff --git a/lib/xcodeproj/project/object/configuration_list.rb b/lib/xcodeproj/project/object/configuration_list.rb index b6abba7df..4162da40c 100644 --- a/lib/xcodeproj/project/object/configuration_list.rb +++ b/lib/xcodeproj/project/object/configuration_list.rb @@ -66,13 +66,16 @@ def build_settings(build_configuration_name) # wether the retrieved setting should take in consideration any # configuration file present. # + # @param [PBXNativeTarget] root_target + # use this to resolve complete recursion between project and targets + # # @return [Hash{String => String}] The value of the build setting # grouped by the name of the build configuration. # - def get_setting(key, resolve_against_xcconfig = false) + def get_setting(key, resolve_against_xcconfig = false, root_target = nil) result = {} build_configurations.each do |bc| - result[bc.name] = resolve_against_xcconfig ? bc.resolve_build_setting(key) : bc.build_settings[key] + result[bc.name] = resolve_against_xcconfig ? bc.resolve_build_setting(key, root_target) : bc.build_settings[key] end result end diff --git a/lib/xcodeproj/project/object/native_target.rb b/lib/xcodeproj/project/object/native_target.rb index 933a42c23..d64e04878 100644 --- a/lib/xcodeproj/project/object/native_target.rb +++ b/lib/xcodeproj/project/object/native_target.rb @@ -52,7 +52,7 @@ class AbstractTarget < AbstractObject # the default values for the platform. # def resolved_build_setting(key, resolve_against_xcconfig = false) - target_settings = build_configuration_list.get_setting(key, resolve_against_xcconfig) + target_settings = build_configuration_list.get_setting(key, resolve_against_xcconfig, self) project_settings = project.build_configuration_list.get_setting(key, resolve_against_xcconfig) target_settings.merge(project_settings) do |_key, target_val, proj_val| target_includes_inherited = Constants::INHERITED_KEYWORDS.any? { |keyword| target_val.include?(keyword) } if target_val diff --git a/spec/fixtures/project.xcconfig b/spec/fixtures/project.xcconfig index 20cdfe458..1eeed47bf 100644 --- a/spec/fixtures/project.xcconfig +++ b/spec/fixtures/project.xcconfig @@ -1,2 +1,17 @@ USER_DEFINED = PROJECT_XCCONFIG_VALUE OTHER_LDFLAGS = -framework UIKit +PRODUCT_BUNDLE_IDENTIFIER_Release = com.cocoapods.app +PRODUCT_BUNDLE_IDENTIFIER_Debug = com.cocoapods.app.dev +PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER_$(CONFIGURATION)) +DEVELOPMENT_TEAM = $(USER_DEFINED) +CONFIG_APPEND = $(USER_DEFINED)_$(CONFIGURATION) + +USER_DEFINED_XCCONFIG_PROJECT_Release = User Defined xcconfig project Release +USER_DEFINED_XCCONFIG_PROJECT_Debug = User Defined xcconfig project Debug +USER_DEFINED_XCCONFIG_PROJECT = $(USER_DEFINED_XCCONFIG_PROJECT_$(CONFIGURATION)) + +PROJECT_REFERENCE_TARGET = $(TARGET_USER_DEFINED) +PROJECT_REFERENCE_PROJECT = $(PROJECT_USER_DEFINED) +PROJECT_REFERENCE_XCCONFIG_TARGET = $(USER_DEFINED_XCCONFIG_TARGET) +PROJECT_REFERENCE_XCCONFIG_PROJECT = $(USER_DEFINED_XCCONFIG_PROJECT) + diff --git a/spec/fixtures/target.xcconfig b/spec/fixtures/target.xcconfig index cf96ebd69..4202d5a8b 100644 --- a/spec/fixtures/target.xcconfig +++ b/spec/fixtures/target.xcconfig @@ -1,2 +1,11 @@ USER_DEFINED = ${inherited} TARGET_XCCONFIG_VALUE OTHER_LDFLAGS = ${inherited} -framework CoreAnimation + +USER_DEFINED_XCCONFIG_TARGET_Release = User Defined xcconfig target Release +USER_DEFINED_XCCONFIG_TARGET_Debug = User Defined xcconfig target Debug +USER_DEFINED_XCCONFIG_TARGET = $(USER_DEFINED_XCCONFIG_TARGET_$(CONFIGURATION)) + +TARGET_REFERENCE_TARGET = $(TARGET_USER_DEFINED) +TARGET_REFERENCE_PROJECT = $(PROJECT_USER_DEFINED) +TARGET_REFERENCE_XCCONFIG_TARGET = $(USER_DEFINED_XCCONFIG_TARGET) +TARGET_REFERENCE_XCCONFIG_PROJECT = $(USER_DEFINED_XCCONFIG_PROJECT) diff --git a/spec/project/object/native_target_spec.rb b/spec/project/object/native_target_spec.rb index 70848a3b5..487be5ee0 100644 --- a/spec/project/object/native_target_spec.rb +++ b/spec/project/object/native_target_spec.rb @@ -122,6 +122,83 @@ module ProjectSpecs @target.build_configuration_list.set_setting('OTHER_LDFLAGS', %w(-framework UIKit)) @target.resolved_build_setting('OTHER_LDFLAGS', true).should == { 'Release' => %w(-framework UIKit), 'Debug' => %w(-framework UIKit) } end + + it 'returns the resolved build setting string value for a given key considering variable substitution recursively' do + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + @target.resolved_build_setting('PRODUCT_BUNDLE_IDENTIFIER', true).should == { 'Release' => 'com.cocoapods.app', 'Debug' => 'com.cocoapods.app.dev' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution appending' do + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + @target.resolved_build_setting('CONFIG_APPEND', true).should == { 'Release' => 'PROJECT_XCCONFIG_VALUE_Release', 'Debug' => 'PROJECT_XCCONFIG_VALUE_Debug' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution in same level' do + @target.build_configuration_list.set_setting('TARGET_USER_DEFINED', '${TARGET_USER_DEFINED_2}') + @target.build_configuration_list.set_setting('TARGET_USER_DEFINED_2', 'TARGET_USER_DEFINED_VALUE') + @target.resolved_build_setting('TARGET_USER_DEFINED', true).should == { 'Release' => 'TARGET_USER_DEFINED_VALUE', 'Debug' => 'TARGET_USER_DEFINED_VALUE' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution' do + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + @target.resolved_build_setting('DEVELOPMENT_TEAM', true).should == { 'Release' => 'PROJECT_XCCONFIG_VALUE', 'Debug' => 'PROJECT_XCCONFIG_VALUE' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: target xcconfig referencing target xcconfig' do + target_xcconfig = @project.new_file(fixture_path('target.xcconfig')) + @target.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = target_xcconfig } + expected_value_release = 'User Defined xcconfig target Release' + expected_value_debug = 'User Defined xcconfig target Debug' + @target.resolved_build_setting('TARGET_REFERENCE_XCCONFIG_TARGET', true).should == { 'Release' => expected_value_release, 'Debug' => expected_value_debug } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: target xcconfig referencing target' do + target_xcconfig = @project.new_file(fixture_path('target.xcconfig')) + @target.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = target_xcconfig } + @target.build_configuration_list.set_setting('TARGET_USER_DEFINED', 'TARGET_USER_DEFINED_VALUE') + @target.resolved_build_setting('TARGET_REFERENCE_TARGET', true).should == { 'Release' => 'TARGET_USER_DEFINED_VALUE', 'Debug' => 'TARGET_USER_DEFINED_VALUE' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: target xcconfig referencing project xcconfig' do + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + target_xcconfig = @project.new_file(fixture_path('target.xcconfig')) + @target.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = target_xcconfig } + expected_value_debug = 'User Defined xcconfig project Debug' + expected_value_release = 'User Defined xcconfig project Release' + @target.resolved_build_setting('TARGET_REFERENCE_XCCONFIG_PROJECT', true).should == { 'Release' => expected_value_release, 'Debug' => expected_value_debug } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: target xcconfig referencing project' do + @project.build_configuration_list.set_setting('PROJECT_USER_DEFINED', 'PROJECT_USER_DEFINED_VALUE') + target_xcconfig = @project.new_file(fixture_path('target.xcconfig')) + @target.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = target_xcconfig } + @target.resolved_build_setting('TARGET_REFERENCE_PROJECT', true).should == { 'Release' => 'PROJECT_USER_DEFINED_VALUE', 'Debug' => 'PROJECT_USER_DEFINED_VALUE' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: project xcconfig referencing project xcconfig' do + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + expected_value_release = 'User Defined xcconfig project Release' + expected_value_debug = 'User Defined xcconfig project Debug' + @target.resolved_build_setting('PROJECT_REFERENCE_XCCONFIG_PROJECT', true).should == { 'Release' => expected_value_release, 'Debug' => expected_value_debug } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: target xcconfig referencing project' do + @project.build_configuration_list.set_setting('PROJECT_USER_DEFINED', 'PROJECT_USER_DEFINED_VALUE') + project_xcconfig = @project.new_file(fixture_path('project.xcconfig')) + @project.build_configuration_list.build_configurations.each { |build_config| build_config.base_configuration_reference = project_xcconfig } + @target.resolved_build_setting('PROJECT_REFERENCE_PROJECT', true).should == { 'Release' => 'PROJECT_USER_DEFINED_VALUE', 'Debug' => 'PROJECT_USER_DEFINED_VALUE' } + end + + it 'returns the resolved build setting string value for a given key considering variable substitution: project referencing target' do + @project.build_configuration_list.set_setting('PROJECT_REFERENCE_TARGET', '$(TARGET_USER_DEFINED)') + @target.build_configuration_list.set_setting('TARGET_USER_DEFINED', 'TARGET_USER_DEFINED_VALUE') + @target.resolved_build_setting('PROJECT_REFERENCE_TARGET', true).should == { 'Release' => 'TARGET_USER_DEFINED_VALUE', 'Debug' => 'TARGET_USER_DEFINED_VALUE' } + end end #----------------------------------------#