import { actionIsTestable , createKeychain , deleteKeychain , getAction , getConfiguration , getDestination , getIdentity , getSchemeFromPackage , spawn , verbosity , xcselect , } from './lib' ; import type { Platform } from './lib' ; import xcodebuildX from './xcodebuild' ; import * as artifact from '@actions/artifact' ; import * as core from '@actions/core' ; import * as fs from 'fs' ; import * as path from 'path' ; import semver , { Range } from 'semver' ; const warningsAsErrorsFlags = 'OTHER_SWIFT_FLAGS=-warnings-as-errors' ; async function main ( ) { const cwd = core . getInput ( 'working-directory' ) ; if ( cwd ) { process . chdir ( cwd ) ; } const swiftPM = fs . existsSync ( 'Package.swift' ) ; const platform = getPlatformInput ( 'platform' ) ; const selected = await xcselect ( getRangeInput ( 'xcode' ) , getRangeInput ( 'swift' ) ) ; const action = getAction ( selected , platform ) ; const configuration = getConfiguration ( ) ; const warningsAsErrors = core . getBooleanInput ( 'warnings-as-errors' ) ; const destination = await getDestination ( selected , platform ) ; const identity = getIdentity ( core . getInput ( 'code-sign-identity' ) , platform ) ; const xcpretty = verbosity ( ) == 'xcpretty' ; core . info ( `» Selected Xcode ${ selected }` ) ; const reason = shouldGenerateXcodeproj ( ) ; if ( reason ) { generateXcodeproj ( reason ) ; } await configureKeychain ( ) ; await build ( await getScheme ( ) ) ; if ( core . getInput ( 'upload-logs' ) == 'always' ) { await uploadLogs ( ) ; } function getPlatformInput ( input ) { const value = core . getInput ( input ) ; if ( ! value ) return undefined ; return value as Platform ; } function getRangeInput ( input ) { const value = core . getInput ( input ) ; if ( ! value ) return undefined ; try { return new Range ( value ) ; } catch ( error ) { throw new Error ( `failed to parse semantic version range from '${ value }': ${ error }` ) ; } } function shouldGenerateXcodeproj ( ) { if ( ! swiftPM ) return false ; if ( platform == 'watchOS' && semver . lt ( selected , '12.5.0' ) ) { return 'Xcode <12.5 fails to build Swift Packages for watchOS if tests exist' ; } else if ( semver . lt ( selected , '11.0.0' ) ) { return 'Xcode <11 cannot build' ; } else if ( warningsAsErrors ) { return '`warningsAsErrors` is set' ; } return false ; } function generateXcodeproj ( reason ) { core . startGroup ( 'Generating `.xcodeproj`' ) ; try { core . info ( `Generating \`.xcodeproj\` ∵ ${ reason }` ) ; spawn ( 'swift' , [ 'package' , 'generate-xcodeproj' ] ) ; } finally { core . endGroup ( ) ; } } async function configureKeychain ( ) { const certificate = core . getInput ( 'code-sign-certificate' ) ; if ( ! certificate ) return ; if ( process . env . RUNNER_OS != 'macOS' ) { throw new Error ( 'code-sign-certificate requires macOS.' ) ; } const passphrase = core . getInput ( 'code-sign-certificate-passphrase' ) ; if ( ! passphrase ) { throw new Error ( 'code-sign-certificate requires code-sign-certificate-passphrase.' ) ; } await core . group ( 'Configuring code signing' , async ( ) => { await createKeychain ( certificate , passphrase ) ; } ) ; } async function build ( scheme ? ) { if ( warningsAsErrors && actionIsTestable ( action ) ) { await xcodebuild ( 'build' , scheme ) ; } await xcodebuild ( action , scheme ) ; } async function xcodebuild ( action ? , scheme ? ) { if ( action === 'none' ) return ; const title = [ 'xcodebuild' , action ] . filter ( ( x ) => x ) . join ( ' ' ) ; await core . group ( title , async ( ) => { let args = destination ; if ( scheme ) args = args . concat ( [ '-scheme' , scheme ] ) ; if ( identity ) args = args . concat ( identity ) ; if ( verbosity ( ) == 'quiet' ) args . push ( '-quiet' ) ; if ( configuration ) args = args . concat ( [ '-configuration' , configuration ] ) ; args = args . concat ( [ '-resultBundlePath' , `${ action ?? 'xcodebuild' }.xcresult` , ] ) ; switch ( action ) { case 'build' : if ( warningsAsErrors ) args . push ( warningsAsErrorsFlags ) ; break ; case 'test' : case 'build-for-testing' : if ( core . getBooleanInput ( 'code-coverage' ) ) { args = args . concat ( [ '-enableCodeCoverage' , 'YES' ] ) ; } break ; } if ( action ) args . push ( action ) ; await xcodebuildX ( args , xcpretty ) ; } ) ; } async function getScheme ( ) { const scheme = core . getInput ( 'scheme' ) ; if ( scheme ) { return scheme ; } if ( swiftPM ) { return getSchemeFromPackage ( ) ; } } } function post ( ) { deleteKeychain ( ) ; } async function run ( ) { const isPost = Boolean ( core . getState ( 'isPost' ) ) ; if ( isPost ) { post ( ) ; return ; } else { core . saveState ( 'isPost' , true ) ; } try { await main ( ) ; } catch ( error ) { await uploadLogs ( ) ; const id = `${ process . env . GITHUB_RUN_ID }` ; const slug = process . env . GITHUB_REPOSITORY ; const href = `https://github.com/${ slug }/actions/runs/${ id }#artifact` ; core . warning ( `
      We feel you.
      CI failures suck.
      Download the \`.xcresult\` files we just artifact’d.
      They *really* help diagnose what went wrong!
      ${ href }
      ` . replace ( /\s+/g , ' ' ) ) ; throw error ; } } run ( ) . catch ( ( e ) => { core . setFailed ( e ) ; if ( e instanceof SyntaxError && e . stack ) { core . error ( e . stack ) ; } } ) ; async function uploadLogs ( ) { const getFiles = ( directory ) => fs . readdirSync ( directory ) . map ( ( entry ) => path . join ( directory , entry ) ) . flatMap ( ( entry ) => fs . lstatSync ( entry ) . isDirectory ( ) ? getFiles ( entry ) : [ entry ] ) ; await core . group ( 'Uploading Logs' , async ( ) => { const xcresults = fs . readdirSync ( '.' ) . filter ( ( entry ) => path . extname ( entry ) == '.xcresult' ) ; if ( xcresults . length === 0 ) { core . warning ( 'strange… no `.xcresult` bundles found' ) ; } for ( const xcresult of xcresults ) { const nonce = Math . random ( ) . toString ( 36 ) . replace ( /[^a-zA-Z0-9]+/g , '' ) . substr ( 0 , 6 ) ; const base = path . basename ( xcresult , '.xcresult' ) ; const name = `${ base }#${ process . env . GITHUB_RUN_NUMBER }.${ nonce }.xcresult` ; await artifact . create ( ) . uploadArtifact ( name , getFiles ( xcresult ) , '.' ) ; } } ) ; }