feat: add verbosity and split parameters per command

pull/413/head
Webber 2022-08-20 18:12:43 +02:00
parent cae5ffaf6c
commit a0372d1ec6
13 changed files with 163 additions and 84 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ lib/
yarn-error.log yarn-error.log
.orig .orig
*.log *.log
logs/*
!**/.gitkeep

0
logs/.gitkeep 100644
View File

View File

@ -18,7 +18,6 @@ export class BuildCommand implements CommandInterface {
public async execute(options: Options): Promise<boolean> { public async execute(options: Options): Promise<boolean> {
try { try {
log.info('options', options);
const { workspace, actionFolder } = Action; const { workspace, actionFolder } = Action;
const { buildParameters } = options; const { buildParameters } = options;

View File

@ -1,8 +1,13 @@
import { CommandInterface } from '../command-interface.ts'; import { CommandInterface } from '../command-interface.ts';
import { Options } from '../../../config/options.ts'; import { Options } from '../../../config/options.ts';
import { CloudRunner, ImageTag, Input, Output } from '../../../model/index.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 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 // Todo - Verify this entire flow
export class BuildRemoteCommand implements CommandInterface { export class BuildRemoteCommand implements CommandInterface {
@ -12,7 +17,39 @@ export class BuildRemoteCommand implements CommandInterface {
this.name = name; 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<boolean> { public async execute(options: Options): Promise<boolean> {
try { try {

View File

@ -20,7 +20,7 @@ export class Options {
this.input = new Input(args); this.input = new Input(args);
this.parameters = await new Parameters(this.input, this.env).registerCommand(this.command).parse(); this.parameters = await new Parameters(this.input, this.env).registerCommand(this.command).parse();
log.debug('Parameters generated.'); log.info('Parameters generated.');
return this; return this;
} }

View File

@ -5,10 +5,24 @@ export class ArgumentsParser {
const [commandName, ...rest] = cliArguments; const [commandName, ...rest] = cliArguments;
const { subCommands, args } = parseArgv(rest); 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 { return {
commandName, commandName,
subCommands, subCommands,
args, args,
verbosity,
}; };
} }
} }

View File

@ -1,36 +1,65 @@
import * as log from 'https://deno.land/std@0.151.0/log/mod.ts'; import * as log from 'https://deno.land/std@0.151.0/log/mod.ts';
import { fileFormatter, consoleFormatter } from './formatter.ts'; import { fileFormatter, consoleFormatter } from './formatter.ts';
// Handlers export enum Verbosity {
const consoleHandler = new log.handlers.ConsoleHandler('DEBUG', { formatter: consoleFormatter }); quiet = -1,
const fileHandler = new log.handlers.FileHandler('WARNING', { filename: './game-ci.log', formatter: fileFormatter }); normal = 0,
verbose = 1,
veryVerbose = 2,
maxVerbose = 3,
}
// Make sure it saves on Ctrl+C interrupt https://github.com/denoland/deno_std/issues/2193 export const configureLogger = async (verbosity: Verbosity) => {
Deno.addSignalListener('SIGINT', () => fileHandler.flush()); // 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
handlers: { let consoleLevel = 'INFO';
consoleHandler, if (isQuiet) consoleLevel = 'ERROR';
fileHandler, 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: { // Make sure it saves on Ctrl+C interrupt https://github.com/denoland/deno_std/issues/2193
default: { Deno.addSignalListener('SIGINT', () => fileHandler.flush());
level: 'DEBUG',
handlers: ['consoleHandler', 'fileHandler'], await log.setup({
handlers: {
consoleHandler,
fileHandler,
}, },
},
});
/** loggers: {
* Allows using `log.debug` and other methods directly from anywhere default: {
* level: 'DEBUG',
* Example handlers: ['consoleHandler', 'fileHandler'],
* 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] } } } * Allows using `log.debug` and other methods directly from anywhere
*/ *
window.log = log.getLogger(); * 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;
};

9
src/global.d.ts vendored
View File

@ -1,10 +1,17 @@
import { Verbosity } from './core/logger/index.ts';
let log: { let log: {
verbosity: Verbosity;
isQuiet: boolean;
isVerbose: boolean;
isVeryVerbose: boolean;
isMaxVerbose: boolean;
debug: (msg: any, ...args: any[]) => void; debug: (msg: any, ...args: any[]) => void;
info: (msg: any, ...args: any[]) => void; info: (msg: any, ...args: any[]) => void;
warning: (msg: any, ...args: any[]) => void; warning: (msg: any, ...args: any[]) => void;
error: (msg: any, ...args: any[]) => void; error: (msg: any, ...args: any[]) => void;
}; };
interface Window { declare interface Window {
log: any; log: any;
} }

View File

@ -1,4 +1,4 @@
import './core/logger/index.ts'; import { configureLogger, Verbosity } from './core/logger/index.ts';
import { Options } from './config/options.ts'; import { Options } from './config/options.ts';
import { CommandFactory } from './commands/command-factory.ts'; import { CommandFactory } from './commands/command-factory.ts';
import { ArgumentsParser } from './core/cli/arguments-parser.ts'; import { ArgumentsParser } from './core/cli/arguments-parser.ts';
@ -15,12 +15,16 @@ export class GameCI {
public async run() { public async run() {
try { try {
const { commandName, subCommands, args } = new ArgumentsParser().parse(this.args); const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args);
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
await configureLogger(verbosity);
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);
const options = await new Options(command, this.env).registerCommand(command).generateParameters(args); const options = await new Options(command, this.env).registerCommand(command).generateParameters(args);
if (log.isVerbose) log.info('Executing', command.name);
await command.execute(options); await command.execute(options);
} catch (error) { } catch (error) {
log.error(error); log.error(error);

View File

@ -5,8 +5,9 @@ import Platform from './platform.ts';
import { CliArguments } from '../core/cli/cli-arguments.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. * 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 * 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) { constructor(argumentsFromCli: CliArguments) {
this.arguments = argumentsFromCli; this.arguments = argumentsFromCli;
log.debug('Input initialised.');
return this; return this;
} }
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) { public getInput(query: string) {
if (this && this.arguments) { if (this && this.arguments) {
const value = this.arguments.get(query); const value = this.arguments.get(query);
log.warning('arg', query, '=', value);
if (log.isVeryVerbose) log.debug('arg', query, '=', value);
return this.arguments.get(query); return this.arguments.get(query);
} }
@ -70,15 +70,17 @@ class Input {
public get githubRepo() { public get githubRepo() {
return this.getInput('GITHUB_REPOSITORY') || this.getInput('GITHUB_REPO') || undefined; return this.getInput('GITHUB_REPOSITORY') || this.getInput('GITHUB_REPO') || undefined;
} }
public get branch() { public get branch() {
if (this.getInput(`GITHUB_REF`)) { if (this.getInput(`GITHUB_REF`)) {
return this.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, ''); return this.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
} else if (this.getInput('branch')) { } else if (this.getInput('branch')) {
return this.getInput('branch'); return this.getInput('branch').replace('/head', '');
} else { } else {
return ''; return '';
} }
} }
public get cloudRunnerBuilderPlatform() { public get cloudRunnerBuilderPlatform() {
const input = this.getInput('cloudRunnerBuilderPlatform'); const input = this.getInput('cloudRunnerBuilderPlatform');
if (input) { if (input) {

View File

@ -98,8 +98,6 @@ class Parameters {
); );
log.debug('androidSdkManagerParameters', androidSdkManagerParameters); log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
// Todo - Don't use process.env directly, that's what the input model class is for.
// ---
let unitySerial = ''; let unitySerial = '';
if (!this.env.UNITY_SERIAL && this.input.githubInputEnabled) { if (!this.env.UNITY_SERIAL && this.input.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert // 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!; 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 = { const parameters = {
editorVersion, editorVersion,
customImage: this.input.customImage, customImage: this.input.customImage,
unitySerial, unitySerial,
runnerTempPath: this.env.RUNNER_TEMP, runnerTempPath: this.env.RUNNER_TEMP,
targetPlatform: this.input.targetPlatform, targetPlatform,
projectPath: this.input.projectPath, projectPath,
buildName: this.input.buildName, buildName: this.input.buildName,
buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`, buildPath: `${this.input.buildsPath}/${this.input.targetPlatform}`,
buildFile, buildFile,
@ -136,41 +143,13 @@ class Parameters {
androidSdkManagerParameters, androidSdkManagerParameters,
customParameters: this.input.customParameters, customParameters: this.input.customParameters,
sshAgent: this.input.sshAgent, sshAgent: this.input.sshAgent,
gitPrivateToken: this.input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()), gitPrivateToken: this.input.gitPrivateToken,
chownFilesTo: this.input.chownFilesTo, 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, customJob: this.input.customJob,
runNumber: this.input.runNumber, branch,
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,
}; };
const commandParameterOverrides = this.command.parseParameters(this.input, parameters); const commandParameterOverrides = await this.command.parseParameters(this.input, parameters);
// Todo - Maybe return an instance instead // Todo - Maybe return an instance instead
return { return {

View File

@ -42,14 +42,20 @@ class System {
process.close(); process.close();
const output = new TextDecoder().decode(outputBuffer); const output = new TextDecoder().decode(outputBuffer).replace(/\n+$/, '');
const error = new TextDecoder().decode(errorBuffer); const error = new TextDecoder().decode(errorBuffer).replace(/\n+$/, '');
const result = { status, output }; const result = { status, output };
const symbol = status.success ? '✅' : '❗';
const truncatedOutput = output.length >= 30 ? `${output.slice(0, 27)}...` : output; // Log command output if verbose is enabled
log.debug('Command:', command, argsString, symbol, { status, output: truncatedOutput }); 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); if (error) throw new Error(error);

View File

@ -132,9 +132,8 @@ export default class Versioning {
await this.fetch(); await this.fetch();
} }
await Versioning.logDiff();
if ((await this.isDirty()) && !allowDirtyBuild) { if ((await this.isDirty()) && !allowDirtyBuild) {
await Versioning.logDiff();
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes'); 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() { static async hasAnyVersionTags() {
const command = `git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`; const command = `git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`;
// Todo - make sure this cwd is actually passed in somehow // Todo - make sure this cwd is actually passed in somehow
const result = await System.shellRun(command, { cwd: this.projectPath, silent: false }); const result = await System.shellRun(command, { cwd: this.projectPath, silent: false });