# This file contains the fastlane.tools configuration # You can find the documentation at https://docs.fastlane.tools # # For a list of all available actions, check out # # https://docs.fastlane.tools/actions # # For a list of all available plugins, check out # # https://docs.fastlane.tools/plugins/available-plugins # fastlane_require "dotenv" default_platform(:ios) platform :ios do #### Pre #### before_all do # Ensure used Xcode version xcversion(version: "~> 11.4") end #### Public #### desc "Builds an adhoc ipa" lane :adhoc do |options| options[:adhoc] = true build_release(options) end desc "Builds an ipa for the App Store" lane :app_store do |options| build_release(options) end desc "Builds the ipa for the AppStore, then uploads it" lane :deploy_release do |options| require 'date' build_number = DateTime.now.strftime("%Y%m%d%H%M%S") options = { build_number: build_number }.merge(options) # build the IPA app_store(options) # upload the IPA and metadata deliver() # We haven't yet implemented/tested deliver/upload_to_appstore properly so keep it manual for now # UI.message("IPA is available at path '#{ENV['IPA_OUTPUT_PATH']}'. Please upload manually using Application Loader.") # UI.confirm("Have you uploaded the IPA to the AppStore now?") # end desc "Point MatrixKit and MatrixSDK to their respective release/*/release branch if they exist, develop otherwise" lane :point_dependencies_to_pending_releases do edit_podfile(branch_pattern: "release/*/release") end desc "Point MatrixKit and MatrixSDK to the branch with the same name as the current branch if such one exist, develop otherwise" lane :point_dependencies_to_same_feature do edit_podfile(branch_pattern: git_branch) end desc "Build the app for simulator to ensure it compiles" lane :build do |options| cocoapods app_name = "Riot" build_app( clean: true, scheme: app_name, derived_data_path: "./DerivedData/", buildlog_path: "./DerivedData/Logs/", # skip_package_ipa: true, skip_archive: true, destination: "generic/platform=iOS Simulator", ) end #### Private #### desc "Download App Store or Ad-Hoc provisioning profiles" private_lane :build_release do |options| UI.user_error!("'APPLE_ID' environment variable should be set to use this lane") unless !ENV["APPLE_ID"].to_s.empty? build_number = options[:build_number] UI.user_error!("'build_number' parameter is missing") unless !build_number.to_s.empty? # ad-hoc or app-store? adhoc = options.fetch(:adhoc, false) # Retrieve GIT branch name git_branch_name = git_branch UI.user_error!("Unable to retrieve GIT branch name") unless !git_branch_name.to_s.empty? # Fetch team id from Appfile team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id) UI.user_error!("Fail to fetch team id from Appfile") unless !team_id.to_s.empty? # Generate versioning preprocessor macros additional_preprocessor_definitions_hash = release_versioning_preprocessor_definitions(git_branch: git_branch_name, build_number: build_number) additional_preprocessor_definitions = additional_preprocessor_definitions_hash.map { |k, v| "#{k}=\"#{v}\"" }.join(" ") # Generate xcodebuild additional arguments xcargs_hash = { "GCC_PREPROCESSOR_DEFINITIONS" => "$(GCC_PREPROCESSOR_DEFINITIONS) #{additional_preprocessor_definitions}", "-UseNewBuildSystem" => "NO", } xcargs = xcargs_hash.map { |k, v| "#{k}=#{v.shellescape}" }.join(" ") # Clear derived data clear_derived_data(derived_data_path: ENV["DERIVED_DATA_PATH"]) # Setup project provisioning profiles download_provisioning_profiles(adhoc: adhoc) disable_automatic_code_signing update_project_provisioning_profiles # Update build number update_build_number(build_number: build_number) # On Xcode 10 with 'Parallelize Build' option on, archive randomly fails with error title "** ARCHIVE FAILED **" for various reasons. # Errors only occur on CocoaPods frameworks and the observed command that failed are CodeSign, CpHeader, CpResource, SetOwnerAndGroup. # To make archive reliable disable 'Parallelize Build' option of scheme ENV["SCHEME"] for the moment. disable_parallelize_builds # Perform a pod install cocoapods(repo_update: true) # Select a config if adhoc export_method = "ad-hoc" main_provisioning_profile = ENV["ADHOC_MAIN_PROVISIONING_PROFILE_SPECIFIER"] share_extension_provisioning_profile = ENV["ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_profile = ENV["ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] nse_provisioning_profile = ENV["ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER"] else export_method = "app-store" main_provisioning_profile = ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"] share_extension_provisioning_profile = ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_profile = ENV["APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] nse_provisioning_profile = ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"] end # Build app and create ipa build_app( clean: true, scheme: ENV["SCHEME"], xcargs: xcargs, export_method: export_method, derived_data_path: ENV["DERIVED_DATA_PATH"], archive_path: ENV["ARCHIVE_PATH"], output_directory: ENV["BUILD_OUTPUT_DIRECTORY"], output_name: "#{ENV["IPA_NAME"]}.ipa", buildlog_path: ENV["BUILD_LOG_DIRECTORY"], codesigning_identity: ENV["APPSTORE_CODESIGNING_IDENTITY"], skip_profile_detection: true, export_options: { method: export_method, signingStyle: "manual", teamID: team_id, signingCertificate: ENV["APPSTORE_SIGNING_CERTIFICATE"], provisioningProfiles: { ENV["MAIN_BUNDLE_ID"] => main_provisioning_profile, ENV["SHARE_EXTENSION_BUNDLE_ID"] => share_extension_provisioning_profile, ENV["NSE_BUNDLE_ID"] => nse_provisioning_profile, ENV["SIRI_INTENTS_EXTENSION_BUNDLE_ID"] => siri_intents_provisioning_profile, }, iCloudContainerEnvironment: "Production", }, ) end #### Private #### desc "Download App Store or Ad-Hoc provisioning profiles" private_lane :download_provisioning_profiles do |options| adhoc = options.fetch(:adhoc, false) output_path = ENV["PROVISIONING_PROFILES_PATH"] skip_certificate_verification = false main_provisioning_name = adhoc ? ENV["ADHOC_MAIN_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"] share_extension_provisioning_name = adhoc ? ENV["ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_name = adhoc ? ENV["ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] notification_service_extension_provisioning_name = adhoc ? ENV["ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"] # Main application get_provisioning_profile( app_identifier: ENV["MAIN_BUNDLE_ID"], provisioning_name: main_provisioning_name, ignore_profiles_with_different_name: true, adhoc: adhoc, skip_certificate_verification: skip_certificate_verification, output_path: output_path, filename: ENV["MAIN_PROVISIONING_PROFILE_FILENAME"], readonly: true, ) # Share extension get_provisioning_profile( app_identifier: ENV["SHARE_EXTENSION_BUNDLE_ID"], provisioning_name: share_extension_provisioning_name, ignore_profiles_with_different_name: true, adhoc: adhoc, skip_certificate_verification: skip_certificate_verification, output_path: output_path, filename: ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"], readonly: true, ) # Siri Intents extension get_provisioning_profile( app_identifier: ENV["SIRI_INTENTS_EXTENSION_BUNDLE_ID"], provisioning_name: siri_intents_provisioning_name, ignore_profiles_with_different_name: true, adhoc: adhoc, skip_certificate_verification: skip_certificate_verification, output_path: output_path, filename: ENV["SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_FILENAME"], readonly: true, ) # NSE get_provisioning_profile( app_identifier: ENV["NSE_BUNDLE_ID"], provisioning_name: notification_service_extension_provisioning_name, ignore_profiles_with_different_name: true, adhoc: adhoc, skip_certificate_verification: skip_certificate_verification, output_path: output_path, filename: ENV["NSE_PROVISIONING_PROFILE_FILENAME"], readonly: true, ) end desc "Update provisioning profiles for each target" private_lane :update_project_provisioning_profiles do provisioning_profiles_path = ENV["PROVISIONING_PROFILES_PATH"] build_configuration = "Release" xcodeproj = ENV["PROJECT_PATH"] # Main application update_project_provisioning( xcodeproj: xcodeproj, profile: "#{provisioning_profiles_path}#{ENV["MAIN_PROVISIONING_PROFILE_FILENAME"]}", target_filter: ENV["MAIN_TARGET"], build_configuration: build_configuration, ) # Share extension update_project_provisioning( xcodeproj: xcodeproj, profile: "#{provisioning_profiles_path}#{ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"]}", target_filter: ENV["SHARE_EXTENSION_TARGET"], build_configuration: build_configuration, ) # Siri Intents extension update_project_provisioning( xcodeproj: xcodeproj, profile: "#{provisioning_profiles_path}#{ENV["SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_FILENAME"]}", target_filter: ENV["SIRI_INTENTS_EXTENSION_TARGET"], build_configuration: build_configuration, ) # NSE update_project_provisioning( xcodeproj: xcodeproj, profile: "#{provisioning_profiles_path}#{ENV["NSE_PROVISIONING_PROFILE_FILENAME"]}", target_filter: ENV["NSE_TARGET"], build_configuration: build_configuration, ) end desc "Update application build number for all targets" private_lane :update_build_number do |options| build_number = options[:build_number] increment_build_number_in_xcodeproj( build_number: build_number, ) end desc "Returns version identifiers hash to inject in GCC_PREPROCESSOR_DEFINITIONS for release builds" private_lane :release_versioning_preprocessor_definitions do |options| preprocessor_definitions = Hash.new git_branch_name = options[:git_branch] build_number = options[:build_number] if !git_branch_name.to_s.empty? preprocessor_definitions["GIT_BRANCH"] = git_branch_name.sub("origin/", "").sub("heads/", "") end if !build_number.to_s.empty? preprocessor_definitions["BUILD_NUMBER"] = build_number end preprocessor_definitions end desc "Disable 'Parallelize Build' option of build action of main scheme" private_lane :disable_parallelize_builds do project_path = "../#{ENV["PROJECT_PATH"]}" scheme_name = ENV["SCHEME"] scheme_path = Xcodeproj::XCScheme.shared_data_dir(project_path) + "#{scheme_name}.xcscheme" scheme = Xcodeproj::XCScheme.new(scheme_path) scheme.build_action.xml_element.attributes["parallelizeBuildables"] = "NO" scheme.save_as(project_path, scheme_name) end desc "Edit the Podfile in order to point MatrixKit and MatrixSDK to the appropriate branches." private_lane :edit_podfile do |options| require 'net/http' branch_pattern = options[:branch_pattern] kit_slug = "matrix-org/matrix-ios-kit" sdk_slug = "matrix-org/matrix-ios-sdk" kit_branch = find_branch(kit_slug, branch_pattern) || 'develop' sdk_branch = find_branch(sdk_slug, branch_pattern) || 'develop' kit_spec = { git: 'https://github.com/matrix-org/matrix-ios-kit.git', branch: kit_branch } kit_podspec = { podspec: 'MatrixKit.edited.podspec' } sdk_spec = { git: 'https://github.com/matrix-org/matrix-ios-sdk.git', branch: sdk_branch } UI.message("✏️ Making a local copy of MatrixKit.podspec from the \`#{kit_branch}\` branch...") podspec_content = Net::HTTP.get(URI("https://raw.githubusercontent.com/#{kit_slug}/#{kit_branch}/MatrixKit.podspec")) UI.message "✏️ Editing local MatrixKit podspec to remove version constaint on 'MatrixSDK*' dependencies..." podspec_content.gsub!(%r{(\.dependency\s+(['"])MatrixSDK(\/[^'"]+)?\2).*$}, '\1') podspec_content.gsub!(%r{(\.source\s*=\s*).*$}, "\\1#{kit_spec}") File.write('../MatrixKit.edited.podspec', podspec_content) # current dir is 'fastlane/' hence the '../' UI.command_output("Content of MatrixKit.edited.podspec:\n" + podspec_content) UI.message "✏️ Modify Podfile to point MatrixKit to local podspec and `MatrixSDK/*` to \`#{sdk_branch}\` branch..." podfile_content = File.read('../Podfile') # current dir is 'fastlane/' hence the '../' podfile_content.gsub!(%r{^\$matrixKitVersion\s*=\s*.*$}, "$matrixKitVersion = { #{kit_podspec} => #{sdk_spec} }") File.write('../Podfile', podfile_content) UI.command_output("Content of modified Podfile:\n" + podfile_content) end # Find the latest branch with the given name pattern in the given repo def find_branch(repo_slug, pattern) list = `git ls-remote --heads --sort=version:refname https://github.com/#{repo_slug} #{pattern}` list.split("\n") .map { |line| line.sub(%r{[0-9a-f]+\trefs/heads/}, '').chomp } .last # Latest ref found, in "version:refname" semantic order end end