diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 0a4f855..3d7af8c 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -1,4 +1,3 @@ -require "fileutils" require "shellwords" default_platform(:ios) @@ -7,27 +6,26 @@ APP_IDENTIFIER = "net.buzzert.sybil2" SCHEME = "Sybil" TEAM_ID = "DQQH5H6GBD" PROFILE_NAME = "Sybil AppStore CI" -SIGNING_IDENTITY = "Apple Distribution: James Magahern (DQQH5H6GBD)" CI_KEYCHAIN_NAME = "sybil_ci_keychain" CI_KEYCHAIN_PASSWORD = "sybil-ci-keychain-password" +CI_KEYCHAIN_DB_PATH = File.expand_path("~/Library/Keychains/#{CI_KEYCHAIN_NAME}-db") IOS_ROOT = File.expand_path("..", __dir__) PROJECT_FILE = File.join(IOS_ROOT, "Sybil.xcodeproj") PROJECT_SPEC = File.join(IOS_ROOT, "project.yml") -CI_KEYCHAIN_PATH = File.join(File.expand_path("~/Library/Keychains"), CI_KEYCHAIN_NAME) -CI_KEYCHAIN_DB_PATH = "#{CI_KEYCHAIN_PATH}-db" -LOGIN_KEYCHAIN_PATH = File.expand_path("~/Library/Keychains/login.keychain") -LOGIN_KEYCHAIN_DB_PATH = "#{LOGIN_KEYCHAIN_PATH}-db" def present?(value) !value.to_s.strip.empty? end +def ci? + present?(ENV["CI"]) +end + def release_version - tag = ENV["SYBIL_VERSION_TAG"].to_s - tag = ENV["GITHUB_REF_NAME"].to_s if !present?(tag) - tag = ENV["GITHUB_REF"].to_s.sub(%r{\Arefs/tags/}, "") if !present?(tag) + tag = ENV["SYBIL_VERSION_TAG"] + tag = ENV["GITHUB_REF_NAME"] if !present?(tag) tag = sh("git describe --tags --abbrev=0").strip if !present?(tag) - version = tag.sub(%r{\Arelease/}, "").sub(/\Av/, "") + version = tag.to_s.sub(%r{\Arelease/}, "").sub(/\Av/, "") unless version.match?(/\A\d+\.\d+\.\d+\z/) UI.user_error!("Release tag must look like v1.2.3; got #{tag.inspect}") @@ -36,29 +34,20 @@ def release_version version end -def ci? - present?(ENV["CI"]) -end - # App Store Connect requires CFBundleVersion to be unique and strictly # increasing app-wide (not just per marketing version), so we derive it from # the monotonic CI run number rather than querying TestFlight (that query can # lag behind builds still processing and hand back a colliding value). def build_number - value = ENV["SYBIL_BUILD_NUMBER"].to_s - value = ENV["GITHUB_RUN_NUMBER"].to_s if !present?(value) + value = present?(ENV["SYBIL_BUILD_NUMBER"]) ? ENV["SYBIL_BUILD_NUMBER"] : ENV["GITHUB_RUN_NUMBER"] - unless value.match?(/\A\d+\z/) + unless value.to_s.match?(/\A\d+\z/) UI.user_error!("Build number must come from SYBIL_BUILD_NUMBER/GITHUB_RUN_NUMBER; got #{value.inspect}") end value.to_i end -def ci_keychain_path - File.file?(CI_KEYCHAIN_DB_PATH) ? CI_KEYCHAIN_DB_PATH : CI_KEYCHAIN_PATH -end - platform :ios do private_lane :app_store_api_key do app_store_connect_api_key( @@ -69,54 +58,27 @@ platform :ios do ) end - private_lane :setup_ci_signing do + # CI has no login keychain, so create a dedicated throwaway one for match to + # import the distribution cert into. The runner's launchd job sets + # SessionCreate, so add_to_search_list actually makes it visible to xcodebuild. + private_lane :prepare_ci_keychain do next unless ci? - FileUtils.mkdir_p(File.dirname(CI_KEYCHAIN_PATH)) - sh("security delete-keychain #{CI_KEYCHAIN_PATH.shellescape} || true", log: false) - FileUtils.rm_f(CI_KEYCHAIN_PATH) - FileUtils.rm_f(CI_KEYCHAIN_DB_PATH) - + delete_keychain(name: CI_KEYCHAIN_NAME) if File.file?(CI_KEYCHAIN_DB_PATH) create_keychain( - path: CI_KEYCHAIN_PATH, + name: CI_KEYCHAIN_NAME, password: CI_KEYCHAIN_PASSWORD, - default_keychain: false, unlock: true, timeout: 3600, - lock_when_sleeps: true, - add_to_search_list: false + add_to_search_list: true ) - sh("security default-keychain -d user -s #{CI_KEYCHAIN_PATH.shellescape}", log: false) - sh("security list-keychains -d user -s #{ci_keychain_path.shellescape}", log: false) - sh("security list-keychains -d dynamic -s #{ci_keychain_path.shellescape} || true", log: false) - sh("security list-keychains -d common -s #{ci_keychain_path.shellescape} || true", log: false) - - ENV["MATCH_KEYCHAIN_NAME"] = CI_KEYCHAIN_PATH + ENV["MATCH_KEYCHAIN_NAME"] = CI_KEYCHAIN_NAME ENV["MATCH_KEYCHAIN_PASSWORD"] = CI_KEYCHAIN_PASSWORD - ENV["MATCH_READONLY"] = "true" - end - - private_lane :cleanup_ci_signing do - next unless ci? - - if File.file?(LOGIN_KEYCHAIN_DB_PATH) || File.file?(LOGIN_KEYCHAIN_PATH) - sh("security default-keychain -d user -s #{LOGIN_KEYCHAIN_PATH.shellescape} || true", log: false) - sh("security list-keychains -d user -s #{LOGIN_KEYCHAIN_DB_PATH.shellescape} || true", log: false) - end - sh("security delete-keychain #{ci_keychain_path.shellescape} || true", log: false) - FileUtils.rm_f(CI_KEYCHAIN_PATH) - FileUtils.rm_f(CI_KEYCHAIN_DB_PATH) - rescue => error - UI.message("Unable to delete temporary CI keychain: #{error.message}") - ensure - ENV.delete("MATCH_KEYCHAIN_NAME") - ENV.delete("MATCH_KEYCHAIN_PASSWORD") - ENV.delete("MATCH_READONLY") end private_lane :sync_signing do |options| - match_options = { + match( type: "appstore", readonly: options.fetch(:readonly), app_identifier: APP_IDENTIFIER, @@ -127,28 +89,7 @@ platform :ios do git_full_name: "Sybil Release Bot", git_user_email: "james.magahern@me.com", api_key: options.fetch(:api_key) - } - match_options[:keychain_name] = ENV["MATCH_KEYCHAIN_NAME"] if present?(ENV["MATCH_KEYCHAIN_NAME"]) - match_options[:keychain_password] = ENV["MATCH_KEYCHAIN_PASSWORD"] if ENV.key?("MATCH_KEYCHAIN_PASSWORD") - - match(match_options) - end - - private_lane :verify_ci_signing do - next unless ci? - - if File.file?(ci_keychain_path) - password = ENV.fetch("MATCH_KEYCHAIN_PASSWORD", "") - sh("security unlock-keychain -p #{password.shellescape} #{ci_keychain_path.shellescape}", log: false) - sh("security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k #{password.shellescape} #{ci_keychain_path.shellescape}", log: false) - end - - identities = sh("security find-identity -v -p codesigning #{ci_keychain_path.shellescape}", log: false) - UI.message(identities) - - unless identities.include?(SIGNING_IDENTITY) - UI.user_error!("The CI keychain search list does not contain #{SIGNING_IDENTITY}") - end + ) end desc "Create or update match signing assets" @@ -158,43 +99,28 @@ platform :ios do desc "Build and upload to TestFlight" lane :beta do - setup_ci_signing + prepare_ci_keychain api_key = app_store_api_key - sh("xcodegen --spec #{PROJECT_SPEC.shellescape}") + sh("xcodegen", "--spec", PROJECT_SPEC) - increment_version_number( - version_number: release_version, - xcodeproj: PROJECT_FILE - ) - - increment_build_number( - build_number: build_number, - xcodeproj: PROJECT_FILE - ) + increment_version_number(version_number: release_version, xcodeproj: PROJECT_FILE) + increment_build_number(build_number: build_number, xcodeproj: PROJECT_FILE) sync_signing(api_key: api_key, readonly: true) - verify_ci_signing - - xcargs = [ - "DEVELOPMENT_TEAM=#{TEAM_ID.shellescape}", - "CODE_SIGN_STYLE=Manual", - "CODE_SIGN_IDENTITY=Apple\\ Distribution", - "PROVISIONING_PROFILE_SPECIFIER=#{PROFILE_NAME.shellescape}" - ] - - if ci? - xcargs << "CODE_SIGN_KEYCHAIN=#{ci_keychain_path.shellescape}" - xcargs << "OTHER_CODE_SIGN_FLAGS=#{("--keychain #{ci_keychain_path}").shellescape}" - end build_app( project: PROJECT_FILE, scheme: SCHEME, export_method: "app-store", codesigning_identity: "Apple Distribution", - xcargs: xcargs.join(" "), + xcargs: [ + "DEVELOPMENT_TEAM=#{TEAM_ID.shellescape}", + "CODE_SIGN_STYLE=Manual", + "CODE_SIGN_IDENTITY=Apple\\ Distribution", + "PROVISIONING_PROFILE_SPECIFIER=#{PROFILE_NAME.shellescape}" + ].join(" "), export_options: { signingStyle: "manual", teamID: TEAM_ID, @@ -208,7 +134,5 @@ platform :ios do api_key: api_key, skip_waiting_for_build_processing: true ) - ensure - cleanup_ci_signing end end