Created
September 7, 2017 20:52
Fastfile for Apple iOS build, resilient against Apple Developer Portal outages. Works with Xcode8.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Customise this file, documentation can be found here: | |
# https://github.com/fastlane/fastlane/tree/master/fastlane/docs | |
# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md | |
# can also be listed using the `fastlane actions` command | |
# Change the syntax highlighting to Ruby | |
# All lines starting with a # are ignored when running `fastlane` | |
# If you want to automatically update fastlane if a new version is available: | |
# update_fastlane | |
# This is the minimum version number required. | |
# Update this, if you use features of a newer version | |
fastlane_require 'versionomy' | |
fastlane_version "2.55.0" | |
default_platform :ios | |
$sentry_auth_token = "01234567890abcdef" | |
######################################### SUBROUTINES ################################################# | |
def gk_set_version | |
UI.verbose("Setting build number to #{$build_number}") | |
increment_build_number(build_number: $build_number) | |
UI.verbose("Setting application version to #{$app_version}") | |
increment_version_number(version_number: $app_version) | |
end | |
def gk_get_live_version | |
Spaceship::Tunes.login($username, ENV['FASTLANE_PASSWORD']) | |
app = Spaceship::Tunes::Application.find($appid) | |
UI.verbose("Current live version obtained from iTC: #{app.live_version.version}") | |
return app.live_version.version.to_s | |
end | |
def gk_create_temporary_keychain | |
# Delete possible leftover keychain file, otherwise creation of a new temporary keychain would fail | |
# Not needed if we can have a keychain file in the current working directory | |
if File.exists?($keychain_path) | |
UI.important("Deleting old keychain file: #{$keychain_path}") | |
FileUtils.rm_f($keychain_path) | |
end | |
# Cleanup keychain search list | |
# Setting keychain search list explicitly to the keychain path; if set to empty string then match would fail | |
sh("security list-keychains -s '#{$keychain_path}'") | |
# Creating temporary keychain | |
create_keychain( | |
path: $keychain_path, | |
default_keychain: true, | |
unlock: true, | |
timeout: false, | |
password: SecureRandom.base64 | |
) | |
end | |
def gk_provisioning( options={} ) | |
begin | |
register_devices( | |
devices_file: "./devices", | |
team_id: $team_id, | |
username: $username | |
) | |
rescue Exception => e | |
UI.error("Registering new devices failed with error #{e.message}.\nIt might be caused by wrong format of 'devices' file," + | |
" beware it has TSV format (tab separated values).\nIt might be also caused by Apple infrastructure issue, e.g. when developer portal" + | |
" returns status 302 or 500.\nThe build proceeds without registering the new devices on Apple developer portal.") | |
end | |
# Remove all existing local provisioning profiles and all distribution provisioning profiles on the development portal | |
sh('fastlane sigh manage --force --clean_pattern ".*"') | |
# Assure we have right adhoc distribution certificates and provisioning profiles | |
begin | |
match( | |
git_url: "git@github.com:ACME/ios-repository.git", | |
git_branch: $team_id, | |
type: "appstore", | |
app_identifier: [ $appid ], | |
keychain_name: $keychain_path, | |
team_id: $team_id, | |
username: $username, | |
verbose: true, | |
force: true, | |
shallow_clone: true, | |
) | |
rescue Exception => e | |
UI.error("Match action failed with error #{e.message}.\n" + | |
"It might be caused by Apple infrastructure issue, e.g. when developer portal returns status 302 or 500.\n" + | |
"Retrying Match action in read-only mode." + | |
" It will checkout provisioning profile and signing certificate from git, without attempting to validate it on Apple developer portal.") | |
begin | |
# As Apple Developer Portal is probably down, it is safe to presume that download of AppleWWDRCA.cer would fail too. | |
# That would fail match even in read-only mode. | |
# Therefore, installing AppleWWDRCA.cer from backup. | |
import_certificate( | |
keychain_path: $keychain_path, | |
certificate_path: "#{ENV['PWD']}/fastlane/AppleWWDRCA.cer", | |
log_output: true | |
) | |
match( | |
git_url: "git@github.com:ACME/ios-repository.git", | |
git_branch: $team_id, | |
type: "appstore", | |
app_identifier: [ $appid ], | |
keychain_name: $keychain_path, | |
team_id: $team_id, | |
username: $username, | |
verbose: true, | |
force: false, | |
shallow_clone: true, | |
readonly: true | |
) | |
rescue Exception => e | |
UI.error("Match action failed in read-only mode with error #{e.message}.\n") | |
raise e | |
end | |
end | |
# Disable automatic codesigning and set team | |
automatic_code_signing( | |
path: "#{ENV['PWD']}/ACMEapp.xcodeproj", | |
use_automatic_signing: false, | |
team_id: $team_id | |
) | |
if options[:mode] == "appstore" | |
profile = "#{ENV["sigh_#{$appid}_appstore_profile-path"]}" | |
else | |
profile = "#{ENV["sigh_#{$appid}_adhoc_profile-path"]}" | |
end | |
update_project_provisioning( | |
xcodeproj: "#{ENV['PWD']}/ACMEapp.xcodeproj", | |
profile: profile | |
# target_filter: ".*WatchKit Extension.*", # matches name or type of a target | |
# build_configuration: "Release" | |
) | |
end | |
def gk_sentry | |
sentry_upload_dsym( | |
auth_token: $sentry_auth_token, | |
org_slug: 'acme', | |
project_slug: $appid.gsub!('.','_'), | |
dsym_path: "#{ENV['PWD']}/build/#{$target}.app.dSYM.zip" | |
) | |
# Sentry's crash reporter will send the crash with release ID "#{appid}-#{app_version}" | |
# This release ID is visible in Sentry only as bubble help while hovering over release name | |
# Release name is app_version | |
# The action below creates the release even before first crash happens, what is useful for | |
# clarity and also browsing of Artifacts and Commits, if they are added to the release | |
sentry_create_release( | |
auth_token: $sentry_auth_token, | |
org_slug: 'acme', | |
project_slug: $appid.gsub('.','_'), | |
version: $app_version, | |
app_identifier: $appid, | |
finalize: true | |
) | |
end | |
def gk_hockey | |
# Uploading to Hockey. The name of IPA file is the same as the name of a target/scheme | |
hockey( | |
ipa: "./build/#{$target}.ipa", | |
bypass_cdn: true | |
) | |
end | |
def gk_changelog | |
# Release Notes (changelog) | |
changelog = "#{ENV['PWD']}/changelog/#{$display_name}-#{$app_version}.txt" | |
begin | |
FileUtils.cp(changelog,"#{ENV['PWD']}/fastlane/metadata/en-US/release_notes.txt") | |
UI.success("Release notes copied from file: '#{changelog}'") | |
rescue Errno::ENOENT => e | |
UI.important("Copying release notes from file '#{changelog}' failed with error #{e.message}." + | |
" Using default release notes '#{ENV['PWD']}/fastlane/metadata/en-US/release_notes.txt'") | |
end | |
end | |
def gk_testflight | |
# Uploading IPA to iTunes Connect for beta testing | |
testflight( | |
username: $username, | |
ipa: "#{ENV['PWD']}/build/#{$target}.ipa", | |
# BEWARE: Once IPA is uploaded to TestFlight and submitted for beta approval ( external distribution), it is not possible to submit another IPA, | |
# for external testing, even if the already uploaded IPA is manually expired. A new submission for TestFlight approval will not succeed until | |
# the original IPA is approved. | |
distribute_external: false, | |
# team_id is distinct from Developer portal team_id | |
team_id: $itc_team_id, | |
# After half an hour of polling with 30 sec interval, iTC returned "403 forbidden" | |
# Setting the polling interval longer means lower chance to running ino this issue | |
# The problem is being worked on https://github.com/fastlane/fastlane/issues/7768 | |
# wait_processing_interval sets polling interval in seconds | |
wait_processing_interval: 120, | |
changelog: "New test build", | |
beta_app_feedback_email: "apple@acme.com" | |
) | |
end | |
def gk_clean_build_archive_export | |
# While lane is executed in the current directory, 'sh' action executes in ./fastlane/ subdirectory | |
sh("xcodebuild -workspace #{ENV['PWD']}/ACMEapp.xcworkspace -scheme '#{$target}' -destination generic/platform=iOS -sdk iphoneos " + | |
" -archivePath #{ENV['PWD']}/build/ACMEapp.xcarchive clean build archive CODE_SIGN_IDENTITY=\"#{$certCN}\"") | |
# Exporting the archive | |
sh("xcodebuild -verbose -exportArchive -archivePath #{ENV['PWD']}/build/ACMEapp.xcarchive " + | |
" -exportOptionsPlist '#{ENV['PWD']}/exportOptions.#{$target}.plist' -exportPath #{ENV['PWD']}/build") | |
end | |
def gk_submit | |
deliver( | |
username: $username, | |
team_id: $itc_team_id, | |
app_identifier: $appid, | |
force: true, | |
#skip_screenshots: true, | |
overwrite_screenshots: true, | |
skip_binary_upload: true, | |
# It is not possible to skip metadata if Release Notes are not filled in already on iTC, | |
# because Release Notes are uploaded to iTC only if metadata upload is enabled. | |
# Without Release Notes it is not possible to submit the app for review. | |
#skip_metadata: true, | |
automatic_release: false, | |
app_version: $app_version, | |
build_number: $build_number, | |
submit_for_review: true, | |
submission_information: { | |
add_id_info_uses_idfa: true, | |
add_id_info_tracks_action: true, | |
add_id_info_serves_ads: true, | |
add_id_info_limits_tracking: true, # This one is checkbox acknowledging that we are compliant with terms, must be true | |
export_compliance_compliance_required: false, | |
export_compliance_uses_encryption: false, | |
} | |
) | |
end | |
######################################### PLATFORM AND LANES ########################################## | |
platform :ios do | |
before_all do | |
# Frequent updates of fastlane as necessary due to ever changing Apple Developer portal interface. | |
# update_fastlane | |
# | |
# ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." | |
cocoapods( | |
clean: true, | |
integrate: true, | |
verbose: true, | |
#repo_update: true, # Could be used to force pod repo update on each run | |
use_bundle_exec: false | |
) | |
end | |
##### Lane: DEVELOPMENT ######################## | |
desc "Development fastlane" | |
lane :development do | |
UI.header("Starting lane #{lane_context[SharedValues::LANE_NAME]}") | |
$keychain_path = "#{ENV['PWD']}/ci.keychain-db" | |
UI.verbose("Keychain path will be: #{$keychain_path}") | |
$team_id = "A0B1C2D3" | |
$target = "ACMEapp Dev" | |
$username = "apple@acme.com" | |
$appid = "com.acme.app.development" | |
$display_name = "Development" | |
# CN of code signing certificate | |
$certCN = "iPhone Distribution: ACME, Inc (A0B1C2D3)" | |
$build_number = ENV['CI_BUILD_NUMBER'] | |
$app_version = ENV['CI_APP_VERSION'] | |
UI.message("App version will be: #{$app_version}") | |
# Clean up derived data | |
clear_derived_data | |
gk_create_temporary_keychain | |
# Manage provisioning profiles and signing certificates, set project accordingly | |
gk_provisioning | |
# Set version and build number | |
gk_set_version | |
sh('env') | |
# Xcodebuild: clean, build, archive, exportArchive | |
gk_clean_build_archive_export | |
# Zipping symbols to be uploaded to Hockey for symbolication | |
sh("zip -r \'#{ENV['PWD']}/build/#{$target}.app.dSYM.zip\' \'#{ENV['PWD']}/build/ACMEapp.xcarchive/dSYMs\'") | |
# Upload symbols to sentry and finalize the release in sentry | |
gk_sentry | |
# Upload symbols and release to Hockey | |
gk_hockey | |
# Deleting temporary keychain, cleanup | |
delete_keychain( | |
keychain_path: $keychain_path | |
) | |
end | |
##### Lane: PRODUCTION APPSTORE ######################## | |
desc "Production Appstore fastlane" | |
lane :production_appstore do | |
UI.header("Starting lane #{lane_context[SharedValues::LANE_NAME]}") | |
$keychain_path = "#{ENV['PWD']}/ci.keychain-db" | |
UI.verbose("Keychain path will be: #{$keychain_path}") | |
$team_id = "A0B1C2D3" | |
$itc_team_id = "11223344" | |
$target = "ACMEapp AppStore" | |
$username = "apple@acme.com" | |
$appid = "com.acme.app" | |
$display_name = "ACME" | |
# CN of code signing certificate | |
$certCN = 'iPhone Distribution: ACME, INC (A0B1C2D3)' | |
$build_number = ENV['CI_BUILD_NUMBER'] | |
$app_version = ENV['CI_APP_VERSION'] | |
$app_version = Versionomy.parse(gk_get_live_version).bump(:tiny).to_s if $app_version.empty? | |
UI.message("App version will be: '#{$app_version}'") | |
# Clean up derived data | |
clear_derived_data | |
gk_create_temporary_keychain | |
# Manage provisioning profiles and signing certificates, set project accordingly | |
gk_provisioning(mode: "appstore") | |
# Set version and build number | |
gk_set_version | |
sh('env') | |
# Xcodebuild: clean, build, archive, exportArchive | |
gk_clean_build_archive_export | |
# Zipping symbols to be uploaded to Hockey for symbolication | |
sh("zip -r \'#{ENV['PWD']}/build/#{$target}.app.dSYM.zip\' \'#{ENV['PWD']}/build/ACMEapp.xcarchive/dSYMs\'") | |
# Upload symbols to sentry and finalize the release in sentry | |
gk_sentry | |
# Upload symbols and release to Hockey | |
gk_hockey | |
gk_changelog | |
# Uploading IPA to iTunes Connect for beta testing | |
gk_testflight | |
# Submitting for appstore review | |
gk_submit | |
# Deleting temporary keychain, cleanup | |
delete_keychain( | |
keychain_path: $keychain_path | |
) | |
end | |
###################################################################################################### | |
### FINAL COMMON BLOCK | |
###################################################################################################### | |
after_all do |lane| | |
# This block is called, only if the executed lane was successful | |
# slack( | |
# message: "Successfully deployed new App Update." | |
# ) | |
end | |
error do |lane, exception| | |
# slack( | |
# message: exception.message, | |
# success: false | |
# ) | |
end | |
end | |
# More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md | |
# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md | |
# fastlane reports which actions are used | |
# No personal data is recorded. Learn more at https://github.com/fastlane/enhancer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment