From fa479f532aacc71502dd5e885fd0c5021047396a Mon Sep 17 00:00:00 2001 From: Webber Date: Wed, 24 Aug 2022 01:21:00 +0200 Subject: [PATCH] feat: cleanup parameters part 1/* --- src/index.ts | 5 + src/logic/unity/platform-setup/setup-mac.ts | 40 +++--- src/model/build-parameters.test.ts | 16 +-- src/model/docker.ts | 64 ++++++--- src/model/input.ts | 140 ++++++++++---------- src/model/parameters.ts | 137 ++++++++++--------- src/model/system.ts | 33 ++++- 7 files changed, 239 insertions(+), 196 deletions(-) diff --git a/src/index.ts b/src/index.ts index c96b0dbf..12ab1167 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { CommandFactory } from './commands/command-factory.ts'; import { ArgumentsParser } from './core/cli/arguments-parser.ts'; import { Environment } from './core/env/environment.ts'; import { EngineDetector } from './core/engine/engine-detector.ts'; +import { ParameterOptions } from './model/parameter-options.ts'; export class GameCI { private readonly env: Environment; @@ -20,6 +21,10 @@ export class GameCI { const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args); 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 const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect(); const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands); diff --git a/src/logic/unity/platform-setup/setup-mac.ts b/src/logic/unity/platform-setup/setup-mac.ts index 353e9f3b..9b8dfe08 100644 --- a/src/logic/unity/platform-setup/setup-mac.ts +++ b/src/logic/unity/platform-setup/setup-mac.ts @@ -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 // the scripts on the host for mac Deno.env.set('ACTION_FOLDER', actionFolder); - Deno.env.set('UNITY_VERSION', buildParameters.editorVersion); - Deno.env.set('UNITY_SERIAL', buildParameters.unitySerial); - Deno.env.set('PROJECT_PATH', buildParameters.projectPath); - Deno.env.set('BUILD_TARGET', buildParameters.targetPlatform); - Deno.env.set('BUILD_NAME', buildParameters.buildName); - Deno.env.set('BUILD_PATH', buildParameters.buildPath); - Deno.env.set('BUILD_FILE', buildParameters.buildFile); - Deno.env.set('BUILD_METHOD', buildParameters.buildMethod); - Deno.env.set('VERSION', buildParameters.buildVersion); - Deno.env.set('ANDROID_VERSION_CODE', buildParameters.androidVersionCode); - Deno.env.set('ANDROID_KEYSTORE_NAME', buildParameters.androidKeystoreName); - Deno.env.set('ANDROID_KEYSTORE_BASE64', buildParameters.androidKeystoreBase64); - Deno.env.set('ANDROID_KEYSTORE_PASS', buildParameters.androidKeystorePass); - Deno.env.set('ANDROID_KEYALIAS_NAME', buildParameters.androidKeyaliasName); - Deno.env.set('ANDROID_KEYALIAS_PASS', buildParameters.androidKeyaliasPass); - Deno.env.set('ANDROID_TARGET_SDK_VERSION', buildParameters.androidTargetSdkVersion); - Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', buildParameters.androidSdkManagerParameters); - Deno.env.set('CUSTOM_PARAMETERS', buildParameters.customParameters); - Deno.env.set('CHOWN_FILES_TO', buildParameters.chownFilesTo); + Deno.env.set('UNITY_VERSION', parameters.editorVersion); + Deno.env.set('UNITY_SERIAL', parameters.unitySerial); + Deno.env.set('PROJECT_PATH', parameters.projectPath); + Deno.env.set('BUILD_TARGET', parameters.targetPlatform); + Deno.env.set('BUILD_NAME', parameters.buildName); + Deno.env.set('BUILD_PATH', parameters.buildPath); + Deno.env.set('BUILD_FILE', parameters.buildFile); + Deno.env.set('BUILD_METHOD', parameters.buildMethod); + Deno.env.set('VERSION', parameters.buildVersion); + Deno.env.set('ANDROID_VERSION_CODE', parameters.androidVersionCode); + Deno.env.set('ANDROID_KEYSTORE_NAME', parameters.androidKeystoreName); + Deno.env.set('ANDROID_KEYSTORE_BASE64', parameters.androidKeystoreBase64); + Deno.env.set('ANDROID_KEYSTORE_PASS', parameters.androidKeystorePass); + Deno.env.set('ANDROID_KEYALIAS_NAME', parameters.androidKeyaliasName); + Deno.env.set('ANDROID_KEYALIAS_PASS', parameters.androidKeyaliasPass); + Deno.env.set('ANDROID_TARGET_SDK_VERSION', parameters.androidTargetSdkVersion); + Deno.env.set('ANDROID_SDK_MANAGER_PARAMETERS', parameters.androidSdkManagerParameters); + Deno.env.set('CUSTOM_PARAMETERS', parameters.customParameters); + Deno.env.set('CHOWN_FILES_TO', parameters.chownFilesTo); } } diff --git a/src/model/build-parameters.test.ts b/src/model/build-parameters.test.ts index 24a39596..695a0fa5 100644 --- a/src/model/build-parameters.test.ts +++ b/src/model/build-parameters.test.ts @@ -93,9 +93,7 @@ describe('BuildParameters', () => { async (targetPlatform) => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); - expect(Parameters.create()).resolves.toEqual( - expect.objectContaining({ buildFile: `${targetPlatform}.exe` }), - ); + expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.exe` })); }, ); @@ -103,18 +101,14 @@ describe('BuildParameters', () => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false); - expect(Parameters.create()).resolves.toEqual( - expect.objectContaining({ buildFile: `${targetPlatform}.apk` }), - ); + expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.apk` })); }); test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => { jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform); jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true); - expect(Parameters.create()).resolves.toEqual( - expect.objectContaining({ buildFile: `${targetPlatform}.aab` }), - ); + expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: `${targetPlatform}.aab` })); }); it('returns the build method', async () => { @@ -156,9 +150,7 @@ describe('BuildParameters', () => { it('returns the android target sdk version', async () => { const mockValue = 'AndroidApiLevelAuto'; jest.spyOn(Input, 'androidTargetSdkVersion', 'get').mockReturnValue(mockValue); - expect(Parameters.create()).resolves.toEqual( - expect.objectContaining({ androidTargetSdkVersion: mockValue }), - ); + expect(Parameters.create()).resolves.toEqual(expect.objectContaining({ androidTargetSdkVersion: mockValue })); }); it('returns the custom parameters', async () => { diff --git a/src/model/docker.ts b/src/model/docker.ts index 41ac8758..99c1ab37 100644 --- a/src/model/docker.ts +++ b/src/model/docker.ts @@ -3,19 +3,43 @@ import { path, fsSync as fs } from '../dependencies.ts'; import System from './system.ts'; class Docker { - static async run(image, parameters, silent = false) { - log.warning('running docker process for', process.platform, silent); + static async run(image, parameters) { + log.warning('running docker process for', process.platform); 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 'darwin': { command = await this.getLinuxCommand(image, parameters); break; - case 'win32': - command = await this.getWindowsCommand(image, parameters); + } } - const test = await System.newRun(`docker`, command.replace(/\s\s+/, ' ').split(' '), { silent, verbose: true }); - log.error('test', test); + try { + 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 { @@ -28,8 +52,8 @@ class Docker { return String.dedent` docker run \ - --workdir /github/workspace \ --rm \ + --workdir /github/workspace \ ${ImageEnvironmentFactory.getEnvVarString(parameters)} \ --env UNITY_SERIAL \ --env GITHUB_WORKSPACE=/github/workspace \ @@ -51,25 +75,23 @@ class Docker { static async getWindowsCommand(image: any, parameters: any): string { const { workspace, actionFolder, unitySerial, gitPrivateToken, cliStoragePath } = parameters; - // Todo - get this to work on a non-github runner local machine - // Note: difference between `docker run` and `run` - return String.dedent`run ${image} powershell c:/steps/entrypoint.ps1`; + // Note: the equals sign (=) is needed in Powershell. return String.dedent` docker run \ - --workdir /github/workspace \ --rm \ + --workdir="c:/github/workspace" \ ${ImageEnvironmentFactory.getEnvVarString(parameters)} \ --env UNITY_SERIAL="${unitySerial}" \ --env GITHUB_WORKSPACE=c:/github/workspace \ - ${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \ - --volume "${workspace}":"c:/github/workspace" \ - --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)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \ - --volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ - --volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \ - --volume "${actionFolder}/platforms/windows":"c:/steps" \ - --volume "${actionFolder}/BlankProject":"c:/BlankProject" \ + --env GIT_PRIVATE_TOKEN="${gitPrivateToken}" \ + --volume="${workspace}":"c:/github/workspace" \ + --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)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \ + --volume="C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ + --volume="${actionFolder}/default-build-script":"c:/UnityBuilderAction" \ + --volume="${actionFolder}/platforms/windows":"c:/steps" \ + --volume="${actionFolder}/BlankProject":"c:/BlankProject" \ ${image} \ powershell c:/steps/entrypoint.ps1 `; diff --git a/src/model/input.ts b/src/model/input.ts index 694d50b3..a45cdbb2 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -21,15 +21,12 @@ class Input { return this; } + // Todo - read something from environment instead and make that into a parameter, then use that. public static githubInputEnabled: boolean = true; // 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) { - const value = this.arguments.get(query); - - if (log.isVeryVerbose) log.debug('arg', query, '=', value); - return this.arguments.get(query); } @@ -64,25 +61,25 @@ class Input { } public get region(): string { - return this.getInput('region') || 'eu-west-2'; + return this.get('region'); } 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() { - 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').replace('/head', ''); + if (this.get(`GITHUB_REF`)) { + return this.get(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, ''); + } else if (this.get('branch')) { + return this.get('branch').replace('/head', ''); } else { return ''; } } public get cloudRunnerBuilderPlatform() { - const input = this.getInput('cloudRunnerBuilderPlatform'); + const input = this.get('cloudRunnerBuilderPlatform'); if (input) { return input; } @@ -94,55 +91,55 @@ class Input { } public get gitSha() { - if (this.getInput(`GITHUB_SHA`)) { - return this.getInput(`GITHUB_SHA`); - } else if (this.getInput(`GitSHA`)) { - return this.getInput(`GitSHA`); + if (this.get(`GITHUB_SHA`)) { + return this.get(`GITHUB_SHA`); + } else if (this.get(`GitSHA`)) { + return this.get(`GitSHA`); } } public get runNumber() { - return this.getInput('GITHUB_RUN_NUMBER') || '0'; + return this.get('GITHUB_RUN_NUMBER') || '0'; } public get targetPlatform() { - return this.getInput('targetPlatform') || Platform.default; + return this.get('targetPlatform') || Platform.default; } public get unityVersion() { - return this.getInput('unityVersion') || 'auto'; + return this.get('unityVersion') || 'auto'; } public get unityEmail() { - return this.getInput('unityEmail') || ''; + return this.get('unityEmail') || ''; } public get unityPassword() { - return this.getInput('unityPassword') || ''; + return this.get('unityPassword') || ''; } public get unityLicense() { - return this.getInput('unityLicense') || ''; + return this.get('unityLicense') || ''; } public get unityLicenseFile() { - return this.getInput('unityLicenseFile') || ''; + return this.get('unityLicenseFile') || ''; } public get unitySerial() { - return this.getInput('unitySerial') || ''; + return this.get('unitySerial') || ''; } public get usymUploadAuthToken() { - return this.getInput('usymUploadAuthToken') || ''; + return this.get('usymUploadAuthToken') || ''; } public get customImage() { - return this.getInput('customImage') || ''; + return this.get('customImage') || ''; } public get projectPath() { - let input = this.getInput('projectPath'); + let input = this.get('projectPath'); // Todo - remove hardcoded test project reference const isTestProject = @@ -156,172 +153,169 @@ class Input { } public get buildName() { - return this.getInput('buildName') || this.targetPlatform; + return this.get('buildName'); } public get buildsPath() { - return this.getInput('buildsPath') || 'build'; + return this.get('buildsPath') || 'build'; } public get buildMethod() { - return this.getInput('buildMethod') || ''; // Processed in docker file + return this.get('buildMethod') || ''; // Processed in docker file } public get customParameters() { - return this.getInput('customParameters') || ''; + return this.get('customParameters') || ''; } public get versioningStrategy() { - return this.getInput('versioning') || 'Semantic'; + return this.get('versioning') || 'Semantic'; } public get specifiedVersion() { - return this.getInput('version') || ''; + return this.get('version') || ''; } public get androidVersionCode() { - return this.getInput('androidVersionCode'); + return this.get('androidVersionCode'); } public get androidAppBundle() { - const input = this.getInput('androidAppBundle') || false; + const input = this.get('androidAppBundle') || false; return input === 'true'; } public get androidKeystoreName() { - return this.getInput('androidKeystoreName') || ''; + return this.get('androidKeystoreName') || ''; } public get androidKeystoreBase64() { - return this.getInput('androidKeystoreBase64') || ''; + return this.get('androidKeystoreBase64') || ''; } public get androidKeystorePass() { - return this.getInput('androidKeystorePass') || ''; + return this.get('androidKeystorePass') || ''; } public get androidKeyaliasName() { - return this.getInput('androidKeyaliasName') || ''; + return this.get('androidKeyaliasName') || ''; } public get androidKeyaliasPass() { - return this.getInput('androidKeyaliasPass') || ''; + return this.get('androidKeyaliasPass') || ''; } public get androidTargetSdkVersion() { - return this.getInput('androidTargetSdkVersion') || ''; + return this.get('androidTargetSdkVersion') || ''; } public get sshAgent() { - return this.getInput('sshAgent') || ''; + return this.get('sshAgent') || ''; } public get gitPrivateToken() { - return core.getInput('gitPrivateToken') || false; + return this.get('gitPrivateToken') || ''; } public get customJob() { - return this.getInput('customJob') || ''; + return this.get('customJob') || ''; } public customJobHooks() { - return this.getInput('customJobHooks') || ''; + return this.get('customJobHooks') || ''; } public cachePushOverrideCommand() { - return this.getInput('cachePushOverrideCommand') || ''; + return this.get('cachePushOverrideCommand') || ''; } public cachePullOverrideCommand() { - return this.getInput('cachePullOverrideCommand') || ''; + return this.get('cachePullOverrideCommand') || ''; } public readInputFromOverrideList() { - return this.getInput('readInputFromOverrideList') || ''; + return this.get('readInputFromOverrideList') || ''; } public readInputOverrideCommand() { - return this.getInput('readInputOverrideCommand') || ''; + return this.get('readInputOverrideCommand') || ''; } public get cloudRunnerBranch() { - return this.getInput('cloudRunnerBranch') || 'cloud-runner-develop'; + return this.get('cloudRunnerBranch') || 'cloud-runner-develop'; } public get chownFilesTo() { - return this.getInput('chownFilesTo') || ''; + return this.get('chownFilesTo') || ''; } public get allowDirtyBuild() { - const input = this.getInput('allowDirtyBuild'); + const input = this.get('allowDirtyBuild'); log.debug('input === ', input); return input || false === true; } public get postBuildSteps() { - return this.getInput('postBuildSteps') || ''; + return this.get('postBuildSteps') || ''; } public get preBuildSteps() { - return this.getInput('preBuildSteps') || ''; + return this.get('preBuildSteps') || ''; } public get awsBaseStackName() { - return this.getInput('awsBaseStackName') || 'game-ci'; + return this.get('awsBaseStackName') || 'game-ci'; } public get cloudRunnerCpu() { - return this.getInput('cloudRunnerCpu'); + return this.get('cloudRunnerCpu'); } public get cloudRunnerMemory() { - return this.getInput('cloudRunnerMemory'); + return this.get('cloudRunnerMemory'); } public get kubeConfig() { - return this.getInput('kubeConfig') || ''; + return this.get('kubeConfig') || ''; } public get kubeVolume() { - return this.getInput('kubeVolume') || ''; + return this.get('kubeVolume') || ''; } public get kubeVolumeSize() { - return this.getInput('kubeVolumeSize') || '5Gi'; + return this.get('kubeVolumeSize') || '5Gi'; } public get kubeStorageClass(): string { - return this.getInput('kubeStorageClass') || ''; + return this.get('kubeStorageClass') || ''; } public get checkDependencyHealthOverride(): string { - return this.getInput('checkDependencyHealthOverride') || ''; + return this.get('checkDependencyHealthOverride') || ''; } public get startDependenciesOverride(): string { - return this.getInput('startDependenciesOverride') || ''; + return this.get('startDependenciesOverride') || ''; } public get cacheKey(): string { - return this.getInput('cacheKey') || Input.branch; + return this.get('cacheKey') || Input.branch; } public get cloudRunnerTests(): boolean { - return this.getInput(`cloudRunnerTests`) || false; + return this.get(`cloudRunnerTests`) || false; } + /** + * @deprecated Use Parameter.toEnvFormat + */ public static toEnvVarFormat(input: string) { - if (input.toUpperCase() === input) { - return input; - } + if (input.toUpperCase() === input) return input; - return input - .replace(/([A-Z])/g, ' $1') - .trim() - .toUpperCase() - .replace(/ /g, '_'); + return input.replace(/([\da-z])([A-Z])/g, '$1_$2').toUpperCase(); } } diff --git a/src/model/parameters.ts b/src/model/parameters.ts index 15a95b76..5f8cfc0f 100644 --- a/src/model/parameters.ts +++ b/src/model/parameters.ts @@ -7,6 +7,7 @@ import Versioning from './versioning.ts'; import { GitRepoReader } from './input-readers/git-repo.ts'; import { CommandInterface } from '../commands/command/command-interface.ts'; import { Environment } from '../core/env/environment.ts'; +import { Parameter } from './parameter.ts'; class Parameters { private command: CommandInterface; @@ -64,85 +65,70 @@ class Parameters { public cloudRunnerBuilderPlatform!: string | undefined; public isCliMode!: boolean; + private defaults: Partial = { + region: 'eu-west-2', + }; + private readonly input: Input; private readonly env: Environment; constructor(input: Input, env: Environment) { this.input = input; 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 { 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( - this.input.buildName, - this.input.targetPlatform, - this.input.androidAppBundle, - ); - log.debug('buildFile:', buildFile); - const editorVersion = UnityVersioning.determineUnityVersion(this.input.projectPath, this.input.unityVersion); - 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 buildName = this.input.get('buildName') || targetPlatform; + const buildFile = Parameters.parseBuildFile(buildName, targetPlatform, this.get('androidAppBundle')); + const buildPath = `${buildsPath}/${targetPlatform}`; + const editorVersion = UnityVersioning.determineUnityVersion(projectPath, unityVersion); + const buildVersion = await Versioning.determineBuildVersion(versioningStrategy, specifiedVersion, allowDirtyBuild); + const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, this.get('androidVersionCode')); + const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(androidTargetSdkVersion); 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 = { + branch, + unityEmail: this.get('unityEmail'), + unityPassword: this.get('unityPassword'), + unityLicense: this.get('unityLicense'), + unityLicenseFile: this.get('unityLicenseFile'), + unitySerial: this.getUnitySerial(), cliStoragePath, editorVersion, - customImage: this.input.customImage, - unityEmail, - unityPassword, - unityLicense, - unityLicenseFile, - unitySerial, - usymUploadAuthToken: this.input.usymUploadAuthToken || this.env.get('USYM_UPLOAD_AUTH_TOKEN'), - runnerTempPath: this.env.RUNNER_TEMP, + customImage: this.get('customImage'), + usymUploadAuthToken: this.get('usymUploadAuthToken'), + runnerTempPath: this.env.get('RUNNER_TEMP'), targetPlatform, projectPath, - buildName: this.input.buildName, - buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`, + buildName, + buildPath, buildFile, buildMethod: this.input.buildMethod, buildVersion, @@ -152,14 +138,13 @@ class Parameters { androidKeystorePass: this.input.androidKeystorePass, androidKeyaliasName: this.input.androidKeyaliasName, androidKeyaliasPass: this.input.androidKeyaliasPass, - androidTargetSdkVersion: this.input.androidTargetSdkVersion, + androidTargetSdkVersion, androidSdkManagerParameters, - customParameters: this.input.customParameters, + customParameters: this.get('customParameters'), sshAgent: this.input.sshAgent, - gitPrivateToken: this.input.gitPrivateToken, + gitPrivateToken: this.get('gitPrivateToken'), chownFilesTo: this.input.chownFilesTo, customJob: this.input.customJob, - branch, }; const commandParameterOverrides = await this.command.parseParameters(this.input, parameters); @@ -183,7 +168,27 @@ class Parameters { 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 = ``; const startIndex = license.indexOf(startKey) + startKey.length; diff --git a/src/model/system.ts b/src/model/system.ts index 73f0de89..f168594d 100644 --- a/src/model/system.ts +++ b/src/model/system.ts @@ -1,5 +1,6 @@ export interface RunOptions { pwd: string; + attach: boolean; } class System { @@ -7,7 +8,7 @@ class System { * Run any command as if you're typing in shell. * 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 * @@ -28,11 +29,15 @@ class System { } static async shellRun(command: string, options: RunOptions = {}): Promise { - 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 { - 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. */ - public static async newRun(command, args: string | string[] = []): Promise { + public static async runAndCapture(command, args: string | string[] = []): Promise { if (!Array.isArray(args)) args = [args]; const argsString = args.join(' '); @@ -92,6 +97,26 @@ class System { 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 { + 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;