default_platform(:ios) platform :ios do desc 'Export ipa' lane :export_ipa do decode_file keychain_password = SecureRandom.uuid create_keychain( name: 'ios-build.keychain', password: keychain_password, default_keychain: true, unlock: true, timeout: 3600 ) if @is_split_cer import_certificate( certificate_path: if !ENV['P12_KEY_PATH'].empty? ENV['P12_KEY_PATH'] else 'ios-build-key.p12' end, certificate_password: ENV['CERTIFICATE_PASSWORD'], keychain_name: 'ios-build.keychain', keychain_password: keychain_password, log_output: true ) import_certificate( certificate_path: if !ENV['P12_CER_PATH'].empty? ENV['P12_CER_PATH'] else 'ios-build-key.cer' end, certificate_password: ENV['CERTIFICATE_PASSWORD'], keychain_name: 'ios-build.keychain', keychain_password: keychain_password, log_output: true ) else import_certificate( certificate_path: !ENV['P12_PATH'].empty? ? ENV['P12_PATH'] : 'ios-build.p12', certificate_password: ENV['CERTIFICATE_PASSWORD'], keychain_name: 'ios-build.keychain', keychain_password: keychain_password, log_output: true ) end @profiles.each { |profile| install_provisioning_profile(path: profile) } update_targets = nil if !ENV['UPDATE_TARGETS'].empty? update_targets = ENV['UPDATE_TARGETS'].split(/\R/) elsif !ENV['DISABLE_TARGETS'].empty? update_targets = ENV['DISABLE_TARGETS'].split(/,/) end update_code_signing_settings( use_automatic_signing: false, path: ENV['PROJECT_PATH'], code_sign_identity: ENV['CODE_SIGNING_IDENTITY'], targets: update_targets, entitlements_file_path: if !ENV['ENTITLMENTS_FILE_PATH'].empty? ENV['ENTITLMENTS_FILE_PATH'] else nil end ) if @profiles.length == 1 update_project_provisioning( xcodeproj: ENV['PROJECT_PATH'], profile: @profiles[0], target_filter: if update_targets update_targets.map { |target| "^#{Regexp.escape(target)}$" }.join( '|' ) else nil end ) end update_project_team( path: ENV['PROJECT_PATH'], teamid: ENV['TEAM_ID'], targets: update_targets ) use_export_options = !ENV['EXPORT_OPTIONS'].empty? if use_export_options doc = REXML::Document.new( if Pathname(ENV['EXPORT_OPTIONS']).absolute? File.read(ENV['EXPORT_OPTIONS']) else File.read("../#{ENV['EXPORT_OPTIONS']}") end ) profile_hash = {} doc.elements.each('//plist/dict/key') do |element| next if element.text != 'provisioningProfiles' element.next_element.elements.each('key') do |profile| profile_hash[profile.text] = profile.next_element.text end end end project = Xcodeproj::Project.open( if Pathname(ENV['PROJECT_PATH']).absolute? ENV['PROJECT_PATH'] else "../#{ENV['PROJECT_PATH']}" end ) project.targets.each do |target| if update_targets && !update_targets.empty? && !update_targets.include?(target.name) next end configuration = target.build_configurations.find do |bc| bc.name == ENV['CONFIGURATION'] end app_identifier = resolve_recursive_build_setting( configuration, 'PRODUCT_BUNDLE_IDENTIFIER' ) configuration.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = app_identifier if use_export_options && !profile_hash[app_identifier].nil? configuration.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = profile_hash[app_identifier] end end project.save use_workspace = !ENV['WORKSPACE_PATH'].empty? use_build_sdk = !ENV['BUILD_SDK'].empty? use_build_destination = !ENV['BUILD_DESTINATION'].empty? use_cloned_source_packages_path = !ENV['CLONED_SOURCE_PACKAGES_PATH'].empty? if !ENV['INCREMENT_BUILD_NUMBER'].empty? if ENV['INCREMENT_BUILD_NUMBER'] == 'true' increment_build_number(xcodeproj: ENV['PROJECT_PATH']) elsif ENV['INCREMENT_BUILD_NUMBER'] == 'testflight' api_key = app_store_connect_api_key( key_id: ENV['APP_STORE_CONNECT_API_KEY_ID'], issuer_id: ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'], key_content: ENV['APP_STORE_CONNECT_API_KEY_BASE64'], is_key_content_base64: true ) current_testflight_build_number = latest_testflight_build_number( api_key: api_key, app_identifier: ENV["BUNDLE_IDENTIFIER"], team_id: ENV['TEAM_ID'] ) increment_build_number( build_number: current_testflight_build_number + 1, xcodeproj: ENV['PROJECT_PATH'] ) else increment_build_number( build_number: ENV['INCREMENT_BUILD_NUMBER'], xcodeproj: ENV['PROJECT_PATH'] ) end end if !ENV['INCREMENT_VERSION_NUMBER'].empty? if ["patch", "minor", "major"].include?(ENV['INCREMENT_VERSION_NUMBER']) increment_version_number( bump_type: ENV['INCREMENT_VERSION_NUMBER'], xcodeproj: ENV['PROJECT_PATH'] ) else increment_version_number( version_number: ENV['INCREMENT_VERSION_NUMBER'], xcodeproj: ENV['PROJECT_PATH'] ) end end build_app( workspace: use_workspace ? ENV['WORKSPACE_PATH'] : nil, project: !use_workspace ? ENV['PROJECT_PATH'] : nil, configuration: ENV['CONFIGURATION'], scheme: ENV['SCHEME'], output_directory: File.dirname(ENV['OUTPUT_PATH']), output_name: File.basename(ENV['OUTPUT_PATH']), clean: true, cloned_source_packages_path: if use_cloned_source_packages_path ENV['CLONED_SOURCE_PACKAGES_PATH'] else nil end, export_method: ENV['EXPORT_METHOD'], export_options: use_export_options ? ENV['EXPORT_OPTIONS'] : nil, skip_profile_detection: use_export_options, sdk: use_build_sdk ? ENV['BUILD_SDK'] : nil, destination: use_build_destination ? ENV['BUILD_DESTINATION'] : nil ) delete_keychain(name: 'ios-build.keychain') end # https://github.com/CocoaPods/Xcodeproj/issues/505#issuecomment-584699008 # Augments config.resolve_build_setting from xcproject # to continue expanding build settings and evaluate modifiers def resolve_recursive_build_setting(config, setting) resolution = config.resolve_build_setting(setting) # finds values with one of # $VALUE # $(VALLUE) # $(VALUE:modifier) # ${VALUE} # ${VALUE:modifier} resolution&.gsub(/\$[\(\{]?.+[\)\}]?/) do |raw_value| # strip $() characters unresolved = raw_value.gsub(/[\$\(\)\{\}]/, '') # Get the modifiers after the ':' characters name, *modifiers = unresolved.split(':') # Expand variable name subresolution = resolve_recursive_build_setting(config, name) # Apply modifiers # NOTE: not all cases accounted for # # See http://codeworkshop.net/posts/xcode-build-setting-transformations # for various modifier options modifiers.each do |modifier| case modifier when 'lower' subresolution.downcase! when 'upper' subresolution.upcase! else # Fastlane message UI.error("Unknown modifier: `#{modifier}` in `#{raw_value}") end end subresolution end end def decode_file @is_split_cer = (!ENV['P12_KEY_BASE64'].empty? && !ENV['P12_CER_BASE64'].empty?) || (!ENV['P12_KEY_PATH'].empty? && !ENV['P12_CER_PATH'].empty?) if ENV['P12_PATH'].empty? && (ENV['P12_KEY_PATH'].empty? || ENV['P12_CER_PATH'].empty?) if @is_split_cer File.write( '../ios-build-key.p12', Base64.decode64(ENV['P12_KEY_BASE64']) ) File.write( '../ios-build-key.cer', Base64.decode64(ENV['P12_CER_BASE64']) ) else File.write('../ios-build.p12', Base64.decode64(ENV['P12_BASE64'])) end end @profiles = [] if ENV['MOBILEPROVISION_PATH'].empty? ENV['MOBILEPROVISION_BASE64'].split(/\R/).each.with_index( 1 ) do |profile, index| filename = "ios-build-#{index}.mobileprovision" puts "creating ../#{filename}" File.write("../#{filename}", Base64.decode64(profile)) @profiles.push(filename) end else ENV['MOBILEPROVISION_PATH'].split(/\R/).each.with_index( 1 ) { |profile, index| @profiles.push(profile) } end end end