feat: introduce deno cli entrypoint

pull/413/head
Webber 2022-08-08 01:51:36 +02:00
parent 6ac9e78e60
commit 5bb32a6bd5
38 changed files with 616 additions and 328 deletions

View File

@ -169,11 +169,62 @@ inputs:
outputs:
volume:
description: 'The Persistent Volume (PV) where the build artifacts have been stored by Kubernetes'
value: ''
buildVersion:
description: 'The generated version used for the Unity build'
value: ''
runs:
using: 'composite'
steps:
- run: echo "Using GameCI CLI to build project"
shell: bash
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- run: |
deno run --allow-run ./src/index.ts build \
--targetPlatform="${{ inputs.targetPlatform }}" \
--unityVersion="${{ inputs.unityVersion }}" \
--customImage="${{ inputs.customImage }}" \
--projectPath="${{ inputs.projectPath }}" \
--buildName="${{ inputs.buildName }}" \
--buildsPath="${{ inputs.buildsPath }}" \
--buildMethod="${{ inputs.buildMethod }}" \
--customParameters="${{ inputs.customParameters }}" \
--versioning="${{ inputs.versioning }}" \
--version="${{ inputs.version }}" \
--androidVersionCode="${{ inputs.androidVersionCode }}" \
--androidAppBundle="${{ inputs.androidAppBundle }}" \
--androidKeystoreName="${{ inputs.androidKeystoreName }}" \
--androidKeystoreBase64="${{ inputs.androidKeystoreBase64 }}" \
--androidKeystorePass="${{ inputs.androidKeystorePass }}" \
--androidKeyaliasName="${{ inputs.androidKeyaliasName }}" \
--androidKeyaliasPass="${{ inputs.androidKeyaliasPass }}" \
--androidTargetSdkVersion="${{ inputs.androidTargetSdkVersion }}" \
--sshAgent="${{ inputs.sshAgent }}" \
--gitPrivateToken="${{ inputs.gitPrivateToken }}" \
--chownFilesTo="${{ inputs.chownFilesTo }}" \
--allowDirtyBuild="${{ inputs.allowDirtyBuild }}" \
--postBuildSteps="${{ inputs.postBuildSteps }}" \
--preBuildSteps="${{ inputs.preBuildSteps }}" \
--customJobHooks="${{ inputs.customJobHooks }}" \
--customJob="${{ inputs.customJob }}" \
--awsBaseStackName="${{ inputs.awsBaseStackName }}" \
--cloudRunnerCluster="${{ inputs.cloudRunnerCluster }}" \
--cloudRunnerCpu="${{ inputs.cloudRunnerCpu }}" \
--cloudRunnerMemory="${{ inputs.cloudRunnerMemory }}" \
--cachePushOverrideCommand="${{ inputs.cachePushOverrideCommand }}" \
--cachePullOverrideCommand="${{ inputs.cachePullOverrideCommand }}" \
--readInputFromOverrideList="${{ inputs.readInputFromOverrideList }}" \
--readInputOverrideCommand="${{ inputs.readInputOverrideCommand }}" \
--kubeConfig="${{ inputs.kubeConfig }}" \
--kubeVolume="${{ inputs.kubeVolume }}" \
--kubeStorageClass="${{ inputs.kubeStorageClass }}" \
--kubeVolumeSize="${{ inputs.kubeVolumeSize }}" \
--cacheKey="${{ inputs.cacheKey }}" \
--checkDependencyHealthOverride="${{ inputs.checkDependencyHealthOverride }}" \
--startDependenciesOverride="${{ inputs.startDependenciesOverride }}"
shell: bash
branding:
icon: 'box'
color: 'gray-dark'
runs:
using: 'node12'
main: 'dist/index.js'

View File

@ -0,0 +1,18 @@
import { NonExistentCommand } from './command/non-existent-command.ts';
import { BuildCommand } from './command/build-command.ts';
import { BuildRemoteCommand } from './command/build-remote-command.ts';
export class CommandFactory {
constructor() {}
public createCommand(commandName) {
switch (commandName) {
case 'build':
return new BuildCommand();
case 'build-remote':
return new BuildRemoteCommand();
default:
return new NonExistentCommand(commandName);
}
}
}

View File

@ -0,0 +1,60 @@
import { exec, OutputMode } from 'https://deno.land/x/exec@0.0.5/mod.ts';
import { CommandInterface } from './command-interface.ts';
import { Options } from '../../config/options.ts';
import { Action, Cache, CloudRunner, Docker, ImageTag, Output } from '../../model/index.ts';
import PlatformSetup from '../../model/platform-setup.ts';
import { core, process } from '../../dependencies.ts';
import MacBuilder from '../../model/mac-builder.ts';
export class BuildCommand implements CommandInterface {
public readonly name: string;
constructor(name: string) {
this.name = name;
}
public async execute(options: Options): Promise<boolean> {
try {
log.info('options', options);
const { workspace, actionFolder } = Action;
const { buildParameters } = options;
Action.checkCompatibility();
Cache.verify();
const baseImage = new ImageTag(buildParameters);
log.debug('baseImage', baseImage);
if (buildParameters.cloudRunnerCluster !== 'local') {
await CloudRunner.run(buildParameters, baseImage.toString());
} else {
log.info('Building locally');
await PlatformSetup.setup(buildParameters, actionFolder);
if (process.platform === 'darwin') {
MacBuilder.run(actionFolder, workspace, buildParameters);
} else {
await Docker.run(baseImage, { workspace, actionFolder, ...buildParameters });
}
}
// Set output
await Output.setBuildVersion(buildParameters.buildVersion);
} catch (error) {
log.error(error);
core.setFailed((error as Error).message);
}
const result = await exec('docker run -it unityci/editor:2020.3.15f2-base-1 /bin/bash -c "echo test"', {
output: OutputMode.Capture,
continueOnError: true,
// verbose: true,
});
log.info('result', result.output);
const { success } = result.status;
log.info('success', success);
return success;
}
}

View File

@ -0,0 +1,30 @@
import { CommandInterface } from './command-interface.ts';
import { Options } from '../../config/options.ts';
import { CloudRunner, ImageTag, Output } from '../../model/index.ts';
import { core } from '../../dependencies.ts';
// Todo - Verify this entire flow
export class BuildRemoteCommand implements CommandInterface {
public readonly name: string;
constructor(name: string) {
this.name = name;
}
public async execute(options: Options): Promise<boolean> {
try {
const { buildParameters } = options;
const baseImage = new ImageTag(buildParameters);
const result = await CloudRunner.run(buildParameters, baseImage.toString());
const { status, output } = result;
await Output.setBuildVersion(buildParameters.buildVersion);
return status.success;
} catch (error) {
log.error(error);
core.setFailed((error as Error).message);
}
}
}

View File

@ -0,0 +1,6 @@
import { Options } from '../../config/options.ts';
export interface CommandInterface {
name: string;
execute: (options: Options) => Promise<boolean>;
}

View File

@ -0,0 +1,14 @@
import { CommandInterface } from './command-interface.ts';
import { Options } from '../../config/options.ts';
export class NonExistentCommand implements CommandInterface {
public name: string;
constructor(name: string) {
this.name = name;
}
public async execute(options: Options): Promise<boolean> {
throw new Error(`Command ${this.name} does not exist`);
}
}

View File

@ -0,0 +1,23 @@
import { CliArguments } from '../core/cli/cli-arguments.ts';
import { EnvVariables } from '../core/env/env-variables.ts';
import { Parameters, Input } from '../model/index.ts';
export class Options {
public input: Input;
public parameters: Parameters;
constructor(env: EnvVariables) {
this.env = env;
return this;
}
public async generateParameters(args: CliArguments) {
this.input = new Input(args);
this.parameters = await new Parameters(this.input).parse();
log.debug('Parameters generated.');
return this;
}
}

View File

@ -0,0 +1,12 @@
import { parseArgv } from './parse-argv.ts';
export class ArgumentsParser {
public parse(cliArguments: string[]) {
const [commandName, ...args] = cliArguments;
return {
commandName,
args: parseArgv(args),
};
}
}

View File

@ -0,0 +1 @@
export type CliArguments = Map<string, string | number | boolean>;

View File

@ -0,0 +1,51 @@
import { CliArguments } from './cli-arguments.ts';
/**
* Parse command line arguments
*
* Usage:
* console.dir(parseArgv(Deno.args)); // Deno
* console.log(parseArgv(process.argv)); // Node
*
* Example:
* deno run my-script -test1=1 -test2 "2" -test3 -test4 false -test5 "one" -test6= -test7=9BX9
*
* Output:
* Map {
* "test1" => 1,
* "test2" => 2,
* "test3" => true,
* "test4" => false,
* "test5" => "one",
* "test6" => "",
* "test7" => "9BX9"
* }
*/
export const parseArgv = (argv: string[] = [], { verbose = false } = {}): CliArguments => {
const providedArguments = new Map<string, string | number | boolean>();
for (let current = 0, next = 1; current < argv.length; current += 1, next += 1) {
// Detect flag
if (!argv[current].startsWith('-')) continue;
let flag = argv[current].replace(/^-+/, '');
// Detect value
const hasNextArgument = next < argv.length && !argv[next].startsWith('-');
let value: string | number | boolean = hasNextArgument ? argv[next] : 'true';
// Split combinations
const isCombination = flag.includes('=');
if (isCombination) [flag, value] = flag.split('=');
// Parse types
if (['true', 'false'].includes(value)) value = value === 'true';
else if (!Number.isNaN(Number(value)) && !Number.isNaN(Number.parseInt(value))) value = Number.parseInt(value);
// Assign
// eslint-disable-next-line no-console
if (verbose) console.log(`Found flag "${flag}" with value "${value}" (${typeof value}).`);
providedArguments.set(flag, value);
}
return providedArguments;
};

1
src/core/env/env-variables.ts vendored 100644
View File

@ -0,0 +1 @@
export type EnvVariables = { [index: string]: string };

View File

@ -19,6 +19,19 @@ export const createFormatter = ({
return pad(value, totalWidth, paddingOptions);
};
const formatValue = (value) => {
switch (typeof value) {
case 'object':
return Deno.inspect(value, { depth });
case 'undefined':
return 'undefined';
case 'string':
return value;
default:
return `${value} (${typeof value})`;
}
};
return ({ level, levelName, msg, args, loggerName }: LogRecord) => {
let line = '';
@ -46,23 +59,12 @@ export const createFormatter = ({
if (msg) {
if (line.length > 0) line += ' ';
line += msg;
line += formatValue(msg);
}
if (args) {
if (line.length > 0) line += ' ';
line += args
.map((value) => {
switch (typeof value) {
case 'object':
return Deno.inspect(value, { depth });
case 'undefined':
return 'undefined';
default:
return value;
}
})
.join(' ');
line += args.map((arg) => formatValue(arg)).join(' ');
}
return line;

View File

@ -1,49 +1,38 @@
import './core/logger/index.ts';
import { core, process } from './dependencies.ts';
import { Action, BuildParameters, Cache, CloudRunner, Docker, ImageTag, Output } from './model/index.ts';
import { Cli } from './model/cli/cli.ts';
import MacBuilder from './model/mac-builder.ts';
import PlatformSetup from './model/platform-setup.ts';
import type { CommandInterface } from './commands/command/command-interface.ts';
import type { EnvVariables } from './core/env/env-variables.ts';
import { Options } from './config/options.ts';
import { CommandFactory } from './commands/command-factory.ts';
import { ArgumentsParser } from './core/cli/arguments-parser.ts';
async function runMain() {
export class GameCI {
private readonly commandFactory: CommandFactory;
private readonly argumentsParser: ArgumentsParser;
private readonly env: EnvVariables;
private options?: Options;
private command?: CommandInterface;
constructor(envVariables: EnvVariables) {
this.env = envVariables;
this.commandFactory = new CommandFactory();
this.argumentsParser = new ArgumentsParser();
}
public async run(cliArguments: string[]) {
try {
if (Cli.InitCliMode()) {
// Todo - this is only here for testing the entire flow in deno and making sure I'm hitting the right path
log.error('CloudBuilder CLI mode');
await Cli.RunCli();
const { commandName, args } = this.argumentsParser.parse(cliArguments);
return;
}
Action.checkCompatibility();
Cache.verify();
this.options = await new Options(this.env).generateParameters(args);
this.command = this.commandFactory.createCommand(commandName);
const { workspace, actionFolder } = Action;
log.debug('workspace', workspace, 'actionFolder', actionFolder);
const buildParameters = await BuildParameters.create();
log.debug('buildParameters', buildParameters);
const baseImage = new ImageTag(buildParameters);
log.debug('baseImage', baseImage);
if (buildParameters.cloudRunnerCluster !== 'local') {
await CloudRunner.run(buildParameters, baseImage.toString());
} else {
log.info('Building locally');
await PlatformSetup.setup(buildParameters, actionFolder);
if (process.platform === 'darwin') {
MacBuilder.run(actionFolder, workspace, buildParameters);
} else {
await Docker.run(baseImage, { workspace, actionFolder, ...buildParameters });
}
}
// Set output
await Output.setBuildVersion(buildParameters.buildVersion);
await this.command.execute(this.options);
} catch (error) {
log.error(error);
core.setFailed((error as Error).message);
Deno.exit(1);
}
}
}
await runMain();
await new GameCI(Deno.env.toObject()).run(Deno.args);

View File

@ -1,7 +1,7 @@
import Versioning from './versioning.ts';
import UnityVersioning from './unity-versioning.ts';
import AndroidVersioning from './android-versioning.ts';
import BuildParameters from './build-parameters.ts';
import Parameters from './parameters.ts';
import Input from './input.ts';
import Platform from './platform.ts';
@ -25,52 +25,52 @@ afterEach(() => {
describe('BuildParameters', () => {
describe('create', () => {
it('does not throw', async () => {
await expect(BuildParameters.create()).resolves.not.toThrow();
await expect(Parameters.create()).resolves.not.toThrow();
});
it('determines the version only once', async () => {
await BuildParameters.create();
await Parameters.create();
expect(determineVersion).toHaveBeenCalledTimes(1);
});
it('determines the unity version only once', async () => {
await BuildParameters.create();
await Parameters.create();
expect(determineUnityVersion).toHaveBeenCalledTimes(1);
});
it('returns the android version code with provided input', async () => {
const mockValue = '42';
jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: mockValue }));
});
it('returns the android version code from version by default', async () => {
const mockValue = '';
jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: 1_003_037 }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: 1_003_037 }));
});
it('determines the android sdk manager parameters only once', async () => {
await BuildParameters.create();
await Parameters.create();
expect(determineSdkManagerParameters).toHaveBeenCalledTimes(1);
});
it('returns the targetPlatform', async () => {
const mockValue = 'somePlatform';
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ targetPlatform: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ targetPlatform: mockValue }));
});
it('returns the project path', async () => {
const mockValue = 'path/to/project';
jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
});
it('returns the build name', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildName: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildName: mockValue }));
});
it('returns the build path', async () => {
@ -79,13 +79,13 @@ describe('BuildParameters', () => {
const expectedBuildPath = `${mockPath}/${mockPlatform}`;
jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildPath: expectedBuildPath }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildPath: expectedBuildPath }));
});
it('returns the build file', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue }));
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
@ -93,7 +93,7 @@ describe('BuildParameters', () => {
async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
expect(BuildParameters.create()).resolves.toEqual(
expect(Parameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
);
},
@ -103,7 +103,7 @@ describe('BuildParameters', () => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
expect(BuildParameters.create()).resolves.toEqual(
expect(Parameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
);
});
@ -112,7 +112,7 @@ describe('BuildParameters', () => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);
expect(BuildParameters.create()).resolves.toEqual(
expect(Parameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.aab` }),
);
});
@ -120,43 +120,43 @@ describe('BuildParameters', () => {
it('returns the build method', async () => {
const mockValue = 'Namespace.ClassName.BuildMethod';
jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildMethod: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildMethod: mockValue }));
});
it('returns the android keystore name', async () => {
const mockValue = 'keystore.keystore';
jest.spyOn(Input, 'androidKeystoreName', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreName: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreName: mockValue }));
});
it('returns the android keystore base64-encoded content', async () => {
const mockValue = 'secret';
jest.spyOn(Input, 'androidKeystoreBase64', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreBase64: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystoreBase64: mockValue }));
});
it('returns the android keystore pass', async () => {
const mockValue = 'secret';
jest.spyOn(Input, 'androidKeystorePass', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystorePass: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidKeystorePass: mockValue }));
});
it('returns the android keyalias name', async () => {
const mockValue = 'secret';
jest.spyOn(Input, 'androidKeyaliasName', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasName: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasName: mockValue }));
});
it('returns the android keyalias pass', async () => {
const mockValue = 'secret';
jest.spyOn(Input, 'androidKeyaliasPass', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasPass: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidKeyaliasPass: mockValue }));
});
it('returns the android target sdk version', async () => {
const mockValue = 'AndroidApiLevelAuto';
jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(
expect(Parameters.create()).resolves.toEqual(
expect.objectContaining({ androidTargetSdkVersion: mockValue }),
);
});
@ -164,7 +164,7 @@ describe('BuildParameters', () => {
it('returns the custom parameters', async () => {
const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue';
jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ customParameters: mockValue }));
expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ customParameters: mockValue }));
});
});
});

View File

@ -1,4 +1,4 @@
import { BuildParameters, CloudRunner, ImageTag, Input } from '../index.ts';
import { Parameters, CloudRunner, ImageTag, Input } from '../index.ts';
import { Command, core } from '../../dependencies.ts';
import { ActionYamlReader } from '../input-readers/action-yaml.ts';
import CloudRunnerLogger from '../cloud-runner/services/cloud-runner-logger.ts';
@ -88,7 +88,7 @@ export class Cli {
@CliFunction(`cli`, `runs a cloud runner build`)
public static async CLIBuild(): Promise<string> {
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
const baseImage = new ImageTag(buildParameter);
return await CloudRunner.run(buildParameter, baseImage.toString());

View File

@ -1,4 +1,4 @@
import { BuildParameters, ImageTag } from '..';
import { Parameters, ImageTag } from '..';
import CloudRunner from './cloud-runner.ts';
import Input from '../input.ts';
import { CloudRunnerStatics } from './cloud-runner-statics.ts';
@ -34,7 +34,7 @@ describe('Cloud Runner', () => {
Input.githubInputEnabled = false;
// Setup parameters
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
Input.githubInputEnabled = true;
const baseImage = new ImageTag(buildParameter);
@ -73,7 +73,7 @@ describe('Cloud Runner', () => {
cacheKey: `test-case-${uuidv4()}`,
};
Input.githubInputEnabled = false;
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
const baseImage = new ImageTag(buildParameter);
const results = await CloudRunner.run(buildParameter, baseImage.toString());
const libraryString = 'Rebuilding Library because the asset database could not be found!';
@ -81,7 +81,7 @@ describe('Cloud Runner', () => {
expect(results).toContain(libraryString);
expect(results).toContain(buildSucceededString);
CloudRunnerLogger.log(`run 1 succeeded`);
const buildParameter2 = await BuildParameters.create();
const buildParameter2 = await Parameters.create();
const baseImage2 = new ImageTag(buildParameter2);
const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
CloudRunnerLogger.log(`run 2 succeeded`);
@ -111,7 +111,7 @@ describe('Cloud Runner', () => {
Input.githubInputEnabled = false;
// Setup parameters
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
const baseImage = new ImageTag(buildParameter);
// Run the job
@ -131,7 +131,7 @@ describe('Cloud Runner', () => {
Input.githubInputEnabled = false;
// Setup parameters
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
const baseImage = new ImageTag(buildParameter);
// Run the job

View File

@ -1,5 +1,5 @@
import AwsBuildPlatform from './providers/aws/index.ts';
import { BuildParameters, Input } from '../index.ts';
import { Parameters, Input } from '../index.ts';
import Kubernetes from './providers/k8s/index.ts';
import CloudRunnerLogger from './services/cloud-runner-logger.ts';
import { CloudRunnerStepState } from './cloud-runner-step-state.ts';
@ -16,10 +16,10 @@ import LocalDockerCloudRunner from './providers/local-docker/index.ts';
class CloudRunner {
public static Provider: ProviderInterface;
static buildParameters: BuildParameters;
static buildParameters: Parameters;
public static defaultSecrets: CloudRunnerSecret[];
public static cloudRunnerEnvironmentVariables: CloudRunnerEnvironmentVariable[];
private static setup(buildParameters: BuildParameters) {
private static setup(buildParameters: Parameters) {
CloudRunnerLogger.setup();
CloudRunner.buildParameters = buildParameters;
CloudRunner.setupBuildPlatform();
@ -57,7 +57,7 @@ class CloudRunner {
}
}
static async run(buildParameters: BuildParameters, baseImage: string) {
static async run(buildParameters: Parameters, baseImage: string) {
CloudRunner.setup(buildParameters);
try {
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('Setup shared cloud runner resources');

View File

@ -4,7 +4,7 @@ import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environm
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def.ts';
import AWSTaskRunner from './aws-task-runner.ts';
import { ProviderInterface } from '../provider-interface.ts';
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import CloudRunnerLogger from '../../services/cloud-runner-logger.ts';
import { AWSJobStack } from './aws-job-stack.ts';
import { AWSBaseStack } from './aws-base-stack.ts';
@ -13,18 +13,18 @@ import { Input } from '../../../index.ts';
class AWSBuildEnvironment implements ProviderInterface {
private baseStackName: string;
constructor(buildParameters: BuildParameters) {
constructor(buildParameters: Parameters) {
this.baseStackName = buildParameters.awsBaseStackName;
}
async cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}
async setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}

View File

@ -1,4 +1,4 @@
import { BuildParameters, Output } from '../../../index.ts';
import { Parameters, Output } from '../../../index.ts';
import { k8sTypes, k8s, waitUntil } from '../../../../dependencies.ts';
import { ProviderInterface } from '../provider-interface.ts';
import CloudRunnerSecret from '../../services/cloud-runner-secret.ts';
@ -16,7 +16,7 @@ class Kubernetes implements ProviderInterface {
private kubeClient: k8sTypes.CoreV1Api;
private kubeClientBatch: k8sTypes.BatchV1Api;
private buildGuid: string = '';
private buildParameters: BuildParameters;
private buildParameters: Parameters;
private pvcName: string = '';
private secretName: string = '';
private jobName: string = '';
@ -26,7 +26,7 @@ class Kubernetes implements ProviderInterface {
private cleanupCronJobName: string = '';
private serviceAccountName: string = '';
constructor(buildParameters: BuildParameters) {
constructor(buildParameters: Parameters) {
this.kubeConfig = new k8s.KubeConfig();
this.kubeConfig.loadFromDefault();
this.kubeClient = this.kubeConfig.makeApiClient(k8sTypes.CoreV1Api);
@ -38,7 +38,7 @@ class Kubernetes implements ProviderInterface {
}
public async setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {
@ -171,7 +171,7 @@ class Kubernetes implements ProviderInterface {
async cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {

View File

@ -1,5 +1,5 @@
import { V1EnvVar, V1EnvVarSource, V1SecretKeySelector } from '../../../../dependencies.ts';
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import { CloudRunnerBuildCommandProcessor } from '../../services/cloud-runner-build-command-process.ts';
import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environment-variable.ts';
import CloudRunnerSecret from '../../services/cloud-runner-secret.ts';
@ -14,7 +14,7 @@ class KubernetesJobSpecFactory {
environment: CloudRunnerEnvironmentVariable[],
secrets: CloudRunnerSecret[],
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
secretName,
pvcName,
jobName,

View File

@ -1,10 +1,10 @@
import { k8sTypes, k8s, core, yaml, waitUntil, http } from '../../../../dependencies.ts';
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import CloudRunnerLogger from '../../services/cloud-runner-logger.ts';
class KubernetesStorage {
public static async createPersistentVolumeClaim(
buildParameters: BuildParameters,
buildParameters: Parameters,
pvcName: string,
kubeClient: k8sTypes.CoreV1Api,
namespace: string,
@ -69,7 +69,7 @@ class KubernetesStorage {
private static async createPVC(
pvcName: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
kubeClient: k8sTypes.CoreV1Api,
namespace: string,
) {

View File

@ -1,4 +1,4 @@
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import { CloudRunnerSystem } from '../../services/cloud-runner-system.ts';
import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environment-variable.ts';
import CloudRunnerLogger from '../../services/cloud-runner-logger.ts';
@ -8,13 +8,13 @@ import CloudRunnerSecret from '../../services/cloud-runner-secret.ts';
class LocalDockerCloudRunner implements ProviderInterface {
cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}
setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}

View File

@ -1,4 +1,4 @@
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import { CloudRunnerSystem } from '../../services/cloud-runner-system.ts';
import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environment-variable.ts';
import CloudRunnerLogger from '../../services/cloud-runner-logger.ts';
@ -8,13 +8,13 @@ import CloudRunnerSecret from '../../services/cloud-runner-secret.ts';
class LocalCloudRunner implements ProviderInterface {
cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}
public setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}

View File

@ -1,17 +1,17 @@
import BuildParameters from '../../build-parameters.ts';
import Parameters from '../../parameters.ts';
import CloudRunnerEnvironmentVariable from '../services/cloud-runner-environment-variable.ts';
import CloudRunnerSecret from '../services/cloud-runner-secret.ts';
export interface ProviderInterface {
cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
);
setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
);

View File

@ -1,4 +1,4 @@
import BuildParameters from '../../../build-parameters.ts';
import Parameters from '../../../parameters.ts';
import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environment-variable.ts';
import CloudRunnerLogger from '../../services/cloud-runner-logger.ts';
import { ProviderInterface } from '../provider-interface.ts';
@ -7,13 +7,13 @@ import CloudRunnerSecret from '../../services/cloud-runner-secret.ts';
class TestCloudRunner implements ProviderInterface {
cleanup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}
setup(
buildGuid: string,
buildParameters: BuildParameters,
buildParameters: Parameters,
branchName: string,
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) {}

View File

@ -1,5 +1,5 @@
import { fs, uuid, path, __dirname } from '../../../dependencies.ts';
import BuildParameters from '../../build-parameters.ts';
import Parameters from '../../parameters.ts';
import { Cli } from '../../cli/cli.ts';
import Input from '../../input.ts';
import UnityVersioning from '../../unity-versioning.ts';
@ -21,7 +21,7 @@ describe('Cloud Runner Caching', () => {
cacheKey: `test-case-${uuid()}`,
};
Input.githubInputEnabled = false;
const buildParameter = await BuildParameters.create();
const buildParameter = await Parameters.create();
CloudRunner.buildParameters = buildParameter;
// Create test folder

View File

@ -1,10 +1,10 @@
import { BuildParameters } from '../../index.ts';
import { Parameters } from '../../index.ts';
import { yaml } from '../../../dependencies.ts';
import CloudRunnerSecret from './cloud-runner-secret.ts';
import CloudRunner from '../cloud-runner.ts';
export class CloudRunnerBuildCommandProcessor {
public static ProcessCommands(commands: string, buildParameters: BuildParameters): string {
public static ProcessCommands(commands: string, buildParameters: Parameters): string {
const hooks = CloudRunnerBuildCommandProcessor.getHooks(buildParameters.customJobHooks).filter((x) =>
x.step.includes(`all`),
);

View File

@ -1,4 +1,4 @@
import BuildParameters from './build-parameters.ts';
import Parameters from './parameters.ts';
import { ReadLicense } from './input-readers/test-license-reader.ts';
class Parameter {
@ -24,7 +24,7 @@ class ImageEnvironmentFactory {
return string;
}
public static getEnvironmentVariables(parameters: BuildParameters) {
public static getEnvironmentVariables(parameters: Parameters) {
const environmentVariables: Parameter[] = [
{ name: 'UNITY_LICENSE', value: Deno.env.get('UNITY_LICENSE') || ReadLicense() },
{ name: 'UNITY_LICENSE_FILE', value: Deno.env.get('UNITY_LICENSE_FILE') },

View File

@ -1,6 +1,6 @@
import Platform from './platform.ts';
import BuildParameters from './build-parameters.ts';
import Parameters from './parameters.ts';
class ImageTag {
public repository: string;
@ -13,7 +13,7 @@ class ImageTag {
public imageRollingVersion: number;
public imagePlatformPrefix: string;
constructor(imageProperties: Partial<BuildParameters>) {
constructor(imageProperties: Partial<Parameters>) {
const { editorVersion = '2019.2.11f1', targetPlatform, customImage, cloudRunnerBuilderPlatform } = imageProperties;
if (!ImageTag.versionPattern.test(editorVersion)) {

View File

@ -1,5 +1,5 @@
import Action from './action.ts';
import BuildParameters from './build-parameters.ts';
import Parameters from './parameters.ts';
import Cache from './cache.ts';
import Docker from './docker.ts';
import Input from './input.ts';
@ -13,7 +13,7 @@ import CloudRunner from './cloud-runner/cloud-runner.ts';
export {
Action,
BuildParameters,
Parameters,
Cache,
Docker,
Input,
@ -23,5 +23,5 @@ export {
Project,
Unity,
Versioning,
CloudRunner as CloudRunner,
CloudRunner,
};

View File

@ -2,6 +2,7 @@ import { fsSync as fs, path, core } from '../dependencies.ts';
import { Cli } from './cli/cli.ts';
import CloudRunnerQueryOverride from './cloud-runner/services/cloud-runner-query-override.ts';
import Platform from './platform.ts';
import { CliArguments } from '../core/cli/cli-arguments.ts';
/**
* Input variables specified in workflows using "with" prop.
@ -11,9 +12,29 @@ import Platform from './platform.ts';
* Todo: rename to UserInput and remove anything that is not direct input from the user / ci workflow
*/
class Input {
private readonly arguments: CliArguments;
constructor(argumentsFromCli: CliArguments) {
this.arguments = argumentsFromCli;
log.debug('Input initialised.');
return this;
}
public static githubInputEnabled: boolean = true;
public static getInput(query) {
// Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess.
public getInput(query) {
if (this && this.arguments) {
const value = this.arguments.get(query);
log.warning('arg', query, '=', value);
return this.arguments.get(query);
}
// Legacy (static)
log.warn(`Querying static`);
if (Input.githubInputEnabled) {
const coreInput = core.getInput(query);
if (coreInput && coreInput !== '') {
@ -42,24 +63,24 @@ class Input {
return;
}
static get region(): string {
return Input.getInput('region') || 'eu-west-2';
public get region(): string {
return this.getInput('region') || 'eu-west-2';
}
static get githubRepo() {
return Input.getInput('GITHUB_REPOSITORY') || Input.getInput('GITHUB_REPO') || undefined;
public get githubRepo() {
return this.getInput('GITHUB_REPOSITORY') || this.getInput('GITHUB_REPO') || undefined;
}
static get branch() {
if (Input.getInput(`GITHUB_REF`)) {
return Input.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
} else if (Input.getInput('branch')) {
return Input.getInput('branch');
public get branch() {
if (this.getInput(`GITHUB_REF`)) {
return this.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
} else if (this.getInput('branch')) {
return this.getInput('branch');
} else {
return '';
}
}
static get cloudRunnerBuilderPlatform() {
const input = Input.getInput('cloudRunnerBuilderPlatform');
public get cloudRunnerBuilderPlatform() {
const input = this.getInput('cloudRunnerBuilderPlatform');
if (input) {
return input;
}
@ -70,32 +91,32 @@ class Input {
return;
}
static get gitSha() {
if (Input.getInput(`GITHUB_SHA`)) {
return Input.getInput(`GITHUB_SHA`);
} else if (Input.getInput(`GitSHA`)) {
return Input.getInput(`GitSHA`);
public get gitSha() {
if (this.getInput(`GITHUB_SHA`)) {
return this.getInput(`GITHUB_SHA`);
} else if (this.getInput(`GitSHA`)) {
return this.getInput(`GitSHA`);
}
}
static get runNumber() {
return Input.getInput('GITHUB_RUN_NUMBER') || '0';
public get runNumber() {
return this.getInput('GITHUB_RUN_NUMBER') || '0';
}
static get targetPlatform() {
return Input.getInput('targetPlatform') || Platform.default;
public get targetPlatform() {
return this.getInput('targetPlatform') || Platform.default;
}
static get unityVersion() {
return Input.getInput('unityVersion') || 'auto';
public get unityVersion() {
return this.getInput('unityVersion') || 'auto';
}
static get customImage() {
return Input.getInput('customImage') || '';
public get customImage() {
return this.getInput('customImage') || '';
}
static get projectPath() {
let input = Input.getInput('projectPath');
public get projectPath() {
let input = this.getInput('projectPath');
// Todo - remove hardcoded test project reference
const isTestProject =
@ -108,168 +129,169 @@ class Input {
return input.replace(/\/$/, '');
}
static get buildName() {
return Input.getInput('buildName') || this.targetPlatform;
public get buildName() {
return this.getInput('buildName') || this.targetPlatform;
}
static get buildsPath() {
return Input.getInput('buildsPath') || 'build';
public get buildsPath() {
return this.getInput('buildsPath') || 'build';
}
static get buildMethod() {
return Input.getInput('buildMethod') || ''; // Processed in docker file
public get buildMethod() {
return this.getInput('buildMethod') || ''; // Processed in docker file
}
static get customParameters() {
return Input.getInput('customParameters') || '';
public get customParameters() {
return this.getInput('customParameters') || '';
}
static get versioningStrategy() {
return Input.getInput('versioning') || 'Semantic';
public get versioningStrategy() {
return this.getInput('versioning') || 'Semantic';
}
static get specifiedVersion() {
return Input.getInput('version') || '';
public get specifiedVersion() {
return this.getInput('version') || '';
}
static get androidVersionCode() {
return Input.getInput('androidVersionCode');
public get androidVersionCode() {
return this.getInput('androidVersionCode');
}
static get androidAppBundle() {
const input = Input.getInput('androidAppBundle') || false;
public get androidAppBundle() {
const input = this.getInput('androidAppBundle') || false;
return input === 'true';
}
static get androidKeystoreName() {
return Input.getInput('androidKeystoreName') || '';
public get androidKeystoreName() {
return this.getInput('androidKeystoreName') || '';
}
static get androidKeystoreBase64() {
return Input.getInput('androidKeystoreBase64') || '';
public get androidKeystoreBase64() {
return this.getInput('androidKeystoreBase64') || '';
}
static get androidKeystorePass() {
return Input.getInput('androidKeystorePass') || '';
public get androidKeystorePass() {
return this.getInput('androidKeystorePass') || '';
}
static get androidKeyaliasName() {
return Input.getInput('androidKeyaliasName') || '';
public get androidKeyaliasName() {
return this.getInput('androidKeyaliasName') || '';
}
static get androidKeyaliasPass() {
return Input.getInput('androidKeyaliasPass') || '';
public get androidKeyaliasPass() {
return this.getInput('androidKeyaliasPass') || '';
}
static get androidTargetSdkVersion() {
return Input.getInput('androidTargetSdkVersion') || '';
public get androidTargetSdkVersion() {
return this.getInput('androidTargetSdkVersion') || '';
}
static get sshAgent() {
return Input.getInput('sshAgent') || '';
public get sshAgent() {
return this.getInput('sshAgent') || '';
}
static get gitPrivateToken() {
public get gitPrivateToken() {
return core.getInput('gitPrivateToken') || false;
}
static get customJob() {
return Input.getInput('customJob') || '';
public get customJob() {
return this.getInput('customJob') || '';
}
static customJobHooks() {
return Input.getInput('customJobHooks') || '';
public customJobHooks() {
return this.getInput('customJobHooks') || '';
}
static cachePushOverrideCommand() {
return Input.getInput('cachePushOverrideCommand') || '';
public cachePushOverrideCommand() {
return this.getInput('cachePushOverrideCommand') || '';
}
static cachePullOverrideCommand() {
return Input.getInput('cachePullOverrideCommand') || '';
public cachePullOverrideCommand() {
return this.getInput('cachePullOverrideCommand') || '';
}
static readInputFromOverrideList() {
return Input.getInput('readInputFromOverrideList') || '';
public readInputFromOverrideList() {
return this.getInput('readInputFromOverrideList') || '';
}
static readInputOverrideCommand() {
return Input.getInput('readInputOverrideCommand') || '';
public readInputOverrideCommand() {
return this.getInput('readInputOverrideCommand') || '';
}
static get cloudRunnerBranch() {
return Input.getInput('cloudRunnerBranch') || 'cloud-runner-develop';
public get cloudRunnerBranch() {
return this.getInput('cloudRunnerBranch') || 'cloud-runner-develop';
}
static get chownFilesTo() {
return Input.getInput('chownFilesTo') || '';
public get chownFilesTo() {
return this.getInput('chownFilesTo') || '';
}
static get allowDirtyBuild() {
const input = Input.getInput('allowDirtyBuild') || false;
public get allowDirtyBuild() {
const input = this.getInput('allowDirtyBuild');
log.debug('input === ', input);
return input === 'true';
return input || false === true;
}
static get postBuildSteps() {
return Input.getInput('postBuildSteps') || '';
public get postBuildSteps() {
return this.getInput('postBuildSteps') || '';
}
static get preBuildSteps() {
return Input.getInput('preBuildSteps') || '';
public get preBuildSteps() {
return this.getInput('preBuildSteps') || '';
}
static get awsBaseStackName() {
return Input.getInput('awsBaseStackName') || 'game-ci';
public get awsBaseStackName() {
return this.getInput('awsBaseStackName') || 'game-ci';
}
static get cloudRunnerCluster() {
public get cloudRunnerCluster() {
if (Cli.isCliMode) {
return Input.getInput('cloudRunnerCluster') || 'aws';
return this.getInput('cloudRunnerCluster') || 'aws';
}
return Input.getInput('cloudRunnerCluster') || 'local';
return this.getInput('cloudRunnerCluster') || 'local';
}
static get cloudRunnerCpu() {
return Input.getInput('cloudRunnerCpu');
public get cloudRunnerCpu() {
return this.getInput('cloudRunnerCpu');
}
static get cloudRunnerMemory() {
return Input.getInput('cloudRunnerMemory');
public get cloudRunnerMemory() {
return this.getInput('cloudRunnerMemory');
}
static get kubeConfig() {
return Input.getInput('kubeConfig') || '';
public get kubeConfig() {
return this.getInput('kubeConfig') || '';
}
static get kubeVolume() {
return Input.getInput('kubeVolume') || '';
public get kubeVolume() {
return this.getInput('kubeVolume') || '';
}
static get kubeVolumeSize() {
return Input.getInput('kubeVolumeSize') || '5Gi';
public get kubeVolumeSize() {
return this.getInput('kubeVolumeSize') || '5Gi';
}
static get kubeStorageClass(): string {
return Input.getInput('kubeStorageClass') || '';
public get kubeStorageClass(): string {
return this.getInput('kubeStorageClass') || '';
}
static get checkDependencyHealthOverride(): string {
return Input.getInput('checkDependencyHealthOverride') || '';
public get checkDependencyHealthOverride(): string {
return this.getInput('checkDependencyHealthOverride') || '';
}
static get startDependenciesOverride(): string {
return Input.getInput('startDependenciesOverride') || '';
public get startDependenciesOverride(): string {
return this.getInput('startDependenciesOverride') || '';
}
static get cacheKey(): string {
return Input.getInput('cacheKey') || Input.branch;
public get cacheKey(): string {
return this.getInput('cacheKey') || Input.branch;
}
static get cloudRunnerTests(): boolean {
return Input.getInput(`cloudRunnerTests`) || false;
public get cloudRunnerTests(): boolean {
return this.getInput(`cloudRunnerTests`) || false;
}
public static toEnvVarFormat(input: string) {

View File

@ -1,5 +1,5 @@
import { exec } from '../dependencies.ts';
import { BuildParameters } from './build-parameters.ts';
import { Parameters } from './parameters.ts';
class MacBuilder {
public static async run(actionFolder, workspace, buildParameters: BuildParameters, silent = false) {

View File

@ -10,7 +10,7 @@ import { GitRepoReader } from './input-readers/git-repo.ts';
import { GithubCliReader } from './input-readers/github-cli.ts';
import { Cli } from './cli/cli.ts';
class BuildParameters {
class Parameters {
public editorVersion!: string;
public customImage!: string;
public unitySerial!: string;
@ -51,7 +51,6 @@ class BuildParameters {
public checkDependencyHealthOverride!: string;
public startDependenciesOverride!: string;
public cacheKey!: string;
public postBuildSteps!: string;
public preBuildSteps!: string;
public customJob!: string;
@ -66,22 +65,36 @@ class BuildParameters {
public cloudRunnerBuilderPlatform!: string | undefined;
public isCliMode!: boolean;
static async create(): Promise<BuildParameters> {
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidAppBundle);
constructor(input: Input) {
this.input = input;
}
public async parse(): Promise<Parameters> {
const buildFile = Parameters.parseBuildFile(
this.input.buildName,
this.input.targetPlatform,
this.input.androidAppBundle,
);
log.debug('buildFile:', buildFile);
const editorVersion = UnityVersioning.determineUnityVersion(Input.projectPath, Input.unityVersion);
const editorVersion = UnityVersioning.determineUnityVersion(this.input.projectPath, this.input.unityVersion);
log.debug('editorVersion:', editorVersion);
const buildVersion = await Versioning.determineBuildVersion(Input.versioningStrategy, Input.specifiedVersion);
const buildVersion = await Versioning.determineBuildVersion(
this.input.versioningStrategy,
this.input.specifiedVersion,
this.input.allowDirtyBuild,
);
log.debug('buildVersion', buildVersion);
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, Input.androidVersionCode);
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.input.androidVersionCode);
log.debug('androidVersionCode', androidVersionCode);
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(Input.androidTargetSdkVersion);
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(
this.input.androidTargetSdkVersion,
);
log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
// Todo - Don't use process.env directly, that's what the input model class is for.
// ---
let unitySerial = '';
if (!Deno.env.get('UNITY_SERIAL') && Input.githubInputEnabled) {
if (!Deno.env.get('UNITY_SERIAL') && this.input.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert
if (!Deno.env.get('UNITY_LICENSE')) {
throw new Error(`Missing Unity License File and no Serial was found. If this
@ -96,59 +109,59 @@ class BuildParameters {
return {
editorVersion,
customImage: Input.customImage,
customImage: this.input.customImage,
unitySerial,
runnerTempPath: Deno.env.get('RUNNER_TEMP'),
targetPlatform: Input.targetPlatform,
projectPath: Input.projectPath,
buildName: Input.buildName,
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
targetPlatform: this.input.targetPlatform,
projectPath: this.input.projectPath,
buildName: this.input.buildName,
buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`,
buildFile,
buildMethod: Input.buildMethod,
buildMethod: this.input.buildMethod,
buildVersion,
androidVersionCode,
androidKeystoreName: Input.androidKeystoreName,
androidKeystoreBase64: Input.androidKeystoreBase64,
androidKeystorePass: Input.androidKeystorePass,
androidKeyaliasName: Input.androidKeyaliasName,
androidKeyaliasPass: Input.androidKeyaliasPass,
androidTargetSdkVersion: Input.androidTargetSdkVersion,
androidKeystoreName: this.input.androidKeystoreName,
androidKeystoreBase64: this.input.androidKeystoreBase64,
androidKeystorePass: this.input.androidKeystorePass,
androidKeyaliasName: this.input.androidKeyaliasName,
androidKeyaliasPass: this.input.androidKeyaliasPass,
androidTargetSdkVersion: this.input.androidTargetSdkVersion,
androidSdkManagerParameters,
customParameters: Input.customParameters,
sshAgent: Input.sshAgent,
gitPrivateToken: Input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
chownFilesTo: Input.chownFilesTo,
cloudRunnerCluster: Input.cloudRunnerCluster,
cloudRunnerBuilderPlatform: Input.cloudRunnerBuilderPlatform,
awsBaseStackName: Input.awsBaseStackName,
kubeConfig: Input.kubeConfig,
cloudRunnerMemory: Input.cloudRunnerMemory,
cloudRunnerCpu: Input.cloudRunnerCpu,
kubeVolumeSize: Input.kubeVolumeSize,
kubeVolume: Input.kubeVolume,
postBuildSteps: Input.postBuildSteps,
preBuildSteps: Input.preBuildSteps,
customJob: Input.customJob,
runNumber: Input.runNumber,
branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
cloudRunnerBranch: Input.cloudRunnerBranch.split('/').reverse()[0],
cloudRunnerIntegrationTests: Input.cloudRunnerTests,
githubRepo: Input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
customParameters: this.input.customParameters,
sshAgent: this.input.sshAgent,
gitPrivateToken: this.input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
chownFilesTo: this.input.chownFilesTo,
cloudRunnerCluster: this.input.cloudRunnerCluster,
cloudRunnerBuilderPlatform: this.input.cloudRunnerBuilderPlatform,
awsBaseStackName: this.input.awsBaseStackName,
kubeConfig: this.input.kubeConfig,
cloudRunnerMemory: this.input.cloudRunnerMemory,
cloudRunnerCpu: this.input.cloudRunnerCpu,
kubeVolumeSize: this.input.kubeVolumeSize,
kubeVolume: this.input.kubeVolume,
postBuildSteps: this.input.postBuildSteps,
preBuildSteps: this.input.preBuildSteps,
customJob: this.input.customJob,
runNumber: this.input.runNumber,
branch: this.input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
cloudRunnerBranch: this.input.cloudRunnerBranch.split('/').reverse()[0],
cloudRunnerIntegrationTests: this.input.cloudRunnerTests,
githubRepo: this.input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
isCliMode: Cli.isCliMode,
awsStackName: Input.awsBaseStackName,
gitSha: Input.gitSha,
awsStackName: this.input.awsBaseStackName,
gitSha: this.input.gitSha,
logId: nanoid.customAlphabet(CloudRunnerConstants.alphabet, 9)(),
buildGuid: CloudRunnerBuildGuid.generateGuid(Input.runNumber, Input.targetPlatform),
customJobHooks: Input.customJobHooks(),
cachePullOverrideCommand: Input.cachePullOverrideCommand(),
cachePushOverrideCommand: Input.cachePushOverrideCommand(),
readInputOverrideCommand: Input.readInputOverrideCommand(),
readInputFromOverrideList: Input.readInputFromOverrideList(),
kubeStorageClass: Input.kubeStorageClass,
checkDependencyHealthOverride: Input.checkDependencyHealthOverride,
startDependenciesOverride: Input.startDependenciesOverride,
cacheKey: Input.cacheKey,
buildGuid: CloudRunnerBuildGuid.generateGuid(this.input.runNumber, this.input.targetPlatform),
customJobHooks: this.input.customJobHooks(),
cachePullOverrideCommand: this.input.cachePullOverrideCommand(),
cachePushOverrideCommand: this.input.cachePushOverrideCommand(),
readInputOverrideCommand: this.input.readInputOverrideCommand(),
readInputFromOverrideList: this.input.readInputFromOverrideList(),
kubeStorageClass: this.input.kubeStorageClass,
checkDependencyHealthOverride: this.input.checkDependencyHealthOverride,
startDependenciesOverride: this.input.startDependenciesOverride,
cacheKey: this.input.cacheKey,
};
}
@ -178,4 +191,4 @@ class BuildParameters {
}
}
export default BuildParameters;
export default Parameters;

View File

@ -1,9 +1,9 @@
import { BuildParameters } from './index.ts';
import { Parameters } from './index.ts';
import { SetupMac, SetupWindows } from './platform-setup/index.ts';
import ValidateWindows from './platform-validation/validate-windows.ts';
class PlatformSetup {
static async setup(buildParameters: BuildParameters, actionFolder: string) {
static async setup(buildParameters: Parameters, actionFolder: string) {
switch (process.platform) {
case 'win32':
ValidateWindows.validate(buildParameters);

View File

@ -1,10 +1,10 @@
import { BuildParameters } from '../index.ts';
import { Parameters } from '../index.ts';
import { fsSync as fs, exec, getUnityChangeSet } from '../../dependencies.ts';
class SetupMac {
static unityHubPath = `"/Applications/Unity Hub.app/Contents/MacOS/Unity Hub"`;
public static async setup(buildParameters: BuildParameters, actionFolder: string) {
public static async setup(buildParameters: Parameters, actionFolder: string) {
const unityEditorPath = `/Applications/Unity/Hub/Editor/${buildParameters.editorVersion}/Unity.app/Contents/MacOS/Unity`;
// Only install unity if the editor doesn't already exist
@ -28,7 +28,7 @@ class SetupMac {
}
}
private static async installUnity(buildParameters: BuildParameters, silent = false) {
private static async installUnity(buildParameters: Parameters, silent = false) {
const unityChangeSet = await getUnityChangeSet(buildParameters.editorVersion);
const command = `${this.unityHubPath} -- --headless install \
--version ${buildParameters.editorVersion} \
@ -44,7 +44,7 @@ class SetupMac {
}
}
private static async setEnvironmentVariables(buildParameters: BuildParameters, actionFolder: string) {
private static async setEnvironmentVariables(buildParameters: Parameters, actionFolder: string) {
// Need to set environment variables from here because we execute
// the scripts on the host for mac
Deno.env.set('ACTION_FOLDER', actionFolder);

View File

@ -1,8 +1,8 @@
import { fsSync as fs, exec } from '../../dependencies.ts';
import { BuildParameters } from '../index.ts';
import { Parameters } from '../index.ts';
class SetupWindows {
public static async setup(buildParameters: BuildParameters) {
public static async setup(buildParameters: Parameters) {
const { targetPlatform } = buildParameters;
await SetupWindows.setupWindowsRun(targetPlatform);

View File

@ -1,8 +1,8 @@
import { fsSync as fs } from '../../dependencies.ts';
import { BuildParameters } from '../index.ts';
import { Parameters } from '../index.ts';
class ValidateWindows {
public static validate(buildParameters: BuildParameters) {
public static validate(buildParameters: Parameters) {
ValidateWindows.validateWindowsPlatformRequirements(buildParameters.targetPlatform);
if (!(Deno.env.get('UNITY_EMAIL') && Deno.env.get('UNITY_PASSWORD'))) {
throw new Error(`Unity email and password must be set for Windows based builds to

View File

@ -1,4 +1,3 @@
import { Buffer } from '../dependencies.ts';
import NotImplementedException from './error/not-implemented-exception.ts';
import ValidationError from './error/validation-error.ts';
import Input from './input.ts';
@ -9,10 +8,6 @@ export default class Versioning {
return Input.projectPath;
}
static get isDirtyAllowed() {
return Input.allowDirtyBuild;
}
static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
}
@ -57,16 +52,6 @@ export default class Versioning {
return 60;
}
/**
* Log up to maxDiffLines of the git diff.
*/
static async logDiff() {
const diffCommand = `git --no-pager diff | head -n ${this.maxDiffLines.toString()}`;
const result = await System.shellRun(diffCommand);
log.debug(result.output);
}
/**
* Regex to parse version description into separate fields
*/
@ -82,7 +67,7 @@ export default class Versioning {
return /^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static async determineBuildVersion(strategy: string, inputVersion: string) {
static async determineBuildVersion(strategy: string, inputVersion: string, allowDirtyBuild: boolean) {
// Validate input
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
throw new ValidationError(`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`);
@ -99,7 +84,7 @@ export default class Versioning {
version = inputVersion;
break;
case this.strategies.Semantic:
version = await this.generateSemanticVersion();
version = await this.generateSemanticVersion(allowDirtyBuild);
break;
case this.strategies.Tag:
version = await this.generateTagVersion();
@ -113,6 +98,16 @@ export default class Versioning {
return version;
}
/**
* Log up to maxDiffLines of the git diff.
*/
static async logDiff() {
const diffCommand = `git --no-pager diff | head -n ${this.maxDiffLines.toString()}`;
const result = await System.shellRun(diffCommand);
log.debug(result.output);
}
/**
* Automatically generates a version based on SemVer out of the box.
*
@ -123,14 +118,14 @@ export default class Versioning {
*
* @See: https://semver.org/
*/
static async generateSemanticVersion() {
static async generateSemanticVersion(allowDirtyBuild) {
if (await this.isShallow()) {
await this.fetch();
}
await Versioning.logDiff();
if ((await this.isDirty()) && !this.isDirtyAllowed) {
if ((await this.isDirty()) && !allowDirtyBuild) {
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
}
@ -209,9 +204,7 @@ export default class Versioning {
hash,
};
} catch {
log.warning(
`Failed to parse git describe output or version can not be determined through: "${description}".`,
);
log.warning(`Failed to parse git describe output or version can not be determined through "${description}".`);
return false;
}
@ -286,14 +279,16 @@ export default class Versioning {
* Note: Currently this is run in all OSes, so the syntax must be cross-platform.
*/
static async hasAnyVersionTags() {
const numberOfTagsAsString = await System.run('sh', undefined, {
input: Buffer.from(`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`),
cwd: this.projectPath,
silent: false,
});
const command = `git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`;
const result = await System.shellRun(command, { cwd: this.projectPath, silent: false });
log.debug(result);
const { output: numberOfTagsAsString } = result;
const numberOfTags = Number.parseInt(numberOfTagsAsString, 10);
log.debug('numberOfTags', numberOfTags);
return numberOfTags !== 0;
}