# Customise this file, documentation can be found here: # https://github.com/KrauseFx/fastlane/tree/master/docs fastlane_require 'fileutils' fastlane_require 'shellwords' fastlane_version "2.141.0" default_platform :ios STAGING_API_URL = 'https://staging.exp.host/' # path relative to Fastfile output_directory = File.expand_path('./Deployment') def update_bundle_versions bundle_short_version = Actions.lane_context[SharedValues::VERSION_NUMBER] bundle_version = bundle_short_version + ".10" + number_of_commits.to_s puts "Setting CFBundleVersion for Exponent to #{bundle_version}..." set_info_plist_value( path: "./ios/Exponent/Supporting/Info.plist", key: "CFBundleVersion", value: bundle_version ) puts "Setting CFBundleVersion to #{bundle_version} and CFBundleShortVersionString #{bundle_short_version} for ExpoNotificationServiceExtension..." set_info_plist_value( path: "./ios/ExpoNotificationServiceExtension/Info.plist", key: "CFBundleVersion", value: bundle_version ) set_info_plist_value( path: "./ios/ExpoNotificationServiceExtension/Info.plist", key: "CFBundleShortVersionString", value: bundle_short_version ) end # This will keep a copy of the original Info.plist @original_info_plist = nil @original_notification_extension_info_plist = nil def save_original_info_plists update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "Exponent/Supporting/Info.plist", block: proc do |plist| @original_info_plist = Marshal.load(Marshal.dump(plist)) end ) update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "ExpoNotificationServiceExtension/Info.plist", block: proc do |plist| @original_notification_extension_info_plist = Marshal.load(Marshal.dump(plist)) end ) end def restore_original_info_plists update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "Exponent/Supporting/Info.plist", block: proc do |plist| puts "Restoring original Exponent/Info.plist..." plist.replace(@original_info_plist) end ) update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "ExpoNotificationServiceExtension/Info.plist", block: proc do |plist| puts "Restoring original ExpoNotificationServiceExtension/Info.plist..." plist.replace(@original_notification_extension_info_plist) end ) end def remove_background_location update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "Exponent/Supporting/Info.plist", block: proc do |plist| puts "Removing background location permission strings and background mode..." puts "Deleting NSLocationAlwaysAndWhenInUseUsageDescription key..." plist.delete("NSLocationAlwaysAndWhenInUseUsageDescription") puts "Deleting NSLocationAlwaysUsageDescription key..." plist.delete("NSLocationAlwaysUsageDescription") puts "Deleting location from UIBackgroundModes..." plist["UIBackgroundModes"].delete("location") end ) end def remove_background_audio update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "Exponent/Supporting/Info.plist", block: proc do |plist| puts "Removing background audio background mode..." plist["UIBackgroundModes"].delete("audio") end ) end def remove_background_notification update_info_plist( xcodeproj: "ios/Exponent.xcodeproj", plist_path: "Exponent/Supporting/Info.plist", block: proc do |plist| puts "Removing remote-notification from UIBackgroundModes..." plist["UIBackgroundModes"].delete("remote-notification") end ) end def configure_for_app_store puts "Configuring app for App Store submission..." remove_background_location remove_background_audio remove_background_notification end platform :ios do before_all do FileUtils.mkdir_p(output_directory) Actions.lane_context[SharedValues::VERSION_NUMBER] = get_version_number( xcodeproj: "./ios/Exponent.xcodeproj", target: "Expo Go (versioned)" ) save_original_info_plists update_bundle_versions setup_circle_ci end lane :sync_screenshots do upload_to_app_store( team_id: "17102800", app_identifier: "host.exp.Exponent", screenshots_path: "./fastlane/screenshots", skip_binary_upload: true, skip_metadata: true ) end lane :prepare_schemes do |options| recreate_schemes( project: "ios/Pods/#{options[:pod]}.xcodeproj" ) end lane :test_module do |options| scan( project: "ios/Pods/#{options[:pod]}.xcodeproj", # This scheme is autogenerated by CocoaPods when a testspec is included in the Podfile scheme: "#{options[:pod]}-Unit-#{options[:testSpecName]}", clean: false, ) end lane :unit_tests do |options| workspace = "apps/native-tests/ios/NativeTests.xcworkspace" generated_scheme = generate_test_scheme( scheme_name: 'NativeTests', workspace_path: workspace, targets: options[:targets] ) run_tests( workspace: workspace, scheme: generated_scheme, devices: ["iPhone 14 Pro"], configuration: 'Debug', clean: false, skip_build: true, prelaunch_simulator: true, skip_detect_devices: true, skip_package_dependencies_resolution: true, derived_data_path: "/tmp/ExpoUnitTestsDerivedData", xcargs: 'CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO', ) end lane :test do scan( workspace: "ios/Exponent.xcworkspace", scheme: "Exponent", devices: ["iPhone SE", "iPhone X"], clean: false, ) end lane :create_simulator_build do |options| directory = "./ios/simulator-build" puts "Build will be written to: " + File.expand_path(directory) flavor = options.fetch(:flavor, "versioned") xcbuild( workspace: "ios/Exponent.xcworkspace", scheme: "Expo Go (#{flavor})", sdk: "iphonesimulator", configuration: "Release", derivedDataPath: directory, xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO -quiet", raw_buildlog: is_ci? ) end lane :create_expo_client_build do |options| directory = "./ios" + prompt( ci_input: "/../expo-client-build", text: "Please enter the path where we should output the build, relative to ./ios (i.e. ../my-build):", ) puts "Build will be written to: " + File.expand_path(directory) flavor = options.fetch(:flavor, "versioned") xcbuild( workspace: "ios/Exponent.xcworkspace", scheme: "Expo Go (#{flavor})", configuration: "Release", derivedDataPath: directory, sdk: "iphoneos", destination: "generic/platform=iOS", archive: true, archive_path: directory + "/Exponent.xcarchive", xcargs: "CODE_SIGNING_ALLOWED=NO APP_OWNER=Public -quiet" ) end lane :release do |options| cert( team_id: "C8D8QTF339", ) sigh( app_identifier: "host.exp.Exponent", output_path: output_directory, filename: "Exponent-distribution.mobileprovision", ) xcodebuild_args = { APP_PROVISIONING_PROFILE: Actions.lane_context[Actions::SharedValues::SIGH_UDID], } xcodebuild_args = xcodebuild_args.map do |k,v| "#{k.to_s.shellescape}=#{v.shellescape}" end.join ' ' configure_for_app_store flavor = options.fetch(:flavor, "versioned") gym( workspace: "ios/Exponent.xcworkspace", scheme: "Expo Go (#{flavor})", configuration: "Release", xcargs: xcodebuild_args, clean: true, output_directory: output_directory, output_name: "Exponent.ipa", ) deliver( team_id: "17102800", ) end after_all do |lane| # FileUtils.rm_rf(output_directory) restore_original_info_plists end error do |lane, exception| # slack( # message: exception.message, # success: false # ) end end platform :android do private_lane :verify_changelog_exists do |version_code: | changelog_path = "android/metadata/en-US/changelogs/#{version_code}.txt" UI.user_error!("Missing changelog file at #{changelog_path}") unless File.exist?(changelog_path) UI.message("Changelog exists for version code #{version_code}") end private_lane :verify_upload_to_staging do |version_name: | UI.message "Skipping staging verification step" # require 'open-uri' # staging_version = nil # begin # versions = JSON.parse(open(URI.join(STAGING_API_URL, "/--/api/v2/versions")).read) # latest_major_version = versions['sdkVersions'].keys.map(&:to_i).sort().reverse().first() # latest_version = versions['sdkVersions']["#{latest_major_version}.0.0"] # staging_version = latest_version['androidClientVersion'] # rescue StandardError => e # UI.user_error!("Unable to validate androidClientVersion for latest SDK version on versions endpoint: #{e.inspect}. If this issue persists, remove the verify_upload_to_staging step from Fastfile and manually verify that the Android client has been deployed and tested on staging.") # end # unless staging_version == version_name # UI.user_error!("APK version #{version_name} is not yet uploaded to staging. Please download from the client_android CI job, test and upload manually.") # end # UI.message "APK version #{version_name} has been uploaded to staging" end lane :start do |flavor: "unversioned"| gradle( project_dir: "android", task: "install", build_type: "Debug", flavor: flavor, ) adb(command: "shell am start -n host.exp.exponent/host.exp.exponent.LauncherActivity") end lane :build do |build_type: "Debug", flavor: "versioned", sign: true, aab: false| ENV['ANDROID_UNSIGNED'] = '1' unless sign gradle( project_dir: "android", task: aab ? "app:bundle": "app:assemble", flavor: flavor, build_type: build_type, ) end lane :upload_crashlytics_symbols do |flavor: "versioned"| gradle( project_dir: "android", task: "app:uploadCrashlyticsSymbolFile", flavor: flavor, build_type: "Release", ) end lane :prod_release do build_gradle = File.read("../android/app/build.gradle") verify_changelog_exists(version_code: build_gradle.match(/versionCode (\d+)/)[1]) verify_upload_to_staging(version_name: build_gradle.match(/versionName '([\d\.]+)'/)[1]) supply( package_name: "host.exp.exponent", metadata_path: "./fastlane/android/metadata", aab: "./android/app/build/outputs/bundle/versionedRelease/app-versioned-release.aab", track: "production", skip_upload_images: true, skip_upload_screenshots: true ) end end