class Fastlane::Setup

Attributes

appfile_content[RW]

Appfile

fastfile_content[RW]

The current content of the generated Fastfile

had_multiple_projects_to_choose_from[RW]

remember if there were multiple projects if so, we set it as part of the Fastfile

is_swift_fastfile[RW]

Is the current `setup` using a swift based configuration file

lane_to_mention[RW]

This is the lane that we tell the user to run to try the new fastlane setup This needs to be setup by each setup

platform[RW]

:ios or :android

preferred_setup_method[RW]

Used for :manual sometimes

project_path[RW]

Path to the xcodeproj or xcworkspace

user[RW]

For iOS projects that's the Apple ID email

Public Class Methods

new(is_swift_fastfile: nil, user: nil, project_path: nil, had_multiple_projects_to_choose_from: nil, preferred_setup_method: nil) click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 152
def initialize(is_swift_fastfile: nil, user: nil, project_path: nil, had_multiple_projects_to_choose_from: nil, preferred_setup_method: nil)
  self.is_swift_fastfile = is_swift_fastfile
  self.user = user
  self.project_path = project_path
  self.had_multiple_projects_to_choose_from = had_multiple_projects_to_choose_from
  self.preferred_setup_method = preferred_setup_method
end
setup_swift_support() click to toggle source

rubocop:enable Metrics/BlockNesting

# File fastlane/lib/fastlane/setup/setup.rb, line 134
def self.setup_swift_support
  runner_source_resources = "#{Fastlane::ROOT}/swift/."
  destination_path = File.expand_path('swift', FastlaneCore::FastlaneFolder.path)

  # Return eearly if already setup
  return if File.exist?(destination_path)

  # Show message if Fastfile.swift exists but missing Swift classes and Xcode project
  if FastlaneCore::FastlaneFolder.swift?
    UI.important("Restoring Swift classes and FastlaneSwiftRunner.xcodeproj...")
  end

  FileUtils.cp_r(runner_source_resources, destination_path)
  UI.success("Copied Swift fastlane runner project to '#{destination_path}'.")

  Fastlane::SwiftLaneManager.first_time_setup
end
start(user: nil, is_swift_fastfile: false) click to toggle source

Start the setup process rubocop:disable Metrics/BlockNesting

# File fastlane/lib/fastlane/setup/setup.rb, line 36
def self.start(user: nil, is_swift_fastfile: false)
  if FastlaneCore::FastlaneFolder.setup? && !Helper.test?

    # If Fastfile.swift exists, but the swift sources folder does not, rebuild it
    setup_swift_support if is_swift_fastfile

    require 'fastlane/lane_list'
    Fastlane::LaneList.output(FastlaneCore::FastlaneFolder.fastfile_path)
    UI.important("------------------")
    UI.important("fastlane is already set up at path `#{FastlaneCore::FastlaneFolder.path}`, see the available lanes above")
    UI.message("")

    setup_ios = self.new
    setup_ios.add_or_update_gemfile(update_gemfile_if_needed: false)
    setup_ios.suggest_next_steps
    return
  end

  # this is used by e.g. configuration.rb to not show warnings when running produce
  ENV["FASTLANE_ONBOARDING_IN_PROCESS"] = 1.to_s

  spinner = TTY::Spinner.new("[:spinner] Looking for iOS and Android projects in current directory...", format: :dots)
  spinner.auto_spin

  ios_projects = Dir["**/*.xcodeproj"] + Dir["**/*.xcworkspace"]
  ios_projects.delete_if do |path|
    Gem.path.any? { |gem_path| File.expand_path(path).start_with?(gem_path) }
  end
  ios_projects.delete_if { |path| path.match("fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj") }
  android_projects = Dir["**/*.gradle"] + Dir["**/*.gradle.kts"]

  spinner.success

  FastlaneCore::FastlaneFolder.create_folder!

  # Currently we prefer iOS app projects, as the `init` process is
  # more intelligent and does more things. The user can easily add
  # the `:android` platform to the resulting Fastfile
  if ios_projects.count > 0
    current_directory = ios_projects.find_all do |current_project_path|
      current_project_path.split(File::Separator).count == 1
    end
    chosen_project = nil
    had_multiple_projects_to_choose_from = false

    if current_directory.count == 1
      chosen_project = current_directory.first
    elsif current_directory.count > 1
      if current_directory.count == 2
        # This is a common case (e.g. with CocoaPods), where the project has an xcodeproj and an xcworkspace file
        extensions = [File.extname(current_directory[0]), File.extname(current_directory[1])]
        if extensions.sort == [".xcodeproj", ".xcworkspace"].sort
          # Yep, that's this kind of setup
          chosen_project = current_directory.find { |d| d.end_with?(".xcworkspace") }
        end
      end
      chosen_project ||= UI.select("Multiple iOS projects found in current directory", current_directory)
      had_multiple_projects_to_choose_from = true
    else
      UI.error("It looks like there is no iOS project in the current directory, though we did find one in a sub-directory")
      UI.error("Please `cd` into the directory of the intended Xcode project you wish to use.")
      UI.user_error!("Please `cd` into the directory of the intended Xcode project you wish to use and run `fastlane init` again")
    end

    if chosen_project == "Pods.xcodeproj"
      unless UI.confirm("Found '#{chosen_project}', which usually isn't normally what you want. Make sure to switch to the directory containing your intended Xcode project. Would you still like to continue with #{chosen_project}?")
        UI.user_error!("Make sure to `cd` into the directory containing the Xcode project you intend to use and then use `fastlane init` again")
      end
    end
    UI.message("Detected an iOS/macOS project in the current directory: '#{chosen_project}'")

    SetupIos.new(
      is_swift_fastfile: is_swift_fastfile,
      user: user,
      project_path: chosen_project,
      had_multiple_projects_to_choose_from: had_multiple_projects_to_choose_from
    ).setup_ios
  elsif android_projects.count > 0
    UI.message("Detected an Android project in the current directory...")
    SetupAndroid.new.setup_android
  else
    UI.error("No iOS or Android projects were found in directory '#{Dir.pwd}'")
    UI.error("Make sure to `cd` into the directory containing your iOS or Android app")
    if UI.confirm("Alternatively, would you like to manually setup a fastlane config in the current directory instead?")
      SetupIos.new(
        is_swift_fastfile: is_swift_fastfile,
        user: user,
        project_path: chosen_project,
        had_multiple_projects_to_choose_from: had_multiple_projects_to_choose_from,
        preferred_setup_method: :ios_manual
      ).setup_ios
    else
      UI.user_error!("Make sure to `cd` into the directory containing your project and then use `fastlane init` again")
    end
  end
end

Public Instance Methods

add_or_update_gemfile(update_gemfile_if_needed: false) click to toggle source

This method is responsible for ensuring there is a working Gemfile, and that `fastlane` is defined as a dependency while also having `rubygems` as a gem source

# File fastlane/lib/fastlane/setup/setup.rb, line 272
def add_or_update_gemfile(update_gemfile_if_needed: false)
  if gemfile_exists?
    ensure_gemfile_valid!(update_gemfile_if_needed: update_gemfile_if_needed)
  else
    if update_gemfile_if_needed || UI.confirm("It is recommended to run fastlane with a Gemfile set up, do you want fastlane to create one for you?")
      setup_gemfile!
    end
  end
  return gemfile_path
end
append_lane(lane) click to toggle source

Append a lane to the current Fastfile template we're generating

# File fastlane/lib/fastlane/setup/setup.rb, line 168
def append_lane(lane)
  lane.compact! # remove nil values

  new_lines = "\n\n"
  if self.is_swift_fastfile
    new_lines = "" unless self.fastfile_content.include?("lane() {") # the first lane we don't want new lines
    self.fastfile_content.gsub!("[[LANES]]", "#{new_lines}\t#{lane.join("\n\t")}[[LANES]]")
  else
    new_lines = "" unless self.fastfile_content.include?("lane :") # the first lane we don't want new lines
    self.fastfile_content.gsub!("[[LANES]]", "#{new_lines}  #{lane.join("\n  ")}[[LANES]]")
  end
end
append_team(team) click to toggle source

Append a team to the Appfile

# File fastlane/lib/fastlane/setup/setup.rb, line 182
def append_team(team)
  self.appfile_content.gsub!("[[TEAMS]]", "#{team}\n[[TEAMS]]")
end
appfile_template_content() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 301
def appfile_template_content
  if self.platform == :ios
    if self.is_swift_fastfile
      path = "#{Fastlane::ROOT}/lib/assets/AppfileTemplate.swift"
    else
      path = "#{Fastlane::ROOT}/lib/assets/AppfileTemplate"
    end
  else
    path = "#{Fastlane::ROOT}/lib/assets/AppfileTemplateAndroid"
  end

  return File.read(path)
end
continue_with_enter() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 329
def continue_with_enter
  UI.input("Continue by pressing Enter ⏎")
end
ensure_gemfile_valid!(update_gemfile_if_needed: false) click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 245
def ensure_gemfile_valid!(update_gemfile_if_needed: false)
  gemfile_content = File.read(gemfile_path)
  unless gemfile_content.include?("https://rubygems.org")
    UI.error("You have a local Gemfile, but RubyGems isn't defined as source")
    UI.error("Please update your Gemfile at path `#{gemfile_path}` to include")
    UI.important("")
    UI.important("source \"https://rubygems.org\"")
    UI.important("")
    UI.error("Update your Gemfile, and run `bundle update` afterwards")
  end

  unless gemfile_content.include?("fastlane")
    if update_gemfile_if_needed
      gemfile_content << "\n\ngem \"fastlane\""
      UI.message("Adding `fastlane` to your existing Gemfile at path '#{gemfile_path}'")

      File.write(gemfile_path, gemfile_content)
    else
      UI.error("You have a local Gemfile, but it doesn't include \"fastlane\" as a dependency")
      UI.error("Please add `gem \"fastlane\"` to your Gemfile")
    end
  end
end
explain_concepts() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 315
def explain_concepts
  UI.header("fastlane lanes")
  UI.message("fastlane uses a " + "`Fastfile`".yellow + " to store the automation configuration")
  UI.message("Within that, you'll see different " + "lanes".yellow + ".")
  UI.message("Each is there to automate a different task, like screenshots, code signing, or pushing new releases")
  continue_with_enter

  UI.header("How to customize your Fastfile")
  UI.message("Use a text editor of your choice to open the newly created Fastfile and take a look")
  UI.message("You can now edit the available lanes and actions to customize the setup to fit your needs")
  UI.message("To get a list of all the available actions, open " + "https://docs.fastlane.tools/actions".cyan)
  continue_with_enter
end
fastfile_template_content() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 291
def fastfile_template_content
  if self.is_swift_fastfile
    path = "#{Fastlane::ROOT}/lib/assets/DefaultFastfileTemplate.swift"
  else
    path = "#{Fastlane::ROOT}/lib/assets/DefaultFastfileTemplate"
  end

  return File.read(path)
end
finish_up() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 283
def finish_up
  write_fastfile!
  self.class.setup_swift_support if is_swift_fastfile
  show_analytics_note
  explain_concepts
  suggest_next_steps
end
gemfile_exists?() click to toggle source

Gemfile related code:

# File fastlane/lib/fastlane/setup/setup.rb, line 219
def gemfile_exists?
  return File.exist?(gemfile_path)
end
gemfile_path() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 214
def gemfile_path
  "Gemfile"
end
setup_gemfile!() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 223
def setup_gemfile!
  # No Gemfile yet
  gemfile_content = []
  gemfile_content << "source \"https://rubygems.org\""
  gemfile_content << ""
  gemfile_content << 'gem "fastlane"'
  gemfile_content << ""
  File.write(gemfile_path, gemfile_content.join("\n"))

  UI.message("Installing dependencies for you...")
  FastlaneCore::CommandExecutor.execute(
    command: "bundle update",
    print_all: FastlaneCore::Globals.verbose?,
    print_command: true,
    error: proc do |error_output|
      UI.error("Something went wrong when running `bundle update` for you")
      UI.error("Please take a look at your Gemfile at path `#{gemfile_path}`")
      UI.error("and make sure you can run `bundle update` on your machine.")
    end
  )
end
show_analytics_note() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 360
def show_analytics_note
  UI.message("fastlane will collect the number of errors for each action to detect integration issues")
  UI.message("No sensitive/private information will be uploaded, more information: " + "https://docs.fastlane.tools/#metrics".cyan)
end
suggest_next_steps() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 333
def suggest_next_steps
  UI.header("Where to go from here?")
  if self.platform == :android
    UI.message("📸  Learn more about how to automatically generate localized Google Play screenshots:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/android/screenshots/".cyan)
    UI.message("👩‍✈️  Learn more about distribution to beta testing services:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/android/beta-deployment/".cyan)
    UI.message("🚀  Learn more about how to automate the Google Play release process:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/android/release-deployment/".cyan)
  else
    UI.message("📸  Learn more about how to automatically generate localized App Store screenshots:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/ios/screenshots/".cyan)
    UI.message("👩‍✈️  Learn more about distribution to beta testing services:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/ios/beta-deployment/".cyan)
    UI.message("🚀  Learn more about how to automate the App Store release process:")
    UI.message("\t\thttps://docs.fastlane.tools/getting-started/ios/appstore-deployment/".cyan)
    UI.message("👩‍⚕️  Learn more about how to setup code signing with fastlane")
    UI.message("\t\thttps://docs.fastlane.tools/codesigning/getting-started/".cyan)
  end

  # we crash here, so that this never happens when a new setup method is added
  return if self.lane_to_mention.to_s.length == 0
  UI.message("")
  UI.message("To try your new fastlane setup, just enter and run")
  UI.command("fastlane #{self.lane_to_mention}")
end
welcome_to_fastlane() click to toggle source

Helpers

# File fastlane/lib/fastlane/setup/setup.rb, line 161
def welcome_to_fastlane
  UI.header("Welcome to fastlane 🚀")
  UI.message("fastlane can help you with all kinds of automation for your mobile app")
  UI.message("We recommend automating one task first, and then gradually automating more over time")
end
write_fastfile!() click to toggle source
# File fastlane/lib/fastlane/setup/setup.rb, line 186
def write_fastfile!
  # Write the Fastfile
  fastfile_file_name = "Fastfile"
  fastfile_file_name += ".swift" if self.is_swift_fastfile

  fastfile_path = File.join(FastlaneCore::FastlaneFolder.path, fastfile_file_name)
  self.fastfile_content.gsub!("[[LANES]]", "") # since we always keep it until writing out
  File.write(fastfile_path, self.fastfile_content) # remove trailing spaces before platform ends

  appfile_file_name = "Appfile"
  appfile_file_name += ".swift" if self.is_swift_fastfile
  appfile_path = File.join(FastlaneCore::FastlaneFolder.path, appfile_file_name)
  self.appfile_content.gsub!("[[TEAMS]]", "")

  File.write(appfile_path, self.appfile_content)

  add_or_update_gemfile(update_gemfile_if_needed: true)

  UI.header("✅  Successfully generated fastlane configuration")
  UI.message("Generated Fastfile at path `#{fastfile_path}`")
  UI.message("Generated Appfile at path `#{appfile_path}`")
  UI.message("Gemfile and Gemfile.lock at path `#{gemfile_path}`")

  UI.message("Please check the newly generated configuration files into git along with your project")
  UI.message("This way everyone in your team can benefit from your fastlane setup")
  continue_with_enter
end