mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
* Temporarily comment out Android in Fastfile build lane * Fix: make draggable flatlist work with reanimated 3 * Add lib and commonjs library patch
459 lines
15 KiB
Ruby
459 lines
15 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "fileutils"
|
|
require "spaceship"
|
|
|
|
appfile_path = File.join( __dir__, "Appfile" )
|
|
unless File.exist?( appfile_path )
|
|
UI.abort_with_message! <<~NO_APPFILE_ERROR.gsub( /\s+/, " " ).strip
|
|
Could not find #{appfile_path}. Copy the example file in that directory to
|
|
that path and fill in the relevant values to use Fastlane.
|
|
NO_APPFILE_ERROR
|
|
end
|
|
|
|
forbidden_env_vars = [
|
|
"MOCK_MODE"
|
|
]
|
|
forbidden_env_vars.each do | env_var |
|
|
next unless ENV[env_var].to_s.size.positive?
|
|
|
|
UI.abort_with_message! <<~NO_ENV_ERROR.gsub( /\s+/, " " ).strip
|
|
ENV is set #{env_var}. Remove the value from your ENV before running fastlane.
|
|
NO_ENV_ERROR
|
|
end
|
|
|
|
required_env_vars = [
|
|
"IOS_PROVISIONING_PROFILE_NAME",
|
|
"IOS_SHARE_BUNDLE_ID",
|
|
"IOS_SHARE_PROVISIONING_PROFILE_NAME"
|
|
]
|
|
required_env_vars.each do | env_var |
|
|
next unless ENV[env_var].to_s.size.zero?
|
|
|
|
UI.abort_with_message! <<~NO_ENV_ERROR.gsub( /\s+/, " " ).strip
|
|
ENV is missing #{env_var}. Add the value to .env or populate your ENV in
|
|
another way.
|
|
NO_ENV_ERROR
|
|
end
|
|
|
|
VERSION = File.open( "../package.json" ) {| f | JSON.parse( f.read )["version"] }
|
|
editor_cmd = [
|
|
ENV["EDITOR"],
|
|
`git config core.editor`,
|
|
`which vi`
|
|
].map {| e | e.to_s.strip }.detect {| e | !e.empty? }
|
|
if editor_cmd.nil?
|
|
UI.abort_with_message! <<~NO_EDITOR_ERROR
|
|
Could not find an editor, not even vi. Set the EDITOR environmental
|
|
variable or the core.editor git config"
|
|
NO_EDITOR_ERROR
|
|
end
|
|
editor_cmd_needs_to_wait = ( editor_cmd =~ /^code / || editor_cmd =~ /^subl / ) && !editor_cmd.include?( "-w" )
|
|
EDITOR = editor_cmd_needs_to_wait ? "#{editor_cmd} -w" : editor_cmd
|
|
XCODEPROJ = "ios/iNaturalistReactNative.xcodeproj"
|
|
PACKAGE_ID = CredentialsManager::AppfileConfig.try_fetch_value( :package_name )
|
|
IOS_BUNDLE_ID = CredentialsManager::AppfileConfig.try_fetch_value( :app_identifier )
|
|
IOS_ITC_TEAM_ID = CredentialsManager::AppfileConfig.try_fetch_value( :itc_team_id )
|
|
|
|
# https://github.com/fastlane/fastlane/issues/20741#issuecomment-1280687500
|
|
# ENV["ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD"] = "true"
|
|
|
|
def set_android_version_code( new_version_code )
|
|
build_gradle_path = "../android/app/build.gradle"
|
|
new_gradle = File.read( build_gradle_path ).sub( /versionCode\s+\d+/, "versionCode #{new_version_code}" )
|
|
File.open( build_gradle_path, "w" ) {| f | f.write( new_gradle ) }
|
|
end
|
|
|
|
def set_android_version_name( _new_version_name )
|
|
build_gradle_path = "../android/app/build.gradle"
|
|
new_gradle = File.read( build_gradle_path ).sub( /versionName\s+".+"/, "versionName \"#{VERSION}\"" )
|
|
File.open( build_gradle_path, "w" ) {| f | f.write( new_gradle ) }
|
|
end
|
|
|
|
def get_changelog_path( build_number = nil )
|
|
build_number ||= get_build_number( xcodeproj: XCODEPROJ )
|
|
changelog_dir_path = File.join( "metadata", "android", "en-US", "changelogs" )
|
|
FileUtils.mkpath( changelog_dir_path )
|
|
File.join( changelog_dir_path, "#{build_number}.txt" )
|
|
end
|
|
|
|
def get_aab_path( build_number = nil )
|
|
build_number ||= get_build_number( xcodeproj: XCODEPROJ )
|
|
File.expand_path( File.join(
|
|
__dir__,
|
|
"..",
|
|
"android",
|
|
"app",
|
|
"build",
|
|
"outputs",
|
|
"bundle",
|
|
"release",
|
|
"#{PACKAGE_ID}-v#{VERSION}+#{build_number}-release.aab"
|
|
) )
|
|
end
|
|
|
|
def get_apk_path( build_number = nil )
|
|
build_number ||= get_build_number( xcodeproj: XCODEPROJ )
|
|
File.expand_path( File.join(
|
|
__dir__,
|
|
"..",
|
|
"android",
|
|
"app",
|
|
"build",
|
|
"outputs",
|
|
"apk",
|
|
"release",
|
|
"#{PACKAGE_ID}-v#{VERSION}+#{build_number}-release.apk"
|
|
) )
|
|
end
|
|
|
|
def get_ipa_path( build_number = nil )
|
|
build_number ||= get_build_number( xcodeproj: XCODEPROJ )
|
|
File.expand_path( File.join(
|
|
__dir__,
|
|
"..",
|
|
"ios",
|
|
"build",
|
|
"#{PACKAGE_ID}-v#{VERSION}+#{build_number}.ipa"
|
|
) )
|
|
end
|
|
|
|
lane :tag do
|
|
desc "Add a new tag with an incremented version"
|
|
ensure_git_status_clean( ignore_files: ["fastlane/Fastfile"] )
|
|
last_tag = last_git_tag
|
|
# Increment the iOS build number
|
|
increment_build_number( xcodeproj: XCODEPROJ )
|
|
increment_version_number( xcodeproj: XCODEPROJ, version_number: VERSION )
|
|
build_number = get_build_number( xcodeproj: XCODEPROJ )
|
|
# set android/app/build.gradle versionCode to this build_number
|
|
set_android_version_code( build_number )
|
|
# set android/app/build.gradle versionName to VERSION
|
|
set_android_version_name( VERSION )
|
|
tag = "v#{VERSION}+#{build_number}"
|
|
|
|
changes = changelog_from_git_commits( pretty: "# * %h %s (%an, %ai)" )
|
|
if last_tag && changes.empty?
|
|
UI.abort_with_message! "Nothing has changed since the last tag (#{last_tag})"
|
|
end
|
|
|
|
# Get release notes
|
|
# Bit silly but takes advantage of existing syntax highlighting
|
|
fname = "COMMIT_EDITMSG"
|
|
File.open( fname, "w" ) do | f |
|
|
f << <<~INSTRUCTIONS
|
|
|
|
|
|
# Enter notes about what's new in #{tag}. Lines beginning with # will be ignored.
|
|
# Keep notes within the 500 character limit required by Google Play.
|
|
#
|
|
# Here's what changed since the last tag:
|
|
#{changes}
|
|
INSTRUCTIONS
|
|
end
|
|
system "#{EDITOR} #{fname}", exception: true
|
|
release_notes = ""
|
|
File.readlines( fname ).each do | line |
|
|
release_notes += line unless line[0] == "#"
|
|
end
|
|
release_notes.strip!
|
|
FileUtils.rm( fname )
|
|
if release_notes.strip.size.zero?
|
|
reset_git_repo skip_clean: true
|
|
UI.abort_with_message! "You gotta enter release notes!"
|
|
end
|
|
if release_notes.strip.length > 500
|
|
reset_git_repo skip_clean: true
|
|
UI.abort_with_message! "Release notes must be 500 characters or less"
|
|
end
|
|
|
|
# Write release notes to a place where they can be translated and add that file to git
|
|
changelog_path = get_changelog_path( build_number )
|
|
File.open( changelog_path, "w" ) do | f |
|
|
f << "#{release_notes}\n"
|
|
end
|
|
changelog_git_path = File.join( "fastlane", changelog_path )
|
|
git_add( path: changelog_git_path )
|
|
|
|
# commit
|
|
commit_version_bump(
|
|
message: tag,
|
|
xcodeproj: XCODEPROJ,
|
|
include: [
|
|
"android/app/build.gradle",
|
|
changelog_git_path
|
|
]
|
|
)
|
|
push_to_git_remote
|
|
|
|
# Create a tag for this release
|
|
add_git_tag( tag: tag )
|
|
push_git_tags
|
|
end
|
|
|
|
platform :android do
|
|
lane :build do
|
|
desc "Build release files for Android"
|
|
Dir.children( "../android/app/src/main/assets/camera" ).each do | file |
|
|
next if ENV.value?( file )
|
|
|
|
UI.abort_with_message! <<~EXTRA_ANDROID_ASSETS_ERROR
|
|
Android assets folder has extraneous file: #{file}, which is not listed
|
|
as an environment variable. Remove any unreferenced files from
|
|
android/app/src/main/assets/camera before running a release build
|
|
EXTRA_ANDROID_ASSETS_ERROR
|
|
end
|
|
assets = File.open( "../android/link-assets-manifest.json" ) {| f | JSON.parse( f.read )["data"] }
|
|
linked_assets = []
|
|
assets.each do | asset |
|
|
linked_assets.push( asset["path"].split( "assets/fonts/" )[1] )
|
|
end
|
|
Dir.children( "../assets/fonts" ).each do | file |
|
|
next unless !linked_assets.include?( file ) && ( file != ".DS_Store" )
|
|
|
|
UI.abort_with_message! <<~EXTRA_ASSETS_ERROR
|
|
Assets folder has extraneous file: #{file}, which is not listed
|
|
in link-assets-manifest.json. Remove any unreferenced assets before
|
|
running a release build
|
|
EXTRA_ASSETS_ERROR
|
|
end
|
|
keystore_properties_path = File.join(
|
|
__dir__,
|
|
"..",
|
|
"android",
|
|
"keystore.properties"
|
|
)
|
|
unless File.exist?( keystore_properties_path )
|
|
UI.abort_with_message! <<~NO_KEYSTORE_PROPERTIES_ERROR.gsub( /\s+/, " " ).strip
|
|
Could not find #{keystore_properties_path}. Copy the example file in that directory to
|
|
that path and fill in the relevant values to build for Android.
|
|
NO_KEYSTORE_PROPERTIES_ERROR
|
|
end
|
|
build_number = get_build_number( xcodeproj: XCODEPROJ )
|
|
# Build AAB. This should write
|
|
# android/app/build/outputs/bundle/release/PACKAGE_ID-vVERSION_NAME+VERSION_CODE-release.aab
|
|
aab_path = get_aab_path( build_number )
|
|
if File.exist?( aab_path )
|
|
UI.important "AAB already exists at #{aab_path}"
|
|
else
|
|
gradle( task: "bundle", project_dir: "android" )
|
|
end
|
|
unless File.exist?( aab_path )
|
|
UI.abort_with_message! "Failed to create AAB at #{aab_path}"
|
|
end
|
|
|
|
# Build APK. This should write
|
|
# android/app/build/outputs/apk/release/PACKAGE_ID-vVERSION_NAME+VERSION_CODE-release.apk
|
|
apk_path = get_apk_path( build_number )
|
|
if File.exist?( apk_path )
|
|
UI.important "APK already exists at #{apk_path}"
|
|
else
|
|
gradle( task: "build", project_dir: "android", flags: "-x lint" )
|
|
end
|
|
unless File.exist?( apk_path )
|
|
UI.abort_with_message! "Failed to create APK at #{apk_path}"
|
|
end
|
|
end
|
|
|
|
lane :clean do
|
|
sh "rm -rf ../android/build", step_name: "Deleting Android build folder"
|
|
sh "(cd ../android && ./gradlew clean)", step_name: "Cleaning Android build environment"
|
|
Dir.glob( File.join( File.dirname( get_aab_path ), "*.aab" ) ).each do | aab_path |
|
|
UI.message "Deleting #{aab_path}"
|
|
File.delete aab_path
|
|
end
|
|
Dir.glob( File.join( File.dirname( get_apk_path ), "*.apk" ) ).each do | apk_path |
|
|
UI.message "Deleting #{apk_path}"
|
|
File.delete apk_path
|
|
end
|
|
end
|
|
end
|
|
|
|
platform :ios do
|
|
lane :build do
|
|
desc "Build release files for iOS"
|
|
# Build iOS app
|
|
get_certificates
|
|
get_provisioning_profile
|
|
ipa_path = get_ipa_path
|
|
if File.exist?( ipa_path )
|
|
UI.important "IPA already exists at #{ipa_path}"
|
|
else
|
|
build_app(
|
|
workspace: File.join( "ios", "iNaturalistReactNative.xcworkspace" ),
|
|
scheme: "iNaturalistReactNative",
|
|
output_directory: File.dirname( ipa_path ),
|
|
output_name: File.basename( ipa_path ),
|
|
# I'd rather not do it this way, but so far I haven't figured out a
|
|
# way for fastlane to automatically detect both of the provisioning
|
|
# profiles we need
|
|
export_options: {
|
|
signingStyle: "manual",
|
|
provisioningProfiles: {
|
|
IOS_BUNDLE_ID => ENV["IOS_PROVISIONING_PROFILE_NAME"],
|
|
ENV["IOS_SHARE_BUNDLE_ID"] => ENV["IOS_SHARE_PROVISIONING_PROFILE_NAME"]
|
|
}
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
lane :clean do
|
|
sh "rm -rf ios/build", step_name: "Deleting iOS build folder"
|
|
Dir.glob( File.join( File.dirname( get_ipa_path ), "*.ipa" ) ).each do | ipa_path |
|
|
UI.message "Deleting #{ipa_path}"
|
|
File.delete ipa_path
|
|
end
|
|
end
|
|
end
|
|
|
|
lane :build do
|
|
desc "Build release files for all platforms"
|
|
Fastlane::LaneManager.cruise_lane "ios", "build"
|
|
# commenting out temporarily to avoid building in Android
|
|
# Fastlane::LaneManager.cruise_lane "android", "build"
|
|
end
|
|
|
|
lane :clean do
|
|
desc "Delete build artifacts"
|
|
sh "rm -rf $TMPDIR/react-*", step_name: "Deleting React Native cache"
|
|
sh "rm -rf $TMPDIR/metro-*", step_name: "Deleting Metro cache"
|
|
sh "watchman watch-del-all", step_name: "Deleting watchman cache"
|
|
Fastlane::LaneManager.cruise_lane "ios", "clean"
|
|
Fastlane::LaneManager.cruise_lane "android", "clean"
|
|
end
|
|
|
|
lane :release do
|
|
desc "Make github release for the latest tag and make builds"
|
|
last_tag = last_git_tag
|
|
if last_tag.nil? || last_tag.empty?
|
|
UI.abort_with_message! "No tags have been added yet. Try starting with `fastlane tag`"
|
|
end
|
|
original_branch = git_branch
|
|
system "git checkout #{last_tag}", exception: true
|
|
build_number = get_build_number( xcodeproj: XCODEPROJ )
|
|
if build_number.to_s != last_tag.split( "+" ).last
|
|
UI.abort_with_message! <<~MSG
|
|
The last tag doesn't match the current build number. Either make a new
|
|
tag or check out the tag before releasing.
|
|
MSG
|
|
end
|
|
changelog_path = get_changelog_path( build_number )
|
|
unless File.exist?( changelog_path )
|
|
UI.abort_with_message! <<~MSG
|
|
No change log file exists at #{changelog_path}. That should have been
|
|
created when you ran `fastlane tag`.
|
|
MSG
|
|
end
|
|
|
|
build
|
|
|
|
# commenting out temporarily to avoid building in Android
|
|
# apk_path = File.expand_path( get_apk_path( build_number ) )
|
|
# if File.exist?( apk_path )
|
|
# UI.success "Found APK at #{apk_path}"
|
|
# else
|
|
# UI.abort_with_message! "Failed to find APK at #{apk_path}"
|
|
# end
|
|
|
|
# github_release = get_github_release(
|
|
# url: "inaturalist/iNaturalistReactNative",
|
|
# version: last_tag,
|
|
# api_token: ENV["GITHUB_TOKEN"]
|
|
# )
|
|
|
|
# if github_release
|
|
# UI.important "Release already exists at #{github_release['url']}. You need to manually upload any missing assets."
|
|
# else
|
|
# set_github_release(
|
|
# repository_name: "inaturalist/iNaturalistReactNative",
|
|
# api_token: ENV["GITHUB_TOKEN"],
|
|
# name: last_tag,
|
|
# tag_name: last_tag,
|
|
# description: begin
|
|
# File.read( changelog_path )
|
|
# rescue StandardError
|
|
# nil
|
|
# end,
|
|
# # This is really just a fallback in case last_tag isn't really a tag
|
|
# commitish: "main",
|
|
# upload_assets: [apk_path]
|
|
# )
|
|
# end
|
|
system "git checkout #{original_branch}", exception: true
|
|
end
|
|
|
|
lane :internal do
|
|
desc "Push builds for the latest tag for internal testing"
|
|
# commenting out temporarily to avoid building in Android
|
|
# Ensure build files exist for the latest tag
|
|
# aab_path = get_aab_path
|
|
# unless File.exist?( aab_path )
|
|
# UI.abort_with_message! <<~MSG
|
|
# AAB does not exist at #{aab_path}. You may need to run the release lane before doing this.
|
|
# MSG
|
|
# end
|
|
last_tag = last_git_tag
|
|
if last_tag.nil? || last_tag.empty?
|
|
UI.abort_with_message! "No tags have been added yet. Try starting with `fastlane tag`"
|
|
end
|
|
changelog_path = get_changelog_path
|
|
unless File.exist?( changelog_path )
|
|
UI.abort_with_message! <<~MSG
|
|
No change log file exists at #{changelog_path}. That should have been
|
|
created when you ran `fastlane tag`.
|
|
MSG
|
|
end
|
|
# commenting out temporarily to avoid building in Android
|
|
# upload_to_play_store(
|
|
# aab: aab_path,
|
|
# track: "internal",
|
|
# version_name: last_tag
|
|
# )
|
|
|
|
upload_to_testflight(
|
|
ipa: get_ipa_path,
|
|
changelog: File.read( changelog_path ),
|
|
# https://github.com/fastlane/fastlane/issues/20756
|
|
itc_provider: CredentialsManager::AppfileConfig.try_fetch_value( :itc_provider ),
|
|
# The following distributes the build to our invite-only external test group
|
|
distribute_external: true,
|
|
groups: [
|
|
"iNat Friends"
|
|
]
|
|
)
|
|
end
|
|
|
|
lane :beta do
|
|
desc "Make latest builds available for public testing"
|
|
|
|
build_number = get_build_number( xcodeproj: XCODEPROJ )
|
|
|
|
UI.message "Play Store: promoting build #{build_number} to beta track"
|
|
upload_to_play_store(
|
|
version_code: build_number,
|
|
track: "internal",
|
|
track_promote_to: "beta"
|
|
)
|
|
|
|
UI.message "TestFlight: adding build #{build_number} to Open Beta group"
|
|
add_build_to_testflight_beta_group(
|
|
version_code: build_number,
|
|
beta_group: "Open Beta"
|
|
)
|
|
end
|
|
|
|
lane :prod do
|
|
desc "Push builds for the latest tag to production"
|
|
build_number = get_build_number( xcodeproj: XCODEPROJ )
|
|
# In theory this will move the release associated with the build_number in
|
|
# the beta track to the production track and 100% rollout... but I haven't
|
|
# been able to test that yet
|
|
upload_to_play_store(
|
|
version_code: build_number,
|
|
track: "beta",
|
|
track_promote_to: "production"
|
|
)
|
|
UI.abort_with_message! "This doesn't upload iOS builds yet"
|
|
end
|