BuildScripts/xcode/commonFastfile

383 lines
12 KiB
Plaintext

$appName = File.basename(Dir['../*.xcworkspace'].first, '.*')
require_relative 'fastlane/touchlane/lib/touchlane'
private_lane :installDependencies do |options|
podsReposPath = File.expand_path "~/.cocoapods/repos/master/"
lockFilePath = "#{podsReposPath}/.git/index.lock"
# check if .lock file exists in pod repos - then remove all master repo
if File.exists? lockFilePath
sh("rm -rf #{podsReposPath}")
end
cocoapods(
try_repo_update_on_error: true
)
end
private_lane :uploadToFirebase do |options|
releaseNotesFile = "release-notes.txt"
sh("touch ../#{releaseNotesFile}")
app_target_folder_name = options[:appName] || $appName
configuration_type = Touchlane::ConfigurationType.from_type(options[:type])
gsp_plist_path = get_google_services_plist_path(app_target_folder_name, configuration_type)
google_app_id = get_info_plist_value(path: gsp_plist_path, key: "GOOGLE_APP_ID")
firebase_app_distibution_groups_path = File.expand_path "../firebase_app_distribution_groups"
# Select groups_file or groups parameter depending on groups file existence
if File.exists? firebase_app_distibution_groups_path
firebase_app_distribution(
app: google_app_id,
ipa_path: options[:ipa_path],
groups_file: firebase_app_distibution_groups_path,
release_notes_file: releaseNotesFile
)
else
firebase_app_distribution(
app: google_app_id,
ipa_path: options[:ipa_path],
groups: "touch-instinct",
release_notes_file: releaseNotesFile
)
end
end
def upload_to_app_store_using_options(options, submit_for_review = false)
upload_to_app_store(
username: options[:username] || options[:apple_id],
api_key_path: options[:api_key_path],
api_key: options[:api_key],
ipa: options[:ipa_path],
build_number: options[:ipa_path].nil? ? options[:buildNumber] : nil,
skip_binary_upload: options[:ipa_path].nil?,
skip_screenshots: true,
force: true, # skip metainfo prompt
submit_for_review: submit_for_review,
submission_information: options[:submission_information],
skip_metadata: true,
team_id: options[:itc_team_id],
dev_portal_team_id: options[:team_id],
precheck_include_in_app_purchases: false
)
end
private_lane :addShield do |options|
buildNumber = options[:buildNumber]
buildDescription = options[:lane_name] # EnterpriseCustomerDev1WithoutSSLPinningRelease
.split(/(?=[A-Z])/) # -> ["Enterprise", "Customer", "Dev1", "Without", "S", "S", "L", "Pinning", "Release"]
.map { |v| v.gsub(/[[:lower:]]+/, "") }[1..2] # -> ["E", "C", "D1", "W", "S", "S", "L", "P", "R"] -> ["C", "D1"]
.join # -> "CD1"
begin
add_badge(
shield: "#{buildDescription}-#{buildNumber}-green",
no_badge: true
)
rescue => error
UI.error(error)
end
end
private_lane :buildConfiguration do |options|
options[:appName] = options[:appName] || $appName
lane_name = options[:lane_name] || lane_context[SharedValues::LANE_NAME]
options[:scheme] = options[:scheme] || options[:appName]
options[:lane_name] = lane_name
ipa_name = "#{options[:appName]}.ipa"
options[:output_name] = ipa_name
options[:ipa_path] = "./#{ipa_name}"
options[:dsym_path] = "./#{options[:appName]}.app.dSYM.zip"
options[:xcodeproj_path] = options[:xcodeproj_path] || "../#{options[:appName]}.xcodeproj"
options[:workspace] = options[:workspace] || File.expand_path("../#{options[:appName]}.xcworkspace")
configuration_type = Touchlane::ConfigurationType.from_lane_name(lane_name)
options = fill_up_options_using_configuration_type(options, configuration_type, true)
generate_xcodeproj_if_needed(options)
openKeychain(options)
if !options[:buildNumber].nil?
increment_build_number(
build_number: options[:buildNumber]
)
end
installDependencies(options)
run_code_generation_phase_if_needed(options)
if !(options[:uploadToFabric] || options[:uploadToAppStore])
options[:skip_package_ipa] = true
install_signing_identities(options)
buildArchive(options) # check build failures and static analysis
end
if options[:uploadToFabric]
install_signing_identities(options)
addShield(options)
buildArchive(options)
uploadToFirebase(options)
end
if options[:uploadToAppStore]
options[:include_symbols] = options[:include_symbols].nil? ? true : options[:include_symbols]
install_signing_identities(options)
buildArchive(options)
upload_to_app_store_using_options(options, false)
end
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(options[:appName], configuration_type)
)
end
private_lane :buildArchive do |options|
require 'json'
icloudEnvironment = options[:iCloudContainerEnvironment] || ""
exportOptions = icloudEnvironment.to_s.empty? ? {} : {iCloudContainerEnvironment: icloudEnvironment}
lane_name = options[:lane_name]
configuration = options[:configuration]
xcodeproj_path = options[:xcodeproj_path]
xcodes(select_for_current_build_only: true)
if configuration != "AppStore" # AppStore uses xcconfig choosen in Xcode
set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
end
gym(
clean: true,
workspace: options[:workspace],
scheme: options[:scheme],
archive_path: "./#{$appName}.xcarchive",
buildlog_path: "./",
output_name: options[:output_name],
configuration: configuration,
export_method: options[:export_method],
export_options: exportOptions,
skip_package_ipa: options[:skip_package_ipa],
include_symbols: options[:include_symbols] || false
)
end
lane :SubmitForReview do |options|
configuration_type = Touchlane::ConfigurationType.from_type("appstore")
options = fill_up_options_using_configuration_type(options, configuration_type, false)
upload_to_app_store_using_options(options, true)
end
lane :InstallDevelopmentSigningIdentities do |options|
configuration_type = Touchlane::ConfigurationType.from_type("development")
options = fill_up_options_using_configuration_type(options, configuration_type)
install_signing_identities(options)
end
lane :RefreshProfiles do |options|
type = options[:type] || "development"
configuration_type = Touchlane::ConfigurationType.from_type(type)
options = fill_up_options_using_configuration_type(options, configuration_type)
refresh_profiles(options)
end
lane :ReplaceDevelopmentCertificate do |options|
configuration_type = Touchlane::ConfigurationType.from_type("development")
options = fill_up_options_using_configuration_type(options, configuration_type, true)
replace_development_certificate(options)
end
lane :SyncAppStoreIdentities do |options|
configuration_type = Touchlane::ConfigurationType.from_type("appstore")
options = fill_up_options_using_configuration_type(options, configuration_type, true)
options[:readonly] = false
sync_signing_identities(options)
end
lane :ManuallyUpdateCodeSigning do |options|
manually_update_code_signing(get_default_options.merge(options))
end
private_lane :openKeychain do |options|
if is_ci?
# workaround to avoid duplication problem
# https://apple.stackexchange.com/questions/350633/multiple-duplicate-keychain-dbs-that-dont-get-cleaned-up
keychain_path = File.expand_path("~/Library/Keychains/#{options[:keychain_name]}")
keychain_exists = File.exist?("#{keychain_path}-db") || File.exist?(keychain_path)
create_keychain(
name: options[:keychain_name],
password: options[:keychain_password],
unlock: true,
timeout: 0,
add_to_search_list: !keychain_exists
)
else
unlock_keychain(
path: options[:keychain_name],
password: options[:keychain_password]
)
end
end
def get_default_options
{
:git_url => get_signing_identities_path(),
:signing_identities_path => get_signing_identities_path(),
:storage_mode => Touchlane::LocalStorage::STORAGE_TYPE
}
end
def get_signing_identities_path
File.expand_path "../EncryptedSigningIdentities"
end
def fill_up_options_using_configuration_type(options, configuration_type, keychain_password_required = false)
configuration = get_configuration_for_type(configuration_type.type)
api_key_path = File.expand_path "../fastlane/#{configuration_type.prefix}_api_key.json"
is_api_key_file_exists = File.exists?(api_key_path)
# default_options required to be empty due to the possibility of skipping the configuration type check below
default_options = get_default_options
# Check whether configuration type is required to configure one of api key parameters or not
if configuration_type.is_app_store || configuration_type.is_development
# Check whether API key JSON file exists or not
if is_api_key_file_exists
# If exists then fill in all required information through api_key_path parameter
# and set a value to an options` parameter respectively
default_options[:api_key_path] = api_key_path
else
# If doesn't exist then build api_key parameter through app_store_connect_api_key action
# and set a value to an options` parameter respectively also
default_options[:api_key] = get_app_store_connect_api_key()
end
end
default_options
.merge(configuration.to_options)
.merge(get_keychain_options(options, keychain_password_required))
.merge(options)
end
def get_app_store_connect_api_key()
require 'json'
api_key_parameters = JSON.parse(ENV['API_KEY_JSON'])
return app_store_connect_api_key(
key_id: api_key_parameters['key_id'],
issuer_id: api_key_parameters['issuer_id'],
key_content: api_key_parameters['key'],
duration: api_key_parameters['duration'],
in_house: api_key_parameters['in_house']
)
end
def get_keychain_options(options, keychain_password_required = false)
keychain_name = options[:keychain_name]
keychain_password = options[:keychain_password]
if is_ci?
keychain_name = keychain_name || "ci.keychain"
keychain_password = keychain_password || ""
elsif keychain_password_required && keychain_password.nil?
keychain_password = prompt(
text: "Please enter your keychain password (account password): ",
secure_text: true
)
end
return {:keychain_name => keychain_name, :keychain_password => keychain_password}
end
def get_configuration_for_type(type)
config_path = File.expand_path "configurations.yaml"
configuration = Touchlane::Configuration.from_file(config_path, type)
end
def get_google_services_plist_path(app_target_folder_name, configuration_type)
File.expand_path "../#{app_target_folder_name}/Resources/GoogleService-Info.plist"
end
def set_xcconfig_for_configuration_of_project(lane_name, configuration, xcodeproj_path)
require 'xcodeproj'
project = Xcodeproj::Project.open(xcodeproj_path)
target_to_modify_selector = lambda do |t|
supported_product_types = [
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:application],
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:app_extension],
Xcodeproj::Constants::PRODUCT_TYPE_UTI[:framework]
]
return !t.test_target_type? && supported_product_types.include?(t.product_type)
end
application_targets = project.native_targets.select(&target_to_modify_selector)
application_targets.each do |target|
config_name = target.name + lane_name
build_configuration_reference = project.files.select { |f| f.path.start_with?(config_name) }.first
if !build_configuration_reference.nil? # target has custom xcconfig
build_configuration = target.build_configuration_list[configuration]
build_configuration.base_configuration_reference = build_configuration_reference
end
end
project.save()
end
def generate_xcodeproj_if_needed(options)
project_yml_path = File.expand_path "../project.yml"
if !File.exists?(options[:xcodeproj_path]) && File.exists?(project_yml_path)
xcodegen(
spec: project_yml_path
)
end
end
# Build phases
def run_code_generation_phase_if_needed(options)
code_generation_script_path = File.expand_path "../.githooks/scripts/CodeGen.sh"
if File.exists? code_generation_script_path
sh(code_generation_script_path, options[:workspace])
end
end