BuildScripts/xcode/commonFastfile

442 lines
14 KiB
Plaintext

$appName = File.basename(Dir['../*.xcworkspace'].first, '.*')
require_relative 'fastlane/touchlane/lib/touchlane'
# ugly hack to add support for custom storage
Match.module_eval do
def self.storage_modes
return %w(git google_cloud s3 local)
end
end
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(
repo_update: true
)
if File.exists? "../Cartfile"
use_rome = File.exists? "../Romefile"
swift_version = sh("xcrun swift --version | head -1 | sed 's/.*\\(\(.*\)\\).*/\\1/' | tr -d \"()\" | tr \" \" \"-\"").chop
rome_path = "Pods/Rome/rome"
rome_options = "--platform iOS --cache-prefix #{swift_version} --romefile Romefile"
carthage_install = lambda do
if use_rome
sh("cd .. && #{rome_path} download #{rome_options}")
end
carthage(command: "bootstrap", platform: "iOS", cache_builds: true)
if use_rome
sh("cd .. && #{rome_path} list --missing #{rome_options} | awk '{print $1}' | xargs -I framework_name #{rome_path} upload framework_name #{rome_options}")
end
end
begin
carthage_install.call
rescue
# workaround for https://github.com/Carthage/Carthage/issues/2298
sh("rm -rf ~/Library/Caches/org.carthage.CarthageKit")
carthage_install.call
end
end
end
private_lane :uploadToFirebase do |options|
releaseNotesFile = "release-notes.txt"
sh("touch ../#{releaseNotesFile}")
sh("yarn install")
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_distribution(
app: google_app_id,
ipa_path: options[:ipa_path],
groups: "touch-instinct",
release_notes_file: releaseNotesFile,
firebase_cli_path: File.expand_path("../node_modules/firebase-tools/lib/bin/firebase.js")
)
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(app_target_folder_name, configuration_type)
)
end
private_lane :uploadToAppStore do |options|
upload_to_app_store(
username: options[:username] || options[:apple_id],
ipa: options[:ipa_path],
force: true, # skip metainfo prompt
skip_metadata: true,
team_id: options[:itc_team_id],
dev_portal_team_id: options[:team_id]
)
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 :generateFeaturesFile do |options|
app_target_folder_name = options[:appName] || $appName
project_features_file_path = File.expand_path "../#{app_target_folder_name}/Resources/Features/Features.json"
build_settings_file_path = File.expand_path "../common/build_settings.yaml"
builder_features_list = options[:features]
.split(",").map { |feature_name| feature_name.strip } # [ "Feature1", "Feature2", "Feature3" ]
.reject { |feature_name| feature_name.empty? }
Touchlane::Features.generate_features_file_in_project(builder_features_list, build_settings_file_path, project_features_file_path)
end
private_lane :buildConfiguration do |options|
appName = options[:appName] || $appName
lane_name = options[:lane_name] || lane_context[SharedValues::LANE_NAME]
options[:scheme] = options[:scheme] || appName
options[:lane_name] = lane_name
configuration_type = Touchlane::ConfigurationType.from_lane_name(lane_name)
options = fill_up_options_using_configuration_type(options, configuration_type)
openKeychain(options)
if !options[:buildNumber].nil?
increment_build_number(
build_number: options[:buildNumber]
)
end
ipa_name = "#{appName}.ipa"
options[:output_name] = ipa_name
options[:ipa_path] = "./#{ipa_name}"
options[:dsym_path] = "./#{appName}.app.dSYM.zip"
options[:xcodeproj_path] = "../#{appName}.xcodeproj"
options[:workspace] = "./#{appName}.xcworkspace"
installDependencies(options)
unless options[:features].nil?
generateFeaturesFile(options)
end
if !(options[:uploadToFabric] || options[:uploadToAppStore])
options[:skip_package_ipa] = true
sync_code_signing_using_options(options)
buildArchive(options) # check build failures and static analysis
end
if options[:uploadToFabric]
sync_code_signing_using_options(options)
addShield(options)
buildArchive(options)
uploadToFirebase(options)
end
if options[:uploadToAppStore]
options[:compileBitcode] = options[:compileBitcode].nil? ? true : options[:compileBitcode]
options[:include_symbols] = options[:include_symbols].nil? ? true : options[:include_symbols]
sync_code_signing_using_options(options)
buildArchive(options)
uploadToAppStore(options)
end
end
private_lane :buildArchive do |options|
icloudEnvironment = options[:iCloudContainerEnvironment] || ""
exportOptions = icloudEnvironment.to_s.empty? ? {} : {iCloudContainerEnvironment: icloudEnvironment}
exportOptions[:compileBitcode] = options[:compileBitcode] || false
lane_name = options[:lane_name]
configuration = options[:configuration]
xcodeproj_path = options[:xcodeproj_path]
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: "./",
output_directory: "./",
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,
include_bitcode: options[:compileBitcode] || false,
)
end
lane :CreatePushCertificate do |options|
configuration = get_configuration_for_type(options[:type] || "development")
options = configuration.to_options.merge(options)
certificates_path = File.expand_path "../Certificates"
Dir.mkdir(certificates_path) unless File.directory?(certificates_path)
app_identifier = options[:app_identifier]
get_push_certificate(
development: options[:development].nil? ? true : options[:development],
generate_p12: true,
active_days_limit: 30, # create new certificate if old one will expire in 30 days
save_private_key: false,
app_identifier: (app_identifier.is_a? Array) ? app_identifier.first : app_identifier,
username: options[:username] || options[:apple_id],
team_id: options[:team_id],
p12_password: "123", # empty password won't work with Pusher
output_path: certificates_path
)
end
lane :SyncCodeSigning do |options|
configuration_type = Touchlane::ConfigurationType.from_type(options[:type])
options = fill_up_options_using_configuration_type(options, configuration_type)
sync_code_signing_using_options(options)
end
lane :SyncSymbols do |options|
configuration = get_configuration_for_type(options[:type])
options = configuration.to_options.merge(options)
appName = options[:appName] || $appName
xcodeproj_path = File.expand_path "../#{appName}.xcodeproj"
version_number = options[:version] || get_version_number(xcodeproj: xcodeproj_path, target: appName)
build_number = options[:build_number] || get_build_number(xcodeproj: xcodeproj_path)
if configuration.type.is_app_store
download_dsyms(
username: options[:username],
app_identifier: options[:app_identifier].first,
team_id: options[:itc_team_id],
version: version_number,
build_number: build_number
)
end
app_target_folder_name = appName
upload_symbols_to_crashlytics(
gsp_path: get_google_services_plist_path(app_target_folder_name, configuration.type)
)
clean_build_artifacts
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: false,
add_to_search_list: !keychain_exists
)
else
unlock_keychain(
path: options[:keychain_name],
password: options[:keychain_password]
)
end
end
lane :ManuallyUpdateCodeSigning do |options|
register_local_storage_for_match()
require 'match'
storage_factory = lambda do
new_storage = Match::Storage.for_mode('local', { git_url: get_signing_identities_path() })
new_storage.download
return new_storage
end
encryption_factory = lambda do |stor|
new_encryption = Match::Encryption.for_storage_mode('local', { working_directory: stor.working_directory })
new_encryption.decrypt_files
return new_encryption
end
get_all_files = lambda do |stor|
Dir[File.join(stor.working_directory, "**", "*.{cer,p12,mobileprovision}")]
end
storage = storage_factory.call
encryption = encryption_factory.call(storage)
old_files = get_all_files.call(storage)
sh("open #{storage.working_directory}")
# we are not using prompt() since it requires non-empty input which is not a case for Enter (\n)
puts "Enter any key when you're done"
STDIN.gets
encryption.encrypt_files
files_to_commit = get_all_files.call(storage)
old_directory = storage.working_directory
storage.save_changes!(files_to_commit: files_to_commit)
# need to check, because saving changes with delete is another function (update repo if needed)
files_diff = old_files - files_to_commit
# match can not work with both save/delete functionality `You can't provide both files_to_delete and files_to_commit right now`
# to avoid this we use storage twice if needed
if files_diff.length > 0
storage = storage_factory.call
encryption = encryption_factory.call(storage)
files_to_delete = files_diff.map do |file|
old_file = file
old_file.slice! old_directory
new_file = File.join(storage.working_directory, old_file)
File.delete(new_file) if File.exist?(new_file)
file = new_file
end
encryption.encrypt_files
storage.save_changes!(files_to_delete: files_to_delete)
end
end
def sync_code_signing_using_options(options)
register_local_storage_for_match()
match(
app_identifier: options[:app_identifier],
username: options[:username] || options[:apple_id],
team_id: options[:team_id],
type: options[:type],
readonly: options[:readonly].nil? ? true : options[:readonly],
storage_mode: "local",
# we can't pass signing_identities_path as parameter name since params is hardcoded in match/runner.rb
git_url: get_signing_identities_path(),
skip_docs: true,
keychain_name: options[:keychain_name],
keychain_password: options[:keychain_password]
)
end
def register_local_storage_for_match
Match::Storage.register_backend(type: 'local', storage_class: Touchlane::LocalStorage)
Match::Encryption.register_backend(type: 'local', encryption_class: Match::Encryption::OpenSSL)
end
def get_signing_identities_path
File.expand_path "../EncryptedSigningIdentities"
end
def fill_up_options_using_configuration_type(options, configuration_type)
configuration = get_configuration_for_type(configuration_type.type)
configuration.to_options
.merge(get_keychain_options(options))
.merge(options)
end
def get_keychain_options(options)
keychain_name = options[:keychain_name]
keychain_password = options[:keychain_password]
if is_ci?
keychain_name = keychain_name || "ci.keychain"
keychain_password = keychain_password || ""
else
keychain_password = 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/#{configuration_type.prefix}-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]
]
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|
build_configuration = target.build_configuration_list[configuration]
config_name = target.name + lane_name
build_configuration_reference = project.files.select { |f| f.path.start_with?(config_name) }.first
build_configuration.base_configuration_reference = build_configuration_reference
end
project.save()
end