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 remove_background_notification 112 update_info_plist( 113 xcodeproj: "ios/Exponent.xcodeproj", 114 plist_path: "Exponent/Supporting/Info.plist", 115 block: proc do |plist| 116 puts "Removing remote-notification from UIBackgroundModes..." 117 plist["UIBackgroundModes"].delete("remote-notification") 118 end 119 ) 120end 121 122def configure_for_app_store 123 puts "Configuring app for App Store submission..." 124 remove_background_location 125 remove_background_audio 126 remove_background_notification 127end 128 129platform :ios do 130 131 before_all do 132 FileUtils.mkdir_p(output_directory) 133 Actions.lane_context[SharedValues::VERSION_NUMBER] = get_version_number( 134 xcodeproj: "./ios/Exponent.xcodeproj", 135 target: "Expo Go (versioned)" 136 ) 137 save_original_info_plists 138 update_bundle_versions 139 setup_circle_ci 140 end 141 142 lane :prepare_schemes do |options| 143 recreate_schemes( 144 project: "ios/Pods/#{options[:pod]}.xcodeproj" 145 ) 146 end 147 148 lane :test_module do |options| 149 scan( 150 project: "ios/Pods/#{options[:pod]}.xcodeproj", 151 # This scheme is autogenerated by CocoaPods when a testspec is included in the Podfile 152 scheme: "#{options[:pod]}-Unit-Tests", 153 clean: false, 154 ) 155 end 156 157 lane :test do 158 scan( 159 workspace: "ios/Exponent.xcworkspace", 160 scheme: "Exponent", 161 devices: ["iPhone SE", "iPhone X"], 162 clean: false, 163 ) 164 end 165 166 lane :create_simulator_build do |options| 167 directory = "./ios/simulator-build" 168 puts "Build will be written to: " + File.expand_path(directory) 169 170 flavor = options.fetch(:flavor, "versioned") 171 172 xcbuild( 173 workspace: "ios/Exponent.xcworkspace", 174 scheme: "Expo Go (#{flavor})", 175 sdk: "iphonesimulator", 176 configuration: "Release", 177 derivedDataPath: directory, 178 xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO -quiet", 179 raw_buildlog: is_ci? 180 ) 181 end 182 183 lane :create_expo_client_build do |options| 184 directory = "./ios" + prompt( 185 ci_input: "/../expo-client-build", 186 text: "Please enter the path where we should output the build, relative to ./ios (i.e. ../my-build):", 187 ) 188 puts "Build will be written to: " + File.expand_path(directory) 189 190 flavor = options.fetch(:flavor, "versioned") 191 192 xcbuild( 193 workspace: "ios/Exponent.xcworkspace", 194 scheme: "Expo Go (#{flavor})", 195 configuration: "Release", 196 derivedDataPath: directory, 197 sdk: "iphoneos", 198 destination: "generic/platform=iOS", 199 archive: true, 200 archive_path: directory + "/Exponent.xcarchive", 201 xcargs: "CODE_SIGNING_ALLOWED=NO APP_OWNER=Public -quiet" 202 ) 203 end 204 205 lane :release do |options| 206 cert( 207 team_id: "C8D8QTF339", 208 ) 209 210 sigh( 211 app_identifier: "host.exp.Exponent", 212 output_path: output_directory, 213 filename: "Exponent-distribution.mobileprovision", 214 ) 215 216 xcodebuild_args = { 217 APP_PROVISIONING_PROFILE: Actions.lane_context[Actions::SharedValues::SIGH_UDID], 218 } 219 xcodebuild_args = xcodebuild_args.map do |k,v| 220 "#{k.to_s.shellescape}=#{v.shellescape}" 221 end.join ' ' 222 223 configure_for_app_store 224 225 flavor = options.fetch(:flavor, "versioned") 226 227 gym( 228 workspace: "ios/Exponent.xcworkspace", 229 scheme: "Expo Go (#{flavor})", 230 configuration: "Release", 231 xcargs: xcodebuild_args, 232 clean: true, 233 output_directory: output_directory, 234 output_name: "Exponent.ipa", 235 ) 236 237 deliver( 238 team_id: "17102800", 239 ) 240 end 241 242 after_all do |lane| 243 # FileUtils.rm_rf(output_directory) 244 restore_original_info_plists 245 end 246 247 error do |lane, exception| 248 # slack( 249 # message: exception.message, 250 # success: false 251 # ) 252 end 253 254end 255 256platform :android do 257 258 private_lane :verify_changelog_exists do |version_code: | 259 changelog_path = "android/metadata/en-US/changelogs/#{version_code}.txt" 260 UI.user_error!("Missing changelog file at #{changelog_path}") unless File.exist?(changelog_path) 261 UI.message("Changelog exists for version code #{version_code}") 262 end 263 264 private_lane :verify_upload_to_staging do |version_name: | 265 require 'open-uri' 266 staging_version = nil 267 begin 268 versions = JSON.parse(open(URI.join(STAGING_API_URL, "/--/api/v2/versions")).read) 269 latest_major_version = versions['sdkVersions'].keys.map(&:to_i).sort().reverse().first() 270 latest_version = versions['sdkVersions']["#{latest_major_version}.0.0"] 271 staging_version = latest_version['androidClientVersion'] 272 rescue StandardError => e 273 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.") 274 end 275 276 unless staging_version == version_name 277 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.") 278 end 279 UI.message "APK version #{version_name} has been uploaded to staging" 280 end 281 282 lane :start do |flavor: "unversioned"| 283 gradle( 284 project_dir: "android", 285 task: "install", 286 build_type: "Debug", 287 flavor: flavor, 288 ) 289 adb(command: "shell am start -n host.exp.exponent/host.exp.exponent.LauncherActivity") 290 end 291 292 def devicefarm_run(arn) 293 JSON.load(`aws devicefarm --region us-west-2 get-run --arn #{arn}`) 294 end 295 296 def devicefarm_follow_run(arn) 297 until (run = devicefarm_run(arn)["run"]) && run["result"] != "PENDING" 298 UI.message("Run pending...") 299 sleep 60 300 end 301 UI.shell_error!("Run #{run["result"]}") unless run["result"] == "PASSED" 302 UI.success("Run successful") 303 end 304 305 lane :devicefarm do 306 commit_info = last_git_commit 307 channel = commit_info[:commit_hash] 308 ENV["TEST_SUITE_URI"] = "exp://exp.host/@exponent_ci_bot/test-suite/--/all?release-channel=#{channel}" 309 ENV["TEST_CONFIG"] = { 310 includeModules: '.*', 311 includeSdkVersions: ['UNVERSIONED'], 312 includeTestTypes: ['test-suite'], 313 isInCI: true, 314 }.to_json 315 ENV["DEVICEFARM_PROJECT_NAME"] = "exponent" 316 ENV["DEVICEFARM_DEVICE_POOL"] = "android-test-device-pool" 317 ENV["AWS_ACCESS_KEY_ID"] ||= `aws configure get aws_access_key_id`.chomp 318 ENV["AWS_SECRET_ACCESS_KEY"] ||= `aws configure get aws_secret_access_key`.chomp 319 upload_result = gradle( 320 project_dir: "android", 321 task: "app:devicefarmUpload", 322 ) 323 result_info = upload_result.match(/View the INSTRUMENTATION run.*\/projects\/(?<project_id>.*)\/runs\/(?<run_id>.*)$/) 324 UI.shell_error!("Apks not uploaded!") unless result_info 325 devicefarm_follow_run("arn:aws:devicefarm:us-west-2:274251141632:run:#{result_info[:project_id]}/#{result_info[:run_id]}") 326 end 327 328 lane :build do |build_type: "Debug", flavor: "versioned", sign: true| 329 ENV['ANDROID_UNSIGNED'] = '1' unless sign 330 gradle( 331 project_dir: "android", 332 task: "app:assemble", 333 flavor: flavor, 334 build_type: build_type, 335 ) 336 end 337 338 lane :prod_release do 339 build_gradle = File.read("../android/app/build.gradle") 340 341 verify_changelog_exists(version_code: build_gradle.match(/versionCode (\d+)/)[1]) 342 verify_upload_to_staging(version_name: build_gradle.match(/versionName '([\d\.]+)'/)[1]) 343 344 supply( 345 package_name: "host.exp.exponent", 346 metadata_path: "./fastlane/android/metadata", 347 apk: "./android/app/build/outputs/apk/release/app-release.apk", 348 track: "production", 349 skip_upload_images: true, 350 skip_upload_screenshots: true 351 ) 352 end 353 354end 355