fastlane_version "2.215.0" default_platform :ios platform :ios do before_all do installKeychainsIfPossible installProfiles xcodes( version: ENV["XCODE_VERSION"] || '15.1', select_for_current_build_only: true ) if !ENV["SKIP_COCOAPODS"] cocoapods(silent: true, try_repo_update_on_error: true) end custom_before_all end after_all do |lanes, options| cleanup(options: options) removeKeychainsIfPossible custom_after_all(lanes: lanes, options: options) end error do |lanes, options| cleanup(options: options) removeKeychainsIfPossible custom_error(lanes: lanes, options: options) end desc "Overridable lane called at the end of before_all." lane :custom_before_all do # Left empty for override. end desc "Overridable lane called at the end of after_all." lane :custom_after_all do |lanes, options| # Left empty for override. end desc "Overridable lane called at the end of error." lane :custom_error do |lanes, options| # Left empty for override. end desc "Runs all the tests." lane :runTests do |options| options[:xcargs] ||= ENV["SCAN_XCARGS"] ||= "-skipMacroValidation" scan( output_types: "junit", device: ENV["DEVICE"] || 'iPhone 15 Pro', xcargs: options[:xcargs] ) end desc "Build the archive and ipa with options (configuration (Release), include_bitcode (false), export_method (enterprise)), export_options ({})." lane :build do |options| options[:configuration] ||= ENV["GYM_CONFIGURATION"] ||= "Release" options[:include_bitcode] = false if options[:include_bitcode].nil? options[:export_method] ||= ENV["GYM_EXPORT_METHOD"] ||= "enterprise" options[:export_options] ||= {} options[:output_name] ||= nil options[:xcargs] ||= ENV["GYM_XCARGS"] ||= "-skipMacroValidation" gym( configuration: options[:configuration], include_bitcode: options[:include_bitcode], export_method: options[:export_method], export_options: options[:export_options], output_name: options[:output_name], xcargs: options[:xcargs] ) end desc "Build and upload to Firebase with (configuration (Release)), (include_bitcode (false)), (export_method (enterprise)), (export_options ({})) and (group)." desc "#### Options" desc " * **`groups`**: Firebase distribution groups to notify of release. Required. (Default: `nil`)" desc " * **`configuration`**: Build configuration to use. (Default: `ENV[\"GYM_CONFIGURATION\"]` or `Release`)" desc " * **`include_bitcode`**: Whether or not to build with bitcode enabled. (Default: `false`)" desc " * **`export_method`**: Export method to use. See `fastlane action gym` for options. (Default: `ENV[\"GYM_EXPORT_METHOD\"]` or `enterprise`)" desc " * **`export_options`**: Custom export options to use (Default: `{}`)" desc " * **`changelog_type`**: Type of changelog to generate, if any. Possible values are jenkins, pr, git, or none. (Default: `none`)" desc " * **`release_notes`**: Release notes for the release, if not using a changelog_type. (Default: `nil`)" desc " * **`upload_symbols`**: Whether or not to upload symbols to Firebase. Requires FirebaseCrashlytics pod to be installed. (Default: `true`)" desc "#### Environment Variables" desc " * **`FIREBASE_APP_ID`**: Firebase app ID. Should be passed as a secure text value. Required." desc " * **`FIREBASE_TOKEN`**: Firebase distribution token. Should be passed as a secure text value. Required." lane :beta do |options| options[:configuration] ||= ENV["GYM_CONFIGURATION"] ||= "Release" options[:include_bitcode] = false if options[:include_bitcode].nil? options[:export_method] ||= ENV["GYM_EXPORT_METHOD"] ||= "enterprise" options[:changelog_type] ||= "none" options[:export_options] ||= {} options[:upload_symbols] = true if options[:upload_symbols].nil? build( configuration: options[:configuration], include_bitcode: options[:include_bitcode], export_method: options[:export_method], export_options: options[:export_options] ) uploadToFirebase( changelog_type: options[:changelog_type], release_notes: options[:release_notes], upload_symbols: options[:upload_symbols], groups: options[:groups] ) end desc "Upload a local IPA and dSYM to Firebase App Distribution." desc "#### Options" desc " * **`groups`**: Firebase distribution groups to notify of release. Required. (Default: `nil`)" desc " * **`upload_symbols`**: Whether or not to upload symbols to Firebase. Requires FirebaseCrashlytics pod to be installed. (Default: `true`)" desc " * **`changelog_type`**: The type of changelog to generate (e.g. `\"git\"`, `\"jenkins\"`, `\"pr\"`, \"none\") (Default: `\"none\"`)" desc " * **`release_notes`**: A string to set as the release notes; overrides any generated changelog. (Default: `nil`)" desc "#### Environment Variables" desc " * **`FIREBASE_APP_ID`**: Firebase app ID. Should be passed as a secure text value. Required." desc " * **`FIREBASE_TOKEN`**: Firebase distribution token. Should be passed as a secure text value. Required." lane :uploadToFirebase do |options| options[:changelog_type] ||= "none" options[:upload_symbols] = true if options[:upload_symbols].nil? options[:app_id] ||= ENV["FIREBASE_APP_ID"] options[:firebase_cli_token] ||= ENV["FIREBASE_TOKEN"] raise "uploadToFirebase: An app ID must be passed as an option (e.g. `\"1:123456789:ios:abcd1234\"`)." unless options[:app_id] raise "uploadToFirebase: A tester group must be passed as an option." unless options[:groups] raise "uploadToFirebase: A CLI token must be provided, either through the firebase_cli_token parameter or FIREBASE_TOKEN environment variable." unless options[:firebase_cli_token] unless options[:release_notes] case options[:changelog_type] when "jenkins" make_changelog_from_jenkins when "pr" raise "uploadToFirebase: Environment variable for PR ID is not set (ghprbPullId)." unless ENV.key?("ghprbPullId") options[:release_notes] = "PR: ##{ENV["ghprbPullId"]}\nBranch: (#{ENV["GIT_BRANCH"]})\nPR Link: #{ENV["ghprbPullLink"]}" when "git" changelog_from_git_commits when "none" UI.message("uploadToFirebase called with a changelog_type of none.") else raise "Unrecognized changelog_type: #{options[:changelog_type]}" end end firebase_app_distribution( firebase_cli_token: options[:firebase_cli_token], app: options[:app_id], release_notes: options[:release_notes], groups: options[:groups] ) if options[:upload_symbols] upload_symbols_to_crashlytics( app_id: options[:app_id] ) end end desc "Create and tag a GitHub release and attach any built IPA and dSYM to the release. This will be performed on the current branch." desc "Adds the IPA and dSYM files using the IPA_OUTPUT_PATH and DSYM_OUTPUT_PATH from earlier builds, like gym. Customize those outputs if you want custom file names." desc "#### Options" desc " * **`repository_name`**: Name of the repo for the release. (Default: `GITHUB_REPO` environment variable.)" desc " * **`name`**: Name for the release. (Default: `v`)" desc " * **`description`**: Description for the release. (Default: `v, build: `)" desc " * **`tag_name`**: Tag name for the release. (Default: `v`)" desc "#### Environment Variables" desc " * **`GITHUB_API_TOKEN`**: GitHub API token. Should be passed as a secure text value. Required." lane :createGitHubRelease do |options| options[:repository_name] ||= ENV["GITHUB_REPO"] options[:api_token] ||= ENV["GITHUB_API_TOKEN"] raise "A respository_name is required. Provide the GITHUB_REPO environment variable or a value directly." unless options[:repository_name] raise "An api_token is required. Provide the GITHUB_API_TOKEN environment variable from secure storage." unless options[:api_token] unless lane_context[SharedValues::VERSION_NUMBER] get_version_number end unless lane_context[SharedValues::BUILD_NUMBER] get_build_number end options[:name] ||= "v#{lane_context[SharedValues::VERSION_NUMBER]}" options[:description] ||= "v#{lane_context[SharedValues::VERSION_NUMBER]}, build: #{lane_context[SharedValues::BUILD_NUMBER]}" options[:tag_name] ||= "v#{lane_context[SharedValues::VERSION_NUMBER]}" full_branch = git_branch branch = full_branch.sub('origin/', '') set_github_release( repository_name: options[:repository_name], api_token: options[:api_token], name: options[:name], description: options[:description], tag_name: options[:tag_name], commitish: branch, upload_assets: ["#{lane_context[SharedValues::IPA_OUTPUT_PATH]}", "#{lane_context[SharedValues::DSYM_OUTPUT_PATH]}"] ) end desc "Sets the build number to the given value or, if none is provided, automatically sets the build number according to the strategy provided." lane :incrementBuildNumber do |options| options[:strategy] ||= "default" if options.key?(:build) increment_build_number(build_number: options[:build]) else case options[:strategy] when "commits" setBuildNumberToCommitCount when "timestamp" setBuildNumberToTimestamp when "default" increment_build_number else raise "Unrecognized incrementBuildNumber strategy: #{options[:strategy]}" end end end desc "Sets the build number to the current commit count." lane :setBuildNumberToCommitCount do increment_build_number(build_number: number_of_commits) end desc "Set the build number to a Unix timestamp." lane :setBuildNumberToTimestamp do increment_build_number(build_number: "#{Time.now.to_i}") end desc "Deletes the archive generated by gym." lane :cleanupArchive do |options| if ("#{lane_context[SharedValues::XCODEBUILD_ARCHIVE]}" != "") && !(options[:keepArchive] || false) sh "rm -r \"#{File.dirname(lane_context[SharedValues::XCODEBUILD_ARCHIVE])}\" || :" end end desc "Shuts down and kills the simulator and background process." lane :cleanupSimulator do # attempt to quit simulators nicely sh "xcrun simctl shutdown booted" # kill simulator sh "killall Simulator || :" # kill SimulatorService sh "killall -9 com.apple.CoreSimulator.CoreSimulatorService || :" end desc "Cleanup simulator and build archives." lane :cleanup do |options| cleanupSimulator cleanupArchive(options: options) end end desc "Installs any provisioning profiles in $PROFILE_DIRECTORY, relative to Fastlane's execution path." lane :installProfiles do raise "installProfiles: PROFILE_DIRECTORY environment variable required." unless ENV["PROFILE_DIRECTORY"] Dir.glob("#{ENV["PROFILE_DIRECTORY"]}/*.mobileprovision") do |profile| FastlaneCore::ProvisioningProfile.install(profile) end end desc "Updates project dependencies in Bundler and CocoaPods, then sends a pull request if there are changes" desc "#### Options" desc " * **`branchname`**: Accepts an optional value to set the dependency updater branch. If no value is passed in, it will use the UPDATE_DEPENDENCIES_BRANCH environment variable, or default to jenkins-update-dependencies if it doesn't exit." desc " * **`base`**: Accepts an optional value to set the base branch for the created dependency update PR. If no value is passed in, it will default to main." desc " * **`pullRequestTitle`**: Accepts an optional value to set the title for the created dependency update PR. If no value is passed in, it will default to Update Dependencies." desc " * **`xcodeResolvedFilePath`**: Path to the Package.resolved file from Xcode for any Xcode installed Swift packages." lane :updateDependencies do |options| options[:branchname] ||= ENV["UPDATE_DEPENDENCIES_BRANCH"] ||= "jenkins-update-dependencies" options[:base] ||= "main" options[:pullRequestTitle] ||= "Update Dependencies" options[:xcodeResolvedFilePath] ||= ENV["XCODE_RESOLVED_FILE_PATH"] fastlane_require 'fastlane-plugin-git_status' xcodes( version: ENV["XCODE_VERSION"] || '15.1', select_for_current_build_only: true ) bundle_update if File.exist?("../Gemfile.lock") cocoapods_update if File.exist?("../Podfile.lock") updateXcodeSwiftPackages(xcodeResolvedFilePath: options[:xcodeResolvedFilePath]) sendUpdatePullRequest( branchname: options[:branchname], base: options[:base], pullRequestTitle: options[:pullRequestTitle] ) unless git_status.empty? end desc "Updates Xcode Swift packages by deleting the .resolved file and reresolving. Should work barring Xcode not properly finding the latest version." lane :updateXcodeSwiftPackages do |options| xcodeResolvedFilePath = options[:xcodeResolvedFilePath] ||= ENV["XCODE_RESOLVED_FILE_PATH"] if xcodeResolvedFilePath && File.exist?(xcodeResolvedFilePath) UI.message("Removing Xcode Package.resolved at #{ xcodeResolvedFilePath }") File.delete(xcodeResolvedFilePath) UI.message("Updating Xcode packages.") packagePath = "#{ File.expand_path("..", Dir.pwd) }/package_cache" sh "cd ..; xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath #{ packagePath }" FileUtils.remove_dir(packagePath) else UI.message("No Xcode Package.resolved file, ignoring...}") end end desc "Sends a pull request with the current changes to dependencies" lane :sendUpdatePullRequest do |options| options[:branchname] ||= ENV["UPDATE_DEPENDENCIES_BRANCH"] options[:base] ||= "master" options[:pullRequestTitle] ||= "Update Dependencies" fastlane_require 'fastlane-plugin-git_status' branchname = options[:branchname] base = options[:base] create_git_branch( branchname: branchname ) checkout_git_branch( branchname: branchname ) git_add( path: "." ) git_commit( path: ".", message: "Update dependencies" ) push_to_git_remote( remote: "origin", local_branch: branchname, remote_branch: branchname, force: true, tags: false ) create_pull_request( api_token: ENV["GITHUB_TOKEN"], repo: ENV["GITHUB_REPO"], head: branchname, base: base, title: options[:pullRequestTitle], body: "Updated dependencies." ) end desc "Installs the DL and project keychains if provided. This is a transitional API that should be removed once all projects move to keychain injections." lane :installKeychainsIfPossible do if ENV["GITHUB_ACTIONS"] UI.message("Detected GitHub Actions, writing base64 DL keychain to disk.") raise "installKeychainsIfPossible requires DL_KEYCHAIN_BASE64 to be set when running on GitHub Actions" unless ENV["DL_KEYCHAIN_BASE64"] raise "installKeychainsIfPossible requires DL_KEYCHAIN_PATH to be set when running on GitHub Actions" unless ENV["DL_KEYCHAIN_PATH"] sh "echo $DL_KEYCHAIN_BASE64 | base64 --decode > $DL_KEYCHAIN_PATH" end if ENV["DL_KEYCHAIN_PATH"] UI.message("Installing DL keychain...") installDLKeychain end if ENV["PROJECT_KEYCHAIN_PATH"] UI.message("Installing project keychain...") installProjectKeychain end end desc "Removes the DL and project keychains if provided. This is a transitional API that should be removed once all projects move to keychain injections." lane :removeKeychainsIfPossible do if ENV["DL_KEYCHAIN_PATH"] UI.message("Removing DL keychain...") removeDLKeychain end if ENV["PROJECT_KEYCHAIN_PATH"] UI.message("Removing project keychain...") removeProjectKeychain end end desc "Installs the DL keychain. Must provide path (or DL_KEYCHAIN_PATH) and password (or DL_KEYCHAIN_PASSWORD) parameters." lane :installDLKeychain do |options| path = options[:path] ||= ENV["DL_KEYCHAIN_PATH"] password = options[:password] ||= ENV["DL_KEYCHAIN_PASSWORD"] raise "installDLKeychain requires a path parameter or DL_KEYCHAIN_PATH to be set." unless path raise "installDLKeychain requires a password parameter or DL_KEYCHAIN_PASSWORD to be set." unless password installKeychain(path: path, password: password) end desc "Installs the project keychain from a path relative to the root of the project. Must provide path (or PROJECT_KEYCHAIN_PATH), and password (or PROJECT_KEYCHAIN_PASSWORD) parameters." lane :installProjectKeychain do |options| path = options[:path] ||= ENV["PROJECT_KEYCHAIN_PATH"] password = options[:password] ||= ENV["PROJECT_KEYCHAIN_PASSWORD"] raise "installProjectKeychain requires a name paramter or PROJECT_KEYCHAIN_PATH to be set." unless path raise "installProjectKeychain requires a password paramter or PROJECT_KEYCHAIN_PASSWORD to be set." unless password installKeychain(path: path, password: password) end desc "Installs a keychain by first copying it. Requires the keychain file path and password parameters." lane :installKeychain do |options| path = options[:path] password = options[:password] raise "installKeychain requires a path parameter." unless path raise "installKeychain requires a password parameter." unless password name = File.basename(path, File.extname(path)) copy_name = "#{name}Copy.keychain" Dir.chdir("..") do UI.message("Copying keychain named #{name} to #{copy_name}...") FileUtils.cp(path, copy_name) UI.message("Unlocking keychain named #{copy_name}...") unlock_keychain(path: File.join(Dir.pwd, copy_name), password: password) end end desc "Remove the Detroit Labs keychain. Requires the DL keychain path, or for DL_KEYCHAIN_PATH to be set." lane :removeDLKeychain do |options| path = options[:path] ||= ENV["DL_KEYCHAIN_PATH"] raise "removeDLKeychain requires path parameter or DL_KEYCHAIN_PATH to be set." unless path name = File.basename(path, File.extname(path)) removeKeychain(name: name) end desc "Remove the project keychain. Requires the project keychain path, or for PROJECT_KEYCHAIN_PATH to be set." lane :removeProjectKeychain do |options| path = options[:path] ||= ENV["PROJECT_KEYCHAIN_PATH"] raise "removeProjectKeychain requires path parameter or PROJECT_KEYCHAIN_PATH to be set." unless path name = File.basename(path, File.extname(path)) removeKeychain(name: name) end desc "Removes a keychain by name by removing {name}Copy." lane :removeKeychain do |options| name = options[:name] raise "removeKeychain requires a name." unless name copy_name = "#{name}Copy" UI.message("Deleting keychain named #{copy_name}...") delete_keychain(name: copy_name) end desc "Run SwiftFormat from CocoaPods." lane :format do if File.exist?("../.swiftformat") if File.exist?("../Pods/SwiftFormat/CommandLineTool/swiftformat") sh "../Pods/SwiftFormat/CommandLineTool/swiftformat ../" else UI.message("SwiftFormat not found, please install the SwiftFormat/CLI pod.") end else UI.message(".swiftformat file not found, please create one.") end end desc "Run Blackboard from CocoaPods." lane :runBlackboard do if File.exist?("../.blackboard.yml") if File.exist?("../Pods/Blackboard/blackboard") sh "../Pods/Blackboard/blackboard" else UI.message("Blackboard not found, please install the Blackboard pod.") end else UI.message(".blackboard.yml file not found, please create one.") end end desc "Generate Crashlytics symbol upload path from module name for SPM usage." desc "Should only be called when using Firebase Crashlytics through SPM." lane :populateCrashlyticsSymbolUploadPath do |options| module_name = options[:module_name] raise "populateCrashlyticsSymbolUploadPath requires a module_name." unless module_name derived_data_path = Fastlane::Actions::ClearDerivedDataAction .available_options[0] .default_value .gsub('~', Dir.home) glob_path = "#{derived_data_path}/#{module_name}-*/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/upload-symbols" ENV["FL_UPLOAD_SYMBOLS_TO_CRASHLYTICS_BINARY_PATH"] = Dir.glob(glob_path)[0] end