From 1da26008189bb31916c4f7278df5bc55025dd4cf Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Jun 2026 11:26:13 +0200 Subject: [PATCH 1/5] feat(developer): run project validation from IDE When option 'SkipMetadataFiles' is not set in the project (inverse of 'Build metadata files for Keyman Cloud' in Project Settings dialog), then will run the validation step and generate .keyboard_info / .model_info from the IDE. Also add corresponding log messages (and add formatting for some others that were missing). Fixes: #16068 Test-bot: skip --- .../docs/help/reference/kmc/cli/reference.md | 7 ++++ developer/src/kmc/src/commands/build.ts | 1 + .../buildClasses/BuildKeyboardInfo.ts | 1 + .../commands/buildClasses/BuildModelInfo.ts | 1 + .../src/commands/buildClasses/BuildProject.ts | 39 ++++++++++++------- .../src/messages/infrastructureMessages.ts | 21 ++++++++++ .../src/kmc/src/util/NodeCompilerCallbacks.ts | 4 ++ .../kmc/src/util/extendedCompilerOptions.ts | 7 ++++ .../Keyman.Developer.System.KmcWrapper.pas | 34 ++++++++++++++++ developer/src/tike/main/UfrmMessages.pas | 18 +++++++-- ...er.System.Project.kpsProjectFileAction.pas | 2 + 11 files changed, 119 insertions(+), 16 deletions(-) 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; From a005e45d25c438d55d577ef3a22dbc22a7f4ff3b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 9 Jun 2026 20:58:12 +0200 Subject: [PATCH 2/5] chore: Apply suggestions from code review Co-authored-by: rc-swag <58423624+rc-swag@users.noreply.github.com> --- .../src/tike/compile/Keyman.Developer.System.KmcWrapper.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas index f64e961b15..6c33760ffc 100644 --- a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas +++ b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas @@ -63,7 +63,7 @@ begin else cmdline := cmdline + ['--no-compiler-warnings-as-errors']; - if not FGlobalProject.Options.WarnDeprecatedCode then + if Assigned(FGlobalProject) and (not FGlobalProject.Options.WarnDeprecatedCode) then cmdline := cmdline + ['--no-warn-deprecated-code']; if debug then From a47fd7d36584d593f44096bc3c32e2805354b0a9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Jun 2026 05:43:42 +0200 Subject: [PATCH 3/5] fix: use meson subsystem and needs_exe_wrapper for cross builds on Windows Relates-to: mesonbuild/meson#15840 Fixes: #15753 Test-bot: skip --- .github/workflows/core-arm64-windows-test.yml | 2 +- core/cross-arm64.build | 1 + core/cross-x64.build | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/core-arm64-windows-test.yml b/.github/workflows/core-arm64-windows-test.yml index 0eb4e8f8f6..76d273de22 100644 --- a/.github/workflows/core-arm64-windows-test.yml +++ b/.github/workflows/core-arm64-windows-test.yml @@ -119,7 +119,7 @@ jobs: - name: Install meson shell: bash run: | - pip3 install --user meson==1.10.1 + pip3 install --user meson==1.11.0 - name: Run test shell: bash diff --git a/core/cross-arm64.build b/core/cross-arm64.build index 9fd7cc6d14..7bea64992e 100644 --- a/core/cross-arm64.build +++ b/core/cross-arm64.build @@ -1,5 +1,6 @@ [host_machine] system = 'windows' +subsystem = 'windows' cpu_family = 'aarch64' cpu = 'arm64' endian = 'little' diff --git a/core/cross-x64.build b/core/cross-x64.build index df3ab0b5dc..ecc0486e08 100644 --- a/core/cross-x64.build +++ b/core/cross-x64.build @@ -1,5 +1,6 @@ [host_machine] system = 'windows' +subsystem = 'windows' cpu_family = 'x86_64' cpu = 'x86_64' endian = 'little' @@ -11,3 +12,6 @@ ar = 'lib' ld = 'link' strip = 'llvm-strip' pkgconfig = 'pkg-config' + +[properties] +needs_exe_wrapper = false From 6cd27503fe1c08390f03f21ef9b58075c61975d9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Jun 2026 10:28:09 +0200 Subject: [PATCH 4/5] docs(windows): add context documentation for additional Advanced options Add Base Keyboard, Proxy Settings, and Keyman System Settings documentation and links to the Options context help. Fixes: keymanapp/help.keyman.com#10 Test-bot: skip --- windows/docs/help/basic/config/options.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/windows/docs/help/basic/config/options.md b/windows/docs/help/basic/config/options.md index 3b4bbd86b0..f4084f6465 100644 --- a/windows/docs/help/basic/config/options.md +++ b/windows/docs/help/basic/config/options.md @@ -180,6 +180,28 @@ To open the Options tab of Keyman Configuration: log on for short and isolated tests as specifically requested by Keyman Technical Support. +- Proxy Settings + + In some environments, you may need to configure proxy settings in + order to download keyboards or updates for Keyman. Check with your + network administrator. More information is available in the + [Proxy Configuration](../../advanced/proxy_config) documentation. + +- Keyman System Settings + + The Keyman System Settings dialog provides access to various low-level + Keyman configuration settings. Generally, these settings do not need + to be changed, unless you instructed to do so by Keyman support staff. + +- Base Keyboard + + The Base Keyboard dialog allows you to select the physical Latin-script + layout of your keyboard, which will allow certain Keyman keyboards to remap + themselves dynamically to match your key caps. + + [Base Keyboard dialog](../../context/base-keyboard) + + ## Related Topics - [Keyman Configuration](../config/) From e9a85dfd2f8f3bf6bcc0ade07a1cfee06ecc00b2 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 15 Jun 2026 13:02:00 -0500 Subject: [PATCH 5/5] auto: increment master version to 19.0.247 Test-bot: skip Build-bot: skip --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index fa463aa2a4..4ef4e13561 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 19.0.246 alpha 2026-06-15 + +* feat(developer): run project validation from IDE (#16076) +* fix(core): use meson subsystem and needs_exe_wrapper for cross builds on Windows (#16088) +* docs(windows): add context documentation for additional Advanced options (#16089) + ## 19.0.245 alpha 2026-06-10 * chore(web): hide KMX keyboard test page (#16083) diff --git a/VERSION.md b/VERSION.md index 825791dd34..5c233ca148 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.246 \ No newline at end of file +19.0.247 \ No newline at end of file