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