class Fastlane::Actions::XcodebuildAction

xcodebuild man page: developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html

Constants

ARGS_MAP

Public Class Methods

author() click to toggle source
# File fastlane/lib/fastlane/actions/xcodebuild.rb, line 398
def self.author
  "dtrenz"
end
available_options() click to toggle source
# 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
category() click to toggle source
# File fastlane/lib/fastlane/actions/xcodebuild.rb, line 71
def self.category
  :building
end
clean_build_setting_value(value) click to toggle source

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

# File fastlane/lib/fastlane/actions/xcodebuild.rb, line 354
def self.clean_build_setting_value(value)
  value.to_s.gsub('$(', '\\$(')
end
description() click to toggle source
# File fastlane/lib/fastlane/actions/xcodebuild.rb, line 374
def self.description
  "Use the `xcodebuild` command to build and sign your app"
end
details() click to toggle source
# 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_workspace() click to toggle source
# 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_code() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File fastlane/lib/fastlane/actions/xcodebuild.rb, line 56
def self.is_supported?(platform)
  [:ios, :mac].include? platform
end
run(params) click to toggle source
# 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