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