From a0372d1ec61256d197574a234acd5cdad51a820e Mon Sep 17 00:00:00 2001 From: Webber Date: Sat, 20 Aug 2022 18:12:43 +0200 Subject: [PATCH] feat: add verbosity and split parameters per command --- .gitignore | 2 + logs/.gitkeep | 0 src/commands/command/unity/build-command.ts | 1 - .../command/unity/build-remote-command.ts | 41 ++++++++- src/config/options.ts | 2 +- src/core/cli/arguments-parser.ts | 14 +++ src/core/logger/index.ts | 85 +++++++++++++------ src/global.d.ts | 9 +- src/index.ts | 10 ++- src/model/input.ts | 14 +-- src/model/parameters.ts | 49 +++-------- src/model/system.ts | 16 ++-- src/model/versioning.ts | 4 +- 13 files changed, 163 insertions(+), 84 deletions(-) create mode 100644 logs/.gitkeep diff --git a/.gitignore b/.gitignore index d3b3dbde..1e037fb4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ lib/ yarn-error.log .orig *.log +logs/* +!**/.gitkeep diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/commands/command/unity/build-command.ts b/src/commands/command/unity/build-command.ts index 941d7b24..5e98233e 100644 --- a/src/commands/command/unity/build-command.ts +++ b/src/commands/command/unity/build-command.ts @@ -18,7 +18,6 @@ export class BuildCommand implements CommandInterface { public async execute(options: Options): Promise { try { - log.info('options', options); const { workspace, actionFolder } = Action; const { buildParameters } = options; diff --git a/src/commands/command/unity/build-remote-command.ts b/src/commands/command/unity/build-remote-command.ts index 9dd089c1..d7542046 100644 --- a/src/commands/command/unity/build-remote-command.ts +++ b/src/commands/command/unity/build-remote-command.ts @@ -1,8 +1,13 @@ import { CommandInterface } from '../command-interface.ts'; import { Options } from '../../../config/options.ts'; import { CloudRunner, ImageTag, Input, Output } from '../../../model/index.ts'; -import { core } from '../../../dependencies.ts'; +import { core, nanoid } from '../../../dependencies.ts'; import Parameters from '../../../model/parameters.ts'; +import { GitRepoReader } from '../../../model/input-readers/git-repo.ts'; +import { Cli } from '../../../model/cli/cli.ts'; +import CloudRunnerConstants from '../../../model/cloud-runner/services/cloud-runner-constants.ts'; +import CloudRunnerBuildGuid from '../../../model/cloud-runner/services/cloud-runner-guid.ts'; +import { GithubCliReader } from '../../../model/input-readers/github-cli.ts'; // Todo - Verify this entire flow export class BuildRemoteCommand implements CommandInterface { @@ -12,7 +17,39 @@ export class BuildRemoteCommand implements CommandInterface { this.name = name; } - public async parseParameters(input: Input, parameters: Parameters) {} + public async parseParameters(input: Input, parameters: Parameters) { + return { + cloudRunnerBranch: input.cloudRunnerBranch.split('/').reverse()[0], + cloudRunnerIntegrationTests: input.cloudRunnerTests, + githubRepo: input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder', + gitPrivateToken: parameters.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()), + isCliMode: Cli.isCliMode, + awsStackName: input.awsBaseStackName, + 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, + runNumber: input.runNumber, + gitSha: 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, + }; + } public async execute(options: Options): Promise { try { diff --git a/src/config/options.ts b/src/config/options.ts index 15288928..5c3f481d 100644 --- a/src/config/options.ts +++ b/src/config/options.ts @@ -20,7 +20,7 @@ export class Options { this.input = new Input(args); this.parameters = await new Parameters(this.input, this.env).registerCommand(this.command).parse(); - log.debug('Parameters generated.'); + log.info('Parameters generated.'); return this; } diff --git a/src/core/cli/arguments-parser.ts b/src/core/cli/arguments-parser.ts index c34e10cf..80f5a683 100644 --- a/src/core/cli/arguments-parser.ts +++ b/src/core/cli/arguments-parser.ts @@ -5,10 +5,24 @@ export class ArgumentsParser { const [commandName, ...rest] = cliArguments; const { subCommands, args } = parseArgv(rest); + let verbosity; + if (args.has('vvv') || args.has('max-verbose') || args.has('maxVerbose') || args.has('debug')) { + verbosity = 3; + } else if (args.has('vv') || args.has('very-verbose') || args.has('veryVerbose')) { + verbosity = 2; + } else if (args.has('v') || args.has('verbose')) { + verbosity = 1; + } else if (args.has('q') || args.has('quiet')) { + verbosity = -1; + } else { + verbosity = 0; + } + return { commandName, subCommands, args, + verbosity, }; } } diff --git a/src/core/logger/index.ts b/src/core/logger/index.ts index 3cc568e3..2cbd417b 100644 --- a/src/core/logger/index.ts +++ b/src/core/logger/index.ts @@ -1,36 +1,65 @@ import * as log from 'https://deno.land/std@0.151.0/log/mod.ts'; import { fileFormatter, consoleFormatter } from './formatter.ts'; -// Handlers -const consoleHandler = new log.handlers.ConsoleHandler('DEBUG', { formatter: consoleFormatter }); -const fileHandler = new log.handlers.FileHandler('WARNING', { filename: './game-ci.log', formatter: fileFormatter }); +export enum Verbosity { + quiet = -1, + normal = 0, + verbose = 1, + veryVerbose = 2, + maxVerbose = 3, +} -// Make sure it saves on Ctrl+C interrupt https://github.com/denoland/deno_std/issues/2193 -Deno.addSignalListener('SIGINT', () => fileHandler.flush()); +export const configureLogger = async (verbosity: Verbosity) => { + // Verbosity + const isQuiet = verbosity === Verbosity.quiet; + const isVerbose = verbosity >= Verbosity.verbose; + const isVeryVerbose = verbosity >= Verbosity.veryVerbose; + const isMaxVerbose = verbosity >= Verbosity.maxVerbose; -await log.setup({ - handlers: { - consoleHandler, - fileHandler, - }, + // Handlers + let consoleLevel = 'INFO'; + if (isQuiet) consoleLevel = 'ERROR'; + if (isVerbose) consoleLevel = 'DEBUG'; + const consoleHandler = new log.handlers.ConsoleHandler(consoleLevel, { formatter: consoleFormatter }); + const fileHandler = new log.handlers.FileHandler('WARNING', { + filename: './logs/game-ci.log', + formatter: fileFormatter, + }); - loggers: { - default: { - level: 'DEBUG', - handlers: ['consoleHandler', 'fileHandler'], + // Make sure it saves on Ctrl+C interrupt https://github.com/denoland/deno_std/issues/2193 + Deno.addSignalListener('SIGINT', () => fileHandler.flush()); + + await log.setup({ + handlers: { + consoleHandler, + fileHandler, }, - }, -}); -/** - * Allows using `log.debug` and other methods directly from anywhere - * - * Example - * log.debug('something', [{ a: { b: { c: { d: ['a', 'b'] } } } }], 'something', { - * a: { b: { c: { d: { e: { f: { g: 'foo' } } } } } }, - * }); - * - * Outputs: - * [DEBUG] something [ { a: { b: [Object] } } ] something { a: { b: { c: [Object] } } } - */ -window.log = log.getLogger(); + loggers: { + default: { + level: 'DEBUG', + handlers: ['consoleHandler', 'fileHandler'], + }, + }, + }); + + /** + * Allows using `log.debug` and other methods directly from anywhere + * + * Example + * log.debug('something', [{ a: { b: { c: { d: ['a', 'b'] } } } }], 'something', { + * a: { b: { c: { d: { e: { f: { g: 'foo' } } } } } }, + * }); + * + * Outputs: + * [DEBUG] something [ { a: { b: [Object] } } ] something { a: { b: { c: [Object] } } } + */ + window.log = log.getLogger(); + + // Verbosity + window.log.verbosity = verbosity; + window.log.isQuiet = isQuiet; + window.log.isVerbose = isVerbose; + window.log.isVeryVerbose = isVeryVerbose; + window.log.isMaxVerbose = isMaxVerbose; +}; diff --git a/src/global.d.ts b/src/global.d.ts index a3668700..ed926f3a 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,10 +1,17 @@ +import { Verbosity } from './core/logger/index.ts'; + let log: { + verbosity: Verbosity; + isQuiet: boolean; + isVerbose: boolean; + isVeryVerbose: boolean; + isMaxVerbose: boolean; debug: (msg: any, ...args: any[]) => void; info: (msg: any, ...args: any[]) => void; warning: (msg: any, ...args: any[]) => void; error: (msg: any, ...args: any[]) => void; }; -interface Window { +declare interface Window { log: any; } diff --git a/src/index.ts b/src/index.ts index 29fa36c3..c05a2904 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import './core/logger/index.ts'; +import { configureLogger, Verbosity } from './core/logger/index.ts'; import { Options } from './config/options.ts'; import { CommandFactory } from './commands/command-factory.ts'; import { ArgumentsParser } from './core/cli/arguments-parser.ts'; @@ -15,12 +15,16 @@ export class GameCI { public async run() { try { - const { commandName, subCommands, args } = new ArgumentsParser().parse(this.args); - const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect(); + const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args); + await configureLogger(verbosity); + + const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect(); const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands); const options = await new Options(command, this.env).registerCommand(command).generateParameters(args); + if (log.isVerbose) log.info('Executing', command.name); + await command.execute(options); } catch (error) { log.error(error); diff --git a/src/model/input.ts b/src/model/input.ts index 376effb6..907bc1c0 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -5,8 +5,9 @@ import Platform from './platform.ts'; import { CliArguments } from '../core/cli/cli-arguments.ts'; /** - * Input variables specified in workflows using "with" prop. + * Input variables specified directly on the commandline. * + * Todo - check if the following statement is still correct: * Note that input is always passed as a string, even booleans. * * Todo: rename to UserInput and remove anything that is not direct input from the user / ci workflow @@ -17,18 +18,17 @@ class Input { constructor(argumentsFromCli: CliArguments) { this.arguments = argumentsFromCli; - log.debug('Input initialised.'); - return this; } public static githubInputEnabled: boolean = true; // Todo - Note that this is now invoked both statically and dynamically - which is a temporary mess. - public getInput(query) { + public getInput(query: string) { if (this && this.arguments) { const value = this.arguments.get(query); - log.warning('arg', query, '=', value); + + if (log.isVeryVerbose) log.debug('arg', query, '=', value); return this.arguments.get(query); } @@ -70,15 +70,17 @@ class Input { public get githubRepo() { return this.getInput('GITHUB_REPOSITORY') || this.getInput('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'); + return this.getInput('branch').replace('/head', ''); } else { return ''; } } + public get cloudRunnerBuilderPlatform() { const input = this.getInput('cloudRunnerBuilderPlatform'); if (input) { diff --git a/src/model/parameters.ts b/src/model/parameters.ts index 22ad8252..f67f8ab9 100644 --- a/src/model/parameters.ts +++ b/src/model/parameters.ts @@ -98,8 +98,6 @@ class Parameters { ); log.debug('androidSdkManagerParameters', androidSdkManagerParameters); - // Todo - Don't use process.env directly, that's what the input model class is for. - // --- let unitySerial = ''; if (!this.env.UNITY_SERIAL && this.input.githubInputEnabled) { // No serial was present, so it is a personal license that we need to convert @@ -114,13 +112,22 @@ class Parameters { unitySerial = this.env.UNITY_SERIAL!; } + 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 = { editorVersion, customImage: this.input.customImage, unitySerial, runnerTempPath: this.env.RUNNER_TEMP, - targetPlatform: this.input.targetPlatform, - projectPath: this.input.projectPath, + targetPlatform, + projectPath, buildName: this.input.buildName, buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`, buildFile, @@ -136,41 +143,13 @@ class Parameters { androidSdkManagerParameters, customParameters: this.input.customParameters, sshAgent: this.input.sshAgent, - gitPrivateToken: this.input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()), + gitPrivateToken: this.input.gitPrivateToken, 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: this.input.awsBaseStackName, - gitSha: this.input.gitSha, - logId: nanoid.customAlphabet(CloudRunnerConstants.alphabet, 9)(), - 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, + branch, }; - const commandParameterOverrides = this.command.parseParameters(this.input, parameters); + const commandParameterOverrides = await this.command.parseParameters(this.input, parameters); // Todo - Maybe return an instance instead return { diff --git a/src/model/system.ts b/src/model/system.ts index f9704f9d..bbf59840 100644 --- a/src/model/system.ts +++ b/src/model/system.ts @@ -42,14 +42,20 @@ class System { process.close(); - const output = new TextDecoder().decode(outputBuffer); - const error = new TextDecoder().decode(errorBuffer); + const output = new TextDecoder().decode(outputBuffer).replace(/\n+$/, ''); + const error = new TextDecoder().decode(errorBuffer).replace(/\n+$/, ''); const result = { status, output }; - const symbol = status.success ? '✅' : '❗'; - const truncatedOutput = output.length >= 30 ? `${output.slice(0, 27)}...` : output; - log.debug('Command:', command, argsString, symbol, { status, output: truncatedOutput }); + // Log command output if verbose is enabled + if (log.isVeryVerbose) { + const symbol = status.success ? '✅' : '❗'; + const truncatedOutput = output.length >= 30 ? `${output.slice(0, 27)}...` : output; + log.debug('Command:', command, argsString, symbol, { + status, + output: log.isMaxVerbose ? output : truncatedOutput, + }); + } if (error) throw new Error(error); diff --git a/src/model/versioning.ts b/src/model/versioning.ts index ca8c65d9..165ba9db 100644 --- a/src/model/versioning.ts +++ b/src/model/versioning.ts @@ -132,9 +132,8 @@ export default class Versioning { await this.fetch(); } - await Versioning.logDiff(); - if ((await this.isDirty()) && !allowDirtyBuild) { + await Versioning.logDiff(); throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes'); } @@ -290,6 +289,7 @@ export default class Versioning { */ static async hasAnyVersionTags() { const command = `git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`; + // Todo - make sure this cwd is actually passed in somehow const result = await System.shellRun(command, { cwd: this.projectPath, silent: false });