xcodeproj 1.7.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/xcodeproj/command/config_dump.rb +5 -1
  3. data/lib/xcodeproj/config.rb +3 -1
  4. data/lib/xcodeproj/config/other_linker_flags_parser.rb +2 -2
  5. data/lib/xcodeproj/constants.rb +70 -44
  6. data/lib/xcodeproj/gem_version.rb +1 -1
  7. data/lib/xcodeproj/project.rb +23 -2
  8. data/lib/xcodeproj/project/object.rb +3 -1
  9. data/lib/xcodeproj/project/object/build_configuration.rb +96 -35
  10. data/lib/xcodeproj/project/object/build_file.rb +9 -1
  11. data/lib/xcodeproj/project/object/build_phase.rb +45 -4
  12. data/lib/xcodeproj/project/object/build_rule.rb +12 -0
  13. data/lib/xcodeproj/project/object/helpers/file_references_factory.rb +11 -4
  14. data/lib/xcodeproj/project/object/helpers/groupable_helper.rb +13 -6
  15. data/lib/xcodeproj/project/object/native_target.rb +52 -9
  16. data/lib/xcodeproj/project/object/root_object.rb +15 -2
  17. data/lib/xcodeproj/project/object/swift_package_product_dependency.rb +19 -0
  18. data/lib/xcodeproj/project/object/swift_package_remote_reference.rb +19 -0
  19. data/lib/xcodeproj/project/object/target_dependency.rb +9 -0
  20. data/lib/xcodeproj/project/project_helper.rb +2 -1
  21. data/lib/xcodeproj/project/uuid_generator.rb +41 -19
  22. data/lib/xcodeproj/scheme.rb +6 -4
  23. data/lib/xcodeproj/scheme/build_action.rb +21 -0
  24. data/lib/xcodeproj/scheme/buildable_reference.rb +3 -1
  25. data/lib/xcodeproj/scheme/launch_action.rb +50 -0
  26. data/lib/xcodeproj/scheme/test_action.rb +36 -1
  27. data/lib/xcodeproj/workspace.rb +16 -7
  28. data/lib/xcodeproj/workspace/file_reference.rb +7 -12
  29. data/lib/xcodeproj/workspace/group_reference.rb +18 -15
  30. data/lib/xcodeproj/workspace/reference.rb +40 -0
  31. metadata +11 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a926103d6bbeeea7900326a53221df41a158228484f36dce922c54eadfa741b1
4
- data.tar.gz: d15b7d1db2f2ad119b4660a5cad26476930866dd1bee818e770ce8d2c1b8ee4e
3
+ metadata.gz: 72f6eb6645b557dea09ae4be0fd400e48b0d4f1e42c31b7b61af87fd3c31a9cd
4
+ data.tar.gz: 0c0bcfa7abc28721f61f48e295198da53576e90d62161c50a6cc7333c1e94455
5
5
  SHA512:
6
- metadata.gz: 4886ecf3b87f33ab29072b8ae56b22bfc7810cd0feb9a460449c612d9c947befb7a101e56ffd3dadc03bda4268057f4a48d46cb2ad2dade6a645aa0a65ab5dff
7
- data.tar.gz: 85e155a16497eab7dafc72b1c4732cf32ed1ecf90cb10d08d144eea810dd79b99193782c5031609cb5be17e2f961526b4b087eb1196ae971e067083e2f81ab94
6
+ metadata.gz: 4f856d8cbee5b9b7b6bcbe7acce4034e8277fd05ac7caf0d2c317f75d622bb5c518dd2ddee67dac1c6a7fb2773aebb5a5de2cf2232af3fffc2353323fd08623d
7
+ data.tar.gz: 9cf088a34dea380b56716f005c35fc601d9b193a0a4908556d4f8ccc80408ee328755ccb767dc0ed50adc0bc3e822cc9bcdcdeac405c97fc514f6dd429f746ac
@@ -76,7 +76,11 @@ module Xcodeproj
76
76
 
77
77
  def dump_config_to_file(settings, file_path, includes = [])
78
78
  dir = @output_path + file_path + '..'
79
- dir.mkdir unless dir.exist?
79
+ dir.mkpath
80
+
81
+ settings = Hash[settings.map do |k, v|
82
+ [k, Array(v).join(' ')]
83
+ end]
80
84
 
81
85
  config = Config.new(settings)
82
86
  config.includes = includes
@@ -157,6 +157,8 @@ module Xcodeproj
157
157
  end
158
158
  end
159
159
 
160
+ alias_method :to_h, :to_hash
161
+
160
162
  # @return [Set<String>] The list of the frameworks required by this
161
163
  # settings file.
162
164
  #
@@ -275,7 +277,7 @@ module Xcodeproj
275
277
  string.split("\n").each do |line|
276
278
  uncommented_line = strip_comment(line)
277
279
  if include = extract_include(uncommented_line)
278
- @includes.push include
280
+ @includes.push normalized_xcconfig_path(include)
279
281
  else
280
282
  key, value = extract_key_value(uncommented_line)
281
283
  next unless key
@@ -55,13 +55,13 @@ module Xcodeproj
55
55
  # The other linker flags value.
56
56
  #
57
57
  def self.split(flags)
58
- flags.strip.shellsplit.map do |string|
58
+ flags.strip.shellsplit.flat_map do |string|
59
59
  if string =~ /\A-l.+/
60
60
  ['-l', string[2..-1]]
61
61
  else
62
62
  string
63
63
  end
64
- end.flatten
64
+ end
65
65
  end
66
66
  end
67
67
  end
@@ -4,17 +4,19 @@ module Xcodeproj
4
4
  module Constants
5
5
  # @return [String] The last known iOS SDK (stable).
6
6
  #
7
- LAST_KNOWN_IOS_SDK = '12.0'
7
+ LAST_KNOWN_IOS_SDK = '14.0'
8
8
 
9
9
  # @return [String] The last known OS X SDK (stable).
10
10
  #
11
- LAST_KNOWN_OSX_SDK = '10.14'
11
+ LAST_KNOWN_OSX_SDK = '10.15'
12
12
 
13
13
  # @return [String] The last known tvOS SDK (stable).
14
- LAST_KNOWN_TVOS_SDK = '12.0'
14
+ #
15
+ LAST_KNOWN_TVOS_SDK = '14.0'
15
16
 
16
17
  # @return [String] The last known watchOS SDK (stable).
17
- LAST_KNOWN_WATCHOS_SDK = '5.0'
18
+ #
19
+ LAST_KNOWN_WATCHOS_SDK = '7.0'
18
20
 
19
21
  # @return [String] The last known archive version to Xcodeproj.
20
22
  #
@@ -22,22 +24,23 @@ module Xcodeproj
22
24
 
23
25
  # @return [String] The last known Swift version (stable).
24
26
  #
25
- LAST_KNOWN_SWIFT_VERSION = '4.0'
27
+ LAST_KNOWN_SWIFT_VERSION = '5.0'
26
28
 
27
29
  # @return [String] The default object version for Xcodeproj.
30
+ #
28
31
  DEFAULT_OBJECT_VERSION = 46
29
32
 
30
33
  # @return [String] The last known object version to Xcodeproj.
31
34
  #
32
- LAST_KNOWN_OBJECT_VERSION = 51
35
+ LAST_KNOWN_OBJECT_VERSION = 54
33
36
 
34
37
  # @return [String] The last known object version to Xcodeproj.
35
38
  #
36
- LAST_UPGRADE_CHECK = '0930'
39
+ LAST_UPGRADE_CHECK = '1100'
37
40
 
38
41
  # @return [String] The last known object version to Xcodeproj.
39
42
  #
40
- LAST_SWIFT_UPGRADE_CHECK = '0930'
43
+ LAST_SWIFT_UPGRADE_CHECK = '1100'
41
44
 
42
45
  # @return [String] The version of `.xcscheme` files supported by Xcodeproj
43
46
  #
@@ -90,12 +93,14 @@ module Xcodeproj
90
93
  'app' => 'wrapper.application',
91
94
  'appex' => 'wrapper.app-extension',
92
95
  'bundle' => 'wrapper.plug-in',
96
+ 'cpp' => 'sourcecode.cpp.cpp',
93
97
  'dylib' => 'compiled.mach-o.dylib',
94
98
  'entitlements' => 'text.plist.entitlements',
95
99
  'framework' => 'wrapper.framework',
96
100
  'gif' => 'image.gif',
97
101
  'gpx' => 'text.xml',
98
102
  'h' => 'sourcecode.c.h',
103
+ 'hpp' => 'sourcecode.cpp.h',
99
104
  'm' => 'sourcecode.c.objc',
100
105
  'markdown' => 'text',
101
106
  'mdimporter' => 'wrapper.cfbundle',
@@ -120,44 +125,65 @@ module Xcodeproj
120
125
  'zip' => 'archive.zip',
121
126
  }.freeze
122
127
 
128
+ # @return [Hash] The compatibility version string for different object versions.
129
+ #
130
+ COMPATIBILITY_VERSION_BY_OBJECT_VERSION = {
131
+ 54 => 'Xcode 12.0',
132
+ 53 => 'Xcode 11.4',
133
+ 52 => 'Xcode 11.0',
134
+ 51 => 'Xcode 10.0',
135
+ 50 => 'Xcode 9.3',
136
+ 48 => 'Xcode 8.0',
137
+ 47 => 'Xcode 6.3',
138
+ 46 => 'Xcode 3.2',
139
+ 45 => 'Xcode 3.1',
140
+ }.freeze
141
+
123
142
  # @return [Hash] The uniform type identifier of various product types.
124
143
  #
125
144
  PRODUCT_TYPE_UTI = {
126
- :application => 'com.apple.product-type.application',
127
- :framework => 'com.apple.product-type.framework',
128
- :dynamic_library => 'com.apple.product-type.library.dynamic',
129
- :static_library => 'com.apple.product-type.library.static',
130
- :bundle => 'com.apple.product-type.bundle',
131
- :octest_bundle => 'com.apple.product-type.bundle',
132
- :unit_test_bundle => 'com.apple.product-type.bundle.unit-test',
133
- :ui_test_bundle => 'com.apple.product-type.bundle.ui-testing',
134
- :app_extension => 'com.apple.product-type.app-extension',
135
- :command_line_tool => 'com.apple.product-type.tool',
136
- :watch_app => 'com.apple.product-type.application.watchapp',
137
- :watch2_app => 'com.apple.product-type.application.watchapp2',
138
- :watch_extension => 'com.apple.product-type.watchkit-extension',
139
- :watch2_extension => 'com.apple.product-type.watchkit2-extension',
140
- :tv_extension => 'com.apple.product-type.tv-app-extension',
141
- :messages_application => 'com.apple.product-type.application.messages',
142
- :messages_extension => 'com.apple.product-type.app-extension.messages',
143
- :sticker_pack => 'com.apple.product-type.app-extension.messages-sticker-pack',
144
- :xpc_service => 'com.apple.product-type.xpc-service',
145
+ :application => 'com.apple.product-type.application',
146
+ :application_on_demand_install_capable => 'com.apple.product-type.application.on-demand-install-capable',
147
+ :framework => 'com.apple.product-type.framework',
148
+ :dynamic_library => 'com.apple.product-type.library.dynamic',
149
+ :static_library => 'com.apple.product-type.library.static',
150
+ :bundle => 'com.apple.product-type.bundle',
151
+ :octest_bundle => 'com.apple.product-type.bundle',
152
+ :unit_test_bundle => 'com.apple.product-type.bundle.unit-test',
153
+ :ui_test_bundle => 'com.apple.product-type.bundle.ui-testing',
154
+ :app_extension => 'com.apple.product-type.app-extension',
155
+ :command_line_tool => 'com.apple.product-type.tool',
156
+ :watch_app => 'com.apple.product-type.application.watchapp',
157
+ :watch2_app => 'com.apple.product-type.application.watchapp2',
158
+ :watch2_app_container => 'com.apple.product-type.application.watchapp2-container',
159
+ :watch_extension => 'com.apple.product-type.watchkit-extension',
160
+ :watch2_extension => 'com.apple.product-type.watchkit2-extension',
161
+ :tv_extension => 'com.apple.product-type.tv-app-extension',
162
+ :messages_application => 'com.apple.product-type.application.messages',
163
+ :messages_extension => 'com.apple.product-type.app-extension.messages',
164
+ :sticker_pack => 'com.apple.product-type.app-extension.messages-sticker-pack',
165
+ :xpc_service => 'com.apple.product-type.xpc-service',
145
166
  }.freeze
146
167
 
147
168
  # @return [Hash] The extensions or the various product UTIs.
148
169
  #
149
170
  PRODUCT_UTI_EXTENSIONS = {
150
- :application => 'app',
151
- :framework => 'framework',
152
- :dynamic_library => 'dylib',
153
- :static_library => 'a',
154
- :bundle => 'bundle',
155
- :octest_bundle => 'octest',
156
- :unit_test_bundle => 'xctest',
157
- :ui_test_bundle => 'xctest',
158
- :app_extension => 'appex',
159
- :watch2_extension => 'appex',
160
- :watch2_app => 'app',
171
+ :application => 'app',
172
+ :application_on_demand_install_capable => 'app',
173
+ :framework => 'framework',
174
+ :dynamic_library => 'dylib',
175
+ :static_library => 'a',
176
+ :bundle => 'bundle',
177
+ :octest_bundle => 'octest',
178
+ :unit_test_bundle => 'xctest',
179
+ :ui_test_bundle => 'xctest',
180
+ :app_extension => 'appex',
181
+ :messages_application => 'app',
182
+ :messages_extension => 'appex',
183
+ :sticker_pack => 'appex',
184
+ :watch2_extension => 'appex',
185
+ :watch2_app => 'app',
186
+ :watch2_app_container => 'app',
161
187
  }.freeze
162
188
 
163
189
  # @return [Hash] The common build settings grouped by platform, and build
@@ -178,11 +204,9 @@ module Xcodeproj
178
204
  }.freeze,
179
205
  [:ios] => {
180
206
  'SDKROOT' => 'iphoneos',
181
- 'CODE_SIGN_IDENTITY' => 'iPhone Developer',
182
207
  }.freeze,
183
208
  [:osx] => {
184
209
  'SDKROOT' => 'macosx',
185
- 'CODE_SIGN_IDENTITY' => '-',
186
210
  }.freeze,
187
211
  [:tvos] => {
188
212
  'SDKROOT' => 'appletvos',
@@ -231,7 +255,6 @@ module Xcodeproj
231
255
  [:debug, :static_library, :swift] => {
232
256
  }.freeze,
233
257
  [:framework] => {
234
- 'CODE_SIGN_IDENTITY' => '',
235
258
  'CURRENT_PROJECT_VERSION' => '1',
236
259
  'DEFINES_MODULE' => 'YES',
237
260
  'DYLIB_COMPATIBILITY_VERSION' => '1',
@@ -249,7 +272,6 @@ module Xcodeproj
249
272
  }.freeze,
250
273
  [:osx, :framework] => {
251
274
  'COMBINE_HIDPI_IMAGES' => 'YES',
252
- 'FRAMEWORK_VERSION' => 'A',
253
275
  'LD_RUNPATH_SEARCH_PATHS' => '$(inherited) @executable_path/../Frameworks @loader_path/Frameworks',
254
276
  }.freeze,
255
277
  [:watchos, :framework] => {
@@ -291,6 +313,7 @@ module Xcodeproj
291
313
  }.freeze,
292
314
  [:application] => {
293
315
  'ASSETCATALOG_COMPILER_APPICON_NAME' => 'AppIcon',
316
+ 'ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME' => 'AccentColor',
294
317
  }.freeze,
295
318
  [:ios, :application] => {
296
319
  'LD_RUNPATH_SEARCH_PATHS' => '$(inherited) @executable_path/Frameworks',
@@ -306,10 +329,12 @@ module Xcodeproj
306
329
  }.freeze,
307
330
  [:tvos, :application] => {
308
331
  'ASSETCATALOG_COMPILER_APPICON_NAME' => 'App Icon & Top Shelf Image',
309
- 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME' => 'LaunchImage',
310
332
  'LD_RUNPATH_SEARCH_PATHS' => '$(inherited) @executable_path/Frameworks',
311
333
  'TARGETED_DEVICE_FAMILY' => '3',
312
334
  }.freeze,
335
+ [:tvos, :application, :swift] => {
336
+ 'ENABLE_PREVIEWS' => 'YES',
337
+ }.freeze,
313
338
  [:watchos, :application, :swift] => {
314
339
  'ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES' => 'YES',
315
340
  }.freeze,
@@ -355,6 +380,7 @@ module Xcodeproj
355
380
  'CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF' => 'YES',
356
381
  'CLANG_WARN_OBJC_LITERAL_CONVERSION' => 'YES',
357
382
  'CLANG_WARN_OBJC_ROOT_CLASS' => 'YES_ERROR',
383
+ 'CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER' => 'YES',
358
384
  'CLANG_WARN_RANGE_LOOP_ANALYSIS' => 'YES',
359
385
  'CLANG_WARN_STRICT_PROTOTYPES' => 'YES',
360
386
  'CLANG_WARN_SUSPICIOUS_MOVE' => 'YES',
@@ -372,7 +398,7 @@ module Xcodeproj
372
398
  'GCC_WARN_UNUSED_VARIABLE' => 'YES',
373
399
  'MTL_FAST_MATH' => 'YES',
374
400
  'PRODUCT_NAME' => '$(TARGET_NAME)',
375
- 'SWIFT_VERSION' => '4.2',
401
+ 'SWIFT_VERSION' => '5.0',
376
402
  },
377
403
  :release => {
378
404
  'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym',
@@ -1,5 +1,5 @@
1
1
  module Xcodeproj
2
2
  # The version of the xcodeproj gem.
3
3
  #
4
- VERSION = '1.7.0'.freeze unless defined? Xcodeproj::VERSION
4
+ VERSION = '1.19.0'.freeze unless defined? Xcodeproj::VERSION
5
5
  end
@@ -80,6 +80,10 @@ module Xcodeproj
80
80
  unless skip_initialization
81
81
  initialize_from_scratch
82
82
  @object_version = object_version.to_s
83
+ unless Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION.key?(object_version)
84
+ raise ArgumentError, "[Xcodeproj] Unable to find compatibility version string for object version `#{object_version}`."
85
+ end
86
+ root_object.compatibility_version = Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION[object_version]
83
87
  end
84
88
  end
85
89
 
@@ -388,7 +392,24 @@ module Xcodeproj
388
392
  # @return [void]
389
393
  #
390
394
  def predictabilize_uuids
391
- UUIDGenerator.new(self).generate!
395
+ UUIDGenerator.new([self]).generate!
396
+ end
397
+
398
+ # Replaces all the UUIDs in the list of provided projects with deterministic MD5 checksums.
399
+ #
400
+ # @param [Array<Project>] projects
401
+ #
402
+ # @note The current sorting of the project is taken into account when
403
+ # generating the new UUIDs.
404
+ #
405
+ # @note This method should only be used for entirely machine-generated
406
+ # projects, as true UUIDs are useful for tracking changes in the
407
+ # project.
408
+ #
409
+ # @return [void]
410
+ #
411
+ def self.predictabilize_uuids(projects)
412
+ UUIDGenerator.new(projects).generate!
392
413
  end
393
414
 
394
415
  public
@@ -568,7 +589,7 @@ module Xcodeproj
568
589
  #
569
590
  def embedded_targets_in_native_target(native_target)
570
591
  native_targets.select do |target|
571
- host_targets_for_embedded_target(target).map(&:uuid).include? native_target.uuid
592
+ host_targets_for_embedded_target(target).any? { |host| host.uuid == native_target.uuid }
572
593
  end
573
594
  end
574
595
 
@@ -350,7 +350,7 @@ module Xcodeproj
350
350
  unless object = project.objects_by_uuid[uuid] || project.new_from_plist(uuid, objects_by_uuid_plist)
351
351
  UI.warn "`#{inspect}` attempted to initialize an object with " \
352
352
  "an unknown UUID. `#{uuid}` for attribute: " \
353
- "`#{attribute.name}`. This can be the result of a merge and " \
353
+ "`#{attribute.name}`. This can be the result of a merge and " \
354
354
  'the unknown UUID is being discarded.'
355
355
  end
356
356
  object
@@ -518,6 +518,8 @@ Xcodeproj::Constants::KNOWN_ISAS.each do |superclass_name, isas|
518
518
  end
519
519
 
520
520
  # Now load the concrete subclasses.
521
+ require 'xcodeproj/project/object/swift_package_remote_reference'
522
+ require 'xcodeproj/project/object/swift_package_product_dependency'
521
523
  require 'xcodeproj/project/object/build_configuration'
522
524
  require 'xcodeproj/project/object/build_file'
523
525
  require 'xcodeproj/project/object/build_phase'
@@ -6,6 +6,10 @@ module Xcodeproj
6
6
  # {PBXProject} or a {PBXNativeTarget}.
7
7
  #
8
8
  class XCBuildConfiguration < AbstractObject
9
+ MUTUAL_RECURSION_SENTINEL = 'xcodeproj.mutual_recursion_sentinel'.freeze
10
+
11
+ private_constant :MUTUAL_RECURSION_SENTINEL
12
+
9
13
  # @!group Attributes
10
14
 
11
15
  # @return [String] the name of the Target.
@@ -79,21 +83,36 @@ module Xcodeproj
79
83
  # the key of the build setting.
80
84
  #
81
85
  # @param [PBXNativeTarget] root_target
82
- # use this to resolve complete recursion between project and targets
86
+ # use this to resolve complete recursion between project and targets.
87
+ #
88
+ # @param [String] previous_key
89
+ # use this to resolve complete recursion between different build settings.
83
90
  #
84
91
  # @return [String] The value of the build setting
85
92
  #
86
- def resolve_build_setting(key, root_target = nil)
93
+ def resolve_build_setting(key, root_target = nil, previous_key = nil)
87
94
  setting = build_settings[key]
88
- setting = resolve_variable_substitution(key, setting, root_target) if setting.is_a?(String)
89
- config_setting = base_configuration_reference && config[key]
90
- config_setting = resolve_variable_substitution(key, config_setting, root_target) if config_setting.is_a?(String)
95
+ setting = resolve_variable_substitution(key, setting, root_target, previous_key)
96
+
97
+ config_setting = config[key]
98
+ config_setting = resolve_variable_substitution(key, config_setting, root_target, previous_key)
91
99
 
92
100
  project_setting = project.build_configuration_list[name]
93
- project_setting = nil if project_setting == self
101
+ project_setting = nil if equal?(project_setting)
94
102
  project_setting &&= project_setting.resolve_build_setting(key, root_target)
95
103
 
96
- [project_setting, config_setting, setting, ENV[key]].compact.reduce do |inherited, value|
104
+ defaults = {
105
+ 'CONFIGURATION' => name,
106
+ 'SRCROOT' => project.project_dir.to_s,
107
+ }
108
+
109
+ # if previous_key is nil, it means that we're back at the first call, so we can replace our sentinel string
110
+ # used to prevent recursion with nil
111
+ if previous_key.nil? && setting == MUTUAL_RECURSION_SENTINEL
112
+ setting = nil
113
+ end
114
+
115
+ [defaults[key], project_setting, config_setting, setting, ENV[key]].compact.reduce(nil) do |inherited, value|
97
116
  expand_build_setting(value, inherited)
98
117
  end
99
118
  end
@@ -102,15 +121,25 @@ module Xcodeproj
102
121
 
103
122
  private
104
123
 
124
+ VARIABLE_NAME_PATTERN =
125
+ '( # capture block
126
+ [_a-zA-Z0-9]+? # non-greedy lookup for everything contained in this list
127
+ )'.freeze
128
+ private_constant :VARIABLE_NAME_PATTERN
129
+
105
130
  CAPTURE_VARIABLE_IN_BUILD_CONFIG = /
106
131
  \$ # matches dollar sign literally
107
- [{(] # matches a single caracter on this list
108
- ( # capture block
109
- [^inherited] # ignore if match characters in this list
110
- [$(){}_a-zA-Z0-9]*? # non-greedy lookup for everything that contains this list
111
- )
112
- [})] # matches a single caracter on this list
132
+ (?: # non-capturing group
133
+ [{] # matches a single character on this list
134
+ #{VARIABLE_NAME_PATTERN}
135
+ [}] # matches a single character on this list
136
+ | # or
137
+ [(] # matches a single character on this list
138
+ #{VARIABLE_NAME_PATTERN}
139
+ [)] # matches a single character on this list
140
+ )
113
141
  /x
142
+ private_constant :CAPTURE_VARIABLE_IN_BUILD_CONFIG
114
143
 
115
144
  def expand_build_setting(build_setting_value, config_value)
116
145
  if build_setting_value.is_a?(Array) && config_value.is_a?(String)
@@ -123,27 +152,52 @@ module Xcodeproj
123
152
  inherited = config_value || default
124
153
 
125
154
  return build_setting_value.gsub(Regexp.union(Constants::INHERITED_KEYWORDS), inherited) if build_setting_value.is_a? String
126
- build_setting_value.map { |value| Constants::INHERITED_KEYWORDS.include?(value) ? inherited : value }.flatten
155
+ build_setting_value.flat_map { |value| Constants::INHERITED_KEYWORDS.include?(value) ? inherited : value }
127
156
  end
128
157
 
129
- def resolve_variable_substitution(key, value, root_target)
130
- variable = match_variable(value)
131
- return nil if key.eql?(variable)
132
- if variable.nil?
133
- return name if value.eql?('CONFIGURATION')
134
- if root_target
135
- return root_target.build_configuration_list[name].resolve_build_setting(value, root_target) || value
136
- else
137
- return resolve_build_setting(value, root_target) || value
138
- end
158
+ def resolve_variable_substitution(key, value, root_target, previous_key = nil)
159
+ case value
160
+ when Array
161
+ return value.map { |v| resolve_variable_substitution(key, v, root_target) }
162
+ when nil
163
+ return
164
+ when String
165
+ # we know how to resolve strings!
166
+ nil
167
+ else
168
+ raise ArgumentError, "Settings values can only be nil, string, or array, got #{value.inspect} for #{key}"
139
169
  end
140
- resolve_variable_substitution(key, value.sub(CAPTURE_VARIABLE_IN_BUILD_CONFIG, resolve_variable_substitution(key, variable, root_target)), root_target)
141
- end
142
170
 
143
- def match_variable(config_setting)
144
- match_data = config_setting.match(CAPTURE_VARIABLE_IN_BUILD_CONFIG)
145
- return match_data.captures.first unless match_data.nil?
146
- match_data
171
+ unless variable_match_data = value.match(CAPTURE_VARIABLE_IN_BUILD_CONFIG)
172
+ # no variables left, return the value unchanged
173
+ return value
174
+ end
175
+ variable_reference, variable = *variable_match_data.values_at(0, 1, 2).compact
176
+
177
+ case variable
178
+ when 'inherited'
179
+ # this is handled separately, after resolving all other variable references
180
+ value
181
+ when key
182
+ # to prevent infinite recursion
183
+ nil
184
+ when previous_key
185
+ # to prevent infinite recursion; we don't return nil as for the self recursion because it needs to be
186
+ # distinguished outside this method too
187
+ MUTUAL_RECURSION_SENTINEL
188
+ else
189
+ configuration_to_resolve_against = root_target ? root_target.build_configuration_list[name] : self
190
+ resolved_value_for_variable = configuration_to_resolve_against.resolve_build_setting(variable, root_target, key) || ''
191
+
192
+ # we use this sentinel string instead of nil, because, otherwise, it would be swallowed by the default empty
193
+ # string from the preceding line, and we want to distinguish between mutual recursion and other cases
194
+ if resolved_value_for_variable == MUTUAL_RECURSION_SENTINEL
195
+ return MUTUAL_RECURSION_SENTINEL
196
+ end
197
+
198
+ value = value.gsub(variable_reference, resolved_value_for_variable)
199
+ resolve_variable_substitution(key, value, root_target)
200
+ end
147
201
  end
148
202
 
149
203
  def sorted_build_settings
@@ -161,14 +215,15 @@ module Xcodeproj
161
215
 
162
216
  settings.keys.each do |key|
163
217
  next unless value = settings[key]
218
+ stripped_key = key.sub(/\[[^\]]+\]$/, '')
164
219
  case value
165
220
  when String
166
- next unless array_settings.include?(key)
221
+ next unless array_settings.include?(stripped_key)
167
222
  array_value = split_build_setting_array_to_string(value)
168
223
  next unless array_value.size > 1
169
224
  settings[key] = array_value
170
225
  when Array
171
- next if value.size > 1 && array_settings.include?(key)
226
+ next if value.size > 1 && array_settings.include?(stripped_key)
172
227
  settings[key] = value.join(' ')
173
228
  end
174
229
  end
@@ -180,9 +235,15 @@ module Xcodeproj
180
235
  end
181
236
 
182
237
  def config
183
- @config ||= Xcodeproj::Config.new(base_configuration_reference.real_path).to_hash.tap do |hash|
184
- normalize_array_settings(hash)
185
- end
238
+ return {} unless base_configuration_reference
239
+ @config ||=
240
+ if base_configuration_reference.real_path.exist?
241
+ Xcodeproj::Config.new(base_configuration_reference.real_path).to_hash.tap do |hash|
242
+ normalize_array_settings(hash)
243
+ end
244
+ else
245
+ {}
246
+ end
186
247
  end
187
248
 
188
249
  #---------------------------------------------------------------------#