Class: Fastlane::Actions::XcodebuildAction

Inherits:
Fastlane::Action show all
Defined in:
fastlane/lib/fastlane/actions/xcodebuild.rb

Overview

Constant Summary collapse

ARGS_MAP =
{
  # actions
  analyze: "analyze",
  archive: "archive",
  build: "build",
  clean: "clean",
  install: "install",
  installsrc: "installsrc",
  test: "test",

  # parameters
  alltargets: "-alltargets",
  arch: "-arch",
  archive_path: "-archivePath",
  configuration: "-configuration",
  derivedDataPath: "-derivedDataPath",
  destination_timeout: "-destination-timeout",
  dry_run: "-dry-run",
  enableAddressSanitizer: "-enableAddressSanitizer",
  enableThreadSanitizer: "-enableThreadSanitizer",
  enableCodeCoverage: "-enableCodeCoverage",
  export_archive: "-exportArchive",
  export_format: "-exportFormat",
  export_installer_identity: "-exportInstallerIdentity",
  export_options_plist: "-exportOptionsPlist",
  export_path: "-exportPath",
  export_profile: "-exportProvisioningProfile",
  export_signing_identity: "-exportSigningIdentity",
  export_with_original_signing_identity: "-exportWithOriginalSigningIdentity",
  hide_shell_script_environment: "-hideShellScriptEnvironment",
  jobs: "-jobs",
  parallelize_targets: "-parallelizeTargets",
  project: "-project",
  result_bundle_path: "-resultBundlePath",
  scheme: "-scheme",
  sdk: "-sdk",
  skip_unavailable_actions: "-skipUnavailableActions",
  target: "-target",
  toolchain: "-toolchain",
  workspace: "-workspace",
  xcconfig: "-xcconfig"
}

Constants inherited from Fastlane::Action

Fastlane::Action::AVAILABLE_CATEGORIES, Fastlane::Action::RETURN_TYPES

Class Method Summary collapse

Methods inherited from Fastlane::Action

action_name, authors, deprecated_notes, lane_context, method_missing, other_action, output, return_type, return_value, sample_return_value, shell_out_should_use_bundle_exec?, step_text

Class Method Details

.authorObject



398
399
400
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 398

def self.author
  "dtrenz"
end

.available_optionsObject



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 378

def self.available_options
  [
    ['archive', 'Set to true to build archive'],
    ['archive_path', 'The path to archive the to. Must contain `.xcarchive`'],
    ['workspace', 'The workspace to use'],
    ['scheme', 'The scheme to build'],
    ['build_settings', 'Hash of additional build information'],
    ['xcargs', 'Pass additional xcodebuild options'],
    ['output_style', 'Set the output format to one of: :standard (Colored UTF8 output, default), :basic (black & white ASCII output)'],
    ['buildlog_path', 'The path where the xcodebuild.log will be created, by default it is created in ~/Library/Logs/fastlane/xcbuild'],
    ['raw_buildlog', 'Set to true to see xcodebuild raw output. Default value is false'],
    ['xcpretty_output', 'specifies the output type for xcpretty. eg. \'test\', or \'simple\''],
    ['xcpretty_utf', 'Specifies xcpretty should use utf8 when reporting builds. This has no effect when raw_buildlog is specified.']
  ]
end

.categoryObject



71
72
73
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 71

def self.category
  :building
end

.clean_build_setting_value(value) ⇒ Object

Cleans values for build settings Only escaping ‘$(inherit)` types of values since “sh” interprets these as sub-commands instead of passing value into xcodebuild



354
355
356
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 354

def self.clean_build_setting_value(value)
  value.to_s.gsub('$(', '\\$(')
end

.descriptionObject



374
375
376
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 374

def self.description
  "Use the `xcodebuild` command to build and sign your app"
end

.detailsObject



394
395
396
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 394

def self.details
  "**Note**: `xcodebuild` is a complex command, so it is recommended to use [_gym_](https://docs.fastlane.tools/actions/gym/) for building your ipa file and [_scan_](https://docs.fastlane.tools/actions/scan/) for testing your app instead."
end

.detect_workspaceObject



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 358

def self.detect_workspace
  workspace = nil
  workspaces = Dir.glob("*.xcworkspace")

  if workspaces.length > 1
    UI.important("Multiple workspaces detected.")
  end

  unless workspaces.empty?
    workspace = workspaces.first
    UI.important("Using workspace \"#{workspace}\"")
  end

  return workspace
end

.example_codeObject



60
61
62
63
64
65
66
67
68
69
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 60

def self.example_code
  [
    'xcodebuild(
      archive: true,
      archive_path: "./build-dir/MyApp.xcarchive",
      scheme: "MyApp",
      workspace: "MyApp.xcworkspace"
    )'
  ]
end

.export_options_to_plist(hash) ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 301

def self.export_options_to_plist(hash)
  # Extract export options parameters from input options
  if hash.has_key?(:export_options_plist) && hash[:export_options_plist].is_a?(Hash)
    export_options = hash[:export_options_plist]

    # Normalize some values
    export_options[:teamID] = CredentialsManager::AppfileConfig.try_fetch_value(:team_id) if !export_options[:teamID] && CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
    export_options[:onDemandResourcesAssetPacksBaseURL] = Addressable::URI.encode(export_options[:onDemandResourcesAssetPacksBaseURL]) if export_options[:onDemandResourcesAssetPacksBaseURL]
    if export_options[:manifest]
      export_options[:manifest][:appURL] = Addressable::URI.encode(export_options[:manifest][:appURL]) if export_options[:manifest][:appURL]
      export_options[:manifest][:displayImageURL] = Addressable::URI.encode(export_options[:manifest][:displayImageURL]) if export_options[:manifest][:displayImageURL]
      export_options[:manifest][:fullSizeImageURL] = Addressable::URI.encode(export_options[:manifest][:fullSizeImageURL]) if export_options[:manifest][:fullSizeImageURL]
      export_options[:manifest][:assetPackManifestURL] = Addressable::URI.encode(export_options[:manifest][:assetPackManifestURL]) if export_options[:manifest][:assetPackManifestURL]
    end

    # Saves options to plist
    path = "#{Tempfile.new('exportOptions').path}.plist"
    File.write(path, export_options.to_plist)
    hash[:export_options_plist] = path
  end
  hash
end

.hash_to_args(hash) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 324

def self.hash_to_args(hash)
  # Remove nil value params
  hash = hash.delete_if { |_, v| v.nil? }

  # Maps nice developer param names to CLI arguments
  hash.map do |k, v|
    v ||= ""
    if arg = ARGS_MAP[k]
      value = (v != true && v.to_s.length > 0 ? "\"#{v}\"" : "")
      "#{arg} #{value}".strip
    elsif k == :build_settings
      v.map do |setting, val|
        val = clean_build_setting_value(val)
        "#{setting}=\"#{val}\""
      end.join(' ')
    elsif k == :destination
      [*v].collect { |dst| "-destination \"#{dst}\"" }.join(' ')
    elsif k == :keychain && v.to_s.length > 0
      # If keychain is specified, append as OTHER_CODE_SIGN_FLAGS
      "OTHER_CODE_SIGN_FLAGS=\"--keychain #{v}\""
    elsif k == :xcargs && v.to_s.length > 0
      # Add more xcodebuild arguments
      "#{v}"
    end
  end.compact
end

.is_supported?(platform) ⇒ Boolean

Returns:



56
57
58
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 56

def self.is_supported?(platform)
  [:ios, :mac].include? platform
end

.run(params) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'fastlane/lib/fastlane/actions/xcodebuild.rb', line 75

def self.run(params)
  unless Helper.test?
    UI.user_error!("xcodebuild not installed") if `which xcodebuild`.length == 0
  end

  # The args we will build with
  xcodebuild_args = Array[]

  # Supported ENV vars
  build_path    = ENV["XCODE_BUILD_PATH"] || nil
  scheme        = ENV["XCODE_SCHEME"]
  workspace     = ENV["XCODE_WORKSPACE"]
  project       = ENV["XCODE_PROJECT"]
  buildlog_path = ENV["XCODE_BUILDLOG_PATH"]

  # Set derived data path.
  params[:derivedDataPath] ||= ENV["XCODE_DERIVED_DATA_PATH"]
  Actions.lane_context[SharedValues::XCODEBUILD_DERIVED_DATA_PATH] = params[:derivedDataPath]

  # Append slash to build path, if needed
  if build_path && !build_path.end_with?("/")
    build_path += "/"
  end

  # By default we use xcpretty
  raw_buildlog = false

  # By default we don't pass the utf flag
  xcpretty_utf = false

  if params
    # Operation bools
    archiving    = params.key? :archive
    exporting    = params.key? :export_archive
    testing      = params.key? :test
    xcpretty_utf = params[:xcpretty_utf]

    if params.key? :raw_buildlog
      raw_buildlog = params[:raw_buildlog]
    end

    if exporting
      # If not passed, retrieve path from previous xcodebuild call
      params[:archive_path] ||= Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]

      # If not passed, construct export path from env vars
      if params[:export_path].nil?
        ipa_filename = scheme ? scheme : File.basename(params[:archive_path], ".*")
        params[:export_path] = "#{build_path}#{ipa_filename}"
      end

      # Default to ipa as export format
      export_format = params[:export_format] || "ipa"

      # Store IPA path for later deploy steps (i.e. Crashlytics)
      Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] = params[:export_path] + "." + export_format.downcase
    else
      # If not passed, check for archive scheme & workspace/project env vars
      params[:scheme] ||= scheme
      params[:workspace] ||= workspace
      params[:project] ||= project

      # If no project or workspace was passed in or set as an environment
      # variable, attempt to autodetect the workspace.
      if params[:project].to_s.empty? && params[:workspace].to_s.empty?
        params[:workspace] = detect_workspace
      end
    end

    if archiving
      # If not passed, construct archive path from env vars
      params[:archive_path] ||= "#{build_path}#{params[:scheme]}.xcarchive"

      # Cache path for later xcodebuild calls
      Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE] = params[:archive_path]
    end

    if params.key? :enable_address_sanitizer
      params[:enableAddressSanitizer] = params[:enable_address_sanitizer] ? 'YES' : 'NO'
    end
    if params.key? :enable_thread_sanitizer
      params[:enableThreadSanitizer] = params[:enable_thread_sanitizer] ? 'YES' : 'NO'
    end
    if params.key? :enable_code_coverage
      params[:enableCodeCoverage] = params[:enable_code_coverage] ? 'YES' : 'NO'
    end

    # Maps parameter hash to CLI args
    params = export_options_to_plist(params)
    if hash_args = hash_to_args(params)
      xcodebuild_args += hash_args
    end

    buildlog_path ||= params[:buildlog_path]
  end

  # By default we put xcodebuild.log in the Logs folder
  buildlog_path ||= File.expand_path("#{FastlaneCore::Helper.buildlog_path}/fastlane/xcbuild/#{Time.now.strftime('%F')}/#{Process.pid}")

  # Joins args into space delimited string
  xcodebuild_args = xcodebuild_args.join(" ")

  # Default args
  xcpretty_args = []

  # Formatting style
  if params && params[:output_style]
    output_style = params[:output_style]
    UI.user_error!("Invalid output_style #{output_style}") unless [:standard, :basic].include?(output_style)
  else
    output_style = :standard
  end

  case output_style
  when :standard
    xcpretty_args << '--color' unless Helper.colors_disabled?
  when :basic
    xcpretty_args << '--no-utf'
  end

  if testing
    if params[:reports]
      # New report options format
      reports = params[:reports].reduce("") do |arguments, report|
        report_string = "--report #{report[:report]}"

        if report[:output]
          report_string << " --output \"#{report[:output]}\""
        elsif report[:report] == 'junit'
          report_string << " --output \"#{build_path}report/report.xml\""
        elsif report[:report] == 'html'
          report_string << " --output \"#{build_path}report/report.html\""
        elsif report[:report] == 'json-compilation-database'
          report_string << " --output \"#{build_path}report/report.json\""
        end

        if report[:screenshots]
          report_string << " --screenshots"
        end

        unless arguments == ""
          arguments << " "
        end

        arguments << report_string
      end

      xcpretty_args.push reports

    elsif params[:report_formats]
      # Test report file format
      report_formats = params[:report_formats].map do |format|
        "--report #{format}"
      end.sort.join(" ")

      xcpretty_args.push report_formats

      # Save screenshots flag
      if params[:report_formats].include?("html") && params[:report_screenshots]
        xcpretty_args.push "--screenshots"
      end

      xcpretty_args.sort!

      # Test report file path
      if params[:report_path]
        xcpretty_args.push "--output \"#{params[:report_path]}\""
      elsif build_path
        xcpretty_args.push "--output \"#{build_path}report\""
      end
    end
  end

  # Stdout format
  if testing && !archiving
    xcpretty_args << (params[:xcpretty_output] ? "--#{params[:xcpretty_output]}" : "--test")
  else
    xcpretty_args << (params[:xcpretty_output] ? "--#{params[:xcpretty_output]}" : "--simple")
  end

  xcpretty_args = xcpretty_args.join(" ")

  xcpretty_command = ""
  xcpretty_command = "| xcpretty #{xcpretty_args}" unless raw_buildlog
  unless raw_buildlog
    xcpretty_command = "#{xcpretty_command} --utf" if xcpretty_utf
  end

  pipe_command = "| tee '#{buildlog_path}/xcodebuild.log' #{xcpretty_command}"

  FileUtils.mkdir_p buildlog_path
  UI.message("For a more detailed xcodebuild log open #{buildlog_path}/xcodebuild.log")

  output_result = ""

  # In some cases the simulator is not booting up in time
  # One way to solve it is to try to rerun it for one more time
  begin
    output_result = Actions.sh "set -o pipefail && xcodebuild #{xcodebuild_args} #{pipe_command}"
  rescue => ex
    exit_status = $?.exitstatus

    raise_error = true
    if exit_status.eql? 65
      iphone_simulator_time_out_error = /iPhoneSimulator: Timed out waiting/

      if (iphone_simulator_time_out_error =~ ex.message) != nil
        raise_error = false

        UI.important("First attempt failed with iPhone Simulator error: #{iphone_simulator_time_out_error.source}")
        UI.important("Retrying once more...")
        output_result = Actions.sh "set -o pipefail && xcodebuild #{xcodebuild_args} #{pipe_command}"
      end
    end

    raise ex if raise_error
  end

  # If raw_buildlog and some reports had to be created, create xcpretty reports from the build log
  if raw_buildlog && xcpretty_args.include?('--report')
    output_result = Actions.sh "set -o pipefail && cat '#{buildlog_path}/xcodebuild.log' | xcpretty #{xcpretty_args} > /dev/null"
  end

  output_result
end