feat: cleanup parameters part 1/*

pull/413/head
Webber 2022-08-24 01:21:00 +02:00
parent bfb847dc81
commit fa479f532a
7 changed files with 239 additions and 196 deletions

View File

@ -5,6 +5,7 @@ import { CommandFactory } from './commands/command-factory.ts';
import { ArgumentsParser } from './core/cli/arguments-parser.ts'; import { ArgumentsParser } from './core/cli/arguments-parser.ts';
import { Environment } from './core/env/environment.ts'; import { Environment } from './core/env/environment.ts';
import { EngineDetector } from './core/engine/engine-detector.ts'; import { EngineDetector } from './core/engine/engine-detector.ts';
import { ParameterOptions } from './model/parameter-options.ts';
export class GameCI { export class GameCI {
private readonly env: Environment; private readonly env: Environment;
@ -20,6 +21,10 @@ export class GameCI {
const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args); const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args);
await configureLogger(verbosity); await configureLogger(verbosity);
// Todo - set default values for parameters to restore functionality.
// Todo - investigate how fitting a CLI lib will be for us
// (now that things are starting to be more separated)
// Determine which command to run // Determine which command to run
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect(); const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands); const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands);

View File

@ -43,29 +43,29 @@ class SetupMac {
} }
} }
private static async setEnvironmentVariables(buildParameters: Parameters, actionFolder: string) { private static async setEnvironmentVariables(parameters: Parameters, actionFolder: string) {
// Need to set environment variables from here because we execute // Need to set environment variables from here because we execute
// the scripts on the host for mac // the scripts on the host for mac
Deno.env.set('ACTION_FOLDER', actionFolder); Deno.env.set('ACTION_FOLDER', actionFolder);
Deno.env.set('UNITY_VERSION', buildParameters.editorVersion); Deno.env.set('UNITY_VERSION', parameters.editorVersion);
Deno.env.set('UNITY_SERIAL', buildParameters.unitySerial); Deno.env.set('UNITY_SERIAL', parameters.unitySerial);
Deno.env.set('PROJECT_PATH', buildParameters.projectPath); Deno.env.set('PROJECT_PATH', parameters.projectPath);
Deno.env.set('BUILD_TARGET', buildParameters.targetPlatform); Deno.env.set('BUILD_TARGET', parameters.targetPlatform);
Deno.env.set('BUILD_NAME', buildParameters.buildName); Deno.env.set('BUILD_NAME', parameters.buildName);
Deno.env.set('BUILD_PATH', buildParameters.buildPath); Deno.env.set('BUILD_PATH', parameters.buildPath);
Deno.env.set('BUILD_FILE', buildParameters.buildFile); Deno.env.set('BUILD_FILE', parameters.buildFile);
Deno.env.set('BUILD_METHOD', buildParameters.buildMethod); Deno.env.set('BUILD_METHOD', parameters.buildMethod);
Deno.env.set('VERSION', buildParameters.buildVersion); Deno.env.set('VERSION', parameters.buildVersion);
Deno.env.set('ANDROID_VERSION_CODE', buildParameters.androidVersionCode); Deno.env.set('ANDROID_VERSION_CODE', parameters.androidVersionCode);
Deno.env.set('ANDROID_KEYSTORE_NAME', buildParameters.androidKeystoreName); Deno.env.set('ANDROID_KEYSTORE_NAME', parameters.androidKeystoreName);
Deno.env.set('ANDROID_KEYSTORE_BASE64', buildParameters.androidKeystoreBase64); Deno.env.set('ANDROID_KEYSTORE_BASE64', parameters.androidKeystoreBase64);
Deno.env.set('ANDROID_KEYSTORE_PASS', buildParameters.androidKeystorePass); Deno.env.set('ANDROID_KEYSTORE_PASS', parameters.androidKeystorePass);
Deno.env.set('ANDROID_KEYALIAS_NAME', buildParameters.androidKeyaliasName); Deno.env.set('ANDROID_KEYALIAS_NAME', parameters.androidKeyaliasName);
Deno.env.set('ANDROID_KEYALIAS_PASS', buildParameters.androidKeyaliasPass); Deno.env.set('ANDROID_KEYALIAS_PASS', parameters.androidKeyaliasPass);
Deno.env.set('ANDROID_TARGET_SDK_VERSION', buildParameters.androidTargetSdkVersion); Deno.env.set('ANDROID_TARGET_SDK_VERSION', parameters.androidTargetSdkVersion);
Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', buildParameters.androidSdkManagerParameters); Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', parameters.androidSdkManagerParameters);
Deno.env.set('CUSTOM_PARAMETERS', buildParameters.customParameters); Deno.env.set('CUSTOM_PARAMETERS', parameters.customParameters);
Deno.env.set('CHOWN_FILES_TO', buildParameters.chownFilesTo); Deno.env.set('CHOWN_FILES_TO', parameters.chownFilesTo);
} }
} }

View File

@ -93,9 +93,7 @@ describe('BuildParameters', () => {
async (targetPlatform) => { async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
expect(Parameters.create()).resolves.toEqual( expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.exe` }));
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
);
}, },
); );
@ -103,18 +101,14 @@ describe('BuildParameters', () => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
expect(Parameters.create()).resolves.toEqual( expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.apk` }));
expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
);
}); });
test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => { test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);
expect(Parameters.create()).resolves.toEqual( expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.aab` }));
expect.objectContaining({ buildFile: `${targetPlatform}.aab` }),
);
}); });
it('returns the build method', async () => { it('returns the build method', async () => {
@ -156,9 +150,7 @@ describe('BuildParameters', () => {
it('returns the android target sdk version', async () => { it('returns the android target sdk version', async () => {
const mockValue = 'AndroidApiLevelAuto'; const mockValue = 'AndroidApiLevelAuto';
jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue); jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue);
expect(Parameters.create()).resolves.toEqual( expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidTargetSdkVersion: mockValue }));
expect.objectContaining({ androidTargetSdkVersion: mockValue }),
);
}); });
it('returns the custom parameters', async () => { it('returns the custom parameters', async () => {

View File

@ -3,19 +3,43 @@ import { path, fsSync as fs } from '../dependencies.ts';
import System from './system.ts'; import System from './system.ts';
class Docker { class Docker {
static async run(image, parameters, silent = false) { static async run(image, parameters) {
log.warning('running docker process for', process.platform, silent); log.warning('running docker process for', process.platform);
let command = ''; let command = '';
switch (process.platform) { switch (Deno.build.os) {
case 'windows': {
// Todo: check if docker daemon is set for Windows or Linux containers.
command = await this.getWindowsCommand(image, parameters);
break;
}
case 'linux': case 'linux':
case 'darwin': {
command = await this.getLinuxCommand(image, parameters); command = await this.getLinuxCommand(image, parameters);
break; break;
case 'win32': }
command = await this.getWindowsCommand(image, parameters);
} }
const test = await System.newRun(`docker`, command.replace(/\s\s+/, ' ').split(' '), { silent, verbose: true }); try {
log.error('test', test); const test = await System.run(command, { attach: true });
log.warning('test', test);
} catch (error) {
if (error.message.includes('docker: image operating system "windows" cannot be used on this platform')) {
throw new Error(String.dedent`
Docker daemon is not set to run Windows containers.
To enable the Hyper-V container backend run:
Enable-WindowsOptionalFeature -Online -FeatureName $("Microsoft-Hyper-V", "Containers") -All
To switch the docker daemon to run Windows containers run:
& $Env:ProgramFiles\\Docker\\Docker\\DockerCli.exe -SwitchDaemon .
For more information see:
https://docs.microsoft.com/en-us/virtualization/windowscontainers/quick-start/set-up-environment?tabs=dockerce#prerequisites
`);
}
throw error;
}
} }
static async getLinuxCommand(image, parameters): string { static async getLinuxCommand(image, parameters): string {
@ -28,8 +52,8 @@ class Docker {
return String.dedent` return String.dedent`
docker run \ docker run \
--workdir /github/workspace \
--rm \ --rm \
--workdir /github/workspace \
${ImageEnvironmentFactory.getEnvVarString(parameters)} \ ${ImageEnvironmentFactory.getEnvVarString(parameters)} \
--env UNITY_SERIAL \ --env UNITY_SERIAL \
--env GITHUB_WORKSPACE=/github/workspace \ --env GITHUB_WORKSPACE=/github/workspace \
@ -51,25 +75,23 @@ class Docker {
static async getWindowsCommand(image: any, parameters: any): string { static async getWindowsCommand(image: any, parameters: any): string {
const { workspace, actionFolder, unitySerial, gitPrivateToken, cliStoragePath } = parameters; const { workspace, actionFolder, unitySerial, gitPrivateToken, cliStoragePath } = parameters;
// Todo - get this to work on a non-github runner local machine // Note: the equals sign (=) is needed in Powershell.
// Note: difference between `docker run` and `run`
return String.dedent`run ${image} powershell c:/steps/entrypoint.ps1`;
return String.dedent` return String.dedent`
docker run \ docker run \
--workdir /github/workspace \
--rm \ --rm \
--workdir="c:/github/workspace" \
${ImageEnvironmentFactory.getEnvVarString(parameters)} \ ${ImageEnvironmentFactory.getEnvVarString(parameters)} \
--env UNITY_SERIAL="${unitySerial}" \ --env UNITY_SERIAL="${unitySerial}" \
--env GITHUB_WORKSPACE=c:/github/workspace \ --env GITHUB_WORKSPACE=c:/github/workspace \
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \ --env GIT_PRIVATE_TOKEN="${gitPrivateToken}" \
--volume "${workspace}":"c:/github/workspace" \ --volume="${workspace}":"c:/github/workspace" \
--volume "${cliStoragePath}/registry-keys":"c:/registry-keys" \ --volume="${cliStoragePath}/registry-keys":"c:/registry-keys" \
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \ --volume="C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
--volume "C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \ --volume="C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ --volume="C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \ --volume="${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
--volume "${actionFolder}/platforms/windows":"c:/steps" \ --volume="${actionFolder}/platforms/windows":"c:/steps" \
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \ --volume="${actionFolder}/BlankProject":"c:/BlankProject" \
${image} \ ${image} \
powershell c:/steps/entrypoint.ps1 powershell c:/steps/entrypoint.ps1
`; `;

View File

@ -21,15 +21,12 @@ class Input {
return this; return this;
} }
// Todo - read something from environment instead and make that into a parameter, then use that.
public static githubInputEnabled: boolean = true; public static githubInputEnabled: boolean = true;
// Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess. // Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess.
public getInput(query: string) { public get(query: string) {
if (this && this.arguments) { if (this && this.arguments) {
const value = this.arguments.get(query);
if (log.isVeryVerbose) log.debug('arg', query, '=', value);
return this.arguments.get(query); return this.arguments.get(query);
} }
@ -64,25 +61,25 @@ class Input {
} }
public get region(): string { public get region(): string {
return this.getInput('region') || 'eu-west-2'; return this.get('region');
} }
public get githubRepo() { public get githubRepo() {
return this.getInput('GITHUB_REPOSITORY') || this.getInput('GITHUB_REPO') || undefined; return this.get('GITHUB_REPOSITORY') || this.get('GITHUB_REPO') || undefined;
} }
public get branch() { public get branch() {
if (this.getInput(`GITHUB_REF`)) { if (this.get(`GITHUB_REF`)) {
return this.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, ''); return this.get(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
} else if (this.getInput('branch')) { } else if (this.get('branch')) {
return this.getInput('branch').replace('/head', ''); return this.get('branch').replace('/head', '');
} else { } else {
return ''; return '';
} }
} }
public get cloudRunnerBuilderPlatform() { public get cloudRunnerBuilderPlatform() {
const input = this.getInput('cloudRunnerBuilderPlatform'); const input = this.get('cloudRunnerBuilderPlatform');
if (input) { if (input) {
return input; return input;
} }
@ -94,55 +91,55 @@ class Input {
} }
public get gitSha() { public get gitSha() {
if (this.getInput(`GITHUB_SHA`)) { if (this.get(`GITHUB_SHA`)) {
return this.getInput(`GITHUB_SHA`); return this.get(`GITHUB_SHA`);
} else if (this.getInput(`GitSHA`)) { } else if (this.get(`GitSHA`)) {
return this.getInput(`GitSHA`); return this.get(`GitSHA`);
} }
} }
public get runNumber() { public get runNumber() {
return this.getInput('GITHUB_RUN_NUMBER') || '0'; return this.get('GITHUB_RUN_NUMBER') || '0';
} }
public get targetPlatform() { public get targetPlatform() {
return this.getInput('targetPlatform') || Platform.default; return this.get('targetPlatform') || Platform.default;
} }
public get unityVersion() { public get unityVersion() {
return this.getInput('unityVersion') || 'auto'; return this.get('unityVersion') || 'auto';
} }
public get unityEmail() { public get unityEmail() {
return this.getInput('unityEmail') || ''; return this.get('unityEmail') || '';
} }
public get unityPassword() { public get unityPassword() {
return this.getInput('unityPassword') || ''; return this.get('unityPassword') || '';
} }
public get unityLicense() { public get unityLicense() {
return this.getInput('unityLicense') || ''; return this.get('unityLicense') || '';
} }
public get unityLicenseFile() { public get unityLicenseFile() {
return this.getInput('unityLicenseFile') || ''; return this.get('unityLicenseFile') || '';
} }
public get unitySerial() { public get unitySerial() {
return this.getInput('unitySerial') || ''; return this.get('unitySerial') || '';
} }
public get usymUploadAuthToken() { public get usymUploadAuthToken() {
return this.getInput('usymUploadAuthToken') || ''; return this.get('usymUploadAuthToken') || '';
} }
public get customImage() { public get customImage() {
return this.getInput('customImage') || ''; return this.get('customImage') || '';
} }
public get projectPath() { public get projectPath() {
let input = this.getInput('projectPath'); let input = this.get('projectPath');
// Todo - remove hardcoded test project reference // Todo - remove hardcoded test project reference
const isTestProject = const isTestProject =
@ -156,172 +153,169 @@ class Input {
} }
public get buildName() { public get buildName() {
return this.getInput('buildName') || this.targetPlatform; return this.get('buildName');
} }
public get buildsPath() { public get buildsPath() {
return this.getInput('buildsPath') || 'build'; return this.get('buildsPath') || 'build';
} }
public get buildMethod() { public get buildMethod() {
return this.getInput('buildMethod') || ''; // Processed in docker file return this.get('buildMethod') || ''; // Processed in docker file
} }
public get customParameters() { public get customParameters() {
return this.getInput('customParameters') || ''; return this.get('customParameters') || '';
} }
public get versioningStrategy() { public get versioningStrategy() {
return this.getInput('versioning') || 'Semantic'; return this.get('versioning') || 'Semantic';
} }
public get specifiedVersion() { public get specifiedVersion() {
return this.getInput('version') || ''; return this.get('version') || '';
} }
public get androidVersionCode() { public get androidVersionCode() {
return this.getInput('androidVersionCode'); return this.get('androidVersionCode');
} }
public get androidAppBundle() { public get androidAppBundle() {
const input = this.getInput('androidAppBundle') || false; const input = this.get('androidAppBundle') || false;
return input === 'true'; return input === 'true';
} }
public get androidKeystoreName() { public get androidKeystoreName() {
return this.getInput('androidKeystoreName') || ''; return this.get('androidKeystoreName') || '';
} }
public get androidKeystoreBase64() { public get androidKeystoreBase64() {
return this.getInput('androidKeystoreBase64') || ''; return this.get('androidKeystoreBase64') || '';
} }
public get androidKeystorePass() { public get androidKeystorePass() {
return this.getInput('androidKeystorePass') || ''; return this.get('androidKeystorePass') || '';
} }
public get androidKeyaliasName() { public get androidKeyaliasName() {
return this.getInput('androidKeyaliasName') || ''; return this.get('androidKeyaliasName') || '';
} }
public get androidKeyaliasPass() { public get androidKeyaliasPass() {
return this.getInput('androidKeyaliasPass') || ''; return this.get('androidKeyaliasPass') || '';
} }
public get androidTargetSdkVersion() { public get androidTargetSdkVersion() {
return this.getInput('androidTargetSdkVersion') || ''; return this.get('androidTargetSdkVersion') || '';
} }
public get sshAgent() { public get sshAgent() {
return this.getInput('sshAgent') || ''; return this.get('sshAgent') || '';
} }
public get gitPrivateToken() { public get gitPrivateToken() {
return core.getInput('gitPrivateToken') || false; return this.get('gitPrivateToken') || '';
} }
public get customJob() { public get customJob() {
return this.getInput('customJob') || ''; return this.get('customJob') || '';
} }
public customJobHooks() { public customJobHooks() {
return this.getInput('customJobHooks') || ''; return this.get('customJobHooks') || '';
} }
public cachePushOverrideCommand() { public cachePushOverrideCommand() {
return this.getInput('cachePushOverrideCommand') || ''; return this.get('cachePushOverrideCommand') || '';
} }
public cachePullOverrideCommand() { public cachePullOverrideCommand() {
return this.getInput('cachePullOverrideCommand') || ''; return this.get('cachePullOverrideCommand') || '';
} }
public readInputFromOverrideList() { public readInputFromOverrideList() {
return this.getInput('readInputFromOverrideList') || ''; return this.get('readInputFromOverrideList') || '';
} }
public readInputOverrideCommand() { public readInputOverrideCommand() {
return this.getInput('readInputOverrideCommand') || ''; return this.get('readInputOverrideCommand') || '';
} }
public get cloudRunnerBranch() { public get cloudRunnerBranch() {
return this.getInput('cloudRunnerBranch') || 'cloud-runner-develop'; return this.get('cloudRunnerBranch') || 'cloud-runner-develop';
} }
public get chownFilesTo() { public get chownFilesTo() {
return this.getInput('chownFilesTo') || ''; return this.get('chownFilesTo') || '';
} }
public get allowDirtyBuild() { public get allowDirtyBuild() {
const input = this.getInput('allowDirtyBuild'); const input = this.get('allowDirtyBuild');
log.debug('input === ', input); log.debug('input === ', input);
return input || false === true; return input || false === true;
} }
public get postBuildSteps() { public get postBuildSteps() {
return this.getInput('postBuildSteps') || ''; return this.get('postBuildSteps') || '';
} }
public get preBuildSteps() { public get preBuildSteps() {
return this.getInput('preBuildSteps') || ''; return this.get('preBuildSteps') || '';
} }
public get awsBaseStackName() { public get awsBaseStackName() {
return this.getInput('awsBaseStackName') || 'game-ci'; return this.get('awsBaseStackName') || 'game-ci';
} }
public get cloudRunnerCpu() { public get cloudRunnerCpu() {
return this.getInput('cloudRunnerCpu'); return this.get('cloudRunnerCpu');
} }
public get cloudRunnerMemory() { public get cloudRunnerMemory() {
return this.getInput('cloudRunnerMemory'); return this.get('cloudRunnerMemory');
} }
public get kubeConfig() { public get kubeConfig() {
return this.getInput('kubeConfig') || ''; return this.get('kubeConfig') || '';
} }
public get kubeVolume() { public get kubeVolume() {
return this.getInput('kubeVolume') || ''; return this.get('kubeVolume') || '';
} }
public get kubeVolumeSize() { public get kubeVolumeSize() {
return this.getInput('kubeVolumeSize') || '5Gi'; return this.get('kubeVolumeSize') || '5Gi';
} }
public get kubeStorageClass(): string { public get kubeStorageClass(): string {
return this.getInput('kubeStorageClass') || ''; return this.get('kubeStorageClass') || '';
} }
public get checkDependencyHealthOverride(): string { public get checkDependencyHealthOverride(): string {
return this.getInput('checkDependencyHealthOverride') || ''; return this.get('checkDependencyHealthOverride') || '';
} }
public get startDependenciesOverride(): string { public get startDependenciesOverride(): string {
return this.getInput('startDependenciesOverride') || ''; return this.get('startDependenciesOverride') || '';
} }
public get cacheKey(): string { public get cacheKey(): string {
return this.getInput('cacheKey') || Input.branch; return this.get('cacheKey') || Input.branch;
} }
public get cloudRunnerTests(): boolean { public get cloudRunnerTests(): boolean {
return this.getInput(`cloudRunnerTests`) || false; return this.get(`cloudRunnerTests`) || false;
} }
/**
* @deprecated Use Parameter.toEnvFormat
*/
public static toEnvVarFormat(input: string) { public static toEnvVarFormat(input: string) {
if (input.toUpperCase() === input) { if (input.toUpperCase() === input) return input;
return input;
}
return input return input.replace(/([\da-z])([A-Z])/g, '$1_$2').toUpperCase();
.replace(/([A-Z])/g, ' $1')
.trim()
.toUpperCase()
.replace(/ /g, '_');
} }
} }

View File

@ -7,6 +7,7 @@ import Versioning from './versioning.ts';
import { GitRepoReader } from './input-readers/git-repo.ts'; import { GitRepoReader } from './input-readers/git-repo.ts';
import { CommandInterface } from '../commands/command/command-interface.ts'; import { CommandInterface } from '../commands/command/command-interface.ts';
import { Environment } from '../core/env/environment.ts'; import { Environment } from '../core/env/environment.ts';
import { Parameter } from './parameter.ts';
class Parameters { class Parameters {
private command: CommandInterface; private command: CommandInterface;
@ -64,85 +65,70 @@ class Parameters {
public cloudRunnerBuilderPlatform!: string | undefined; public cloudRunnerBuilderPlatform!: string | undefined;
public isCliMode!: boolean; public isCliMode!: boolean;
private defaults: Partial<Parameters> = {
region: 'eu-west-2',
};
private readonly input: Input; private readonly input: Input;
private readonly env: Environment; private readonly env: Environment;
constructor(input: Input, env: Environment) { constructor(input: Input, env: Environment) {
this.input = input; this.input = input;
this.env = env; this.env = env;
// Todo - ~/.gameci should hold a config with default settings, like cloud region = 'eu-west-2'
// this.config = config;
}
public get(query, useDefault = true) {
const defaultValue = useDefault ? this.default(query) : undefined;
const value = this.input.get(query) || this.env.get(Parameter.toUpperSnakeCase(query)) || defaultValue;
if (log.isVeryVerbose) log.debug('Argument:', query, '=', value);
return value;
}
private default(query) {
return this.defaults[query];
} }
public async parse(): Promise<Parameters> { public async parse(): Promise<Parameters> {
const cliStoragePath = `${getHomeDir()}/.game-ci`; const cliStoragePath = `${getHomeDir()}/.game-ci`;
const targetPlatform = this.input.get('targetPlatform');
const buildsPath = this.input.get('buildsPath');
const projectPath = this.get('projectPath');
const unityVersion = this.get('unityVersion');
const versioningStrategy = this.get('versioningStrategy');
const specifiedVersion = this.get('specifiedVersion');
const allowDirtyBuild = this.get('allowDirtyBuild');
const androidTargetSdkVersion = this.get('androidTargetSdkVersion');
const buildFile = Parameters.parseBuildFile( const buildName = this.input.get('buildName') || targetPlatform;
this.input.buildName, const buildFile = Parameters.parseBuildFile(buildName, targetPlatform, this.get('androidAppBundle'));
this.input.targetPlatform, const buildPath = `${buildsPath}/${targetPlatform}`;
this.input.androidAppBundle, const editorVersion = UnityVersioning.determineUnityVersion(projectPath, unityVersion);
); const buildVersion = await Versioning.determineBuildVersion(versioningStrategy, specifiedVersion, allowDirtyBuild);
log.debug('buildFile:', buildFile); const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.get('androidVersionCode'));
const editorVersion = UnityVersioning.determineUnityVersion(this.input.projectPath, this.input.unityVersion); const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(androidTargetSdkVersion);
log.info('Detected editorVersion', editorVersion);
const buildVersion = await Versioning.determineBuildVersion(
this.input.versioningStrategy,
this.input.specifiedVersion,
this.input.allowDirtyBuild,
);
log.debug('buildVersion', buildVersion);
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.input.androidVersionCode);
log.debug('androidVersionCode', androidVersionCode);
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(
this.input.androidTargetSdkVersion,
);
log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
// Commandline takes precedence over environment variables
const unityEmail = this.input.unityEmail || this.env.get('UNITY_EMAIL');
const unityPassword = this.input.unityPassword || this.env.get('UNITY_PASSWORD');
const unityLicense = this.input.unityLicense || this.env.get('UNITY_LICENSE');
const unityLicenseFile = this.input.unityLicenseFile || this.env.get('UNITY_LICENSE_FILE');
let unitySerial = this.input.unitySerial || this.env.get('UNITY_SERIAL');
// For Windows, we need to use the serial from the license file
if (!unitySerial && this.input.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert
if (!unityLicense) {
throw new Error(String.dedent`
Missing Unity License File and no Serial was found. If this is a personal license,
make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
a Unity serial number inside the UNITY_SERIAL variable.
`);
}
unitySerial = this.getSerialFromLicenseFile(this.env.UNITY_LICENSE);
} else {
unitySerial = this.env.UNITY_SERIAL!;
}
const branch = (await Versioning.getCurrentBranch()) || (await GitRepoReader.GetBranch()); const branch = (await Versioning.getCurrentBranch()) || (await GitRepoReader.GetBranch());
log.info(`branch: "${branch}"`);
const projectPath = this.input.projectPath;
log.info(`projectPath: "${projectPath}"`);
const targetPlatform = this.input.targetPlatform;
log.info(`targetPlatform: "${targetPlatform}"`);
const parameters = { const parameters = {
branch,
unityEmail: this.get('unityEmail'),
unityPassword: this.get('unityPassword'),
unityLicense: this.get('unityLicense'),
unityLicenseFile: this.get('unityLicenseFile'),
unitySerial: this.getUnitySerial(),
cliStoragePath, cliStoragePath,
editorVersion, editorVersion,
customImage: this.input.customImage, customImage: this.get('customImage'),
unityEmail, usymUploadAuthToken: this.get('usymUploadAuthToken'),
unityPassword, runnerTempPath: this.env.get('RUNNER_TEMP'),
unityLicense,
unityLicenseFile,
unitySerial,
usymUploadAuthToken: this.input.usymUploadAuthToken || this.env.get('USYM_UPLOAD_AUTH_TOKEN'),
runnerTempPath: this.env.RUNNER_TEMP,
targetPlatform, targetPlatform,
projectPath, projectPath,
buildName: this.input.buildName, buildName,
buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`, buildPath,
buildFile, buildFile,
buildMethod: this.input.buildMethod, buildMethod: this.input.buildMethod,
buildVersion, buildVersion,
@ -152,14 +138,13 @@ class Parameters {
androidKeystorePass: this.input.androidKeystorePass, androidKeystorePass: this.input.androidKeystorePass,
androidKeyaliasName: this.input.androidKeyaliasName, androidKeyaliasName: this.input.androidKeyaliasName,
androidKeyaliasPass: this.input.androidKeyaliasPass, androidKeyaliasPass: this.input.androidKeyaliasPass,
androidTargetSdkVersion: this.input.androidTargetSdkVersion, androidTargetSdkVersion,
androidSdkManagerParameters, androidSdkManagerParameters,
customParameters: this.input.customParameters, customParameters: this.get('customParameters'),
sshAgent: this.input.sshAgent, sshAgent: this.input.sshAgent,
gitPrivateToken: this.input.gitPrivateToken, gitPrivateToken: this.get('gitPrivateToken'),
chownFilesTo: this.input.chownFilesTo, chownFilesTo: this.input.chownFilesTo,
customJob: this.input.customJob, customJob: this.input.customJob,
branch,
}; };
const commandParameterOverrides = await this.command.parseParameters(this.input, parameters); const commandParameterOverrides = await this.command.parseParameters(this.input, parameters);
@ -183,7 +168,27 @@ class Parameters {
return filename; return filename;
} }
static getSerialFromLicenseFile(license) { static getUnitySerial() {
let unitySerial = this.get('unitySerial');
if (!unitySerial && this.env.getOS() === 'windows') {
const unityLicense = this.get('unityLicense');
unitySerial = this.getSerialFromLicense(unityLicense);
}
return unitySerial;
}
static getSerialFromLicense(license) {
if (!license) {
throw new Error(String.dedent`
Missing Unity License File and no Unity Serial was found. If this is a personal license,
make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
a Unity serial number inside the UNITY_SERIAL variable.
`);
}
const startKey = `<DeveloperData Value="`; const startKey = `<DeveloperData Value="`;
const endKey = `"/>`; const endKey = `"/>`;
const startIndex = license.indexOf(startKey) + startKey.length; const startIndex = license.indexOf(startKey) + startKey.length;

View File

@ -1,5 +1,6 @@
export interface RunOptions { export interface RunOptions {
pwd: string; pwd: string;
attach: boolean;
} }
class System { class System {
@ -7,7 +8,7 @@ class System {
* Run any command as if you're typing in shell. * Run any command as if you're typing in shell.
* Make sure it's Windows/MacOS/Ubuntu compatible or has alternative commands. * Make sure it's Windows/MacOS/Ubuntu compatible or has alternative commands.
* *
* Intended to always be silent and capture the output. * Intended to always be silent and capture the output, unless attach is passed.
* *
* @returns {string} output of the command on success or failure * @returns {string} output of the command on success or failure
* *
@ -28,11 +29,15 @@ class System {
} }
static async shellRun(command: string, options: RunOptions = {}): Promise<string> { static async shellRun(command: string, options: RunOptions = {}): Promise<string> {
return System.newRun('sh', ['-c', command]); const { attach } = options;
return attach ? System.runAndAttach('sh', ['-c', command]) : System.runAndCapture('sh', ['-c', command]);
} }
static async powershellRun(command: string, options: RunOptions = {}): Promise<string> { static async powershellRun(command: string, options: RunOptions = {}): Promise<string> {
return System.newRun('powershell', [command]); const { attach } = options;
return attach ? System.runAndAttach('powershell', [command]) : System.runAndCapture('powershell', [command]);
} }
/** /**
@ -51,7 +56,7 @@ class System {
* *
* @deprecated not really deprecated, but please use System.run instead because this method will be made private. * @deprecated not really deprecated, but please use System.run instead because this method will be made private.
*/ */
public static async newRun(command, args: string | string[] = []): Promise<string> { public static async runAndCapture(command, args: string | string[] = []): Promise<string> {
if (!Array.isArray(args)) args = [args]; if (!Array.isArray(args)) args = [args];
const argsString = args.join(' '); const argsString = args.join(' ');
@ -92,6 +97,26 @@ class System {
return result; return result;
} }
/**
* Output stdout and stderr to the terminal and attach to the process.
*
* Note that the return signature is slightly different from runAndCapture, because we don't have stderrOutput.
*
* Todo - it would be nice to pipe the output to both stdout and capture it in the result object, but this doesn't seem possible yet.
*/
private static async runAndAttach(command, args: string | string[] = []): Promise<string> {
if (!Array.isArray(args)) args = [args];
const process = Deno.run({ cmd: [command, ...args] });
const status = await process.status();
process.close();
if (!status.success) throw new Error(`Command failed with code ${status.code}`);
return { status, output: 'runAndAttach has access to the output stream' };
}
} }
export default System; export default System;