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