# This file contains the fastlane.tools configuration # You can find the documentation at https://docs.fastlane.tools # # For a list of all available actions, check out # # https://docs.fastlane.tools/actions # # Fastlane is updated quite frequently with security patches # update_fastlane # There are a few env variables defined in the .env file in # this directory (fastlane/.env) # unlocks keychain if KEYCHAIN_PASSWORD variable is present # (to be used on CI machines) def unlock_keychain_if_needed if ENV["KEYCHAIN_PASSWORD"] unlock_keychain( path:"login.keychain", password:ENV["KEYCHAIN_PASSWORD"], set_default: true) end end def curl_upload(url, file, user, conn_timeout=5, timeout=60, retries=3) begin return sh( "curl", "--fail", "--silent", "--user", user, "--request", "POST", "--header", "Content-Type: application/octet-stream", "--data-binary", "@../#{file}", # `fastlane` is the cwd so we go one folder up # we retry few times if upload doesn't succeed in sensible time "--retry-connrefused", # consider ECONNREFUSED as error too retry "--connect-timeout", conn_timeout.to_s, # max time in sec. for establishing connection "--max-time", timeout.to_s, # max time in sec. for whole transfer to take "--retry", retries.to_s, # number of retries to attempt "--retry-max-time", timeout.to_s, # same as --max-time but for retries "--retry-delay", "0", # an exponential backoff algorithm in sec. url ) rescue => error UI.error "Error: #{error}" raise end end def retry_curl_upload(url, file, user, conn_timeout=5, timeout=60, retries=3) # since curl doesn't retry on connection and operation timeouts we roll our own try = 0 begin return curl_upload(url, file, user, conn_timeout, timeout, retries) rescue => error try += 1 if try <= retries UI.important "Warning: Retrying cURL upload! (attempt #{try}/#{retries})" retry else raise end end end # uploads `file` to sauce labs (overwrites if there is anoter file from the # same commit) def upload_to_saucelabs(file) key = ENV["SAUCE_ACCESS_KEY"] username = ENV["SAUCE_USERNAME"] unique_name = ENV["SAUCE_LABS_NAME"] url = "https://saucelabs.com/rest/v1/storage/#{username}/#{unique_name}?overwrite=true" upload_result = retry_curl_upload(url, file, "#{username}:#{key}") # fail the lane if upload fails UI.user_error!( "failed to upload file to saucelabs despite retries: #{upload_result}" ) unless upload_result.include? "filename" end # builds an ios app with ad-hoc configuration and put it # to "status-adhoc" output folder # `readonly`: # if true - only fetch existing certificates and profiles, don't upgrade from AppStoreConnect # if false - read list of devices from AppStoreConnect, and upgrade the provisioning profiles from it def build_ios_adhoc(readonly) match( type: "adhoc", force_for_new_devices: true, readonly: readonly, keychain_name: "login.keychain" ) build_ios_app( scheme: "StatusIm", workspace: "ios/StatusIm.xcworkspace", configuration: "Release", clean: true, export_method: "ad-hoc", # Temporary fix for Xcode 10.1 xcargs: "-UseModernBuildSystem=N", output_directory: "status-adhoc", export_options: { "UseModernBuildSystem": "N" } ) end # builds an ios app with e2e configuration and put it # to "status-e2e" output folder def build_ios_e2e # determine a simulator SDK installed showsdks_output = sh('xcodebuild', '-showsdks') simulator_sdk = showsdks_output.scan(/iphonesimulator\d\d?\.\d\d?/).first match( type: "adhoc", force_for_new_devices: true, readonly: true, keychain_name: "login.keychain" ) build_ios_app( # Creating a build for the iOS Simulator # 1. https://medium.com/rocket-fuel/fastlane-to-the-simulator-87549b2601b9 sdk: simulator_sdk, destination: "generic/platform=iOS Simulator", # 2. fixing compilations issues as stated in https://stackoverflow.com/a/20505258 # it looks like i386 isn't supported by React Native # UseModernBuildSystem: Temporary fix for Xcode 10.1 xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO -UseModernBuildSystem=N", # 3. directory where to up StatusIm.app derived_data_path: "status-e2e", output_name: "StatusIm.app", # ------------------------------------- # Normal stuff scheme: "StatusIm", workspace: "ios/StatusIm.xcworkspace", configuration: "Release", # Simulator apps can't be archived... skip_archive: true, # ...and we don't need an .ipa file for them, because we use .app directly skip_package_ipa: true, export_options: { "UseModernBuildSystem": "N" } ) zip( path: "status-e2e/Build/Products/Release-iphonesimulator/StatusIm.app", output_path: "status-e2e/StatusIm.app.zip", verbose: false, ) end def upload_to_diawi(source) diawi( token: ENV["DIAWI_TOKEN"], file: source ) File.write("diawi.out", lane_context[SharedValues::UPLOADED_FILE_LINK_TO_DIAWI]) end platform :ios do desc "`fastlane ios adhoc` - ad-hoc lane for iOS." desc "This lane is used for PRs, Releases, etc." desc "It creates an .ipa that can be used by a list of devices, registeded in the App Store Connect." desc "This .ipa is ready to be distibuted through diawi.com" lane :adhoc do unlock_keychain_if_needed build_ios_adhoc(true) end desc "`fastlane ios e2e` - e2e lane for iOS." desc "This lane is used for SauceLabs end-to-end testing." desc "It creates an .app that can be used inside of a iPhone simulator." lane :e2e do unlock_keychain_if_needed build_ios_e2e end desc "`fastlane ios pr` - makes a new pr build" desc "This lane builds a new adhoc build and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)" lane :pr do unlock_keychain_if_needed build_ios_adhoc(false) end desc "`fastlane ios nightly` - makes a new nightly" desc "This lane builds a new nightly and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)" lane :nightly do unlock_keychain_if_needed build_ios_adhoc(false) end desc "`fastlane ios release` builds a release & uploads it to TestFlight" lane :release do match( type: "appstore", readonly: true, keychain_name: "login.keychain" ) build_ios_app( scheme: "StatusIm", workspace: "ios/StatusIm.xcworkspace", configuration: "Release", clean: true, export_method: "app-store", output_directory: "status_appstore", # Temporary fix for Xcode 10.1 xcargs: "-UseModernBuildSystem=N", export_options: { "UseModernBuildSystem": "N", "combileBitcode": true, "uploadBitcode": false, "ITSAppUsesNonExemptEncryption": false } ) upload_to_testflight( ipa: "status_appstore/StatusIm.ipa" ) end desc "`fastlane ios clean` - remove inactive TestFlight users" desc "uses custom plugin, installed via" desc "`sudo get install fastlane-plugin-clean_testflight_testers`" lane :clean do clean_testflight_testers(username: ENV["FASTLANE_APPLE_ID"]) end desc "`fastlane ios upload-diawi` - upload .ipa to diawi" desc "expects to have an .ipa prepared: `status-adhoc/StatusIm.ipa`" desc "expects to have a diawi token as DIAWI_TOKEN env variable" desc "expects to have a github token as GITHUB_TOKEN env variable" desc "will fails if file isn't there" desc "---" desc "Output: writes `fastlane/diawi.out` file url of the uploded file" lane :upload_diawi do ipa = ENV["DIAWI_IPA"] || "status-adhoc/StatusIm.ipa" upload_to_diawi(ipa) end desc "`fastlane ios saucelabs` - upload .app to sauce labs" desc "also notifies in a GitHub comments" desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`" desc "expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable" desc "expects to have a saucelabs username token as SAUCE_USERNAME env variable" desc "expects to have a saucelabs destination name as SAUCE_LABS_NAME env variable" desc "will fails if file isn't there" lane :saucelabs do upload_to_saucelabs( "status-e2e/StatusIm.app.zip" ) end desc "This fastlane step cleans up XCode DerivedData folder" lane :cleanup do clear_derived_data end end platform :android do desc "Deploy a new internal build to Google Play" desc "expects GOOGLE_PLAY_JSON_KEY environment variable" lane :nightly do upload_to_play_store( track: "internal", apk: "android/app/build/outputs/apk/release/app-release.apk", json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"] ) end desc "Deploy a new alpha (public) build to Google Play" desc "expects GOOGLE_PLAY_JSON_KEY environment variable" lane :release do upload_to_play_store( track: "alpha", apk: "android/app/build/outputs/apk/release/app-release.apk", json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"] ) end desc "Upload metadata to Google Play." desc "Metadata is always updated when builds are uploaded," desc "but this action can update metadata without uploading a build." desc "expects GOOGLE_PLAY_JSON_KEY environment variable" lane :upload_metadata do upload_to_play_store( skip_upload_apk: true, json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"] ) end desc "`fastlane android upload_diawi` - upload .apk to diawi" desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`" desc "expects to have a diawi token as DIAWI_TOKEN env variable" desc "expects to have a github token as GITHUB_TOKEN env variable" desc "will fails if file isn't there" desc "---" desc "Output: writes `fastlane/diawi.out` file url of the uploded file" lane :upload_diawi do apk = ENV["DIAWI_APK"] || "android/app/build/outputs/apk/release/app-release.apk" upload_to_diawi(apk) end desc "`fastlane android saucelabs` - upload .apk to sauce labs" desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`" desc "expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable" desc "expects to have a saucelabs username token as SAUCE_USERNAME env variable" desc "expects to have a saucelabs destination name as SAUCE_LABS_NAME env variable" desc "will fails if file isn't there" lane :saucelabs do upload_to_saucelabs( "android/app/build/outputs/apk/release/app-release.apk" ) end end