class FastlaneCore::Project

Represents an Xcode project

Attributes

is_workspace[RW]

Is this project a workspace?

options[RW]

The config object containing the scheme, configuration, etc.

path[RW]

Path to the project/workspace

xcodebuild_list_silent[RW]

Should the output of xcodebuild commands be silenced?

xcodebuild_suppress_stderr[RW]

Should we redirect stderr to /dev/null for xcodebuild commands? Gets rid of annoying plugin info warnings.

Public Class Methods

detect_projects(config) click to toggle source

Project discovery

# File lib/fastlane_core/project.rb, line 6
def detect_projects(config)
  if config[:workspace].to_s.length > 0 and config[:project].to_s.length > 0
    UI.user_error!("You can only pass either a workspace or a project path, not both")
  end

  return if config[:project].to_s.length > 0

  if config[:workspace].to_s.length == 0
    workspace = Dir["./*.xcworkspace"]
    if workspace.count > 1
      puts "Select Workspace: "
      config[:workspace] = choose(*workspace)
    elsif !workspace.first.nil?
      config[:workspace] = workspace.first
    end
  end

  return if config[:workspace].to_s.length > 0

  if config[:workspace].to_s.length == 0 and config[:project].to_s.length == 0
    project = Dir["./*.xcodeproj"]
    if project.count > 1
      puts "Select Project: "
      config[:project] = choose(*project)
    elsif !project.first.nil?
      config[:project] = project.first
    end
  end

  if config[:workspace].nil? and config[:project].nil?
    select_project(config)
  end
end
new(options, xcodebuild_list_silent: false, xcodebuild_suppress_stderr: false) click to toggle source
# File lib/fastlane_core/project.rb, line 76
def initialize(options, xcodebuild_list_silent: false, xcodebuild_suppress_stderr: false)
  self.options = options
  self.path = File.expand_path(options[:workspace] || options[:project])
  self.is_workspace = (options[:workspace].to_s.length > 0)
  self.xcodebuild_list_silent = xcodebuild_list_silent
  self.xcodebuild_suppress_stderr = xcodebuild_suppress_stderr

  if !path or !File.directory?(path)
    UI.user_error!("Could not find project at path '#{path}'")
  end
end
run_command(command, timeout: 0, retries: 0, print: true) click to toggle source

@internal to module runs the specified command with the specified number of retries, killing each run if it times out @raises Timeout::Error if all tries result in a timeout @returns the output of the command Note: - currently affected by github.com/fastlane/fastlane/issues/1504

- retry feature added to solve https://github.com/fastlane/fastlane/issues/4059
# File lib/fastlane_core/project.rb, line 404
def self.run_command(command, timeout: 0, retries: 0, print: true)
  require 'timeout'

  UI.command(command) if print

  result = ''

  total_tries = retries + 1
  try = 1
  begin
    Timeout.timeout(timeout) do
      # Using Helper.backticks didn't work here. `Timeout` doesn't time out, and the command hangs forever
      result = `#{command}`.to_s
    end
  rescue Timeout::Error
    try_limit_reached = try >= total_tries

    message = "Command timed out after #{timeout} seconds on try #{try} of #{total_tries}"
    message += ", trying again..." unless try_limit_reached

    UI.important(message)

    raise if try_limit_reached

    try += 1
    retry
  end

  return result
end
select_project(config) click to toggle source
# File lib/fastlane_core/project.rb, line 40
def select_project(config)
  loop do
    path = UI.input("Couldn't automatically detect the project file, please provide a path: ")
    if File.directory? path
      if path.end_with? ".xcworkspace"
        config[:workspace] = path
        break
      elsif path.end_with? ".xcodeproj"
        config[:project] = path
        break
      else
        UI.error("Path must end with either .xcworkspace or .xcodeproj")
      end
    else
      UI.error("Couldn't find project at path '#{File.expand_path(path)}'")
    end
  end
end
xcode_build_settings_retries() click to toggle source

@internal to module

# File lib/fastlane_core/project.rb, line 394
def self.xcode_build_settings_retries
  (ENV['FASTLANE_XCODEBUILD_SETTINGS_RETRIES'] || 3).to_i
end
xcode_build_settings_timeout() click to toggle source

@internal to module

# File lib/fastlane_core/project.rb, line 389
def self.xcode_build_settings_timeout
  (ENV['FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT'] || 10).to_i
end
xcode_list_retries() click to toggle source

@internal to module

# File lib/fastlane_core/project.rb, line 384
def self.xcode_list_retries
  (ENV['FASTLANE_XCODE_LIST_RETRIES'] || 3).to_i
end
xcode_list_timeout() click to toggle source

@internal to module

# File lib/fastlane_core/project.rb, line 379
def self.xcode_list_timeout
  (ENV['FASTLANE_XCODE_LIST_TIMEOUT'] || 10).to_i
end

Public Instance Methods

app_name() click to toggle source
# File lib/fastlane_core/project.rb, line 166
def app_name
  # WRAPPER_NAME: Example.app
  # WRAPPER_SUFFIX: .app
  name = build_settings(key: "WRAPPER_NAME")

  return name.gsub(build_settings(key: "WRAPPER_SUFFIX"), "") if name
  return "App" # default value
end
application?() click to toggle source
# File lib/fastlane_core/project.rb, line 191
def application?
  (build_settings(key: "PRODUCT_TYPE") == "com.apple.product-type.application")
end
build_settings(key: nil, optional: true) click to toggle source

Get the build settings for our project this is used to properly get the DerivedData folder @param [String] The key of which we want the value for (e.g. “PRODUCT_NAME”)

# File lib/fastlane_core/project.rb, line 282
def build_settings(key: nil, optional: true)
  unless @build_settings
    command = build_xcodebuild_showbuildsettings_command

    # xcode might hang here and retrying fixes the problem, see fastlane#4059
    begin
      timeout = FastlaneCore::Project.xcode_build_settings_timeout
      retries = FastlaneCore::Project.xcode_build_settings_retries
      @build_settings = FastlaneCore::Project.run_command(command, timeout: timeout, retries: retries, print: !self.xcodebuild_list_silent)
    rescue Timeout::Error
      UI.crash!("xcodebuild -showBuildSettings timed-out after #{timeout} seconds and #{retries} retries." \
        " You can override the timeout value with the environment variable FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT," \
        " and the number of retries with the environment variable FASTLANE_XCODEBUILD_SETTINGS_RETRIES ")
    end
  end

  begin
    result = @build_settings.split("\n").find do |c|
      sp = c.split(" = ")
      next if sp.length == 0
      sp.first.strip == key
    end
    return result.split(" = ").last
  rescue => ex
    return nil if optional # an optional value, we really don't care if something goes wrong

    UI.error(caller.join("\n\t"))
    UI.error("Could not fetch #{key} from project file: #{ex}")
  end

  nil
end
build_xcodebuild_list_command() click to toggle source
# File lib/fastlane_core/project.rb, line 321
def build_xcodebuild_list_command
  # Unfortunately since we pass the workspace we also get all the
  # schemes generated by CocoaPods
  options = xcodebuild_parameters.delete_if { |a| a.to_s.include? "scheme" }
  command = "xcodebuild -list #{options.join(' ')}"
  command += " 2> /dev/null" if xcodebuild_suppress_stderr
  command
end
build_xcodebuild_showbuildsettings_command() click to toggle source

@!group Raw Access

# File lib/fastlane_core/project.rb, line 269
def build_xcodebuild_showbuildsettings_command
  # We also need to pass the workspace and scheme to this command.
  #
  # The 'clean' portion of this command is a workaround for an xcodebuild bug with Core Data projects.
  # See: https://github.com/fastlane/fastlane/pull/5626
  command = "xcodebuild clean -showBuildSettings #{xcodebuild_parameters.join(' ')}"
  command += " 2> /dev/null" if xcodebuild_suppress_stderr
  command
end
command_line_tool?() click to toggle source
# File lib/fastlane_core/project.rb, line 227
def command_line_tool?
  (build_settings(key: "PRODUCT_TYPE") == "com.apple.product-type.tool")
end
configurations() click to toggle source

Get all available configurations in an array

# File lib/fastlane_core/project.rb, line 148
def configurations
  parsed_info.configurations
end
default_app_identifier() click to toggle source

Returns bundle_id and sets the scheme for xcrun

# File lib/fastlane_core/project.rb, line 153
def default_app_identifier
  default_build_settings(key: "PRODUCT_BUNDLE_IDENTIFIER")
end
default_app_name() click to toggle source

Returns app name and sets the scheme for xcrun

# File lib/fastlane_core/project.rb, line 158
def default_app_name
  if is_workspace
    return default_build_settings(key: "PRODUCT_NAME")
  else
    return app_name
  end
end
default_build_settings(key: nil, optional: true) click to toggle source

Returns the build settings and sets the default scheme to the options hash

# File lib/fastlane_core/project.rb, line 316
def default_build_settings(key: nil, optional: true)
  options[:scheme] = schemes.first if is_workspace
  build_settings(key: key, optional: optional)
end
dynamic_library?() click to toggle source
# File lib/fastlane_core/project.rb, line 175
def dynamic_library?
  (build_settings(key: "PRODUCT_TYPE") == "com.apple.product-type.library.dynamic")
end
framework?() click to toggle source
# File lib/fastlane_core/project.rb, line 187
def framework?
  (build_settings(key: "PRODUCT_TYPE") == "com.apple.product-type.framework")
end
ios?() click to toggle source
# File lib/fastlane_core/project.rb, line 239
def ios?
  supported_platforms.include?(:iOS)
end
ios_app?() click to toggle source
# File lib/fastlane_core/project.rb, line 207
def ios_app?
  (application? && build_settings(key: "PLATFORM_NAME") == "iphoneos")
end
ios_framework?() click to toggle source
# File lib/fastlane_core/project.rb, line 203
def ios_framework?
  (framework? && build_settings(key: "PLATFORM_NAME") == "iphoneos")
end
ios_library?() click to toggle source
# File lib/fastlane_core/project.rb, line 195
def ios_library?
  ((static_library? or dynamic_library?) && build_settings(key: "PLATFORM_NAME") == "iphoneos")
end
ios_tvos_app?() click to toggle source
# File lib/fastlane_core/project.rb, line 199
def ios_tvos_app?
  (ios? || tvos?)
end
library?() click to toggle source
# File lib/fastlane_core/project.rb, line 183
def library?
  (static_library? || dynamic_library?)
end
mac?() click to toggle source
# File lib/fastlane_core/project.rb, line 231
def mac?
  supported_platforms.include?(:macOS)
end
mac_app?() click to toggle source
# File lib/fastlane_core/project.rb, line 215
def mac_app?
  (application? && build_settings(key: "PLATFORM_NAME") == "macosx")
end
mac_framework?() click to toggle source
# File lib/fastlane_core/project.rb, line 223
def mac_framework?
  (framework? && build_settings(key: "PLATFORM_NAME") == "macosx")
end
mac_library?() click to toggle source
# File lib/fastlane_core/project.rb, line 219
def mac_library?
  ((dynamic_library? or static_library?) && build_settings(key: "PLATFORM_NAME") == "macosx")
end
produces_archive?() click to toggle source
# File lib/fastlane_core/project.rb, line 211
def produces_archive?
  !(framework? || static_library? || dynamic_library?)
end
project_name() click to toggle source
# File lib/fastlane_core/project.rb, line 92
def project_name
  if is_workspace
    return File.basename(options[:workspace], ".xcworkspace")
  else
    return File.basename(options[:project], ".xcodeproj")
  end
end
raw_info(silent: false) click to toggle source
# File lib/fastlane_core/project.rb, line 330
def raw_info(silent: false)
  # Examples:

  # Standard:
  #
  # Information about project "Example":
  #     Targets:
  #         Example
  #         ExampleUITests
  #
  #     Build Configurations:
  #         Debug
  #         Release
  #
  #     If no build configuration is specified and -scheme is not passed then "Release" is used.
  #
  #     Schemes:
  #         Example
  #         ExampleUITests

  # CococaPods
  #
  # Example.xcworkspace
  # Information about workspace "Example":
  #     Schemes:
  #         Example
  #         HexColors
  #         Pods-Example

  return @raw if @raw

  command = build_xcodebuild_list_command

  # xcode >= 6 might hang here if the user schemes are missing
  begin
    timeout = FastlaneCore::Project.xcode_list_timeout
    retries = FastlaneCore::Project.xcode_list_retries
    @raw = FastlaneCore::Project.run_command(command, timeout: timeout, retries: retries, print: !silent)
  rescue Timeout::Error
    UI.user_error!("xcodebuild -list timed-out after #{timeout * retries} seconds. You might need to recreate the user schemes." \
      " You can override the timeout value with the environment variable FASTLANE_XCODE_LIST_TIMEOUT")
  end

  UI.user_error!("Error parsing xcode file using `#{command}`") if @raw.length == 0

  return @raw
end
schemes() click to toggle source

Get all available schemes in an array

# File lib/fastlane_core/project.rb, line 101
def schemes
  parsed_info.schemes
end
select_scheme(preferred_to_include: nil) click to toggle source

Let the user select a scheme Use a scheme containing the preferred_to_include string when multiple schemes were found

# File lib/fastlane_core/project.rb, line 107
def select_scheme(preferred_to_include: nil)
  if options[:scheme].to_s.length > 0
    # Verify the scheme is available
    unless schemes.include?(options[:scheme].to_s)
      UI.error("Couldn't find specified scheme '#{options[:scheme]}'.")
      options[:scheme] = nil
    end
  end

  return if options[:scheme].to_s.length > 0

  if schemes.count == 1
    options[:scheme] = schemes.last
  elsif schemes.count > 1
    preferred = nil
    if preferred_to_include
      preferred = schemes.find_all { |a| a.downcase.include?(preferred_to_include.downcase) }
    end

    if preferred_to_include and preferred.count == 1
      options[:scheme] = preferred.last
    elsif automated_scheme_selection? && schemes.include?(project_name)
      UI.important("Using scheme matching project name (#{project_name}).")
      options[:scheme] = project_name
    elsif Helper.is_ci?
      UI.error("Multiple schemes found but you haven't specified one.")
      UI.error("Since this is a CI, please pass one using the `scheme` option")
      UI.user_error!("Multiple schemes found")
    else
      puts "Select Scheme: "
      options[:scheme] = choose(*schemes)
    end
  else
    UI.error("Couldn't find any schemes in this project, make sure that the scheme is shared if you are using a workspace")
    UI.error("Open Xcode, click on `Manage Schemes` and check the `Shared` box for the schemes you want to use")

    UI.user_error!("No Schemes found")
  end
end
static_library?() click to toggle source
# File lib/fastlane_core/project.rb, line 179
def static_library?
  (build_settings(key: "PRODUCT_TYPE") == "com.apple.product-type.library.static")
end
supported_platforms() click to toggle source
# File lib/fastlane_core/project.rb, line 243
def supported_platforms
  supported_platforms = build_settings(key: "SUPPORTED_PLATFORMS").split
  supported_platforms.map do |platform|
    case platform
    when "macosx" then :macOS
    when "iphonesimulator", "iphoneos" then :iOS
    when "watchsimulator", "watchos" then :watchOS
    when "appletvsimulator", "appletvos" then :tvOS
    end
  end.uniq.compact
end
tvos?() click to toggle source
# File lib/fastlane_core/project.rb, line 235
def tvos?
  supported_platforms.include?(:tvOS)
end
workspace?() click to toggle source
# File lib/fastlane_core/project.rb, line 88
def workspace?
  self.is_workspace
end
xcodebuild_parameters() click to toggle source
# File lib/fastlane_core/project.rb, line 255
def xcodebuild_parameters
  proj = []
  proj << "-workspace #{options[:workspace].shellescape}" if options[:workspace]
  proj << "-scheme #{options[:scheme].shellescape}" if options[:scheme]
  proj << "-project #{options[:project].shellescape}" if options[:project]
  proj << "-configuration #{options[:configuration].shellescape}" if options[:configuration]

  return proj
end

Private Instance Methods

automated_scheme_selection?() click to toggle source

If scheme not specified, do we want the scheme matching project name?

# File lib/fastlane_core/project.rb, line 446
def automated_scheme_selection?
  FastlaneCore::Env.truthy?("AUTOMATED_SCHEME_SELECTION")
end
parsed_info() click to toggle source
# File lib/fastlane_core/project.rb, line 437
def parsed_info
  unless @parsed_info
    @parsed_info = FastlaneCore::XcodebuildListOutputParser.new(raw_info(silent: xcodebuild_list_silent))
  end
  @parsed_info
end