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
.orig
*.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> {
try {
log.info('options', options);
const { workspace, actionFolder } = Action;
const { buildParameters } = options;

View File

@ -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<boolean> {
try {

View File

@ -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;
}

View File

@ -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,
};
}
}

View File

@ -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;
};

9
src/global.d.ts vendored
View File

@ -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;
}

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 { 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);

View File

@ -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) {

View File

@ -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 {

View File

@ -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);

View File

@ -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 });