Skip to content

Instantly share code, notes, and snippets.

@tlvenn
Forked from GoodMirek/Fastfile
Created Sep 8, 2017
Embed
What would you like to do?
Fastfile for Apple iOS build, resilient against Apple Developer Portal outages. Works with Xcode8.
# 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