xref: /expo/fastlane/Fastfile (revision bb8f4f99)
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