383 lines
12 KiB
Plaintext
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 |