fastlane-plugin-xcconfig_actions 1.1.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1de54217087690915ba20ee2e6cb210e198d0663696dcb59314aab235391142b
4
- data.tar.gz: 4cce95907529eb88ffff000abadbe3bcbf7a0cc119418fd8ac09996a8809b571
3
+ metadata.gz: 1ff5347f5b29ae989604b428568e7a6f6f76a782699a2d6f0afdb1696f9d41ce
4
+ data.tar.gz: ccabe7e926c527995f7d648916ff5113d510cb9099ea9d312fd49a8db3c08573
5
5
  SHA512:
6
- metadata.gz: 4ca5c56e3317e7407d37f2175ec367bf392ad010377191fe56b65c6c07a28302f87ec1253021182fa88449220c8e6fdd3ba6baaf60e45336d7298e0400e0d5e1
7
- data.tar.gz: c5e29e943f531cb921c7a1f11ea542293ab4296e757379510cce3d4759f19bd195f6b5c62cca971c9f022e2372767b778780285248197db1511346b38d7e77e5
6
+ metadata.gz: a5cf33cdd535fe366170caafe3b6d9a46262dcf9273f14b4c3fa3cebdefd84989d60f59ee59e5e189f394186b5d98666def29db384f753f1e8c62919060dfd66
7
+ data.tar.gz: 510bed17f4306c2d6aaac5c48992554846ecb19d724574a701a0244fdf32c1fd0d13c3a0baf79bb714459683ee9a643c7f1309ce51353b031185c7c40df75dc9
data/README.md CHANGED
@@ -34,6 +34,41 @@ Things **not supported** at the moment:
34
34
  - Use of `<DEVELOPER_DIR>` in include paths
35
35
  - Use of curly braces in variable references, e.g. `${VAR}`
36
36
 
37
+ The build settings are also saved as a dictionary under `SharedValues::XCCONFIG_ACTIONS_BUILD_SETTINGS` key in current `lane_context`.
38
+
39
+ ### build_settings_to_flags
40
+
41
+ Map build settings to Clang Cxx/Objective-C compiler, Swift compiler and Linker flags.
42
+
43
+ This action is useful when you plan to reuse xcconfigs with other tools, such as [Buck](https://buckbuild.com/), and you want to translate xcconfigs into the compiler/linker flags.
44
+
45
+ Build flags can be printed to standard output or saved to file.
46
+ The flags are also available via lane context as `lane_context[SharedValues::XCCONFIG_ACTIONS_BUILD_FLAGS]`.
47
+
48
+ The result is a dictionary with following keys:
49
+
50
+ - `compiler_flags` for Clang CXX/Objective-C compiler.
51
+ - `swift_compiler_flags` for Swift compiler.
52
+ - `linker_flags` for Clang linker.
53
+
54
+ <!-- TODO: Add info on how it works. -->
55
+
56
+ References:
57
+
58
+ - [Xcode Build Settings](https://help.apple.com/xcode/mac/10.2/#/itcaec37c2a6)
59
+ - [LLVM Clang Command Line Options](https://clang.llvm.org/docs/ClangCommandLineReference.html)
60
+ - [LLVM Clang Diagnostics](https://clang.llvm.org/docs/DiagnosticsReference.html)
61
+ - [GCC Warning Options](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html)
62
+ - https://gist.github.com/fabiopelosin/4560417
63
+ - https://pewpewthespells.com/blog/xcode_build_system.html
64
+
65
+ #### Known Issues
66
+
67
+ Flags like `-sdk iphoneos` and `-isysroot iphoneos` may not be suitable for all uses, so may have to remove them from all flags.
68
+
69
+ The flag like `-std=gnu++14` is added to `compiler_flags` but it's not applicable for Objective-C code.
70
+ Most tools have differentiation between C flags (C and Objective-C) and Cxx flags (C++/Objective-C++).
71
+
37
72
  ### validate_xcconfig
38
73
 
39
74
  Validate xcconfig using set of very opinionated rules:
@@ -96,6 +131,9 @@ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plu
96
131
  # Read xcconfig example.
97
132
  bundle exec fastlane read
98
133
 
134
+ # Map build settings to build flags.
135
+ bundle exec fastlane build_flags
136
+
99
137
  # Validate xcconfig example.
100
138
  bundle exec fastlane validate
101
139
  ```
@@ -0,0 +1,122 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/xcconfig_actions_helper'
3
+ require_relative '../helper/xcspec'
4
+
5
+ module Fastlane
6
+ module Actions
7
+ ActionHelper = Fastlane::Helper::XcconfigActionsHelper
8
+ Xcspec = Fastlane::Helper::Xcspec
9
+
10
+ module SharedValues
11
+ XCCONFIG_ACTIONS_BUILD_FLAGS = :XCCONFIG_ACTIONS_BUILD_FLAGS
12
+ end
13
+
14
+ class BuildSettingsToFlagsAction < Action
15
+ def self.run(params)
16
+ build_settings = params[:build_settings] || Actions.lane_context[SharedValues::XCCONFIG_ACTIONS_BUILD_SETTINGS]
17
+ UI.user_error!("Missing build settings input") unless build_settings
18
+ xcode = params[:xcode]
19
+
20
+ clang_spec = load_tool_spec("Clang*", xcode: xcode)
21
+ swift_spec = load_tool_spec("Swift*", xcode: xcode)
22
+ linker_spec = load_tool_spec("Ld*", xcode: xcode)
23
+
24
+ clang_mapping = clang_spec.map_build_settings(build_settings)
25
+ swift_mapping = swift_spec.map_build_settings(build_settings)
26
+ linker_mapping = linker_spec.map_build_settings(build_settings)
27
+
28
+ flags = {
29
+ "compiler_flags" => clang_mapping.flags,
30
+ "swift_compiler_flags" => swift_mapping.flags,
31
+ "linker_flags" => [
32
+ linker_mapping.flags,
33
+ linker_mapping.linker_flags,
34
+ clang_mapping.linker_flags,
35
+ swift_mapping.linker_flags
36
+ ].reject(&:empty?).join(" ")
37
+ }
38
+
39
+ Actions.lane_context[SharedValues::XCCONFIG_ACTIONS_BUILD_FLAGS] = flags
40
+
41
+ if params[:output_path]
42
+ File.open(params[:output_path], "w") { |f| f.puts(flags.to_json) }
43
+ else
44
+ return flags
45
+ end
46
+ end
47
+
48
+ ###
49
+ # @!group Xcspecs
50
+ ###
51
+
52
+ def self.load_tool_spec(tool, xcode:)
53
+ core_spec_path = ActionHelper.find_xcspec("CoreBuildSystem*", xcode: xcode)
54
+ standard_spec = Xcspec.new(core_spec_path, id: "com.apple.buildsettings.standard")
55
+ core_spec = Xcspec.new(core_spec_path, id: "com.apple.build-system.core")
56
+
57
+ Xcspec.new(
58
+ ActionHelper.find_xcspec(tool, xcode: xcode),
59
+ standard_spec: standard_spec,
60
+ core_spec: core_spec
61
+ )
62
+ end
63
+
64
+ ###
65
+ # @!group Info and Options
66
+ ###
67
+
68
+ def self.description
69
+ "Map xcconfig build settings to compiler and linker build flags"
70
+ end
71
+
72
+ def self.authors
73
+ ["Maksym Grebenets"]
74
+ end
75
+
76
+ def self.return_value
77
+ "Build flags dictionary"
78
+ end
79
+
80
+ def self.details
81
+ [
82
+ "Build flags keys:",
83
+ "- compiler_flags: CXX compiler flags for clang compiler",
84
+ "- swift_compiler_flags: Compiler flags for Swift compiler",
85
+ "- linker_flags: Linker flags for clang linker (Cxx and Swift)"
86
+ ].join("\n")
87
+ end
88
+
89
+ def self.category
90
+ :building
91
+ end
92
+
93
+ def self.available_options
94
+ [
95
+ FastlaneCore::ConfigItem.new(key: :build_settings,
96
+ env_name: "XCCONFIG_ACTIONS_BUILD_FLAGS_BUILD_SETTINGS",
97
+ description: "Build settings to convert to build flags",
98
+ optional: true,
99
+ type: Hash,
100
+ verify_block: proc do |value|
101
+ UI.user_error!("Missing build settings") if value.nil?
102
+ end),
103
+ FastlaneCore::ConfigItem.new(key: :xcode,
104
+ env_name: "XCCONFIG_ACTIONS_BUILD_FLAGS_XCODE",
105
+ description: "Xcode version of path to Xcode.app",
106
+ optional: true,
107
+ default_value: "10.2",
108
+ type: String),
109
+ FastlaneCore::ConfigItem.new(key: :output_path,
110
+ env_name: "XCCONFIG_ACTIONS_BUILD_FLAGS_OUTPUT_PATH",
111
+ description: "Output path to save build settings JSON",
112
+ optional: true,
113
+ type: String)
114
+ ]
115
+ end
116
+
117
+ def self.is_supported?(platform)
118
+ [:ios, :mac].include?(platform)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -4,6 +4,10 @@ require_relative '../helper/xcconfig_actions_helper'
4
4
 
5
5
  module Fastlane
6
6
  module Actions
7
+ module SharedValues
8
+ XCCONFIG_ACTIONS_BUILD_SETTINGS = :XCCONFIG_ACTIONS_BUILD_SETTINGS
9
+ end
10
+
7
11
  class ReadXcconfigAction < Action
8
12
  def self.run(params)
9
13
  path = params[:path]
@@ -13,9 +17,7 @@ module Fastlane
13
17
 
14
18
  config = read_config(path)
15
19
 
16
- if params[:no_resolve]
17
- json = config.to_json
18
- else
20
+ unless params[:no_resolve]
19
21
  parent_config = read_config(parent)
20
22
 
21
23
  parent_config["SRCROOT"] = srcroot
@@ -24,20 +26,22 @@ module Fastlane
24
26
  if Helper::XcconfigActionsHelper.command_exist?("xcodebuild")
25
27
  # Set value of XCODE_VERSION_MAJOR not available when reading xcconfigs directly.
26
28
  xcode_version = `xcodebuild -version | head -n1 | cut -d' ' -f2 | xargs`.strip
27
- xcode_version_majon_padded = xcode_version.split(".").first.rjust(2, "0") + "00"
28
- parent_config["XCODE_VERSION_MAJOR"] = xcode_version_majon_padded
29
+ xcode_version_major_padded = xcode_version.split(".").first.rjust(2, "0") + "00"
30
+ parent_config["XCODE_VERSION_MAJOR"] = xcode_version_major_padded
29
31
  end
30
32
 
31
33
  resolved_parent_config = resolve_config(parent_config)
32
34
  resolved_config = resolve_config(config, parent: resolved_parent_config)
33
35
 
34
- json = resolved_parent_config.merge(resolved_config).to_json
36
+ config = resolved_parent_config.merge(resolved_config)
35
37
  end
36
38
 
39
+ Actions.lane_context[SharedValues::XCCONFIG_ACTIONS_BUILD_SETTINGS] = config
40
+
37
41
  if params[:output_path]
38
- File.open(params[:output_path], "w") { |f| f.puts(json) }
42
+ File.open(params[:output_path], "w") { |f| f.puts(config.to_json) }
39
43
  else
40
- return json
44
+ return config
41
45
  end
42
46
  end
43
47
 
@@ -96,6 +100,7 @@ module Fastlane
96
100
  def self.resolve_value(value, key:, resolved: {}, parent: {})
97
101
  matches = value.scan(/(\$\([^$\)]*\))/)
98
102
 
103
+ mutable_value = value.dup # Prevent unwanted side-effect of input modification.
99
104
  matches.each do |group|
100
105
  group.each do |match|
101
106
  var_name = match.delete("$()")
@@ -105,13 +110,13 @@ module Fastlane
105
110
  else
106
111
  resolved[var_name] || parent[var_name]
107
112
  end
108
- value.gsub!(match, var_value || "")
109
- resolved[key] = value
113
+ mutable_value.gsub!(match, var_value || "")
114
+ resolved[key] = mutable_value
110
115
  end
111
116
  end
112
117
 
113
118
  # If there are still variables, keep resolving then.
114
- value.include?("$(") ? resolve_value(value, key: key, resolved: resolved, parent: parent) : value
119
+ mutable_value.include?("$(") ? resolve_value(mutable_value, key: key, resolved: resolved, parent: parent) : mutable_value
115
120
  end
116
121
 
117
122
  # Resolve xcconfig values using parent config.
@@ -147,6 +152,10 @@ module Fastlane
147
152
  ""
148
153
  end
149
154
 
155
+ def self.category
156
+ :building
157
+ end
158
+
150
159
  def self.available_options
151
160
  [
152
161
  FastlaneCore::ConfigItem.new(key: :path,
@@ -190,7 +199,7 @@ module Fastlane
190
199
  end
191
200
 
192
201
  def self.is_supported?(platform)
193
- true
202
+ [:ios, :mac].include?(platform)
194
203
  end
195
204
  end
196
205
  end
@@ -91,6 +91,10 @@ module Fastlane
91
91
  ].join("\n")
92
92
  end
93
93
 
94
+ def self.category
95
+ :linting
96
+ end
97
+
94
98
  def self.available_options
95
99
  [
96
100
  FastlaneCore::ConfigItem.new(key: :path,
@@ -113,7 +117,7 @@ module Fastlane
113
117
  end
114
118
 
115
119
  def self.is_supported?(platform)
116
- true
120
+ [:ios, :mac].include?(platform)
117
121
  end
118
122
  end
119
123
  end
@@ -0,0 +1,18 @@
1
+ from bs4 import BeautifulSoup
2
+ import re
3
+
4
+ soup = BeautifulSoup(open("page.html"), "html.parser")
5
+
6
+ entries = soup.find_all("div", id=re.compile("dev.*"))
7
+
8
+ title_re = re.compile(".*\((.*)\)")
9
+ settings = []
10
+ for e in entries:
11
+ text = e.find("h2", class_="Name").text
12
+ m = title_re.match(text)
13
+ if m == None:
14
+ settings.append(text)
15
+ else:
16
+ settings.append(m.group(1))
17
+
18
+ print settings
@@ -0,0 +1,23 @@
1
+ # https://stackoverflow.com/questions/8049520/web-scraping-javascript-page-with-python
2
+ from selenium import webdriver
3
+ from selenium.webdriver.support.ui import WebDriverWait
4
+ from selenium.webdriver.support import expected_conditions
5
+ from selenium.webdriver.common.by import By
6
+
7
+ options = webdriver.FirefoxOptions()
8
+ options.headless = True
9
+ driver = webdriver.Firefox(firefox_options=options)
10
+
11
+ id = "itcaec37c2a6"
12
+ url = "https://help.apple.com/xcode/mac/10.2/#/{}".format(id)
13
+ driver.get(url)
14
+
15
+ # https://stackoverflow.com/questions/26566799/wait-until-page-is-loaded-with-selenium-webdriver-for-python
16
+ WebDriverWait(driver, 10).until(
17
+ expected_conditions.text_to_be_present_in_element(
18
+ (By.ID, id),
19
+ 'Build settings reference'
20
+ )
21
+ )
22
+
23
+ print driver.page_source.encode('utf-8').strip()
@@ -42,6 +42,16 @@ module Fastlane
42
42
  includes: includes
43
43
  }
44
44
  end
45
+
46
+ def self.find_xcspec(name, xcode:)
47
+ search_path = File.exist?(xcode) ? File.join(xcode, "Contents/Plugins") : File.join(File.dirname(__FILE__), "xcspecs", xcode)
48
+ UI.user_error!("Can't find app path of xcspecs folder for xcode: #{xcode}") unless File.exist?(search_path)
49
+
50
+ query = File.join(search_path, "**", name + ".xcspec")
51
+ xcspec = Dir[query].first
52
+ UI.user_error!("Can't find xcspec with name: #{name}") unless File.exist?(xcspec)
53
+ xcspec
54
+ end
45
55
  end
46
56
  end
47
57
  end
@@ -0,0 +1,259 @@
1
+ require "plist"
2
+ require "nokogiri-plist"
3
+
4
+ module Fastlane
5
+ UI = UI unless Fastlane.const_defined?("UI")
6
+
7
+ module Helper
8
+ # Xcspec helper class.
9
+ class Xcspec
10
+ ###
11
+ # @!group Mapping
12
+ ###
13
+ class Mapping
14
+ attr_reader :flags, :linker_flags
15
+
16
+ def initialize(flags = "", linker_flags = "")
17
+ @flags = flags
18
+ @linker_flags = linker_flags
19
+ end
20
+
21
+ # Join with other mapping and return new mapping.
22
+ def join(other)
23
+ return self if other.nil?
24
+
25
+ joined_flags = [flags, other.flags].reject(&:empty?).join(" ")
26
+ joined_linker_flags = [linker_flags, other.linker_flags].reject(&:empty?).join(" ")
27
+
28
+ Mapping.new(joined_flags, joined_linker_flags)
29
+ end
30
+ end
31
+
32
+ ###
33
+ # @!group Initialization
34
+ ###
35
+
36
+ attr_reader :tool
37
+
38
+ def initialize(path, id: nil, standard_spec: nil, core_spec: nil)
39
+ UI.user_error!("No such file: #{path}") unless path && File.exist?(path)
40
+
41
+ @plist = Xcspec.load_plist(path)
42
+ @tool = find_tool(id)
43
+
44
+ all_tools = [@tool]
45
+ all_tools << standard_spec.tool if standard_spec
46
+ all_tools << core_spec.tool if core_spec
47
+
48
+ @all_options = all_tools.flat_map { |t| t["Options"] }
49
+ end
50
+
51
+ def self.load_plist(path)
52
+ file_type = `file -b --mime-type #{path.shellescape}`.chomp
53
+ if file_type == "text/xml" || file_type == "application/xml"
54
+ xml_plist = path
55
+ else
56
+ if FastlaneCore::Helper.mac?
57
+ xml_plist = Tempfile.new("xcspec.plist").path
58
+ result = system("plutil -convert xml1 -o #{xml_plist.shellescape} #{path.shellescape}")
59
+ UI.user_error!("Couldn't convert #{path} xcspec to XML plist") unless result
60
+ else
61
+ # There is plist-utils library, but it can only convert binary to XML, can't handle ASCII.
62
+ UI.user_error!("Can't convert ASCII plists to XML on Linux or Windows platform")
63
+ end
64
+ end
65
+
66
+ Nokogiri::PList(File.open(xml_plist))
67
+ end
68
+
69
+ def find_tool(id)
70
+ return @plist unless @plist.kind_of?(Array)
71
+ id ? @plist.find { |t| t["Identifier"] == id } : @plist.first
72
+ end
73
+
74
+ ###
75
+ # @!group Mapping
76
+ ###
77
+
78
+ def map_build_settings(build_settings)
79
+ # Build settings provided for mapping will not include all possible build settings.
80
+ # Add default values for missing build settings.
81
+ missing_build_settings = @all_options.reduce({}) do |memo, opt|
82
+ name = opt["Name"]
83
+ build_settings.key?(name) ? memo : memo.merge({ name => opt["DefaultValue"] })
84
+ end
85
+ complete_build_settings = build_settings.merge(missing_build_settings)
86
+
87
+ mappings = build_settings.flat_map do |setting, value|
88
+ map_build_setting_value(setting, value, complete_build_settings)
89
+ end.compact
90
+
91
+ mappings.reduce(Mapping.new) { |memo, m| memo.join(m) }
92
+ end
93
+
94
+ def map_build_setting_value(name, value, build_settings)
95
+ option = @tool["Options"].find { |o| o["Name"] == name }
96
+ return nil unless option
97
+
98
+ map_option(option, value, build_settings)
99
+ end
100
+
101
+ def map_option(option, value, build_settings)
102
+ # Evaluate and check the 'Condition'.
103
+ return nil unless check_condition(option, build_settings)
104
+
105
+ # If type of the value is one of List types (StringList, PathList),
106
+ # then split it into value list, else just use scalar value itself.
107
+ scalar_values = option["Type"].downcase.end_with?("list") ? value.split : [value]
108
+ scalar_values.flat_map { |v| map_option_scalar_value(option, v, build_settings) }.compact
109
+ end
110
+
111
+ def map_option_scalar_value(option, value, build_settings)
112
+ # At this point we deal with scalar value.
113
+
114
+ # Type = StringList
115
+ # - CommandLineFlag and CommandLinePrefixFlag - map each string in the list.
116
+ # - CommandLineArgs - map each string in the list.
117
+ # - Finally there are entries with only Category = CustomFlags.
118
+ # Those end up in compiler flags for sure, so looks like if category is CustomFlags,
119
+ # then should use them as is.
120
+ # There's only 3 of them: OTHER_CFLAGS, OTHER_CPLUSPLUSFLAGS and WARNING_CFLAGS.
121
+ # PathList, that for all intents and purposes can be handed as StringList.
122
+
123
+ # - CommandLineArgs
124
+ # - If input is StringList, then map each entry from string list according to CommandLineArgs value
125
+ # The value of CommandLineArgs can be an array, use flat map
126
+ # Can have a switch, e.g.
127
+ # CommandLineArgs = {
128
+ # "" = ();
129
+ # "<<otherwise>>" = (
130
+ # "-$(DEPLOYMENT_TARGET_CLANG_FLAG_NAME)=$(value)",
131
+ # );
132
+ # };
133
+ # All these switches, however, map to nothing for empty string and to something for non-empty string.
134
+ # The vey same switch is used for enums. Switching for enums vs string is no different,
135
+ # since switching happens on string values.
136
+ # <<otherwise>> is for other values
137
+ # - AdditionalLinkerArgs
138
+ # Few build settings have them, collected separately, work just like CommandLineArgs for parsing.
139
+ # - CommandLineFlag and CommandLinePrefixFlag
140
+ # - If input is StringList then applied to each entry in the list, to each entry applied as to a scalar value:
141
+ # - If input is scalar String value, then applied just once as -<flag> $(value)
142
+ # - If input is boolean, then just the flag is used, e.g. -<flag>, no Prefix option supported for boolean.
143
+ # - While for prefix it appears there's no space and it is -<prefix>$(value)
144
+ # - An exception(!) is MACH_O_TYPE, where no value is appended but just the flag is used.
145
+ # It is also the only enum where list of values has command line flag for each value.
146
+
147
+ # Type = Enumeration
148
+ # Most of them have CommandLineArgs with a switch.
149
+ # Some also come with AdditionalLinkerArgs, which is mapped in similar way.
150
+ # Then there's MACH_O_TYPE, which has CommandLineFlag under Values...
151
+ # But only when used for linker flags:
152
+ # Value = "mh_dylib";
153
+ # CommandLineFlag = "-dynamiclib";
154
+ # -dynamiclib is a flag that doesn't take input, so only specified as is.
155
+
156
+ flags = ""
157
+ if (cli_args = option["CommandLineArgs"])
158
+ flags = map_args(option, cli_args, value, build_settings)
159
+ elsif (cli_flag = option["CommandLineFlag"])
160
+ # If Boolean and YES, then use CommandLineFlag value.
161
+ if option["Type"] == "Boolean"
162
+ flags = cli_flag if value == "YES"
163
+ else
164
+ flags = [cli_flag, value].join(" ")
165
+ end
166
+ elsif (cli_prefix_flag = option["CommandLinePrefixFlag"])
167
+ flags = [cli_prefix_flag, value].join # Join with no space in between.
168
+ elsif option["Category"] == "CustomFlags"
169
+ flags = value
170
+ else
171
+ # Nothing to map to, except for when it's MACH_O_TYPE, when it has list of dictionaries:
172
+ # Values = ( { Value = "v", CommandLineFlag = "f", ... } )
173
+ match = (option["Values"] || []).find { |v| v["Value"] == value } || {}
174
+ flags = match["CommandLineFlag"] || ""
175
+ end
176
+
177
+ linker_flags = ""
178
+ if (linker_args = option["AdditionalLinkerArgs"])
179
+ linker_flags = map_args(option, linker_args, value, build_settings)
180
+ end
181
+
182
+ Mapping.new(flags, linker_flags)
183
+ end
184
+
185
+ def map_args(option, args, value, build_settings)
186
+ return "" unless args
187
+
188
+ if args.kind_of?(Hash)
189
+ map_args(option, args[value] || args["<<otherwise>>"], value, build_settings)
190
+ else
191
+ # Args should be an array here, but can be just single string.
192
+
193
+ resolved_args = args.kind_of?(Array) ? args.dup : [args]
194
+ # Replacing $(value) is just one part, need to resolve any build settings present too.
195
+ resolved_args = resolved_args.map { |a| a.gsub("$(value)", value) }.join(" ")
196
+ Fastlane::Actions::ReadXcconfigAction.resolve_value(
197
+ resolved_args,
198
+ key: "resolved_args",
199
+ resolved: {},
200
+ parent: build_settings
201
+ )
202
+ end
203
+ end
204
+
205
+ # Constants for condition evaluation.
206
+ NO = false
207
+ YES = true
208
+
209
+ # Evaluate and check the condition.
210
+ # Conditions come in form like this:
211
+ # "$(COMPILER_INDEX_STORE_ENABLE) == YES || ( $(COMPILER_INDEX_STORE_ENABLE) == Default && $(GCC_OPTIMIZATION_LEVEL) == 0 )"
212
+ # Return true if there's no condition to evaluate.
213
+ def check_condition(option, build_settings)
214
+ condition = option["Condition"]
215
+ return true unless condition
216
+
217
+ # Need to resolve all $(VAR) references using build settings.
218
+ # At this point using complete build settings,
219
+ # which include default values for all known build settings.
220
+
221
+ # Handy that read_xcconfig action already has a helper to resolve a value.
222
+ # Just pass current build settings as parent config.
223
+ resolved_condition = Fastlane::Actions::ReadXcconfigAction.resolve_value(
224
+ condition,
225
+ key: "condition",
226
+ resolved: {},
227
+ parent: build_settings
228
+ )
229
+
230
+ # Resolved condition is now a C-like expression, which also can be evaluated as Ruby code.
231
+ # With small changes though.
232
+ # All literals in condition can be treated as strings, except for YES/NO boolean literals,
233
+ # which are replaced with `true` and `false`.
234
+ # However, xcspecs are very inconsistent when it comes to strings.
235
+ # Some values are used unquoted in the conditions, such as:
236
+ # Defatult, mh_object, bitcode.
237
+ # There's also use of '' and \"\" for empty string, the latter may cause issues.
238
+ # Then "same-as-input" that may have to be resolved - do not handle for now.
239
+ # Finally, $(variant) == profile - just leave it for now.
240
+
241
+ # Ways to fix.
242
+ # 1. Scan for all enums in xcspecs and define module vars for each enum value,
243
+ # so when evaluated, is replaced with variable.
244
+ # 2. Process resolved condition by wrapping all unwrapped entries in quotes.
245
+ # Using approach 2 for now with insane regex.
246
+
247
+ # In all cases replace \"\" with ''.
248
+ resolved_condition.gsub!('"', "'")
249
+
250
+ # Quote everything except YES and NO.
251
+ resolved_condition = (" " + resolved_condition + " ").gsub(/\s((\w|\d|-|\+|\.)+?)\s/, " '\\1' ").gsub(/'(YES|NO)'/, '\\1')
252
+
253
+ # rubocop:disable Security/Eval
254
+ eval(resolved_condition)
255
+ # rubocop:enable Security/Eval
256
+ end
257
+ end
258
+ end
259
+ end