Skip to content

Commit

Permalink
Add workflowInterpolationContext
Browse files Browse the repository at this point in the history
  • Loading branch information
sjchmiela committed Oct 15, 2024
1 parent 2235734 commit 2572e31
Show file tree
Hide file tree
Showing 18 changed files with 216 additions and 52 deletions.
8 changes: 5 additions & 3 deletions packages/build-tools/src/customBuildContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import path from 'path';
import {
BuildJob,
BuildPhase,
BuildStaticContext,
BuildTrigger,
Env,
Job,
Metadata,
Platform,
StaticJobInterpolationContext,
} from '@expo/eas-build-job';
import { bunyan } from '@expo/logger';
import { ExternalBuildContextProvider, BuildRuntimePlatform } from '@expo/steps';
Expand Down Expand Up @@ -92,11 +92,13 @@ export class CustomBuildContext<TJob extends Job = Job> implements ExternalBuild
return this._env;
}

public staticContext(): BuildStaticContext {
public staticContext(): StaticJobInterpolationContext {
return {
...this.job.workflowInterpolationContext,
job: this.job,
metadata: this.metadata ?? null,
env: this.env,
metadata: this.metadata ?? null,
steps: {},
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/eas-build-job/src/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EnvironmentSecret,
BuildTrigger,
BuildMode,
StaticWorkflowInterpolationContext,
} from './common';

export interface Keystore {
Expand Down Expand Up @@ -110,6 +111,8 @@ export interface Job {
submitProfile?: string;
};
loggerLevel?: LoggerLevel;

workflowInterpolationContext?: StaticWorkflowInterpolationContext;
}

const SecretsSchema = Joi.object({
Expand Down Expand Up @@ -175,4 +178,6 @@ export const JobSchema = Joi.object({
submitProfile: Joi.string(),
}),
loggerLevel: Joi.string().valid(...Object.values(LoggerLevel)),

workflowInterpolationContext: Joi.any(),
});
35 changes: 35 additions & 0 deletions packages/eas-build-job/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,38 @@ export interface BuildPhaseStats {
result: BuildPhaseResult;
durationMs: number;
}

export const StaticWorkflowInterpolationContextZ = z.object({
needs: z.record(
z.string(),
z.object({
status: z.string(),
outputs: z.record(z.string(), z.string().nullable()),
})
),
github: z
.object({
event_name: z.enum(['push', 'pull_request', 'workflow_dispatch']),
sha: z.string(),
ref: z.string(),
})
// We need to .optional() to support jobs that are not triggered by a GitHub event.
.optional(),
env: z.record(z.string()),
});

export type StaticWorkflowInterpolationContext = z.infer<
typeof StaticWorkflowInterpolationContextZ
>;

export type DynamicInterpolationContext = {
success: () => boolean;
failure: () => boolean;
always: () => boolean;
never: () => boolean;
fromJSON: (value: string) => unknown;
toJSON: (value: unknown) => string;
};

export type WorkflowInterpolationContext = StaticWorkflowInterpolationContext &
DynamicInterpolationContext;
16 changes: 14 additions & 2 deletions packages/eas-build-job/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { Env } from './common';
import { DynamicInterpolationContext, Env, StaticWorkflowInterpolationContext } from './common';
import { Job } from './job';
import { Metadata } from './metadata';

export type BuildStaticContext = {
type StaticJobOnlyInterpolationContext = {
job: Job;
metadata: Metadata | null;
env: Env;
steps: Record<
string,
{
outputs: Record<string, string | undefined>;
}
>;
};

export type StaticJobInterpolationContext =
| (StaticWorkflowInterpolationContext & StaticJobOnlyInterpolationContext)
| StaticJobOnlyInterpolationContext;

export type JobInterpolationContext = StaticJobInterpolationContext & DynamicInterpolationContext;
8 changes: 7 additions & 1 deletion packages/eas-build-job/src/generic.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { z } from 'zod';
import { LoggerLevel } from '@expo/logger';

import { ArchiveSourceSchemaZ, BuildTrigger, EnvironmentSecretZ } from './common';
import {
ArchiveSourceSchemaZ,
BuildTrigger,
EnvironmentSecretZ,
StaticWorkflowInterpolationContextZ,
} from './common';
import { StepZ } from './step';

export namespace Generic {
Expand Down Expand Up @@ -33,6 +38,7 @@ export namespace Generic {
type: z.never().optional(),
triggeredBy: z.literal(BuildTrigger.GIT_BASED_INTEGRATION),
loggerLevel: z.nativeEnum(LoggerLevel).optional(),
workflowInterpolationContext: StaticWorkflowInterpolationContextZ.optional(),
});

const PathJobZ = CommonJobZ.extend({
Expand Down
1 change: 1 addition & 0 deletions packages/eas-build-job/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
Workflow,
Platform,
Cache,
WorkflowInterpolationContext,
} from './common';
export { Metadata, sanitizeMetadata, FingerprintSource, FingerprintSourceType } from './metadata';
export * from './job';
Expand Down
4 changes: 4 additions & 0 deletions packages/eas-build-job/src/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EnvironmentSecret,
BuildTrigger,
BuildMode,
StaticWorkflowInterpolationContext,
} from './common';

export type DistributionType = 'store' | 'internal' | 'simulator';
Expand Down Expand Up @@ -124,6 +125,8 @@ export interface Job {
submitProfile?: string;
};
loggerLevel?: LoggerLevel;

workflowInterpolationContext?: StaticWorkflowInterpolationContext;
}

const SecretsSchema = Joi.object({
Expand Down Expand Up @@ -209,4 +212,5 @@ export const JobSchema = Joi.object({
submitProfile: Joi.string(),
}),
loggerLevel: Joi.string().valid(...Object.values(LoggerLevel)),
workflowInterpolationContext: Joi.any(),
});
45 changes: 44 additions & 1 deletion packages/steps/src/BuildStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs/promises';
import path from 'path';

import { v4 as uuidv4 } from 'uuid';
import { JobInterpolationContext } from '@expo/eas-build-job';

import { BuildStepContext, BuildStepGlobalContext } from './BuildStepContext.js';
import { BuildStepInput, BuildStepInputById, makeBuildStepInputByIdMap } from './BuildStepInput.js';
Expand All @@ -26,6 +27,7 @@ import { BuildStepRuntimeError } from './errors.js';
import { BuildStepEnv } from './BuildStepEnv.js';
import { BuildRuntimePlatform } from './BuildRuntimePlatform.js';
import { jsepEval } from './utils/jsepEval.js';
import { interpolateJobContext } from './interpolation.js';

export enum BuildStepStatus {
NEW = 'new',
Expand Down Expand Up @@ -69,6 +71,10 @@ export class BuildStepOutputAccessor {
protected readonly outputById: BuildStepOutputById
) {}

public get outputs(): BuildStepOutput[] {
return Object.values(this.outputById);
}

public getOutputValueByName(name: string): string | undefined {
if (!this.executed) {
throw new BuildStepRuntimeError(
Expand Down Expand Up @@ -341,11 +347,48 @@ export class BuildStep extends BuildStepOutputAccessor {
);
}

private getInterpolationContext(): JobInterpolationContext {
const hasAnyPreviousStepFailed = this.ctx.global.hasAnyPreviousStepFailed;

return {
always: () => true,
never: () => false,
success: () => !hasAnyPreviousStepFailed,
failure: () => hasAnyPreviousStepFailed,
...this.ctx.global.staticContext.job.workflowInterpolationContext,
env: this.getScriptEnv(),
steps: Object.fromEntries(
this.ctx.global.steps.map((step) => [
step.id,
{
outputs: Object.fromEntries(
step.outputs.map((output) => {
try {
return [output.id, output.value];
} catch {
return [output.id, undefined];
}
})
),
},
])
),
job: this.ctx.global.staticContext.job,
metadata: this.ctx.global.staticContext.metadata,
fromJSON: (json: string) => JSON.parse(json),
toJSON: (value: unknown) => JSON.stringify(value),
};
}
private async executeCommandAsync(): Promise<void> {
assert(this.command, 'Command must be defined.');

const interpolatedCommand = interpolateJobContext({
target: this.command,
context: this.getInterpolationContext(),
});

const command = this.interpolateInputsOutputsAndGlobalContextInTemplate(
this.command,
`${interpolatedCommand}`,
this.inputs
);
this.ctx.logger.debug(`Interpolated inputs in the command template`);
Expand Down
8 changes: 4 additions & 4 deletions packages/steps/src/BuildStepContext.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os from 'os';
import path from 'path';

import { BuildStaticContext } from '@expo/eas-build-job';
import { StaticJobInterpolationContext } from '@expo/eas-build-job';
import { bunyan } from '@expo/logger';
import { v4 as uuidv4 } from 'uuid';

Expand All @@ -25,7 +25,7 @@ interface SerializedExternalBuildContextProvider {
defaultWorkingDirectory: string;
buildLogsDirectory: string;
runtimePlatform: BuildRuntimePlatform;
staticContext: BuildStaticContext;
staticContext: StaticJobInterpolationContext;
env: BuildStepEnv;
}

Expand All @@ -37,7 +37,7 @@ export interface ExternalBuildContextProvider {
readonly runtimePlatform: BuildRuntimePlatform;
readonly logger: bunyan;

readonly staticContext: () => BuildStaticContext;
readonly staticContext: () => StaticJobInterpolationContext;

readonly env: BuildStepEnv;
updateEnv(env: BuildStepEnv): void;
Expand Down Expand Up @@ -88,7 +88,7 @@ export class BuildStepGlobalContext {
return this.provider.env;
}

public get staticContext(): BuildStaticContext {
public get staticContext(): StaticJobInterpolationContext {
return this.provider.staticContext();
}

Expand Down
10 changes: 10 additions & 0 deletions packages/steps/src/__tests__/BuildStep-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
import { jest } from '@jest/globals';
import { instance, mock, verify, when } from 'ts-mockito';
import { v4 as uuidv4 } from 'uuid';
import { StaticJobInterpolationContext } from '@expo/eas-build-job';

import { BuildStep, BuildStepFunction, BuildStepStatus } from '../BuildStep.js';
import {
Expand Down Expand Up @@ -62,6 +63,7 @@ describe(BuildStep, () => {
it('throws when neither command nor fn is set', () => {
const mockCtx = mock<BuildStepGlobalContext>();
when(mockCtx.baseLogger).thenReturn(createMockLogger());
when(mockCtx.staticContext).thenReturn({ job: {} } as StaticJobInterpolationContext);
const ctx = instance(mockCtx);
expect(() => {
const id = 'test1';
Expand Down Expand Up @@ -1047,7 +1049,15 @@ describe(BuildStep.prototype.shouldExecuteStep, () => {
command: 'echo 123',
ifCondition: '${ success() }',
});
<<<<<<< HEAD
expect(step.shouldExecuteStep()).toBe(false);
||||||| parent of 38d191a (Add `workflowInterpolationContext`)
const hasAnyPreviousStepsFailed = true;
expect(step.shouldExecuteStep(hasAnyPreviousStepsFailed)).toBe(false);
=======
ctx.markAsFailed();
expect(step.shouldExecuteStep()).toBe(false);
>>>>>>> 38d191a (Add `workflowInterpolationContext`)
});

it('returns true when a dynamic expression matches', () => {
Expand Down
12 changes: 6 additions & 6 deletions packages/steps/src/__tests__/BuildStepContext-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os from 'os';

import { BuildStaticContext } from '@expo/eas-build-job';
import { JobInterpolationContext } from '@expo/eas-build-job';
import { instance, mock, when } from 'ts-mockito';

import { BuildStep } from '../BuildStep.js';
Expand All @@ -23,7 +23,7 @@ describe(BuildStepGlobalContext, () => {
'/another/non/existent/path',
'/working/dir/path',
'/non/existent/path',
{} as unknown as BuildStaticContext
{} as unknown as JobInterpolationContext
),
false
);
Expand All @@ -42,7 +42,7 @@ describe(BuildStepGlobalContext, () => {
projectTargetDirectory,
workingDirectory,
'/non/existent/path',
{} as unknown as BuildStaticContext
{} as unknown as JobInterpolationContext
),
false
);
Expand All @@ -60,7 +60,7 @@ describe(BuildStepGlobalContext, () => {
projectTargetDirectory,
workingDirectory,
'/non/existent/path',
{} as unknown as BuildStaticContext
{} as unknown as JobInterpolationContext
),
false
);
Expand All @@ -82,7 +82,7 @@ describe(BuildStepGlobalContext, () => {
projectSourceDirectory: '/a/b/c',
projectTargetDirectory: '/d/e/f',
relativeWorkingDirectory: 'i',
staticContextContent: { a: 1 } as unknown as BuildStaticContext,
staticContextContent: { a: 1 } as unknown as JobInterpolationContext,
});
expect(ctx.serialize()).toEqual(
expect.objectContaining({
Expand Down Expand Up @@ -114,7 +114,7 @@ describe(BuildStepGlobalContext, () => {
defaultWorkingDirectory: '/g/h/i',
buildLogsDirectory: '/j/k/l',
runtimePlatform: BuildRuntimePlatform.DARWIN,
staticContext: { a: 1 } as unknown as BuildStaticContext,
staticContext: { a: 1 } as unknown as JobInterpolationContext,
env: {},
},
skipCleanup: true,
Expand Down
Loading

0 comments on commit 2572e31

Please sign in to comment.