From f3613732a5642058dd8c60760db42f0ab2048cf9 Mon Sep 17 00:00:00 2001 From: Szymon Dziedzic Date: Mon, 14 Oct 2024 08:32:51 +0200 Subject: [PATCH 1/2] [local-build-plugin][build-tools][eas-build-job] copy Xcode build logs to target destination on local iOS build failure to simplify debuging --- packages/build-tools/src/builders/common.ts | 5 +++- packages/build-tools/src/context.ts | 3 +++ packages/eas-build-job/src/artifacts.ts | 11 +++++++++ packages/local-build-plugin/src/android.ts | 12 +++++---- packages/local-build-plugin/src/artifacts.ts | 26 +++++++++++++++++--- packages/local-build-plugin/src/ios.ts | 13 ++++++---- 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/build-tools/src/builders/common.ts b/packages/build-tools/src/builders/common.ts index 387eedf1..6d2ecc65 100644 --- a/packages/build-tools/src/builders/common.ts +++ b/packages/build-tools/src/builders/common.ts @@ -31,7 +31,10 @@ export async function runBuilderWithHooksAsync( }); }); - if (ctx.job.platform === Platform.IOS) { + if ( + ctx.job.platform === Platform.IOS && + (!buildSuccess || (buildSuccess && ctx.shouldUploadXcodeBuildLogsOnSuccess)) + ) { await findAndUploadXcodeBuildLogsAsync(ctx as BuildContext, { logger: ctx.logger, }); diff --git a/packages/build-tools/src/context.ts b/packages/build-tools/src/context.ts index 57ae90d8..66467106 100644 --- a/packages/build-tools/src/context.ts +++ b/packages/build-tools/src/context.ts @@ -62,6 +62,7 @@ export interface BuildContextOptions { reportBuildPhaseStats?: (stats: BuildPhaseStats) => void; skipNativeBuild?: boolean; metadata?: Metadata; + shouldUploadXcodeBuildLogsOnSuccess?: boolean; } export class SkipNativeBuildError extends Error {} @@ -77,6 +78,7 @@ export class BuildContext { options?: { tags?: Record; extras?: Record } ) => void; public readonly skipNativeBuild?: boolean; + public readonly shouldUploadXcodeBuildLogsOnSuccess: boolean; public artifacts: Artifacts = {}; private _env: Env; @@ -98,6 +100,7 @@ export class BuildContext { this.cacheManager = options.cacheManager; this._uploadArtifact = options.uploadArtifact; this.reportError = options.reportError; + this.shouldUploadXcodeBuildLogsOnSuccess = options.shouldUploadXcodeBuildLogsOnSuccess ?? true; // default to true if not provided const shouldApplyRepackOverrides = job.platform && job.mode === BuildMode.REPACK; this._job = { diff --git a/packages/eas-build-job/src/artifacts.ts b/packages/eas-build-job/src/artifacts.ts index 643176e3..be13d983 100644 --- a/packages/eas-build-job/src/artifacts.ts +++ b/packages/eas-build-job/src/artifacts.ts @@ -27,3 +27,14 @@ export const isGenericArtifact = < } return false; }; + +export const isManagedArtifact = < + TSpec extends { type: GenericArtifactType | ManagedArtifactType }, +>( + artifactSpec: TSpec +): artifactSpec is TSpec & { type: ManagedArtifactType } => { + if (Object.values(ManagedArtifactType).includes(artifactSpec.type as ManagedArtifactType)) { + return true; + } + return false; +}; diff --git a/packages/local-build-plugin/src/android.ts b/packages/local-build-plugin/src/android.ts index 1763e31c..37fed2e8 100644 --- a/packages/local-build-plugin/src/android.ts +++ b/packages/local-build-plugin/src/android.ts @@ -1,4 +1,4 @@ -import { Android, ManagedArtifactType, BuildPhase, Env } from '@expo/eas-build-job'; +import { Android, BuildPhase, Env, isManagedArtifact } from '@expo/eas-build-job'; import { Builders, BuildContext, Artifacts } from '@expo/build-tools'; import omit from 'lodash/omit'; @@ -23,10 +23,12 @@ export async function buildAndroidAsync( logger, logBuffer, uploadArtifact: async ({ artifact, logger }) => { - if (artifact.type === ManagedArtifactType.APPLICATION_ARCHIVE) { - return await prepareArtifacts(artifact.paths, logger); - } else if (artifact.type === ManagedArtifactType.BUILD_ARTIFACTS) { - return await prepareArtifacts(artifact.paths, logger); + if (isManagedArtifact(artifact)) { + return await prepareArtifacts({ + artifactPaths: artifact.paths, + logger, + artifactType: artifact.type, + }); } else { return null; } diff --git a/packages/local-build-plugin/src/artifacts.ts b/packages/local-build-plugin/src/artifacts.ts index 846dcbd1..fa1d7ffc 100644 --- a/packages/local-build-plugin/src/artifacts.ts +++ b/packages/local-build-plugin/src/artifacts.ts @@ -3,10 +3,19 @@ import path from 'path'; import { bunyan } from '@expo/logger'; import fs from 'fs-extra'; import * as tar from 'tar'; +import { ManagedArtifactType } from '@expo/eas-build-job'; import config from './config'; -export async function prepareArtifacts(artifactPaths: string[], logger?: bunyan): Promise { +export async function prepareArtifacts({ + artifactPaths, + artifactType, + logger, +}: { + artifactPaths: string[]; + artifactType: ManagedArtifactType; + logger?: bunyan; +}): Promise { const l = logger?.child({ phase: 'PREPARE_ARTIFACTS' }); l?.info('Preparing artifacts'); let suffix; @@ -35,10 +44,21 @@ export async function prepareArtifacts(artifactPaths: string[], logger?: bunyan) suffix = '.tar.gz'; localPath = archivePath; } - const artifactName = `build-${Date.now()}${suffix}`; + const artifactName = + artifactType === ManagedArtifactType.APPLICATION_ARCHIVE + ? `build-${Date.now()}${suffix}` + : artifactType === ManagedArtifactType.BUILD_ARTIFACTS + ? `artifacts-${Date.now()}${suffix}` + : `xcode-logs-${Date.now()}${suffix}`; const destPath = config.artifactPath ?? path.join(config.artifactsDir, artifactName); await fs.copy(localPath, destPath); - l?.info({ phase: 'PREPARE_ARTIFACTS' }, `Writing artifacts to ${destPath}`); + if (artifactType === ManagedArtifactType.APPLICATION_ARCHIVE) { + l?.info({ phase: 'PREPARE_ARTIFACTS' }, `Writing application archive to ${destPath}`); + } else if (artifactType === ManagedArtifactType.BUILD_ARTIFACTS) { + l?.info({ phase: 'PREPARE_ARTIFACTS' }, `Writing build artifacts to ${destPath}`); + } else { + l?.info({ phase: 'PREPARE_ARTIFACTS' }, `Writing Xcode logs to ${destPath}`); + } return destPath; } diff --git a/packages/local-build-plugin/src/ios.ts b/packages/local-build-plugin/src/ios.ts index 54ea1756..5f8d1c1c 100644 --- a/packages/local-build-plugin/src/ios.ts +++ b/packages/local-build-plugin/src/ios.ts @@ -1,4 +1,4 @@ -import { Ios, BuildPhase, Env, ManagedArtifactType } from '@expo/eas-build-job'; +import { Ios, BuildPhase, Env, isManagedArtifact } from '@expo/eas-build-job'; import { Builders, BuildContext, Artifacts } from '@expo/build-tools'; import omit from 'lodash/omit'; @@ -23,10 +23,12 @@ export async function buildIosAsync( logger, logBuffer, uploadArtifact: async ({ artifact, logger }) => { - if (artifact.type === ManagedArtifactType.APPLICATION_ARCHIVE) { - return await prepareArtifacts(artifact.paths, logger); - } else if (artifact.type === ManagedArtifactType.BUILD_ARTIFACTS) { - return await prepareArtifacts(artifact.paths, logger); + if (isManagedArtifact(artifact)) { + return await prepareArtifacts({ + artifactPaths: artifact.paths, + logger, + artifactType: artifact.type, + }); } else { return null; } @@ -34,6 +36,7 @@ export async function buildIosAsync( env, metadata, skipNativeBuild: config.skipNativeBuild, + shouldUploadXcodeBuildLogsOnSuccess: false, }); await ctx.runBuildPhase(BuildPhase.START_BUILD, async () => { From b14f0745942913f0c7c543f9756799bca2843b3d Mon Sep 17 00:00:00 2001 From: Szymon Dziedzic Date: Thu, 17 Oct 2024 16:01:09 +0200 Subject: [PATCH 2/2] apply suggested changes --- packages/build-tools/src/builders/common.ts | 6 ++-- packages/build-tools/src/builders/custom.ts | 31 ++++++++++++------- packages/build-tools/src/context.ts | 10 +++--- .../build-tools/src/ios/xcodeBuildLogs.ts | 3 +- .../functions/findAndUploadBuildArtifacts.ts | 1 + .../src/steps/functions/uploadArtifact.ts | 18 ++++++++--- packages/local-build-plugin/src/ios.ts | 9 ++++-- 7 files changed, 51 insertions(+), 27 deletions(-) diff --git a/packages/build-tools/src/builders/common.ts b/packages/build-tools/src/builders/common.ts index 6d2ecc65..c6e829b9 100644 --- a/packages/build-tools/src/builders/common.ts +++ b/packages/build-tools/src/builders/common.ts @@ -31,12 +31,10 @@ export async function runBuilderWithHooksAsync( }); }); - if ( - ctx.job.platform === Platform.IOS && - (!buildSuccess || (buildSuccess && ctx.shouldUploadXcodeBuildLogsOnSuccess)) - ) { + if (ctx.job.platform === Platform.IOS) { await findAndUploadXcodeBuildLogsAsync(ctx as BuildContext, { logger: ctx.logger, + runStatus: buildSuccess ? 'success' : 'errored', }); } diff --git a/packages/build-tools/src/builders/custom.ts b/packages/build-tools/src/builders/custom.ts index 916e5ed3..e9c3e48e 100644 --- a/packages/build-tools/src/builders/custom.ts +++ b/packages/build-tools/src/builders/custom.ts @@ -56,20 +56,29 @@ export async function runCustomBuildAsync(ctx: BuildContext): Promise< } }); try { - try { - await workflow.executeAsync(); - } finally { - if (!ctx.artifacts.XCODE_BUILD_LOGS && ctx.job.platform === Platform.IOS) { - try { - await findAndUploadXcodeBuildLogsAsync(ctx as BuildContext, { - logger: ctx.logger, - }); - } catch { - // do nothing, it's a non-breaking error. - } + await workflow.executeAsync(); + + if (!ctx.artifacts.XCODE_BUILD_LOGS && ctx.job.platform === Platform.IOS) { + try { + await findAndUploadXcodeBuildLogsAsync(ctx as BuildContext, { + logger: ctx.logger, + runStatus: 'success', + }); + } catch { + // do nothing, it's a non-breaking error. } } } catch (err: any) { + if (!ctx.artifacts.XCODE_BUILD_LOGS && ctx.job.platform === Platform.IOS) { + try { + await findAndUploadXcodeBuildLogsAsync(ctx as BuildContext, { + logger: ctx.logger, + runStatus: 'errored', + }); + } catch { + // do nothing, it's a non-breaking error. + } + } err.artifacts = ctx.artifacts; throw err; } diff --git a/packages/build-tools/src/context.ts b/packages/build-tools/src/context.ts index 66467106..221852a5 100644 --- a/packages/build-tools/src/context.ts +++ b/packages/build-tools/src/context.ts @@ -38,9 +38,14 @@ export interface LogBuffer { export type ArtifactToUpload = | { - type: ManagedArtifactType; + type: ManagedArtifactType.APPLICATION_ARCHIVE | ManagedArtifactType.BUILD_ARTIFACTS; paths: string[]; } + | { + type: ManagedArtifactType.XCODE_BUILD_LOGS; + paths: string[]; + runStatus: 'success' | 'errored'; + } | { type: GenericArtifactType; key: string; @@ -62,7 +67,6 @@ export interface BuildContextOptions { reportBuildPhaseStats?: (stats: BuildPhaseStats) => void; skipNativeBuild?: boolean; metadata?: Metadata; - shouldUploadXcodeBuildLogsOnSuccess?: boolean; } export class SkipNativeBuildError extends Error {} @@ -78,7 +82,6 @@ export class BuildContext { options?: { tags?: Record; extras?: Record } ) => void; public readonly skipNativeBuild?: boolean; - public readonly shouldUploadXcodeBuildLogsOnSuccess: boolean; public artifacts: Artifacts = {}; private _env: Env; @@ -100,7 +103,6 @@ export class BuildContext { this.cacheManager = options.cacheManager; this._uploadArtifact = options.uploadArtifact; this.reportError = options.reportError; - this.shouldUploadXcodeBuildLogsOnSuccess = options.shouldUploadXcodeBuildLogsOnSuccess ?? true; // default to true if not provided const shouldApplyRepackOverrides = job.platform && job.mode === BuildMode.REPACK; this._job = { diff --git a/packages/build-tools/src/ios/xcodeBuildLogs.ts b/packages/build-tools/src/ios/xcodeBuildLogs.ts index d1758a79..92be4621 100644 --- a/packages/build-tools/src/ios/xcodeBuildLogs.ts +++ b/packages/build-tools/src/ios/xcodeBuildLogs.ts @@ -9,7 +9,7 @@ import { BuildContext } from '../context'; export async function findAndUploadXcodeBuildLogsAsync( ctx: BuildContext, - { logger }: { logger: bunyan } + { logger, runStatus }: { logger: bunyan; runStatus: 'success' | 'errored' } ): Promise { try { const xcodeBuildLogsPath = await findXcodeBuildLogsPathAsync(ctx.buildLogsDirectory); @@ -18,6 +18,7 @@ export async function findAndUploadXcodeBuildLogsAsync( artifact: { type: ManagedArtifactType.XCODE_BUILD_LOGS, paths: [xcodeBuildLogsPath], + runStatus, }, logger, }); diff --git a/packages/build-tools/src/steps/functions/findAndUploadBuildArtifacts.ts b/packages/build-tools/src/steps/functions/findAndUploadBuildArtifacts.ts index 6abb02d0..e3352a28 100644 --- a/packages/build-tools/src/steps/functions/findAndUploadBuildArtifacts.ts +++ b/packages/build-tools/src/steps/functions/findAndUploadBuildArtifacts.ts @@ -148,6 +148,7 @@ async function uploadXcodeBuildLogs({ artifact: { type: ManagedArtifactType.XCODE_BUILD_LOGS, paths: [xcodeBuildLogsPath], + runStatus: global.hasAnyPreviousStepFailed ? 'errored' : 'success', }, logger, }); diff --git a/packages/build-tools/src/steps/functions/uploadArtifact.ts b/packages/build-tools/src/steps/functions/uploadArtifact.ts index dedf9a05..1e35d8b3 100644 --- a/packages/build-tools/src/steps/functions/uploadArtifact.ts +++ b/packages/build-tools/src/steps/functions/uploadArtifact.ts @@ -92,11 +92,19 @@ export function createUploadArtifactBuildFunction(ctx: CustomBuildContext): Buil throw result.reason; }); - const artifact = { - type: parseArtifactTypeInput(`${inputs.type.value}`), - paths: artifactPaths, - key: inputs.key.value as string, - }; + const artifactType = parseArtifactTypeInput(`${inputs.type.value}`); + const artifact = + artifactType === ManagedArtifactType.XCODE_BUILD_LOGS + ? ({ + type: artifactType, + paths: artifactPaths, + runStatus: global.hasAnyPreviousStepFailed ? 'errored' : 'success', + } as const) + : { + type: artifactType, + paths: artifactPaths, + key: inputs.key.value as string, + }; try { await ctx.runtimeApi.uploadArtifact({ diff --git a/packages/local-build-plugin/src/ios.ts b/packages/local-build-plugin/src/ios.ts index 5f8d1c1c..bf234a96 100644 --- a/packages/local-build-plugin/src/ios.ts +++ b/packages/local-build-plugin/src/ios.ts @@ -1,4 +1,4 @@ -import { Ios, BuildPhase, Env, isManagedArtifact } from '@expo/eas-build-job'; +import { Ios, BuildPhase, Env, isManagedArtifact, ManagedArtifactType } from '@expo/eas-build-job'; import { Builders, BuildContext, Artifacts } from '@expo/build-tools'; import omit from 'lodash/omit'; @@ -24,6 +24,12 @@ export async function buildIosAsync( logBuffer, uploadArtifact: async ({ artifact, logger }) => { if (isManagedArtifact(artifact)) { + if ( + artifact.type === ManagedArtifactType.XCODE_BUILD_LOGS && + artifact.runStatus !== 'errored' + ) { + return null; + } return await prepareArtifacts({ artifactPaths: artifact.paths, logger, @@ -36,7 +42,6 @@ export async function buildIosAsync( env, metadata, skipNativeBuild: config.skipNativeBuild, - shouldUploadXcodeBuildLogsOnSuccess: false, }); await ctx.runBuildPhase(BuildPhase.START_BUILD, async () => {