Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve variable substitution for xcconfig declared build settings #501

Merged
merged 6 commits into from
Aug 18, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 21 additions & 2 deletions lib/xcodeproj/project/object/build_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.nil? && setting.is_a?(String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking both nil? and is_a? is redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes totally sense!

config_setting = base_configuration_reference && config[key]
config_setting = resolve_variable_substitution(config_setting, root_target) if !config_setting.nil? && config_setting.is_a?(String)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking both nil? and is_a? is redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes totally sense!


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)
Expand All @@ -103,6 +108,20 @@ 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)
expression = /\$[{(]([^inherited][$(){}_a-zA-Z0-9]*?)[})]/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract this into a constant and please comment the regexp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

match_data = config_setting.match(expression)
if match_data.nil?
return name if config_setting.eql?('CONFIGURATION')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is an exhaustive way to resolve the value, would love feedback on this.

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(expression, resolve_variable_substitution(match_data.captures.first, root_target)), root_target)
end

def sorted_build_settings
sorted = {}
build_settings.keys.sort.each do |key|
Expand Down
7 changes: 5 additions & 2 deletions lib/xcodeproj/project/object/configuration_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/xcodeproj/project/object/native_target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions spec/fixtures/project.xcconfig
Original file line number Diff line number Diff line change
@@ -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)

9 changes: 9 additions & 0 deletions spec/fixtures/target.xcconfig
Original file line number Diff line number Diff line change
@@ -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)
77 changes: 77 additions & 0 deletions spec/project/object/native_target_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

#----------------------------------------#
Expand Down