1# Customise this file, documentation can be found here: 2# https://github.com/KrauseFx/fastlane/tree/master/docs 3fastlane_require 'fileutils' 4fastlane_require 'shellwords' 5 6fastlane_version "2.141.0" 7default_platform :ios 8 9STAGING_API_URL = 'https://staging.exp.host/' 10 11# path relative to Fastfile 12output_directory = File.expand_path('./Deployment') 13 14def update_bundle_versions 15 bundle_short_version = Actions.lane_context[SharedValues::VERSION_NUMBER] 16 bundle_version = bundle_short_version + ".10" + number_of_commits.to_s 17 18 puts "Setting CFBundleVersion for Exponent to #{bundle_version}..." 19 set_info_plist_value( 20 path: "./ios/Exponent/Supporting/Info.plist", 21 key: "CFBundleVersion", 22 value: bundle_version 23 ) 24 25 puts "Setting CFBundleVersion to #{bundle_version} and CFBundleShortVersionString #{bundle_short_version} for ExpoNotificationServiceExtension..." 26 set_info_plist_value( 27 path: "./ios/ExpoNotificationServiceExtension/Info.plist", 28 key: "CFBundleVersion", 29 value: bundle_version 30 ) 31 32 set_info_plist_value( 33 path: "./ios/ExpoNotificationServiceExtension/Info.plist", 34 key: "CFBundleShortVersionString", 35 value: bundle_short_version 36 ) 37end 38 39# This will keep a copy of the original Info.plist 40@original_info_plist = nil 41@original_notification_extension_info_plist = nil 42 43def save_original_info_plists 44 update_info_plist( 45 xcodeproj: "ios/Exponent.xcodeproj", 46 plist_path: "Exponent/Supporting/Info.plist", 47 block: proc do |plist| 48 @original_info_plist = Marshal.load(Marshal.dump(plist)) 49 end 50 ) 51 52 update_info_plist( 53 xcodeproj: "ios/Exponent.xcodeproj", 54 plist_path: "ExpoNotificationServiceExtension/Info.plist", 55 block: proc do |plist| 56 @original_notification_extension_info_plist = Marshal.load(Marshal.dump(plist)) 57 end 58 ) 59end 60 61def restore_original_info_plists 62 update_info_plist( 63 xcodeproj: "ios/Exponent.xcodeproj", 64 plist_path: "Exponent/Supporting/Info.plist", 65 block: proc do |plist| 66 puts "Restoring original Exponent/Info.plist..." 67 plist.replace(@original_info_plist) 68 end 69 ) 70 71 update_info_plist( 72 xcodeproj: "ios/Exponent.xcodeproj", 73 plist_path: "ExpoNotificationServiceExtension/Info.plist", 74 block: proc do |plist| 75 puts "Restoring original ExpoNotificationServiceExtension/Info.plist..." 76 plist.replace(@original_notification_extension_info_plist) 77 end 78 ) 79end 80 81def remove_background_location 82 update_info_plist( 83 xcodeproj: "ios/Exponent.xcodeproj", 84 plist_path: "Exponent/Supporting/Info.plist", 85 block: proc do |plist| 86 puts "Removing background location permission strings and background mode..." 87 88 puts "Deleting NSLocationAlwaysAndWhenInUseUsageDescription key..." 89 plist.delete("NSLocationAlwaysAndWhenInUseUsageDescription") 90 91 puts "Deleting NSLocationAlwaysUsageDescription key..." 92 plist.delete("NSLocationAlwaysUsageDescription") 93 94 puts "Deleting location from UIBackgroundModes..." 95 plist["UIBackgroundModes"].delete("location") 96 end 97 ) 98end 99 100def remove_background_audio 101 update_info_plist( 102 xcodeproj: "ios/Exponent.xcodeproj", 103 plist_path: "Exponent/Supporting/Info.plist", 104 block: proc do |plist| 105 puts "Removing background audio background mode..." 106 plist["UIBackgroundModes"].delete("audio") 107 end 108 ) 109end 110 111def configure_for_app_store 112 puts "Configuring app for App Store submission..." 113 remove_background_location 114 remove_background_audio 115end 116 117platform :ios do 118 119 before_all do 120 FileUtils.mkdir_p(output_directory) 121 Actions.lane_context[SharedValues::VERSION_NUMBER] = get_version_number( 122 xcodeproj: "./ios/Exponent.xcodeproj", 123 target: "Exponent" 124 ) 125 save_original_info_plists 126 update_bundle_versions 127 setup_circle_ci 128 end 129 130 lane :test do 131 scan( 132 workspace: "ios/Exponent.xcworkspace", 133 scheme: "Exponent", 134 devices: ["iPhone SE", "iPhone X"], 135 clean: false, 136 ) 137 end 138 139 lane :create_simulator_build do 140 directory = "./ios/simulator-build" 141 puts "Build will be written to: " + File.expand_path(directory) 142 xcbuild( 143 workspace: "ios/Exponent.xcworkspace", 144 scheme: "Exponent", 145 sdk: "iphonesimulator", 146 configuration: "Release", 147 derivedDataPath: directory, 148 xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO -quiet", 149 raw_buildlog: is_ci? 150 ) 151 end 152 153 lane :create_expo_client_build do 154 directory = "./ios" + prompt( 155 ci_input: "/../expo-client-build", 156 text: "Please enter the path where we should output the build, relative to ./ios (i.e. ../my-build):", 157 ) 158 puts "Build will be written to: " + File.expand_path(directory) 159 xcbuild( 160 workspace: "ios/Exponent.xcworkspace", 161 scheme: "Exponent", 162 configuration: "Release", 163 derivedDataPath: directory, 164 sdk: "iphoneos", 165 destination: "generic/platform=iOS", 166 archive: true, 167 archive_path: directory + "/Exponent.xcarchive", 168 xcargs: "CODE_SIGNING_ALLOWED=NO APP_OWNER=Public -quiet" 169 ) 170 end 171 172 lane :release do 173 cert( 174 team_id: "C8D8QTF339", 175 ) 176 177 sigh( 178 app_identifier: "host.exp.Exponent", 179 output_path: output_directory, 180 filename: "Exponent-distribution.mobileprovision", 181 ) 182 183 xcodebuild_args = { 184 APP_PROVISIONING_PROFILE: Actions.lane_context[Actions::SharedValues::SIGH_UDID], 185 } 186 xcodebuild_args = xcodebuild_args.map do |k,v| 187 "#{k.to_s.shellescape}=#{v.shellescape}" 188 end.join ' ' 189 190 configure_for_app_store 191 192 gym( 193 workspace: "ios/Exponent.xcworkspace", 194 scheme: "Exponent", 195 configuration: "Release", 196 xcargs: xcodebuild_args, 197 clean: true, 198 output_directory: output_directory, 199 output_name: "Exponent.ipa", 200 ) 201 202 deliver( 203 team_id: "17102800", 204 ) 205 end 206 207 after_all do |lane| 208 # FileUtils.rm_rf(output_directory) 209 restore_original_info_plists 210 end 211 212 error do |lane, exception| 213 # slack( 214 # message: exception.message, 215 # success: false 216 # ) 217 end 218 219end 220 221platform :android do 222 223 private_lane :verify_changelog_exists do |version_code: | 224 changelog_path = "android/metadata/en-US/changelogs/#{version_code}.txt" 225 UI.user_error!("Missing changelog file at #{changelog_path}") unless File.exist?(changelog_path) 226 UI.message("Changelog exists for version code #{version_code}") 227 end 228 229 private_lane :verify_upload_to_staging do |version_name: | 230 require 'open-uri' 231 staging_version = nil 232 begin 233 versions = JSON.parse(open(URI.join(STAGING_API_URL, "/--/api/v2/versions")).read) 234 latest_major_version = versions['sdkVersions'].keys.map(&:to_i).sort().reverse().first() 235 latest_version = versions['sdkVersions']["#{latest_major_version}.0.0"] 236 staging_version = latest_version['androidClientVersion'] 237 rescue StandardError => e 238 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.") 239 end 240 241 unless staging_version == version_name 242 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.") 243 end 244 UI.message "APK version #{version_name} has been uploaded to staging" 245 end 246 247 lane :start do 248 gradle( 249 project_dir: "android", 250 task: "install", 251 build_type: "Debug", 252 ) 253 adb(command: "shell am start -n host.exp.exponent/host.exp.exponent.LauncherActivity") 254 end 255 256 def devicefarm_run(arn) 257 JSON.load(`aws devicefarm --region us-west-2 get-run --arn #{arn}`) 258 end 259 260 def devicefarm_follow_run(arn) 261 until (run = devicefarm_run(arn)["run"]) && run["result"] != "PENDING" 262 UI.message("Run pending...") 263 sleep 60 264 end 265 UI.shell_error!("Run #{run["result"]}") unless run["result"] == "PASSED" 266 UI.success("Run successful") 267 end 268 269 lane :devicefarm do 270 commit_info = last_git_commit 271 channel = commit_info[:commit_hash] 272 ENV["TEST_SUITE_URI"] = "exp://exp.host/@exponent_ci_bot/test-suite/--/all?release-channel=#{channel}" 273 ENV["TEST_CONFIG"] = { 274 includeModules: '.*', 275 includeSdkVersions: ['UNVERSIONED'], 276 includeTestTypes: ['test-suite'], 277 isInCI: true, 278 }.to_json 279 ENV["DEVICEFARM_PROJECT_NAME"] = "exponent" 280 ENV["DEVICEFARM_DEVICE_POOL"] = "android-test-device-pool" 281 ENV["AWS_ACCESS_KEY_ID"] ||= `aws configure get aws_access_key_id`.chomp 282 ENV["AWS_SECRET_ACCESS_KEY"] ||= `aws configure get aws_secret_access_key`.chomp 283 upload_result = gradle( 284 project_dir: "android", 285 task: "app:devicefarmUpload", 286 ) 287 result_info = upload_result.match(/View the INSTRUMENTATION run.*\/projects\/(?<project_id>.*)\/runs\/(?<run_id>.*)$/) 288 UI.shell_error!("Apks not uploaded!") unless result_info 289 devicefarm_follow_run("arn:aws:devicefarm:us-west-2:274251141632:run:#{result_info[:project_id]}/#{result_info[:run_id]}") 290 end 291 292 lane :build do |build_type: "Debug", sign: true| 293 ENV['ANDROID_UNSIGNED'] = '1' unless sign 294 gradle( 295 project_dir: "android", 296 task: "app:assemble", 297 build_type: build_type, 298 ) 299 end 300 301 lane :prod_release do 302 build_gradle = File.read("../android/app/build.gradle") 303 304 verify_changelog_exists(version_code: build_gradle.match(/versionCode (\d+)/)[1]) 305 verify_upload_to_staging(version_name: build_gradle.match(/versionName '([\d\.]+)'/)[1]) 306 307 supply( 308 package_name: "host.exp.exponent", 309 metadata_path: "./fastlane/android/metadata", 310 apk: "./android/app/build/outputs/apk/release/app-release.apk", 311 track: "production", 312 skip_upload_images: true, 313 skip_upload_screenshots: true 314 ) 315 end 316 317end 318