xref: /expo/fastlane/Fastfile (revision dfd15ebd)
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-#{options[:testSpecName]}",
153      clean: false,
154    )
155  end
156
157  lane :unit_tests do |options|
158    workspace = "apps/native-tests/ios/NativeTests.xcworkspace"
159
160    generated_scheme = generate_test_scheme(
161      scheme_name: 'NativeTests',
162      workspace_path: workspace,
163      targets: options[:targets]
164    )
165
166    run_tests(
167      workspace: workspace,
168      scheme: generated_scheme,
169      devices: ["iPhone 13 Pro Max"],
170      configuration: 'Debug',
171      clean: false,
172      skip_build: true,
173      prelaunch_simulator: true,
174      skip_detect_devices: true,
175      skip_package_dependencies_resolution: true,
176      derived_data_path: "/tmp/ExpoUnitTestsDerivedData",
177    )
178  end
179
180  lane :test do
181    scan(
182      workspace: "ios/Exponent.xcworkspace",
183      scheme: "Exponent",
184      devices: ["iPhone SE", "iPhone X"],
185      clean: false,
186    )
187  end
188
189  lane :create_simulator_build do |options|
190    directory = "./ios/simulator-build"
191    puts "Build will be written to: " + File.expand_path(directory)
192
193    flavor = options.fetch(:flavor, "versioned")
194
195    xcbuild(
196      workspace: "ios/Exponent.xcworkspace",
197      scheme: "Expo Go (#{flavor})",
198      sdk: "iphonesimulator",
199      configuration: "Release",
200      derivedDataPath: directory,
201      xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO -quiet",
202      raw_buildlog: is_ci?
203    )
204  end
205
206  lane :create_expo_client_build do |options|
207    directory = "./ios" + prompt(
208      ci_input: "/../expo-client-build",
209      text: "Please enter the path where we should output the build, relative to ./ios (i.e. ../my-build):",
210    )
211    puts "Build will be written to: " + File.expand_path(directory)
212
213    flavor = options.fetch(:flavor, "versioned")
214
215    xcbuild(
216      workspace: "ios/Exponent.xcworkspace",
217      scheme: "Expo Go (#{flavor})",
218      configuration: "Release",
219      derivedDataPath: directory,
220      sdk: "iphoneos",
221      destination: "generic/platform=iOS",
222      archive: true,
223      archive_path: directory + "/Exponent.xcarchive",
224      xcargs: "CODE_SIGNING_ALLOWED=NO APP_OWNER=Public -quiet"
225    )
226  end
227
228  lane :release do |options|
229    cert(
230      team_id: "C8D8QTF339",
231    )
232
233    sigh(
234      app_identifier: "host.exp.Exponent",
235      output_path: output_directory,
236      filename: "Exponent-distribution.mobileprovision",
237    )
238
239    xcodebuild_args = {
240      APP_PROVISIONING_PROFILE: Actions.lane_context[Actions::SharedValues::SIGH_UDID],
241    }
242    xcodebuild_args = xcodebuild_args.map do |k,v|
243      "#{k.to_s.shellescape}=#{v.shellescape}"
244    end.join ' '
245
246    configure_for_app_store
247
248    flavor = options.fetch(:flavor, "versioned")
249
250    gym(
251      workspace: "ios/Exponent.xcworkspace",
252      scheme: "Expo Go (#{flavor})",
253      configuration: "Release",
254      xcargs: xcodebuild_args,
255      clean: true,
256      output_directory: output_directory,
257      output_name: "Exponent.ipa",
258    )
259
260    deliver(
261      team_id: "17102800",
262    )
263  end
264
265  after_all do |lane|
266    # FileUtils.rm_rf(output_directory)
267    restore_original_info_plists
268  end
269
270  error do |lane, exception|
271    # slack(
272    #   message: exception.message,
273    #   success: false
274    # )
275  end
276
277end
278
279platform :android do
280
281  private_lane :verify_changelog_exists do |version_code: |
282    changelog_path = "android/metadata/en-US/changelogs/#{version_code}.txt"
283    UI.user_error!("Missing changelog file at #{changelog_path}") unless File.exist?(changelog_path)
284    UI.message("Changelog exists for version code #{version_code}")
285  end
286
287  private_lane :verify_upload_to_staging do |version_name: |
288    UI.message "Skipping staging verification step"
289    # require 'open-uri'
290    # staging_version = nil
291    # begin
292    #   versions = JSON.parse(open(URI.join(STAGING_API_URL, "/--/api/v2/versions")).read)
293    #   latest_major_version = versions['sdkVersions'].keys.map(&:to_i).sort().reverse().first()
294    #   latest_version = versions['sdkVersions']["#{latest_major_version}.0.0"]
295    #   staging_version = latest_version['androidClientVersion']
296    # rescue StandardError => e
297    #   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.")
298    # end
299
300    # unless staging_version == version_name
301    #   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.")
302    # end
303    # UI.message "APK version #{version_name} has been uploaded to staging"
304  end
305
306  lane :start do |flavor: "unversioned"|
307    gradle(
308      project_dir: "android",
309      task: "install",
310      build_type: "Debug",
311      flavor: flavor,
312    )
313    adb(command: "shell am start -n host.exp.exponent/host.exp.exponent.LauncherActivity")
314  end
315
316  lane :build do |build_type: "Debug", flavor: "versioned", sign: true, aab: false|
317    ENV['ANDROID_UNSIGNED'] = '1' unless sign
318    gradle(
319      project_dir: "android",
320      task: aab ? "app:bundle": "app:assemble",
321      flavor: flavor,
322      build_type: build_type,
323    )
324  end
325
326  lane :prod_release do
327    build_gradle = File.read("../android/app/build.gradle")
328
329    verify_changelog_exists(version_code: build_gradle.match(/versionCode (\d+)/)[1])
330    verify_upload_to_staging(version_name: build_gradle.match(/versionName '([\d\.]+)'/)[1])
331
332    supply(
333      package_name: "host.exp.exponent",
334      metadata_path: "./fastlane/android/metadata",
335      aab: "./android/app/build/outputs/bundle/versionedRelease/app-versioned-release.aab",
336      track: "production",
337      skip_upload_images: true,
338      skip_upload_screenshots: true
339    )
340  end
341
342end
343