diff --git a/developer/docs/help/reference/kmc/cli/reference.md b/developer/docs/help/reference/kmc/cli/reference.md index 1d5ed9fc20..662d85a0a3 100644 --- a/developer/docs/help/reference/kmc/cli/reference.md +++ b/developer/docs/help/reference/kmc/cli/reference.md @@ -198,6 +198,13 @@ The following parameters are available: project (which can also be controlled at a project level with the `skipMetadataFiles` option). This option is only valid for compiling projects. +`--publish-only` + +: Skip building component files within a project, and only run the validation + step. Must be combined with `--for-publishing` in order to have an effect. + This option is only valid for compiling projects, and is mostly intended for + use by the IDE. Will fail if build artifacts for components are not present. + ### Examples ```shell diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 20f1092a9f..7e21f99f9c 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -35,6 +35,7 @@ export function declareBuild(program: Command) { buildCommand.command('file [infile...]', {isDefault: true}) .description(`Compile one or more source files or projects ('file' subcommand is default).`) .option('--for-publishing', 'Verify that project meets @keymanapp repository requirements') + .option('--publish-only', 'Only run the for-publishing validation, skip all other build steps') .addHelpText('after', ` Supported file types: * folder: Keyman project in folder diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 3109038a3a..d3d42f9ce6 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -49,6 +49,7 @@ export class BuildKeyboardInfo extends BuildActivity { sourcePath: calculateSourcePath(infile), lastCommitDate, forPublishing: !!options.forPublishing, + publishOnly: !!options.publishOnly, }; // Note: should we always ignore the passed-in output filename for .keyboard_info? const outputFilename = project.getOutputFilePath(KeymanFileTypes.Binary.KeyboardInfo); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts index f6a44ced5f..7f641a9b8e 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts @@ -75,6 +75,7 @@ export class BuildModelInfo extends BuildActivity { kpsFilename: project.resolveInputFilePath(kps), lastCommitDate, forPublishing: !!options.forPublishing, + publishOnly: !!options.publishOnly, }; // Note: should we always ignore the passed-in output filename for .model_info? diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 82e1347aa5..76fdfe1d04 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -51,15 +51,17 @@ class ProjectBuilder { } } - // Go through the various file types and build them - for(const builder of buildActivities) { - if(builder.sourceExtension == KeymanFileTypes.Source.Project) { - // We don't support nested projects - continue; - } + if(!this.options.publishOnly) { + // Go through the various file types and build them + for(const builder of buildActivities) { + if(builder.sourceExtension == KeymanFileTypes.Source.Project) { + // We don't support nested projects + continue; + } - if(!await this.buildProjectTargets(builder)) { - return false; + if(!await this.buildProjectTargets(builder)) { + return false; + } } } @@ -113,7 +115,11 @@ class ProjectBuilder { const buildFilename = path.relative(process.cwd(), infile).replace(/\\/g, '/'); const callbacks = new CompilerFileCallbacks(buildFilename, options, this.callbacks); - callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: infile, relativeFilename:buildFilename})); + if(activity.sourceExtension == KeymanFileTypes.Source.Project) { + callbacks.reportMessage(InfrastructureMessages.Info_ValidatingProject({filename: infile, relativeFilename:buildFilename})); + } else { + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: infile, relativeFilename:buildFilename})); + } fs.mkdirSync(path.dirname(outfile), {recursive:true}); @@ -123,12 +129,19 @@ class ProjectBuilder { // note: command line option here, if set, overrides project setting result = result && !callbacks.hasFailureMessage(this.options.compilerWarningsAsErrors ?? this.project.options.compilerWarningsAsErrors); - if(result) { - callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: infile, relativeFilename:buildFilename})); + if(activity.sourceExtension == KeymanFileTypes.Source.Project) { + if(result) { + callbacks.reportMessage(InfrastructureMessages.Info_ProjectValidatedSuccessfully({filename: infile, relativeFilename:buildFilename})); + } else { + callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotValidatedSuccessfully({filename: infile, relativeFilename:buildFilename})); + } } else { - callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: infile, relativeFilename: buildFilename})); + if(result) { + callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: infile, relativeFilename:buildFilename})); + } else { + callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: infile, relativeFilename: buildFilename})); + } } - return result; } diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index d384aa7d86..338af89073 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -197,5 +197,26 @@ export class InfrastructureMessages { `Failed to generate new project '${def(o.id)}'.`, )}); + // For this message, we override the filename with the passed-in file. A bit of a hack but does the job + static INFO_ValidatingProject = SevInfo | 0x0029; + static Info_ValidatingProject = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m( + this.INFO_ValidatingProject, + `Validating ${def(o.relativeFilename)}`, + )}); + + // For this message, we override the filename with the passed-in file. A bit of a hack but does the job + static INFO_ProjectValidatedSuccessfully = SevInfo | 0x002A; + static Info_ProjectValidatedSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m( + this.INFO_ProjectValidatedSuccessfully, + `${def(o.relativeFilename)} validated successfully.`, + )}); + + // For this message, we override the filename with the passed-in file. A bit of a hack but does the job + static INFO_ProjectNotValidatedSuccessfully = SevInfo | 0x002B; + static Info_ProjectNotValidatedSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m( + this.INFO_ProjectNotValidatedSuccessfully, + `${def(o.relativeFilename)} failed to validate.` + )}); + } diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts index a92314aa5f..68209d95ce 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -242,22 +242,26 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { * We treat a few certain infrastructure messages with special colours * @param event * @returns + * Keep in sync with: UfrmMessages.pas, TfrmMessages.Add */ messageSpecialColor(event: CompilerEvent) { switch(event.code) { case InfrastructureMessages.INFO_BuildingFile: case InfrastructureMessages.INFO_CopyingProject: case InfrastructureMessages.INFO_GeneratingProject: + case InfrastructureMessages.INFO_ValidatingProject: return color.whiteBright; case InfrastructureMessages.INFO_FileNotBuiltSuccessfully: case InfrastructureMessages.INFO_ProjectNotBuiltSuccessfully: case InfrastructureMessages.INFO_ProjectNotCopiedSuccessfully: case InfrastructureMessages.INFO_ProjectNotGeneratedSuccessfully: + case InfrastructureMessages.INFO_ProjectNotValidatedSuccessfully: return color.red; case InfrastructureMessages.INFO_FileBuiltSuccessfully: case InfrastructureMessages.INFO_ProjectBuiltSuccessfully: case InfrastructureMessages.INFO_ProjectCopiedSuccessfully: case InfrastructureMessages.INFO_ProjectGeneratedSuccessfully: + case InfrastructureMessages.INFO_ProjectValidatedSuccessfully: return color.green; } return null; diff --git a/developer/src/kmc/src/util/extendedCompilerOptions.ts b/developer/src/kmc/src/util/extendedCompilerOptions.ts index 53f68288f0..b94be88419 100644 --- a/developer/src/kmc/src/util/extendedCompilerOptions.ts +++ b/developer/src/kmc/src/util/extendedCompilerOptions.ts @@ -9,6 +9,12 @@ export interface ExtendedCompilerOptions extends CompilerOptions { * MIT */ forPublishing?: boolean; + /** + * Do not build components, just do the publish phase for the project. This is + * used mostly by Keyman Developer IDE, which will run the .keyboard_info / + * .package_info validation and compilation step if skipMetadataFiles is true. + */ + publishOnly?: boolean; /** * Overrides for message reporting */ @@ -198,6 +204,7 @@ export function commanderOptionsToCompilerOptions(options: any, callbacks: Compi warnDeprecatedCode: options.warnDeprecatedCode, // ExtendedOptions forPublishing: options.forPublishing, + publishOnly: options.publishOnly, messageOverrides: overrides, } } diff --git a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas index 168ea8c77d..f64e961b15 100644 --- a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas +++ b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas @@ -17,6 +17,7 @@ type function Run(const parameters: TArray; const path, logFilename: string; Callback: TUtilExecuteCallbackEvent): Boolean; public function Compile(ProjectFile: TProjectFile; const infile, outfile: string; debug: Boolean; Callback: TUtilExecuteCallbackEvent = nil): Boolean; + function CompileForPublishing(ProjectFile: TProjectFile; const projectFilename: string; debug: Boolean; Callback: TUtilExecuteCallbackEvent = nil): Boolean; function Copy(const source, dest, cwd: string; relocateExternal: Boolean; Callback: TUtilExecuteCallbackEvent = nil): Boolean; end; @@ -39,6 +40,39 @@ uses { TKmcWrapper } +function TKmcWrapper.CompileForPublishing( + ProjectFile: TProjectFile; + const projectFilename: string; + debug: Boolean; + Callback: TUtilExecuteCallbackEvent +): Boolean; +var + cmdline: TArray; +begin + cmdline := [ + 'build', + '--log-format', 'tsv', + '--log-level', 'info', + '--for-publishing', + '--publish-only', + projectFilename + ]; + + if Assigned(FGlobalProject) and (FGlobalProject.Options.CompilerWarningsAsErrors) then + cmdline := cmdline + ['--compiler-warnings-as-errors'] + else + cmdline := cmdline + ['--no-compiler-warnings-as-errors']; + + if not FGlobalProject.Options.WarnDeprecatedCode then + cmdline := cmdline + ['--no-warn-deprecated-code']; + + if debug then + cmdline := cmdline + ['--debug']; + + Result := Run(cmdline, ExtractFileDir(projectFilename), projectFilename, Callback); +end; + + function TKmcWrapper.Compile( ProjectFile: TProjectFile; const infile: string; diff --git a/developer/src/tike/main/UfrmMessages.pas b/developer/src/tike/main/UfrmMessages.pas index 9a76254c96..924bec9acd 100644 --- a/developer/src/tike/main/UfrmMessages.pas +++ b/developer/src/tike/main/UfrmMessages.pas @@ -170,6 +170,12 @@ const // ABGR - text on white bg, Colors similar to Light+ VSCode color theme INFO_FileNotBuiltSuccessfully = NAMESPACE_Infrastructure or $0007; INFO_ProjectBuiltSuccessfully = NAMESPACE_Infrastructure or $000B; INFO_ProjectNotBuiltSuccessfully = NAMESPACE_Infrastructure or $000C; + INFO_ProjectCopiedSuccessfully = NAMESPACE_Infrastructure or $0024; + INFO_ProjectNotCopiedSuccessfully = NAMESPACE_Infrastructure or $0025; + INFO_ProjectGeneratedSuccessfully = NAMESPACE_Infrastructure or $0027; + INFO_ProjectNotGeneratedSuccessfully = NAMESPACE_Infrastructure or $0028; + INFO_ProjectValidatedSuccessfully = NAMESPACE_Infrastructure or $002A; + INFO_ProjectNotValidatedSuccessfully = NAMESPACE_Infrastructure or $002B; Segment_Filename = 0; Segment_Filename_Separator = 1; @@ -254,12 +260,18 @@ begin FColor := clBlack; FTextColor := Color_Text; - // Override formatting for 4 known messages + // Override formatting for known messages -- keep in sync with messageSpecialColor() in NodeCompilerCallbacks.ts if (MsgCode = INFO_FileBuiltSuccessfully) or - (MsgCode = INFO_ProjectBuiltSuccessfully) then + (MsgCode = INFO_ProjectBuiltSuccessfully) or + (MsgCode = INFO_ProjectCopiedSuccessfully) or + (MsgCode = INFO_ProjectGeneratedSuccessfully) or + (MsgCode = INFO_ProjectValidatedSuccessfully) then state := plsSuccess else if (MsgCode = INFO_FileNotBuiltSuccessfully) or - (MsgCode = INFO_ProjectNotBuiltSuccessfully) then + (MsgCode = INFO_ProjectNotBuiltSuccessfully) or + (MsgCode = INFO_ProjectNotCopiedSuccessfully) or + (MsgCode = INFO_ProjectNotGeneratedSuccessfully) or + (MsgCode = INFO_ProjectNotValidatedSuccessfully) then state := plsFailure; case state of diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas index c7a7cafcc5..94cb28c151 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas @@ -29,6 +29,8 @@ begin w := TKmcWrapper.Create; try Result := w.Compile(Self, FileName, TargetFilename, False); + if not OwnerProject.Options.SkipMetadataFiles then + Result := Result and w.CompileForPublishing(Self, OwnerProject.FileName, False); // TODO(lowpri): FDebug flag finally w.Free;