diff --git a/.gitignore b/.gitignore index 7b872195880..d66507ce9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ /windows/src/**/*.identcache /windows/src/**/*.vcxproj.user /windows/src/**/version.res +/windows/src/**/version*.res /windows/src/**/*.pch /windows/src/**/*.wixobj /windows/src/**/*.sbr @@ -186,3 +187,16 @@ lcov.info # flag file for build script .configured + +# Local artifacts from the Delphi 12 CE dev workflow (see docs/build/windows-d12.md) +/delphi-library-paths.backup*.reg +/local-delphi-12-patches.patch +/configure.log +/core-build.log +/cpp-engine-build.log +/install-build-env.log +/kmcmplib-build.log +/web-build.log + +# Meson wrap dependency lock files +**/subprojects/.wraplock diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas index 50cb444a55f..8d3c525d842 100644 --- a/common/windows/delphi/components/FixedTrackbar.pas +++ b/common/windows/delphi/components/FixedTrackbar.pas @@ -70,10 +70,14 @@ procedure TTntFixedDrawGrid.WMEraseBkgnd(var Message: TMessage); {$MESSAGE WARN 'Not yet checked against Delphi 10.4'} {$IFNDEF VER330} {$IFNDEF VER320} +{$IFNDEF VER350} +{$IFNDEF VER360} {$MESSAGE ERROR 'Check that this fix is still applicable for a new version of Delphi. Checked against Delphi 10.2, 10.3' } {$ENDIF} {$ENDIF} {$ENDIF} +{$ENDIF} +{$ENDIF} procedure TTntFixedDrawGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); diff --git a/common/windows/delphi/general/CleartypeDrawCharacter.pas b/common/windows/delphi/general/CleartypeDrawCharacter.pas index 6d1a51976de..44562012ed2 100644 --- a/common/windows/delphi/general/CleartypeDrawCharacter.pas +++ b/common/windows/delphi/general/CleartypeDrawCharacter.pas @@ -591,11 +591,11 @@ function TestFont(FFontName: string): Boolean; StrPCopy(lf.lfFaceName, FFontName); //'Code2000'); hdc := GetDC(0); //FPlane0FontName := 'Code2000'; -{$IFDEF VER340} +{$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} if EnumFontFamiliesEx(hdc, lf, @EnumFallbackFonts, 0, 0) <> 0 then {$ELSE} if EnumFontFamiliesEx(hdc, lf, @EnumFallbackFonts, 0, 0) then -{$ENDIF} +{$IFEND} begin FPlane0FontName := FFontName; Result := True; diff --git a/common/windows/delphi/general/JsonUtil.pas b/common/windows/delphi/general/JsonUtil.pas index ae2b9b0e9c6..16a90bc729d 100644 --- a/common/windows/delphi/general/JsonUtil.pas +++ b/common/windows/delphi/general/JsonUtil.pas @@ -52,7 +52,11 @@ function JSONToString(obj: TJSONAncestor; ReplaceSlashes: Boolean = False): stri begin builder := TStringBuilder.Create; try +{$IF Defined(VER350) or Defined(VER360)} + obj.ToChars(builder, []); +{$ELSE} obj.ToChars(builder); +{$IFEND} Result := builder.ToString; finally builder.Free; diff --git a/common/windows/delphi/tools/devtools/SourceRootPath.pas b/common/windows/delphi/tools/devtools/SourceRootPath.pas index 360477641e7..c8372c3c6e2 100644 --- a/common/windows/delphi/tools/devtools/SourceRootPath.pas +++ b/common/windows/delphi/tools/devtools/SourceRootPath.pas @@ -14,11 +14,19 @@ interface {$IFDEF VER340} const DelphiMajorVersion = '21.0'; {$ELSE} +{$IFDEF VER350} +const DelphiMajorVersion = '22.0'; +{$ELSE} +{$IFDEF VER360} +const DelphiMajorVersion = '23.0'; +{$ELSE} ERROR: must define Delphi version {$ENDIF} {$ENDIF} {$ENDIF} {$ENDIF} +{$ENDIF} +{$ENDIF} const DelphiBasePath = 'C:\Program Files (x86)\Embarcadero\Studio\' + DelphiMajorVersion + '\'; diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index d1daf087c39..8df4e732056 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -46,7 +46,13 @@ function CrackUTF8ZeroExtendedString(CommandType: THTTPCommandType; const p: str // Indy's UTF8 handling of URLs is *completely* broken. // We may need to check this with updated versions of Delphi {$IFNDEF VER330} +{$IFNDEF VER340} +{$IFNDEF VER350} +{$IFNDEF VER360} ERROR! Check if this is still needed with Delphi update +{$ENDIF} +{$ENDIF} +{$ENDIF} {$ENDIF} SetLength(s, p.Length); diff --git a/dev-docs/README.md b/dev-docs/README.md new file mode 100644 index 00000000000..53f7b74c069 --- /dev/null +++ b/dev-docs/README.md @@ -0,0 +1,88 @@ +# Keyman Developer Onboarding + +Welcome. This folder is the entry point for newcomers to the Keyman +ecosystem. There are two distinct audiences here: + +* **People building tools *around* Keyman from external repos** — AI + extensions for keyboard authoring, submission-triage bots for + [keymanapp/keyboards](https://github.com/keymanapp/keyboards), + validation tools, instrumented test rigs. You're consuming Keyman's + formats and CLI/library surface; you're not modifying Keyman itself. + Start at [keyboard-anatomy.md](keyboard-anatomy.md) and + [external-tooling.md](external-tooling.md). + +* **People contributing to Keyman itself** — runtime engines, the + developer tooling, KeymanWeb, the Delphi-removal effort. You'll + modify code in this repo. Start at [repository-map.md](repository-map.md) + and [migration-guide.md](migration-guide.md). + +The canonical user-facing documentation is at +[help.keyman.com](https://help.keyman.com). The canonical build +instructions are under [docs/build/](../docs/build/). This `dev-docs/` +folder is *only* for the "where is everything, what shape is it, and +why" questions. + +## The docs + +### For external tool builders + +> **Strategic frame, read this first.** The CLDR LDML keyboard format +> is the future-canonical Keyman keyboard format. The spec is complete +> for both desktop and touch keyboards. Keyman's *read* side for LDML +> touch isn't finished yet — runtime still drives off the legacy +> multi-file setup — but LDML is forward-compatible, so the right move +> for a generation tool is to emit LDML XML as the canonical source and +> only produce the transitional `.kmn`+`.kvks`+`.keyman-touch-layout` +> bundle when the deliverable has to run on touch *today*. See +> [external-tooling.md § The strategic frame](external-tooling.md#the-strategic-frame-for-tools-generating-keyboards) +> and [migration-guide.md § LDML status](migration-guide.md#status--what-works-today-vs-whats-still-in-flight) +> for the full picture. + +1. **[keyboard-anatomy.md](keyboard-anatomy.md)** — Reference for what + files make up a Keyman keyboard project (`.kpj`, `.kmn`, LDML XML, + `.kvks`, `.keyboard_info`, `.kps`, `.kmp`, ...), what each does, and + where the JSON schemas live for validation. + +2. **[external-tooling.md](external-tooling.md)** — How to drive the + Keyman compiler and engine programmatically: the `kmc` CLI, the + `@keymanapp/kmc-*` npm package surface, schema-based validation, + the cross-file consistency contract that any generator must enforce + today, triage criteria for keyboard submissions, and the approach + for instrumented forks of the compiler or engine. + +### For Keyman contributors + +3. **[repository-map.md](repository-map.md)** — A guided tour of the + top-level directory structure, what each major component does, and + how they connect. Includes Mermaid diagrams of the component graph, + build dependencies, and the cross-platform matrix. External tool + builders should read this too if they want to understand what their + tool is hooking *into*. + +4. **[migration-guide.md](migration-guide.md)** — Keyman has been + actively modernizing for years, replacing Delphi components with + C++ (Keyman Core), TypeScript (the kmc compiler family and + KeymanWeb), and embracing LDML as the canonical keyboard format. + This guide explains what has already moved, what's still in flight, + and where the long-running migration branches live. + +## Then look at the build docs + +Once you've internalized the layout, the standard build instructions live at: + +* [docs/build/windows.md](../docs/build/windows.md) — the canonical Windows + flow (Delphi 10.3 / Pro) +* [docs/build/windows-d12.md](../docs/build/windows-d12.md) — fallback for + contributors stuck on Delphi 12 Community Edition (the only free Delphi + tier today); covers IDE-based workflow plus helper scripts +* `linux/`, `mac/`, `android/`, `ios/` — platform-specific READMEs in each + top-level directory + +## Help / Getting Stuck + +* [SIL/Keyman Community Forum](https://community.software.sil.org/c/keyman) +* [Keyman roadmap blog](https://blog.keyman.com/category/roadmap/) — the + long-form context for the migrations described in `migration-guide.md` +* [Issue tracker](https://github.com/keymanapp/keyman/issues) — search for + the component or migration branch you're touching before filing new issues +* [CONTRIBUTING.md](../CONTRIBUTING.md) (at repo root) for PR conventions diff --git a/dev-docs/external-tooling.md b/dev-docs/external-tooling.md new file mode 100644 index 00000000000..477a4c93b9f --- /dev/null +++ b/dev-docs/external-tooling.md @@ -0,0 +1,543 @@ +# External Tooling Guide + +For people building tools *around* Keyman in other repositories: AI +extensions that assist keyboard development, automated submission +triage for the [keymanapp/keyboards](https://github.com/keymanapp/keyboards) +repo, lint bots, exhaustive test rigs, instrumented forks of the +compiler or engine for documentation work. + +This guide is for *you*. It assumes you know what a Keyman keyboard *is* +(see [keyboard-anatomy.md](keyboard-anatomy.md) first if not) and +explains how to drive the Keyman pipeline from external code. + +## The strategic frame for tools generating keyboards + +Two facts shape every tooling decision here: + +1. **The CLDR LDML keyboard spec is complete, including touch.** Layers + (hardware + touch), forms, flicks, longpress, multi-tap, transforms — + all defined. A spec-correct LDML keyboard with full touch markup is + a perfectly valid document today. +2. **Keyman's *read side* for LDML touch isn't complete yet.** Compiler + has TODOs, the Core C API doesn't expose layer/form data to hosts, + and the KeymanWeb / mobile OSKs still drive off the legacy + `.keyman-touch-layout` JSON, not kmx+. The runtime gap is detailed + in [migration-guide.md § LDML](migration-guide.md#what-s-not-working-yet-the-touch-gap-all-layers) + and tracked under + [#7238](https://github.com/keymanapp/keyman/issues/7238) and the + [`epic-ldml` label](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-ldml). + Realistic timeline: v21-ish (months out, funding-dependent). + +The implication for an AI extension that generates keyboards: **emit +LDML XML as the canonical source of truth**, even though today Keyman +can't yet run all of it natively on mobile/web. The LDML is +forward-compatible — when Keyman closes the read side, the same +generated keyboards become natively runnable with no source rewrite. + +### Recommended generation strategy + +```mermaid +flowchart LR + src[AI extension intent
name, language, target, layout, rules] + ldml[LDML XML
canonical source] + desk{Target
= desktop only?} + done[Ship LDML alone
compile with kmc-ldml] + bundle[Also emit parallel
.kmn + .kvks +
.keyman-touch-layout] + ship[Ship LDML + bundle
kmc build runs
the .kpj path today] + + src --> ldml + ldml --> desk + desk -- yes --> done + desk -- no, touch needed --> bundle + bundle --> ship + + style src fill:#28a,color:#fff + style ldml fill:#46c,color:#fff + style done fill:#284,color:#fff + style ship fill:#a52,color:#fff +``` + +Concretely: + +* **For desktop-only keyboards:** generate LDML XML, compile with + `kmc build`. Done. No consistency contract to manage. +* **For keyboards that need to run on touch today** (mobile, web): + generate the LDML XML *and* a derived `.kmn` + `.kvks` + + `.keyman-touch-layout` bundle. Treat the LDML as the source of + truth; treat the bundle as a transitional deliverable that exists + only because the runtime can't read LDML touch yet. When Keyman + catches up, you delete the bundle and regenerate. + +The cross-file consistency contract documented below is for the +**transitional bundle** — keeping its files in sync with the LDML and +with each other. It exists because today's Keyman runtime requires it, +not because the LDML format does. + +### Two practical tactics + +1. **Start from a kmc-generated scaffold and mutate consistently.** Run + `kmc generate keyboard ...` to produce a known-good starting set, + then modify with strict cross-file invariants (see § Cross-file + consistency contract below). kmc owns the source of truth for "what + a fresh project looks like" and your tool inherits future kmc + improvements automatically. +2. **Validate the LDML XML against the CLDR spec independently of + Keyman** — that way the canonical source is correct even if your + transitional bundle has gaps. Use the CLDR keyboard schema directly + (see [§ Validating metadata with JSON Schemas](#validating-metadata-with-json-schemas)). + +## The two integration surfaces + +```mermaid +graph LR + subgraph EXT[Your external tool] + yt[AI extension / triage bot] + end + subgraph CLI[CLI surface] + kmc[kmc
command-line] + end + subgraph LIB[Library surface] + npm[@keymanapp/kmc-*
npm packages] + end + subgraph CORE[Native surface] + core_so[Keyman Core
libkmcore.so / .dll / .dylib] + wasm[Keyman Core
WebAssembly] + end + + yt --shell out--> kmc + yt --import--> npm + yt --FFI / Emscripten--> core_so + yt --import wasm--> wasm + + style EXT fill:#28a,color:#fff + style CLI fill:#284,color:#fff + style LIB fill:#46c,color:#fff + style CORE fill:#a52,color:#fff +``` + +* **CLI surface** — shell out to `kmc` (or `kmcomp` for legacy `.kmn`-only + work). Easiest, least coupling, but you pay process-startup cost per + invocation and you parse text output. +* **Library surface** — import the `@keymanapp/kmc-*` packages from npm + in a Node.js tool. Get TypeScript types, programmatic results, no shell + parsing. Recommended for any non-trivial tool. +* **Native surface** — for tools that need to *execute* keyboards (not + just compile them), link Keyman Core as a shared library (desktop / + server) or import the WebAssembly build (Node.js or browser). + +## The `kmc` CLI + +`kmc` is the unified TypeScript entry point. Subcommands available: + +``` +kmc analyze Analyze a keyboard / project +kmc build Compile a keyboard, project, model, or package +kmc copy Duplicate a project with a new id +kmc generate Scaffold a new project (keyboard, model, etc.) +kmc message Print info about a kmc compiler message (warning/error) +``` + +Install (Node 20+): + +```bash +npm install -g @keymanapp/kmc +``` + +Or use directly from a checkout: + +```bash +node /path/to/keyman/developer/src/kmc/build/kmc.js +``` + +Get help: + +```bash +kmc --help +kmc --help +``` + +### Compiling a keyboard programmatically + +```bash +# Compile a single .kmn or LDML XML file +kmc build path/to/keyboard.kmn + +# Compile an entire .kpj project (produces .kmx + .js + .kmp) +kmc build path/to/keyboard.kpj + +# JSON output for machine consumption +kmc build --log-format json path/to/keyboard.kpj +``` + +Exit code is 0 on success, non-zero on errors. Use +`--log-format json` to get a structured stream of messages your tool +can parse. + +### Analyzing a keyboard + +```bash +# Lint / static analysis +kmc analyze path/to/keyboard.kpj + +# Get the rendered Osk image and other artifacts +kmc analyze osk-char-use path/to/keyboard.kmn +``` + +### Scaffolding a new keyboard + +```bash +kmc generate keyboard \ + --author "Your Name" \ + --copyright "(c) Your Org" \ + --bcp47 "und-Latn" \ + --name "my_keyboard" \ + --license "MIT" \ + --target "any" \ + --description "Demo keyboard" +``` + +Result is a directory with `.kpj`, `.kmn`, `.keyboard_info`, `.kps`, +`.kvks` (basic touch layout), `welcome.htm`, `README.md`, +`HISTORY.md`, `build.sh`. It compiles out of the box. + +This is your starting point for any "reliably produce a Keyman developer +folder that compiles" tool. + +## The `@keymanapp/kmc-*` library surface + +Each `kmc-*` directory under `developer/src/` is published as an npm +package. Use them as a library when you're already in a Node.js tool +and want structured results instead of shelling out. + +Notable packages and their roles: + +| Package | What it does | +|---|---| +| `@keymanapp/kmc` | The unified CLI (drives the others) | +| `@keymanapp/kmc-kmn` | Compile `.kmn` → `.kmx` (delegates to native kmcmplib) | +| `@keymanapp/kmc-ldml` | Compile LDML XML → `.kmx+` (TypeScript-native) | +| `@keymanapp/kmc-package` | Build `.kmp` package from `.kps` | +| `@keymanapp/kmc-keyboard-info` | Validate / compile `.keyboard_info` | +| `@keymanapp/kmc-analyze` | Static analysis / lint | +| `@keymanapp/kmc-copy` | Duplicate a project under a new id | +| `@keymanapp/kmc-generate` | Scaffold new projects | +| `@keymanapp/kmc-model` | Compile lexical models | +| `@keymanapp/kmc-model-info` | Validate `.model_info` metadata | +| `@keymanapp/common-types` | TypeScript types for `.kpj`, `.keyboard_info`, etc., plus generated JSON-schema validators | + +Example: compiling a keyboard from a Node script. + +```ts +import { KmnCompiler } from '@keymanapp/kmc-kmn'; +import { CompilerCallbacks, NodeCompilerCallbacks } from '@keymanapp/common-types'; + +const callbacks: CompilerCallbacks = new NodeCompilerCallbacks(); +const compiler = new KmnCompiler(); +await compiler.init(callbacks); + +const result = await compiler.runCompiler('path/to/keyboard.kmn', /*infile_str=*/null, { + shouldAddCompilerVersion: true, +}); + +if (!result) { + // compile failure: read messages from callbacks + console.error(callbacks.messages); +} +// result.kmx is the compiled binary +``` + +The exact API may evolve; check each package's `src/` and exported `index.ts` +for the current shape. + +## Cross-file consistency contract + +For tools generating or mutating a `.kmn`-based keyboard project (the +non-LDML path), these values must stay aligned across the project files. +Most validation failures and weird runtime behaviors come from breaking +one of these. + +### Identity + +The **keyboard ID** is the most-replicated value. Treat it as the +canonical key everywhere: + +| Where | How it appears | +|---|---| +| Directory name | `/` (convention) | +| File names | `.kpj`, `.kmn`, `.kps`, `.kvks`, `.keyman-touch-layout`, `.ico`, `.keyboard_info` | +| `.kpj` | The `` references files by relative path; the ID is implicit in the filenames | +| `.keyboard_info` | The filename stem IS the ID; not repeated inside the JSON | +| `.kps` | Lists output artifacts (`.kmx`, `.js`, `.kvk`) with `.` names | +| Compiled `.kmx`/`.kmp` | Identity baked into the binaries; tools downstream key off it | + +Rules: lowercase, ASCII letters / digits / underscores. Must be unique +across the [keymanapp/keyboards](https://github.com/keymanapp/keyboards) +catalog if you plan to submit. + +### Display name + +The human-readable keyboard name appears in two places: + +| Where | Form | +|---|---| +| `.kmn` | `store(&NAME) 'Display Name'` | +| `.kps` | `Display Name` | + +(`.keyboard_info` does *not* carry the name; consumers display the +package metadata.) + +### Versions — three different things, don't confuse them + +This is the easiest place to mess up: + +| Concept | `.kmn` | `.keyboard_info` | `HISTORY.md` | +|---|---|---|---| +| Keyboard version (what you ship) | `store(&KEYBOARDVERSION) '1.2.3'` | (no field) | top-most heading: `1.2.3 (YYYY-MM-DD)` | +| Min Keyman engine version | `store(&VERSION) '14.0'` | (no field) | n/a | +| Schema/file format version | n/a | n/a (implicit in schema) | n/a | + +A bump to the keyboard version touches `.kmn` AND `HISTORY.md`. The +engine min-version (`&VERSION`) only changes when you start using +features that require a newer Keyman. + +### Copyright / Author / License + +| Where | Form | +|---|---| +| `.kmn` | `store(©RIGHT) '© Your Name'` | +| `.kps` | `Your Name` | +| `.keyboard_info` | `"license": "mit"` (SPDX id) | +| `LICENSE.md` | full license text | +| `README.md` / `welcome.htm` | display only | + +The `.keyboard_info` license is an SPDX identifier (e.g. `"mit"`, +`"apache-2.0"`) and is what triage uses. The `LICENSE.md` must be the +matching text. + +### Language tags (BCP-47) + +| Where | Form | +|---|---| +| `.keyboard_info` | `"languages": ["en", "und-Latn", "am"]` (array of BCP-47) | +| `.kmn` (optional) | `store(&BCP47CODE) 'en'` for the primary language | + +The `.keyboard_info` array is the authoritative list. Each entry must +be a valid BCP-47 tag — use `@keymanapp/langtags` or `kmc analyze` to +validate. + +### Resource references inside `.kmn` + +Each "embedded" resource needs the file to exist alongside the `.kmn`: + +| Store | Points at | Required if | +|---|---|---| +| `store(&BITMAP)` | `.ico` (or `.bmp`) | Always (or omit if no icon) | +| `store(&VISUALKEYBOARD)` | `.kvks` | Desktop OSK / mobile touch support | +| `store(&LAYOUTFILE)` | `.keyman-touch-layout` | Mobile touch (newer touch layout format) | + +If a store points at a missing file, `kmc build` fails. If a `.kvks` +exists but isn't referenced, the OSK is silently omitted from the +package — the failure mode that bites AI generators hardest. + +### Targets / platforms + +| Where | Form | Notes | +|---|---|---| +| `.kmn` | `store(&TARGETS) 'any'` or `'desktop mobile'` etc. | Space-separated list | +| `.keyboard_info` (optional) | `"platformSupport": { "windows": "full", ... }` | More granular per-platform support | + +If `.kmn` says `'mobile'` but there's no `.kvks` / `.keyman-touch-layout`, +`kmc build` warns or errors depending on flags. + +### Quick check: does the generated project compile + validate? + +A scaffolded keyboard from a healthy generator passes all of: + +```bash +kmc build path/to/.kpj # exit 0 = compile clean +kmc analyze path/to/.kpj # exit 0 = lint clean +# JSON Schema validate .keyboard_info +ajv validate -s common/schemas/keyboard_info/keyboard_info.schema.json \ + -d path/to/.keyboard_info +``` + +Treat all three as required checks in your AI extension's +generate-keyboard pipeline. + +## Validating metadata with JSON Schemas + +For triage tools that just want to *validate* keyboard submissions +without compiling, the schemas live at: + +``` +common/schemas/keyboard_info/keyboard_info.schema.json # .keyboard_info +common/schemas/keyboard-info-source/ # source-side +common/schemas/displaymap/displaymap.schema.json # display maps +common/schemas/kvk/kvk.schema.json # visual keyboards +common/schemas/package/package.schema.json # package metadata +common/schemas/kpj/kpj.schema.json # project files +``` + +Pre-generated validators (ajv-style) and TypeScript types are at: + +``` +common/web/types/obj/schemas/*.schema.validator.{cjs,mjs} # runtime validators +common/web/types/src/schemas/*.schema.ts # TS types +``` + +If your tool is Node-based, install `@keymanapp/common-types` and +import: + +```ts +import { KeymanFileTypes, validateKeyboardInfoFile } from '@keymanapp/common-types'; +const { valid, errors } = await validateKeyboardInfoFile('path/to/x.keyboard_info'); +``` + +If your tool isn't Node-based, point any JSON Schema validator (ajv-cli, +jsonschema for Python, etc.) at the `.schema.json` files directly. + +## Triage criteria for keymanapp/keyboards submissions + +The [keymanapp/keyboards](https://github.com/keymanapp/keyboards) repo +expects each submitted keyboard to clear these gates. A triage bot +should check them in order: + +1. **Compiles.** `kmc build path/to/keyboard.kpj` returns exit 0. +2. **Metadata is schema-valid.** `.keyboard_info` validates against the + schema; `.kps` validates against the package schema. +3. **Identifiers match.** The `id` in `.keyboard_info`, the filename of + the `.kmn`/`.xml`, and the `id` attribute in `.kpj` are consistent. +4. **License is recognized.** `license` in `.keyboard_info` is one of + the accepted SPDX identifiers (MIT, Apache-2.0, etc.). +5. **Target language tags are valid BCP-47.** Each `bcp47` in + `.keyboard_info` is a valid tag (use `kmc analyze` or + `@keymanapp/langtags`). +6. **Tests pass** (when present). If `kmc analyze` reports test + failures, the keyboard isn't ready. +7. **No bundled binaries the source doesn't justify.** The package + shouldn't ship a `.kmx` if there's no matching `.kmn`/`.xml` source. +8. **Documentation present.** A non-empty `welcome.htm` (or + `welcome.md`) and a `HISTORY.md`. +9. **Version monotonic.** If updating an existing keyboard, version in + `.keyboard_info` must increase. + +Most of these are programmatically checkable via the `@keymanapp/kmc-*` +packages plus the JSON Schemas. Layered approach for a triage bot: + +```mermaid +flowchart TD + pr[New PR / submission] + s1{Schemas valid?} + s2{kmc build OK?} + s3{kmc analyze OK?} + s4{Custom checks
license, ids, version?} + pass[Auto-approve / queue for human] + fail[Bot comment with specifics] + + pr --> s1 + s1 -- yes --> s2 + s1 -- no --> fail + s2 -- yes --> s3 + s2 -- no --> fail + s3 -- yes --> s4 + s3 -- no --> fail + s4 -- yes --> pass + s4 -- no --> fail +``` + +## Forking the compiler or engine for instrumentation + +If your goal is to exhaustively test keyboards or compare behavior +(e.g. desktop vs touch), the practical approach is to fork a `kmc-*` +package or Keyman Core itself, add instrumentation, and run your +keyboard corpus through it. + +### Instrumenting `kmc` for compile-time analysis + +* Fork `developer/src/kmc-kmn/` (for `.kmn` keyboards) or + `developer/src/kmc-ldml/` (for LDML). +* Add hooks in the compiler's `CompilerCallbacks` interface — log every + rule, every grouping, every transform. +* Build with `developer/build.sh build:kmc-kmn` (or `:kmc-ldml`); run + via your fork's CLI. +* Keep the patched files small and isolated so you can rebase the fork + onto upstream periodically. + +### Instrumenting Keyman Core for runtime comparison + +For runtime behavior comparisons (e.g. does this keyboard produce the +same output on desktop and touch?): + +* Fork `core/src/` and add logging or trace hooks where the + `km_core_state_process_event()` function dispatches events. +* Build with `core/build.sh build:x64` (or `:wasm` for browser-side). +* For desktop testing: link your instrumented `libkmcore` into a small + C++ harness that feeds simulated key events. +* For touch testing: build to WASM, load in a Node.js harness with + `@keymanapp/keyman/engine/web/test` helpers, simulate touch input. + +The `core/tests/` directory has examples of how Keyman Core is exercised +from C++ test harnesses — these are good starting points for your own +harness. + +### Comparing desktop vs touch behavior + +The behavior difference between desktop and touch usually comes from: + +* The `.kvks` (OSK layout) — different keys / modifiers exposed +* `&platform()` rules in `.kmn` (or LDML transforms scoped to platforms) +* The host's event handling — what the OS / browser passes to the engine + +For exhaustive comparison, instrument both contexts using the same input +corpus: + +```mermaid +graph TD + corpus[Keyboard test corpus] + desk[Desktop harness
libkmcore + simulated kbd events] + touch[Touch harness
WASM kmcore + simulated tap events] + diff[Diff tool
compare action streams] + + corpus --> desk + corpus --> touch + desk --> diff + touch --> diff + diff --> report[Comparison report
per-keyboard, per-input] +``` + +The `web/src/test/` directory has fixtures and helpers for the touch +side; `core/tests/` has fixtures for the desktop side. Reusing the same +keyboard files in both harnesses is the easy part — generating an +equivalent simulated-input stream is the harder design problem. + +## Don't reinvent these + +Things the `kmc` / Keyman Core ecosystem already does — don't roll your +own: + +* **`.kmn` parsing**: use `kmc-kmn`'s parser (or the C++ parser in + `kmcmplib` if you need native code). +* **LDML XML parsing**: use `kmc-ldml`'s parser (it handles the + CLDR-specific quirks). +* **`.keyboard_info` validation**: use the schemas + validators in + `common/schemas/` and `common/web/types/`. +* **Language-tag handling**: use `@keymanapp/common-types` / + `@keymanapp/langtags`; the BCP-47 + IANA subtag stuff is non-trivial + and the team has invested in getting it right. + +## Pointers + +* [keyboard-anatomy.md](keyboard-anatomy.md) — file format reference +* [repository-map.md](repository-map.md) — where things live inside Keyman +* [migration-guide.md](migration-guide.md) — what's old, what's new, + what to bias toward +* [help.keyman.com/developer/](https://help.keyman.com/developer/) — + authoritative user-facing developer docs (the Keyman language + reference, the touch-layout designer guide, etc.) +* [common/test/keyboards/](../common/test/keyboards/) — small, + feature-focused example keyboards +* [keymanapp/keyboards](https://github.com/keymanapp/keyboards) — the + production keyboard catalog; the data your triage tools will work with +* [keymanapp/lexical-models](https://github.com/keymanapp/lexical-models) + — predictive text models (separate from keyboards but use the same + `kmc-*` ecosystem) diff --git a/dev-docs/keyboard-anatomy.md b/dev-docs/keyboard-anatomy.md new file mode 100644 index 00000000000..eaf8f92a421 --- /dev/null +++ b/dev-docs/keyboard-anatomy.md @@ -0,0 +1,244 @@ +# Keyboard Project Anatomy + +A reference for tools that read, write, validate, or generate Keyman +keyboard projects. If you're building something that produces Keyman +keyboards (a scaffolder, an AI-assisted authoring extension, a triage +bot), start here. + +For the Keyman *runtime* — how a compiled keyboard actually runs on the +user's machine — see [repository-map.md](repository-map.md). For *why* +some of these formats exist (and which are being phased out), see +[migration-guide.md](migration-guide.md). + +## File extensions at a glance + +| Extension | Role | Hand-edit? | Notes | +|---|---|---|---| +| `.kpj` | Project file (XML) | Mostly — Tike maintains it | Lists the source files; references metadata | +| `.kmn` | Keyman keyboard source | Yes | The historical proprietary format | +| `.xml` (LDML) | LDML keyboard source | Yes | The modern open-standard format ([CLDR keyboards](https://cldr.unicode.org/index/keyboard-workgroup)) | +| `.kvks` | Visual / on-screen keyboard layout (XML) | Yes (or via Tike) | Touch + desktop OSK definitions | +| `.kvk` | Compiled visual keyboard binary | No | Output of `.kvks` | +| `.keyboard_info` | Keyboard metadata (JSON) | Yes | Schema-validated; published to keyman.com | +| `.kps` | Package source (XML) | Mostly — Tike maintains | Lists what goes into the package | +| `.kmp` | Package binary (ZIP) | No | The single deliverable: contains kmx, js, kvk, fonts, docs | +| `.kmx` | Compiled keyboard binary | No | Output of `.kmn` compilation; runs in Keyman Core | +| `.kmx+` | LDML-extended kmx (same `.kmx` filename) | No | Output of LDML compilation; runs in Keyman Core | +| `.js` | KeymanWeb keyboard | No | Output of compilation; runs in browser | +| `.html` | Welcome / help page | Yes (optional) | Shipped inside the `.kmp` | +| `HISTORY.md` | Version history | Yes | Convention; rendered on keyman.com | +| `README.md` | Documentation | Yes | Optional | +| `fonts/*.ttf` `*.otf` | Embedded keyboard fonts | n/a | Shipped inside the `.kmp` | + +## Source → compiled outputs + +```mermaid +flowchart LR + subgraph SRC[Source files] + kpj[.kpj
project] + kmn[.kmn
source] + ldml[LDML XML
source] + kvks[.kvks
OSK layout] + kps[.kps
package source] + info[.keyboard_info
metadata] + end + subgraph CC[kmc compiler] + kmc[kmc CLI
TypeScript] + kmnc[kmcmplib
C++] + end + subgraph OUT[Compiled outputs] + kmx[.kmx /
.kmx+] + js[.js] + kvk[.kvk] + kmp[.kmp
package] + end + + kpj --> kmc + kmn --> kmnc + kmnc --> kmx + kmn --> kmc + kmc --> js + ldml --> kmc + kvks --> kmc + kvks --> kvk + kps --> kmc + info --> kmc + + kmx --> kmp + js --> kmp + kvk --> kmp + info --> kmp + + style SRC fill:#46c,color:#fff + style CC fill:#284,color:#fff + style OUT fill:#a52,color:#fff +``` + +## File-by-file deep dive + +### `.kpj` — Keyman project file + +XML, root element ``. Lists every file in the +project: source `.kmn` / LDML XML files, the `.kvks` OSK layout, the +package source `.kps`, the metadata `.keyboard_info`, the documentation +pages, the fonts. + +Hand-editing is sometimes necessary (e.g. adding a file Tike didn't pick +up); for generated projects, follow the structure of one of the +canonical examples (see [Examples in the repo](#examples-in-the-repo)). + +### `.kmn` — Keyman source + +The historical Keyman keyboard source format. Defines rules in a +domain-specific syntax: + +``` +store(&NAME) 'My Keyboard' +store(&VERSION) '1.0' +begin Unicode > use(main) +group(main) using keys +'a' > 'A' +``` + +Reference: . New keyboards +should be authored in LDML XML instead where possible — see § below and +[migration-guide.md § LDML](migration-guide.md#migration-2-ldml-keyboards). +`.kmn` is the today-required deliverable for touch keyboards because +Keyman's LDML read side isn't yet complete for touch (the CLDR spec +itself is — the gap is purely on Keyman's side). + +### LDML XML — Keyman keyboard authored in CLDR LDML + +**The future-canonical keyboard source format.** LDML is the Unicode CLDR +open standard for keyboards. The spec covers everything Keyman needs +including full touch markup (layers/forms/flicks/longpress/multi-tap), +so a spec-correct LDML keyboard is a complete authoring artifact — +hand-editable XML, no parallel files needed. + +File extension is `.xml`. Compiles to `.kmx+` (a `.kmx` with extra +LDML-specific sections inside). References: +* [Unicode CLDR keyboard workgroup](https://cldr.unicode.org/index/keyboard-workgroup) +* Keyman compiler: `developer/src/kmc-ldml/` +* Keyman runtime: `core/src/ldml/` + +**What works today**: desktop keyboards compile and run end-to-end from +LDML. + +**What doesn't work today**: touch keyboards authored in LDML compile, +but Keyman doesn't yet read the LDML touch markup through to the OSK on +mobile/web — the runtime still drives off the legacy `.keyman-touch-layout` +JSON. See [migration-guide.md § LDML touch gap](migration-guide.md#what-s-not-working-yet-the-touch-gap-all-layers) +for details. + +**Practical guidance**: +* For desktop-only keyboards, prefer LDML XML. No consistency contract. +* For touch keyboards, generate LDML *and* a parallel `.kmn` + `.kvks` + + `.keyman-touch-layout` bundle. Treat LDML as the source of truth; + treat the bundle as transitional until Keyman closes the read gap. + See [external-tooling.md § Recommended generation strategy](external-tooling.md#recommended-generation-strategy). + +### `.kvks` — Visual keyboard source + +XML, defines the layout of the on-screen keyboard for *both* desktop OSK +and mobile touch keyboards. Each key is positioned, sized, and bound to +a `.kmn`/LDML rule via key codes or named keys. Reference: +. + +Compiles to the binary `.kvk` shipped inside the `.kmp`. + +### `.keyboard_info` — Keyboard metadata + +JSON document describing the keyboard for the keyboards.keyman.com +catalog and the Keyman app stores. Required fields include name, id, +license, version, supported platforms, supported languages. + +**Schema** (validate against this): +[`common/schemas/keyboard_info/keyboard_info.schema.json`](../common/schemas/keyboard_info/keyboard_info.schema.json). + +Auto-validators (CommonJS + ESM) are generated and live under +`common/web/types/obj/schemas/keyboard_info.schema.validator.{cjs,mjs}`. +TypeScript types live under +`common/web/types/src/schemas/keyboard_info.schema.ts`. + +### `.kps` — Package source + +XML describing what goes into the final `.kmp` package: keyboards, +visual keyboards, documentation files, fonts, install behavior. +Reference: . + +### `.kmp` — Package (deliverable) + +ZIP file containing the compiled `.kmx`/`.kmx+` (one per keyboard), +`.js` (for KeymanWeb), `.kvk` (compiled OSK), `kmp.json`, fonts, and +optional `welcome.htm` / `readme.htm`. This is the single artifact a user +or store installs. + +Inspection: any zip tool works. The package's identity, version, and +contents are described in `kmp.json` at the root of the archive. + +### `.kmx`, `.kmx+`, `.js` + +Binary compiled outputs. Don't hand-edit. Documented for reference: +* `.kmx` format: [docs/file-formats/kmx-file-format.md](../docs/file-formats/kmx-file-format.md) +* `.kmx+` (LDML extensions): [docs/file-formats/kmx-plus-file-format.md](../docs/file-formats/kmx-plus-file-format.md) +* `.js` (KeymanWeb): not documented in repo; generated by `kmc-kmn` / + `kmc-ldml` to be loaded by KeymanWeb at runtime. + +## Schemas + +Programmatically validating keyboard metadata / project structure uses +the JSON Schemas under `common/schemas/`: + +| Schema | Validates | Path | +|---|---|---| +| `keyboard_info.schema.json` | `.keyboard_info` files | `common/schemas/keyboard_info/keyboard_info.schema.json` | +| `package.schema.json` | `.kps` package metadata | `common/schemas/package/` | +| `kpj.schema.json` (where applicable) | `.kpj` project files | `common/schemas/kpj/` | +| `displaymap.schema.json`, `kvk.schema.json`, etc. | Various subformats | `common/schemas/` | + +The TypeScript-generated validators (used by `kmc` internally) live +under `common/web/types/`. External tools can either: +* call the JSON Schemas directly with their own validator (ajv, etc.), or +* import the `@keymanapp/common-types` npm package for the + pre-generated validators and TS types. + +## Examples in the repo + +The most useful starting points for tooling work: + +| Location | What it is | +|---|---| +| `common/test/keyboards/baseline/` | A directory full of small `.kmn` keyboards, each illustrating one Keyman language feature (deadkeys, virtual keys, options, etc.) | +| `common/test/keyboards/text_selection_tests_keyboard_9073/` | A complete project with `.kpj`, `.kmn`, `.keyboard_info`, `build.sh` | +| `common/test/keyboards/platform-rules/` | Demonstrates platform-specific rule behavior (desktop vs touch) | +| `web/src/test/manual/web/keyboards/diacritic_rota/` | A KeymanWeb test keyboard with full `.keyboard_info` | + +To see a *complete*, real-world example, point at the +[keymanapp/keyboards](https://github.com/keymanapp/keyboards) repo — +that's where production keyboards live and where AI submission-triage +tools will spend most of their time. Layout: one keyboard per +subdirectory under `release///`. + +## Minimum viable keyboard project + +For a generation tool, this is the smallest set of files that compiles +to a valid `.kmp`: + +``` +my_keyboard/ +├── my_keyboard.kpj XML +├── my_keyboard.kmn store(&NAME), begin Unicode > use(main), +│ group(main) using keys, at least one rule +├── my_keyboard.keyboard_info JSON validated against keyboard_info.schema.json +└── my_keyboard.kps XML referencing the above +``` + +Plus optional but normal: +* `my_keyboard.kvks` — touch / OSK layout +* `welcome.htm` — first-run welcome page +* `HISTORY.md` — version history +* `fonts/*.ttf` — embedded fonts + +The `kmc generate` subcommand (see +[external-tooling.md](external-tooling.md#scaffolding-a-new-keyboard)) +will produce a working scaffold of this set. diff --git a/dev-docs/migration-guide.md b/dev-docs/migration-guide.md new file mode 100644 index 00000000000..f6345f509ed --- /dev/null +++ b/dev-docs/migration-guide.md @@ -0,0 +1,396 @@ +# Migration Guide: The Modernization in Flight + +Keyman is in the middle of several long-running migrations. As a contributor, +knowing where each migration is on the timeline is the difference between +modifying code that ships for another decade and modifying code that is +weeks away from deletion. This guide is the map. + +Three things to understand before you write any non-trivial patch: + +1. **The Delphi removal** — replacing Delphi components with C++ and TypeScript. +2. **LDML keyboards** — replacing the proprietary `.kmn`/`.kmx` keyboard + format with the open Unicode CLDR LDML keyboard standard. +3. **KeymanWeb modernization** — folding the historical KeymanWeb codebase + onto the shared Keyman Core (C++ compiled to WebAssembly) and a modern + TypeScript host. + +These are tracked across several long-lived branches and a multi-year +roadmap. The [Keyman roadmap blog](https://blog.keyman.com/category/roadmap/) +is the authoritative high-level source; this document summarizes the state +as understood from the codebase and links into specific issues and source +trees you can read for yourself. + +## Migration 1: Delphi removal + +### Why + +The original Keyman Developer IDE and the Windows Engine were written in +Delphi starting in the 1990s. Delphi was a practical choice then — strong +Win32 integration, a mature VCL UI toolkit. Today the licensing situation +is hostile to open-source contribution: the only freely-available tier +(Community Edition) was crippled in 10.4+ to remove command-line +compilation, leaving contributors stuck on the no-longer-distributed 10.3 +CE or on a paid Professional license. + +The Keyman team has been [tracking removal](https://github.com/keymanapp/keyman/issues/4599) +since 2022. Progress has been steady but slow as team capacity has +shrunk, so the long tail keeps stretching. + +### Scorecard (as of 2026 mid-year) + +```mermaid +graph LR + subgraph DONE[Migrated] + kmcomp[kmcomp.exe
.kmn compiler] + kmanalyze_old[kmanalyze
analysis tool] + kmconvert_old[kmconvert
keyboard converter] + kmdecomp_old[kmdecomp] + runtime[Keyboard runtime
processing] + lang[Language code utilities] + end + subgraph DELPHI[Still Delphi] + tike[TIKE
Keyman Developer IDE] + kmshell[kmshell
Keyman Desktop UI] + engine[Windows Engine
keyman.exe, kmcomapi.dll, ...] + setup_dev[Keyman Developer setup] + codegen[Build/codegen tools
devtools, build_standards_data, ...] + end + subgraph NEW[New stack] + kmcmplib[kmcmplib
C++] + kmcli[kmc & kmc-*
TypeScript] + core[Keyman Core
C++] + end + + kmcomp -.->|replaced by| kmcmplib + kmcomp -.->|wrapped by| kmcli + kmanalyze_old -.->|replaced by| kmcli + kmconvert_old -.->|replaced by| kmcli + runtime -.->|replaced by| core + lang -.->|replaced by| kmcli + + style DONE fill:#284,color:#fff + style DELPHI fill:#a52,color:#fff + style NEW fill:#46c,color:#fff +``` + +**Migrated away from Delphi:** + +| Old (Delphi) | New stack | Where to find the new code | +|---|---|---| +| `kmcomp.exe` — the `.kmn` keyboard compiler | C++ (`kmcmplib`) + TS wrapper (`kmc-kmn`) | `developer/src/kmcmplib/`, `developer/src/kmc-kmn/` | +| `kmanalyze` — analysis tool | `kmc-analyze` (TS) | `developer/src/kmc-analyze/` | +| `kmconvert` — generation/conversion | `kmc-generate`, `kmc-copy`, `kmc-keyboard-info` (TS) | `developer/src/kmc-{generate,copy,keyboard-info}/` | +| `kmdecomp` — decompiler | (none — marked Unsupported) | — | +| Keyboard runtime engine | Keyman Core (C++) | `core/` | +| Build-time language tag utilities | `kmc` family + `Keyman.System.LanguageCodeUtils` shim | `developer/src/kmc*/` | + +**Still Delphi (no replacement yet):** + +| Component | Path | Why still Delphi | +|---|---|---| +| Tike — the Keyman Developer IDE | `developer/src/Tike/` | Largest VCL app in the project; replacement is a major design exercise | +| Keyman for Windows shell | `windows/src/desktop/kmshell/` | Deeply integrated with Windows TSF / shell | +| Windows Engine | `windows/src/engine/{keyman,kmcomapi,insthelper,tsysinfo,tsysinfox64}/` | COM API + TSF text service plumbing | +| Desktop helpers | `windows/src/desktop/{kmbrowserhost,kmconfig,setup,insthelp}/` | UI / installer infrastructure | +| Developer installer | `developer/src/setup/` | WiX-driven Delphi setup wizard | +| Build tools | `common/windows/delphi/tools/{devtools,build_standards_data,sentrytool,certificates,test-klog,buildunidata}/` | Codegen used by the Delphi build cascade | + +**About the "C# migration"** — there is no Delphi → C# migration in flight. +The repo has 6 `.cs` files in a tiny test sandbox (`windows/src/support/NetInputBoxTest/`) +and nothing else. The actual modernization targets are **C++** (Keyman +Core, kmcmplib, Windows C++ engine pieces like `keyman32.dll`/`kmtip.dll`) +and **TypeScript** (the `kmc-*` compiler suite, KeymanWeb). + +### Working with code that's mid-migration + +Two practical rules for new contributors: + +* **Prefer the new stack for new functionality.** If you're adding a new + compiler feature, add it to `kmc-*` (TypeScript) or `kmcmplib` (C++), + not to Tike or kmconvert. +* **Resist the urge to refactor Delphi.** Touching `developer/src/Tike/` + or `windows/src/engine/keyman/` to "clean up" is often wasted effort + because the long-term plan is to retire the code. Fix the immediate bug, + flag the deeper issue in a comment + issue, move on. + +### Building Delphi components today + +If you must work in the Delphi tree, see the build doc that matches your +Delphi version: + +* [docs/build/windows.md](../docs/build/windows.md) — the canonical flow + (Delphi 10.3 / Pro) +* [docs/build/windows-d12.md](../docs/build/windows-d12.md) — the + workaround for Delphi 12 Community Edition contributors + +## Migration 2: LDML keyboards + +### What's changing + +Keyman's historical keyboard formats — `.kmn` (source) and `.kmx` +(compiled binary) — predate the Unicode CLDR project's LDML keyboard +standard. The team is adding first-class support for LDML XML keyboards +so that: + +* Keyboards authored for LDML work natively in Keyman everywhere +* Keyman authors can publish keyboards to the CLDR repository +* Tooling between Keyman and other CLDR-aware projects becomes shared + +### Where it lives + +* **Compiler**: `developer/src/kmc-ldml/` — TypeScript module that compiles + LDML XML directly to a `.kmx` (the existing binary format that Keyman + Core runs). +* **Core runtime**: `core/src/ldml/` and `core/include/ldml/` — the C++ + LDML processor inside Keyman Core. Same C API as `.kmx` keyboards; the + binary format `kmx+` ([docs/file-formats/kmx-plus-file-format.md](../docs/file-formats/kmx-plus-file-format.md)) + carries the LDML data through to runtime. +* **Tests**: `core/tests/unit/ldml/` (runtime) and + `developer/src/kmc-ldml/test/` (compiler). + +### Status — what works today vs what's still in flight + +LDML support is shipping in pieces. The +[March 2026 roadmap](https://blog.keyman.com/2026/03/keyman-roadmap-march-2026/) +lays out the next several versions: + +* **v19 (current)**: "web-core" — bring LDML keyboard support to mobile, + tablet, and embedded web. This is where most LDML work landed in + shipped releases. +* **v20**: an **LDML keyboard visual editor** (built as a VSCode plugin — + see [epic-ldml-editor scaffolding, issue #12798](https://github.com/keymanapp/keyman/issues/12798)), + plus the Delphi-to-C++ Windows-UI migration. The visual editor is the + long-term replacement for hand-editing LDML XML. +* **v21**: rebuild the on-screen keyboard rendering using web technologies + so the touch/OSK experience is consistent across platforms. +* **v22**: deprecate `.js` keyboards in favor of LDML everywhere. + +#### What works today (master) + +* `.kmn` → `.kmx`: fully working via `kmcmplib` +* LDML XML → `.kmx+`: working for the **desktop** keyboard layer. + Compile via `kmc build path/to/keyboard.xml`. +* Keyman Core runtime executes both `.kmx` and `.kmx+` keyboards. +* `core/src/ldml/` handles transforms, markers, deadkeys, syllable-based + scripts (with some known edge cases under + [active LDML-runtime work](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-ldml+is%3Aopen)). + +#### What's NOT working yet (the touch gap, all layers) + +**The CLDR LDML keyboard spec itself is complete for touch** — layers +(hardware and touch), forms, flicks, longpress, multi-tap, transforms, +markers, all defined. The gap is entirely on **Keyman's read side**: an +LDML keyboard with full touch layer markup is a valid spec-compliant +document today, but Keyman doesn't yet consume that touch markup +end-to-end. + +A consequence for tool builders: an AI extension *can* generate +spec-correct LDML touch keyboards today, and those keyboards will +become natively runnable on mobile/web when Keyman finishes the read +side — no source rewriting needed at that point. Generating to the +LDML spec is a forward-compatible bet even though runtime delivery +today still requires a parallel `.kmn` + `.kvks` set. + +That said, fully consuming the LDML touch markup end-to-end on mobile +and the browser through the kmx+ pipeline requires **four** layers of +implementation, not just the compiler: + +| Layer | State | Where to look | +|---|---|---| +| kmc-ldml writing LAYR data | **Partial** | TODOs in `developer/src/kmc-ldml/src/compiler/` (see table below) | +| kmx+ binary carrying LAYR | Done | Format defined; runtime parser at `core/src/kmx/kmx_plus.cpp:659+` | +| Keyman Core C API exposing layout to hosts | **Missing** | No `km_core_keyboard_get_layer` / `get_form` in `core/include/` | +| KeymanWeb / Mobile OSK consuming kmx+ layout | **Missing** | KeymanWeb OSK still drives off the legacy `.keyman-touch-layout` JSON spec; mobile engines the same | + +The interim today: kmc-ldml's +`visual-keyboard-compiler.ts` emits a legacy `.kvk` from the LDML source +so a touch-LDML keyboard *can* render through the existing OSK path. But +that's a stopgap — it's not "LDML XML drives the touch experience +natively through kmx+ on mobile and web", which is what the design aims +for and what the roadmap's v21 OSK rebuild promises to deliver +cross-platform. + +#### Compiler-side gaps (kmc-ldml) + +The specific TODOs in current master, all in +`developer/src/kmc-ldml/src/compiler/`: + +| File | TODO | Effect | +|---|---|---| +| `metadata-compiler.ts:52` | `dpString: 'desktop'` hardcoded | `&TARGETS` store always says `'desktop'` even if the source declares touch layers | +| `keys.ts:240` | "do nothing if only touch layers" | A touch-only LDML keyboard produces no keymap | +| `keys.ts:148` | "TODO-LDML: } else { touch?" | Touch layer validation skipped | +| `layr.ts:60` | "does not validate touch layers yet" | Layer count gate doesn't enforce touch | +| `visual-keyboard-compiler.ts:7` | "This is an interim solution until Keyman Core supports interrogation of the KMX+ data for OSK" | Today's stopgap is to emit a legacy `.kvk` from the LDML source rather than carry touch through in kmx+ | + +Tracking issue: **[#7238 — support touch layouts in kmc-ldml](https://github.com/keymanapp/keyman/issues/7238)** +(labelled `epic-ldml`, milestone A19S1). + +For the broader LDML editor / authoring story, see the +[`epic-ldml-editor` label](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-ldml-editor) +and the umbrella scaffolding issue [#12798](https://github.com/keymanapp/keyman/issues/12798). + +#### Cheapest contributions if you want to accelerate + +In order of leverage (smallest to largest): + +1. **`metadata-compiler.ts:52` one-liner** — emit `'desktop touch'` + when the source declares touch layers instead of the current + hardcoded `'desktop'`. Doesn't fix runtime, but lets correctly-built + LDML keyboards declare their actual platform support. Probably a + 1-line PR + tests. +2. **Finish `keys.ts` touch handling** — the `keys.ts:148, 240` TODOs. + A few hundred lines; lands "touch-aware LDML compilation" without + needing runtime changes. +3. **Finish `layr.ts:60` validation** — modest PR. + +Steps 1–3 close the compiler-side gap. The runtime gap (Core C API + +KeymanWeb/Mobile OSK refactors) is the v21-scale effort; not a small +PR. If a contributor wanted to make LDML touch keyboards *compile +correctly today* (with the legacy `.kvk` runtime stopgap still doing +the actual touch rendering), the three items above are the path. + +### What this means for you as an intern + +* **For desktop-only keyboards**: emit LDML XML. No consistency + contract; works today end-to-end. Bias new test keyboards toward + LDML. +* **For keyboards that need to run on touch today** (mobile, web): + emit LDML XML as the canonical source AND a derived `.kmn` + `.kvks` + + `.keyman-touch-layout` bundle. Treat the LDML as authoritative; the + bundle is transitional until Keyman closes the read gap. See + [external-tooling.md § Recommended generation strategy](external-tooling.md#recommended-generation-strategy). +* The CLDR LDML spec is complete for touch — your generated LDML is + forward-compatible. When Keyman finishes the read side, you delete + the transitional bundle and the same LDML just works. +* If you encounter an LDML-related bug at runtime, look in `core/src/ldml/` + (the C++ runtime); at compile time, look in `developer/src/kmc-ldml/`. +* `kmx+` files are still `.kmx` files on disk — same Keyman Core entry + point. The "+" refers to additional LDML-specific binary sections. + +## Migration 3: KeymanWeb modernization + +### What changed + +Original KeymanWeb was a large standalone JavaScript engine — a parallel +implementation of the Keyman keyboard runtime in JavaScript, separate +from the desktop/mobile engines. Two engines meant two divergent feature +sets, two bug surfaces, two test matrices. + +The modernization replaces the JS-side keyboard engine with **Keyman Core +compiled to WebAssembly**. The TypeScript code in `web/` is now a host +that integrates with browser inputs (DOM events, OSK rendering, language +switcher) and delegates *all* keyboard logic to Keyman Core via the WASM +binding. + +```mermaid +graph LR + subgraph WEB[Modern KeymanWeb / web/] + host[TypeScript host
DOM, OSK, UI] + wasm[Keyman Core
compiled to WASM] + end + user[Browser keyboard event] + page[Page text input] + + user --> host + host --> wasm + wasm --> host + host --> page + + style WEB fill:#83a,color:#fff +``` + +### Where it lives + +* **Host**: `web/src/` — TypeScript organized into: + * `web/src/engine/` — the WASM-binding glue and runtime helpers + * `web/src/app/` — browser entrypoints (KeymanWeb embedded library, + keyman.com app integration) + * `web/src/test/` — Karma + Mocha test suites +* **WASM core**: built from `core/` via `core/build.sh build:wasm` and + consumed by `web/`'s build at link time. Lives in `web/build/engine/` + after build. +* **Build**: `web/build.sh build` is the single entry point. Internally + it manages the emsdk toolchain, node packages, and the wasm dependency. + +### Status + +The Core-via-WASM model is shipping in 17+ release lines. Active work +focuses on feature parity edge cases (touch-keyboard interactions, +predictive text, IME interaction) and removing remaining legacy paths +inherited from the original JS engine. Search the issue tracker for +`web/` and `kmw` for current work. + +### What this means for you + +* When you debug a KeymanWeb keyboard bug, ask first: is the bug in the + keyboard logic (Keyman Core / WASM) or in the host (DOM event handling, + rendering)? The split is more useful than the old "look anywhere in the + KeymanWeb monolith." +* If a behavior differs between desktop Keyman and KeymanWeb, the bug is + *probably* in the web host (`web/src/`), not in Keyman Core — Core is + shared. + +## Long-running migration branches + +The three migrations above don't always land directly on `master`. Some +larger work happens on long-lived branches that periodically merge back. +Conventional name prefixes you'll see in `git branch -r`: + +* `feat/core/...` — runtime features in `core/`, often coordinated with + LDML or platform-engine work +* `feat/developer/...` — compiler / Developer-side features (kmc-* etc.) +* `feat/web/...` — KeymanWeb modernization work +* `chore/windows/...` — incremental cleanups in the Windows / Delphi tree +* `auto/...` — automated branches (version bumps, etc.); not for human + contribution + +Before starting a non-trivial feature, search the open issues by `epic-*` +label first, then `git branch -r | grep -i ` against +`upstream/` to see if a long-running branch already covers it. + +## Roadmap and decision context + +The Keyman team publishes high-level direction on the +[Keyman roadmap blog](https://blog.keyman.com/category/roadmap/). The +current public roadmap is the +[**March 2026 update**](https://blog.keyman.com/2026/03/keyman-roadmap-march-2026/) — +read it before making bets on what to invest effort in. Headlines: + +| Version | Theme | Highlights | +|---|---|---| +| **19** (current) | LDML / web-core | CLDR keyboards on mobile, tablet, embedded web | +| **20** | Compiler & UI modernization | **Delphi → C++ for Windows UI**, LDML visual editor (VSCode plugin, [#12798](https://github.com/keymanapp/keyman/issues/12798)), compiler rewrite | +| **21** | Touch/OSK rebuild | On-screen keyboard rebuilt with web tech for cross-platform consistency | +| **22** | Distribution & deprecation | KeymanWeb on npm; deprecate `.js` keyboards in favor of LDML; CLDR predictive text wordlists | + +All version targets are funding-dependent (the roadmap is explicit about +this). + +**Umbrella tracking issues to watch:** + +* [#4599 — Delphi removal](https://github.com/keymanapp/keyman/issues/4599) + (filed 2022, umbrella) +* [#7238 — LDML touch layout compilation](https://github.com/keymanapp/keyman/issues/7238) + (epic-ldml, see § Migration 2 above) +* [#12798 — VSCode plugin scaffolding](https://github.com/keymanapp/keyman/issues/12798) + (epic-ldml-editor, milestone 20.0) +* [`epic-ldml` label](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-ldml) + — open work on the LDML keyboard format support +* [`epic-ldml-editor` label](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-ldml-editor) + — open work on LDML authoring tooling +* [`epic-keyman-developer` label](https://github.com/keymanapp/keyman/issues?q=label%3Aepic-keyman-developer) + — broader Developer-side work + +## TL;DR for new contributors + +* **New features**: write them in the new stack (C++ for runtime, TS for + tooling). Don't add to Delphi unless you specifically have to. +* **Bug fixes**: fix them where the bug is. If it's in Delphi-bound code, + patch Delphi. Don't refactor surrounding code. +* **LDML**: it's the future format. Bias new test keyboards toward LDML + XML, not `.kmn`. +* **KeymanWeb**: the engine *is* Keyman Core. The host is TypeScript. + Debug accordingly. +* **When in doubt**: check the roadmap blog, search issues, ask in the + [SIL/Keyman forum](https://community.software.sil.org/c/keyman). diff --git a/dev-docs/repository-map.md b/dev-docs/repository-map.md new file mode 100644 index 00000000000..09379c1a888 --- /dev/null +++ b/dev-docs/repository-map.md @@ -0,0 +1,344 @@ +# Repository Map + +This is a tour of the Keyman monorepo. By the end of it you should know +where to find each major component, what tech stack it uses, which +platforms it targets, and how the components connect to each other. + +If you only read one section, read [Top-level layout](#top-level-layout) and +[The four big subsystems](#the-four-big-subsystems). + +## What Keyman is, in three sentences + +Keyman is an open-source cross-platform input system that lets people type in +any of the world's languages. It runs on Windows, macOS, Linux, iOS, Android, +and in web browsers — same keyboard files everywhere. The repo contains the +runtime engines for each platform, the developer tooling for authoring +keyboards, the Keyman Developer IDE, the KeymanWeb JavaScript engine, and a +shared C++ keyboard-processing core. + +## Top-level layout + +```mermaid +graph TD + root[keyman/] + root --> core[core/
Keyman Core
C++ shared runtime] + root --> common[common/
Shared code & tools] + root --> developer[developer/
Keyman Developer
compiler + IDE] + root --> web[web/
KeymanWeb
browser engine] + root --> windows[windows/
Windows engine + desktop] + root --> mac[mac/
macOS] + root --> linux[linux/
Linux] + root --> ios[ios/
iOS] + root --> android[android/
Android] + root --> oem[oem/
OEM builds
e.g. FirstVoices] + root --> docs[docs/
Build & format docs] + root --> resources[resources/
Build scripts & data] + root --> devdocs[dev-docs/
You are here] + + style root fill:#444,color:#fff + style devdocs fill:#28a,color:#fff + style core fill:#284,color:#fff +``` + +## The four big subsystems + +```mermaid +graph LR + subgraph SC[Shared C++] + kmcore[core/
Keyman Core] + end + subgraph DV[Developer tooling] + kmcmplib[kmcmplib
.kmn compiler library] + kmc[kmc & kmc-*
CLI compiler suite] + tike[Tike
Developer IDE] + end + subgraph RT[Runtime engines] + eng_win[Windows Engine
Delphi + C++] + eng_mac[macOS Engine
Swift + ObjC] + eng_lin[Linux Engine
C++] + eng_ios[iOS Engine
Swift] + eng_and[Android Engine
Java/Kotlin + C++] + end + subgraph WB[KeymanWeb] + kmweb[KeymanWeb
TypeScript + WASM] + end + + kmcore -.shared lib.-> eng_win + kmcore -.shared lib.-> eng_mac + kmcore -.shared lib.-> eng_lin + kmcore -.shared lib.-> eng_ios + kmcore -.shared lib.-> eng_and + kmcore -.wasm.-> kmweb + + kmcmplib --> kmc + kmc --> tike + kmcmplib -.compiles .kmn.-> tike + + style SC fill:#284,color:#fff + style DV fill:#46c,color:#fff + style RT fill:#a52,color:#fff + style WB fill:#83a,color:#fff +``` + +* **Shared C++** — `core/` is a single shared keyboard-processing engine + used by *every* runtime. When you press a key, the platform-specific + engine translates that into Keyman Core API calls; Core runs the keyboard + logic and returns the resulting text actions; the engine then injects + text into the focused application using platform APIs. + +* **Developer tooling** — under `developer/src/`. The historical Delphi + IDE (Tike) and the modern TypeScript CLI compiler family (`kmc` and the + ten `kmc-*` modules) live side by side. The `.kmn` keyboard compiler + itself is C++ (`kmcmplib`). + +* **Runtime engines** — one per platform under `windows/`, `mac/`, + `linux/`, `ios/`, `android/`. Each is a thin shell over Keyman Core that + handles the platform's keyboard hooks, IME registration, OS integration, + and configuration UI. + +* **KeymanWeb** — under `web/`. The same Keyman Core code compiled to + WebAssembly via Emscripten, plus a TypeScript host that integrates with + HTML inputs and embeds in Keyman web apps and `keyman.com`. + +## Tech stack per directory + +| Directory | Primary language(s) | Build system | Platforms | +|---|---|---|---| +| `core/` | C++ | Meson + Ninja | Cross (Win, Mac, Linux, WASM) | +| `common/` | Mixed (C++, TS, Delphi, data) | Various | Shared | +| `common/web/` | TypeScript | npm + esbuild | Cross | +| `common/windows/delphi/` | Delphi | build.sh / Delphi IDE | Windows only | +| `developer/src/kmcmplib/` | C++ | Meson | Cross | +| `developer/src/kmc*/` | TypeScript | npm + esbuild | Cross | +| `developer/src/Tike/` | Delphi (VCL) | build.sh + Delphi IDE | Windows only | +| `developer/src/kmconvert/`, `setup/` | Delphi | build.sh + Delphi IDE | Windows only | +| `web/` | TypeScript + WASM | build.sh + npm | Browsers | +| `windows/src/engine/` | Delphi + C++ | build.sh + Delphi/VS | Windows | +| `windows/src/desktop/` | Delphi | build.sh + Delphi IDE | Windows | +| `mac/` | Swift, Objective-C | Xcode | macOS | +| `linux/` | C++, Python | meson, debhelper | Linux | +| `ios/` | Swift | Xcode | iOS | +| `android/` | Java, Kotlin, C++ | Gradle | Android | +| `oem/firstvoices/` | Platform-specific | Per-platform | Cross | +| `resources/` | Bash, data files | Plain | Build infra | + +## How a keystroke flows (runtime) + +```mermaid +sequenceDiagram + participant User + participant OS as OS Input Layer + participant Engine as Platform Engine
(e.g. windows/src/engine) + participant Core as Keyman Core
(core/, C++) + participant App as Target App + + User->>OS: Press key + OS->>Engine: Keyboard hook callback + Engine->>Core: km_core_state_process_event() + Core->>Core: Run active keyboard rules
(.kmx or LDML) + Core-->>Engine: List of TActionItem
(emit char, backspace, ...) + Engine->>OS: SendInput / TSF actions + OS->>App: Text typed +``` + +The platform engine never implements keyboard logic itself; it always +delegates to Keyman Core. That's why a single keyboard file works across +Windows, macOS, Linux, iOS, Android, and the web. + +## How a keyboard is built (developer flow) + +```mermaid +flowchart LR + src[Source
.kmn or LDML XML] + kmc[kmc
TypeScript CLI] + kmcmplib[kmcmplib
C++ compiler] + kmx[.kmx
Keyman binary] + js[.js
KeymanWeb bundle] + kmp[.kmp
Package zip] + + src --> kmc + kmc -.delegates .kmn.-> kmcmplib + kmcmplib --> kmx + kmc --> js + kmc --> kmp + kmx --> kmp + js --> kmp +``` + +The TypeScript `kmc` CLI is the modern entry point. For `.kmn` source it +delegates to the C++ `kmcmplib`. For LDML XML source, `kmc-ldml` handles +compilation directly in TypeScript. The single `.kmp` package contains +the platform binaries plus metadata (`keyboard_info`, fonts, docs). + +## Subsystem deep-dives + +### `core/` — Keyman Core + +The cross-platform shared C++ runtime. Implements the keyboard-rule +processor in a host-agnostic way. Exposed through a stable C API +(`km_core_*` functions) so every platform engine and KeymanWeb can call +the same logic. + +* **Build**: `core/build.sh build:x64` / `:x86` / `:wasm` / per-platform + targets. Output goes to `core/build///src/libkmcore-*.{dll,so,dylib}`. +* **Tests**: `core/tests/` with `kmnkbd`, `ldml`, `kmx`, `kmx_plus` + unit-test directories. +* **API**: see `core/docs/api.md` and the public headers in `core/include/`. + +### `developer/src/` — Keyman Developer + +The keyboard authoring side. Two halves: + +* **Modern compiler stack** (TypeScript): `kmc/` is the unified CLI; + `kmc-kmn/` delegates `.kmn` to `kmcmplib`; `kmc-ldml/` handles LDML + keyboards; `kmc-package/`, `kmc-keyboard-info/`, `kmc-analyze/`, + `kmc-copy/`, `kmc-generate/`, `kmc-model/`, `kmc-model-info/` cover the + other authoring tasks. +* **Legacy Delphi IDE**: `Tike/Tike.dproj` is the visual authoring tool, + built with VCL. `setup/` is the Developer installer. `kmconvert/` is a + legacy conversion tool — marked Legacy and being phased out in favor of + the kmc-* family. See [migration-guide.md](migration-guide.md). + +### `web/` — KeymanWeb + +The browser-side keyboard engine. Same Keyman Core, compiled to +WebAssembly via Emscripten and wrapped in a TypeScript host. Used by +keyman.com web apps, by sites that embed the keyman.js script, and by +Keyman Developer's preview mode. + +* **Build**: `web/build.sh build` (needs node + emsdk; sets up both). +* **Output**: `web/build/app/`, `web/build/engine/`, `web/build/publish/` + for the release bundles. +* **Tests**: `web/build/test/` after `build`, runnable via Karma. + +### `windows/src/engine/` — Windows runtime engine + +A mix of Delphi (the COM API, configuration helpers, TSF text service +plumbing) and C++ (the actual input hook, `keyman32.dll`, the TSF text +processor `kmtip.dll`). For a tour of which dproj does what, see +`windows/src/engine/engine.groupproj` — the five Delphi projects build +together as a group; the C++ pieces (`keyman32`, `kmtip`, `keymanhp`, +`kmrefresh`, `mcompile`, `testhost`) build standalone via msbuild. + +### `windows/src/desktop/` — Keyman for Windows (the user-facing app) + +Delphi VCL apps: `kmshell` is the configuration tray app; `kmconfig` is +the legacy settings UI; `kmbrowserhost` embeds CEF for the +in-app browser; `setup` is the installer; `insthelp` is the install helper. + +### `mac/`, `linux/`, `ios/`, `android/` + +Each runs Keyman Core under the hood, with a platform-native shell: + +* **macOS** (`mac/Keyman4MacIM/`): Objective-C / Swift Input Method bundle. +* **Linux** (`linux/ibus-keyman/`): IBus engine module written in C++, + packaged via the `keyman-config` Python helpers. +* **iOS** (`ios/keyman/`): Swift app + KeymanEngine framework. +* **Android** (`android/KMAPro/`): Kotlin/Java app + KMEA library; + Keyman Core compiled for NDK is loaded via JNI. + +### `common/` — Shared anything-and-everything + +Anything used by more than one platform lives here. Highlights: + +* `common/web/` — shared TypeScript helpers (`keyman-version`, `langtags`, + `types`, `utils`). +* `common/windows/delphi/` — Delphi units shared by every Windows-Delphi + project (engine, desktop, developer). Plus bundled third-party Delphi + libraries: JCL, JVCL, mbcolor, dcpcrypt, CEF4Delphi. +* `common/windows/cpp/` — Windows-specific C++ headers. +* `common/include/`, `common/test/` — cross-platform shared includes and + test fixtures. +* `common/tools/` — small cross-platform helpers like `hextobin`. + +### `resources/`, `build/`, `docs/` + +* `resources/build/` — the shared bash-builder framework (`builder-full.inc.sh` + etc.) that every project's `build.sh` sources. Read this once when you + start writing a new `build.sh`. +* `resources/standards-data/` — language tag and ISO data used to generate + the BCP-47 Pascal modules. +* `docs/` — build instructions, file-format references, history. + +## Build orchestration + +Every top-level directory has a `build.sh` that obeys the same conventions. +A typical invocation is: + +```bash +.//build.sh [:] [options...] +``` + +Where: +* `` is `clean`, `configure`, `build`, `test`, `publish`, `install`, + `api`, or `edit`. +* `` is component-specific (e.g. `./web/build.sh build:engine`). + +Read `/build.sh --help` to see available targets. Most scripts inherit +the cross-platform builder framework from `resources/build/builder-*.inc.sh`, +so they all have the same look-and-feel. + +```mermaid +graph TD + root_build[./build.sh
top-level orchestrator] + root_build --> core_b[core/build.sh] + root_build --> dev_b[developer/build.sh] + root_build --> web_b[web/build.sh] + root_build --> win_b[windows/src/build.sh] + root_build --> linux_b[linux/build.sh] + root_build --> mac_b[mac/build.sh] + root_build --> ios_b[ios/build.sh] + root_build --> and_b[android/build.sh] + + dev_b --> kmc_b[developer/src/kmc/build.sh] + dev_b --> kmcmplib_b[developer/src/kmcmplib/build.sh] + dev_b --> tike_b[developer/src/Tike/build.sh] + dev_b --> setup_b[developer/src/setup/build.sh] + + win_b --> eng_b[windows/src/engine/build.sh] + win_b --> desk_b[windows/src/desktop/build.sh] + eng_b --> k32_b[engine/keyman32/build.sh] + eng_b --> kmtip_b[engine/kmtip/build.sh] + eng_b --> ihelp_b[engine/insthelper/build.sh] + + style root_build fill:#444,color:#fff +``` + +## Cross-platform matrix + +Which directories actually build on which OS: + +| Directory | Win 11 | macOS | Linux | +|---|---|---|---| +| `core/` | ✓ (x86, x64, ARM64, WASM) | ✓ (universal) | ✓ | +| `common/` | ✓ | ✓ | ✓ | +| `developer/src/kmcmplib/` | ✓ | ✓ | ✓ | +| `developer/src/kmc*/` | ✓ | ✓ | ✓ | +| `developer/src/Tike/`, `setup/`, `kmconvert/` | Delphi only | — | — | +| `web/` | ✓ | ✓ | ✓ | +| `windows/` | ✓ | — | — | +| `linux/` | — | — | ✓ | +| `mac/`, `ios/` | — | ✓ | — | +| `android/` | ✓ (Java/Kotlin/NDK) | ✓ | ✓ | + +When the `docs/build/windows.md` doc says "the following projects cannot be +built on Windows: Keyman for Linux / Keyman for macOS / Keyman for iOS" — +this matrix is what it's reflecting. + +## Where to start as an intern + +Depending on what you're working on: + +* **Keyboard runtime work**: start with `core/` and one of the platform + engines (most likely `windows/src/engine/` or `linux/ibus-keyman/`). +* **Compiler / authoring work**: start with `developer/src/kmc/` and + follow the dependency from there into the `kmc-*` modules. +* **Web work**: start with `web/` and the TypeScript app under + `web/src/app/`. +* **Mobile**: `android/KMAPro/` or `ios/keyman/`. +* **Delphi UI work (Tike, kmshell)**: read [migration-guide.md](migration-guide.md) + *first* — the long-term direction matters before you invest time in + Delphi changes. + +Whatever you touch first, read its `build.sh --help` and the `README.md` +in that directory (most components have one). diff --git a/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas b/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas index f73b1722b09..eb405224249 100644 --- a/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas +++ b/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas @@ -937,11 +937,15 @@ function TJclCriticalSectionEx.TryEnter: Boolean; //== { TJclEvent } =========================================================== +// Keyman local patch: Delphi 11/12 compat (vendored JCL). Explicit BOOL() casts +// added below to satisfy Delphi 12's stricter implicit-Boolean->BOOL conversion. +// Backwards-compatible with Delphi 10.3 (BOOL is an ordinal-preserving typecast). +// On JCL refresh from upstream: re-apply if upstream hasn't picked up the cast. constructor TJclEvent.Create(SecAttr: PSecurityAttributes; Manual, Signaled: Boolean; const Name: string); begin inherited Create; FName := Name; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateEvent(SecAttr, Manual, Signaled, PChar(FName)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateEvent(SecAttr, BOOL(Manual), BOOL(Signaled), PChar(FName)); if FHandle = 0 then raise EJclEventError.CreateRes(@RsSynchCreateEvent); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -952,7 +956,7 @@ constructor TJclEvent.Open(Access: Cardinal; Inheritable: Boolean; begin FName := Name; FExisted := True; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenEvent(Access, Inheritable, PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenEvent(Access, BOOL(Inheritable), PChar(Name)); if FHandle = 0 then raise EJclEventError.CreateRes(@RsSynchOpenEvent); end; @@ -980,7 +984,7 @@ constructor TJclWaitableTimer.Create(SecAttr: PSecurityAttributes; begin FName := Name; FResume := False; - FHandle := CreateWaitableTimer(SecAttr, Manual, PChar(Name)); + FHandle := CreateWaitableTimer(SecAttr, BOOL(Manual), PChar(Name)); if FHandle = 0 then raise EJclWaitableTimerError.CreateRes(@RsSynchCreateWaitableTimer); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -1000,7 +1004,7 @@ constructor TJclWaitableTimer.Open(Access: Cardinal; Inheritable: Boolean; FExisted := True; FName := Name; FResume := False; - FHandle := OpenWaitableTimer(Access, Inheritable, PChar(Name)); + FHandle := OpenWaitableTimer(Access, BOOL(Inheritable), PChar(Name)); if FHandle = 0 then raise EJclWaitableTimerError.CreateRes(@RsSynchOpenWaitableTimer); end; @@ -1048,7 +1052,7 @@ constructor TJclSemaphore.Open(Access: Cardinal; Inheritable: Boolean; begin FName := Name; FExisted := True; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenSemaphore(Access, Inheritable, PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenSemaphore(Access, BOOL(Inheritable), PChar(Name)); if FHandle = 0 then raise EJclSemaphoreError.CreateRes(@RsSynchOpenSemaphore); end; @@ -1075,7 +1079,7 @@ constructor TJclMutex.Create(SecAttr: PSecurityAttributes; InitialOwner: Boolean begin inherited Create; FName := Name; - FHandle := JclWin32.CreateMutex(SecAttr, InitialOwner, PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateMutex(SecAttr, BOOL(InitialOwner), PChar(Name)); if FHandle = 0 then raise EJclMutexError.CreateRes(@RsSynchCreateMutex); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -1086,7 +1090,7 @@ constructor TJclMutex.Open(Access: Cardinal; Inheritable: Boolean; const Name: s inherited Create; FName := Name; FExisted := True; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenMutex(Access, Inheritable, PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenMutex(Access, BOOL(Inheritable), PChar(Name)); if FHandle = 0 then raise EJclMutexError.CreateRes(@RsSynchOpenMutex); end; diff --git a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas index 1877ee48efe..fab8609e2c8 100644 --- a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas +++ b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas @@ -123,8 +123,16 @@ constructor TJvForm.Create(AOwner: TComponent); finally Exclude(FFormState, fsCreating); end; + // Keyman local patch: Delphi 11/12 compat (vendored JVCL). The + // OldCreateOrder property was removed in Delphi 11; the modern semantics + // are equivalent to OldCreateOrder=True, so always call DoCreate on + // VER350+. On JVCL refresh from upstream: re-apply if not yet upstreamed. +{$IF Defined(VER350) or Defined(VER360)} + DoCreate; +{$ELSE} if OldCreateOrder then DoCreate; +{$IFEND} end; finally GlobalNameSpace.EndWrite; diff --git a/developer/src/ext/mbcolor/mxs.inc b/developer/src/ext/mbcolor/mxs.inc index 41b82ad96e5..75ca0b34e73 100644 --- a/developer/src/ext/mbcolor/mxs.inc +++ b/developer/src/ext/mbcolor/mxs.inc @@ -7,6 +7,28 @@ {$define DELPHI_10_UP} {$endif} + // Keyman local patch: Delphi 11/12 compat (vendored mbcolor). Without + // these blocks DELPHI_*_UP flags are never defined on VER350/VER360, + // which causes HTMLColors.pas to drop "uses Variants" and fail to + // resolve the Null constant. On mbcolor refresh: re-apply if needed. + {$ifdef VER350} + {$define DELPHI_5_UP} + {$define DELPHI_6_UP} + {$define DELPHI_7_UP} + {$define DELPHI_8_UP} + {$define DELPHI_9_UP} + {$define DELPHI_10_UP} + {$endif} + + {$ifdef VER360} + {$define DELPHI_5_UP} + {$define DELPHI_6_UP} + {$define DELPHI_7_UP} + {$define DELPHI_8_UP} + {$define DELPHI_9_UP} + {$define DELPHI_10_UP} + {$endif} + {$ifdef VER330} {$define DELPHI_5_UP} {$define DELPHI_6_UP} diff --git a/docs/build/windows-d12.md b/docs/build/windows-d12.md new file mode 100644 index 00000000000..b7e740c6b0e --- /dev/null +++ b/docs/build/windows-d12.md @@ -0,0 +1,933 @@ +# Build Keyman on Windows with Delphi 12 Community Edition + +> [!WARNING] +> This is a workaround guide for developers using **Delphi 12 Athens Community +> Edition (CE)**. Delphi CE 11+ deliberately blocks command-line `dcc32` +> compilation: +> +> ``` +> This version of the product does not support command line compiling +> ``` +> +> Because of that, the repo's `build.sh` chain cannot drive Delphi builds +> end-to-end on CE. Everything Delphi must be opened and built in the IDE by +> hand, and the codegen / resource-compilation steps that `build.sh` would +> normally perform must be run manually first. This guide documents that +> workflow plus the local source patches needed to compile against Delphi 12. +> +> If you have a Delphi 10.3 / 10.4 Professional or Enterprise license, follow +> the canonical [windows.md](windows.md) instead; none of the workarounds in +> this file are needed. + +## Scope + +This guide covers: + +1. Prerequisites (Windows + Delphi 12 CE specific notes) +2. One-time environment setup +3. Local-only source patches required for Delphi 12 +4. Build order and the implicit cross-project dependencies +5. Helper scripts that paper over what `build.sh` would normally do +6. Running and debugging the dev build alongside an installed Keyman 19 +7. Troubleshooting common error messages + +The canonical reference for the standard (non-CE) flow is +[windows.md](windows.md). Refer to it for repository layout, the +`KEYMAN_ROOT` / `KEYMAN_CEF4DELPHI_ROOT` / `EMSCRIPTEN_BASE` env vars, +Chocolatey package list, Visual Studio workloads, and Android dependencies. +This document only describes the deltas. + +> [!IMPORTANT] +> All patches and registry edits described here are **local-only** and must be +> reverted before opening a pull request. The repo's CI runs against +> Delphi 10.3 and will not accept any of these workarounds upstream. + +## 1. Prerequisites + +Beyond the [Base](windows.md#base-dependencies) and +[Web](windows.md#web-dependencies) dependencies in `windows.md`, Delphi 12 CE +needs the following: + +* **Delphi 12 Athens Community Edition** + * Download from + https://www.embarcadero.com/products/delphi/starter/free-download + * Installs to `C:\Program Files (x86)\Embarcadero\Studio\23.0\` -- note the + `23.0` version dir, not `20.0` (10.3) or `21.0` (10.4). + * During the install wizard, select **DUnit Unit Testing Frameworks** + (required by Keyman test projects). + * Launch the IDE once after install to complete registration. The first + launch populates the per-user `BDS\23.0` registry hive used in step 4. +* **Visual Studio 2022 Community** with the C++ native desktop workload, plus + Windows 10 SDK (10.0.19041.0) and Windows 11 SDK (10.0.26100). See + [Windows Platform Dependencies](windows.md#windows-platform-dependencies) + for the full winget command. +* **Keyman 19 (official release)** installed from + https://keyman.com/desktop. This is *not* optional for Delphi 12 CE + developers -- the dev kmshell.exe runtime-discovers its install path via + `TKeymanPaths.KeymanDesktopInstallPath()`, which is hardcoded to + `C:\Program Files (x86)\Keyman\Keyman Desktop\`. Without the install, + kmshell crashes at startup with `SKApplicationTitle has had a fatal + error...` before its main form appears. See section 7 (Running). +* **Keyman Developer (official release)** is recommended in parallel for TIKE + overlay debugging -- TIKE loads templates and language data from the + installed Developer support files. +* **7-Zip** (`choco install 7zip`) for extracting the CEF libcef payload. + +## 2. One-time setup + +### 2.1 Clone the repos + +Use the layout from [windows.md](windows.md#repository-paths). This guide +assumes the keyman checkout is at `C:\Projects\keyman\keyman\` (matches the local +machine). Use the same parent directory for the CEF and emsdk checkouts so +the `KEYMAN_CEF4DELPHI_ROOT` / `EMSCRIPTEN_BASE` paths below line up. + +```bash +# Git Bash -- use forward slashes via msys +mkdir -p /c/Projects/keyman +cd /c/Projects/keyman +git clone https://github.com/keymanapp/keyman +git clone https://github.com/keymanapp/CEF4Delphi_Binary +git clone https://github.com/emscripten-core/emsdk +``` + +### 2.2 Environment variables + +```cmd +:: Standard Windows cmd / PowerShell -- backslash paths +SETX KEYMAN_ROOT "C:\Projects\keyman\keyman" +SETX KEYMAN_CEF4DELPHI_ROOT "C:\Projects\keyman\CEF4Delphi_Binary" +SETX EMSCRIPTEN_BASE "C:\Projects\keyman\emsdk\upstream\emscripten" +``` + +`SETX` is persistent but does not affect the current shell -- open a fresh +shell to pick them up. Also add `\windows\lib` to `PATH` so +Delphi's design-time packages can resolve. + +### 2.3 Emscripten + +```bash +# Git Bash +cd /c/Projects/keyman/emsdk +emsdk install 3.1.58 +emsdk activate 3.1.58 +cd upstream/emscripten +npm install +``` + +### 2.4 node.js via nvm-windows + +```cmd +nvm install 20.16.0 +nvm use 20.16.0 +``` + +### 2.5 CEF4Delphi_Binary -- switch to the pinned tag and extract libcef + +`common/windows/cef-checkout.sh` normally does this, but if you're driving +Delphi from the IDE you'll want to do it once by hand. Some payload files +exceed GitHub's 100 MB limit and ship as `.zip` files inside the repo; they +must be extracted in place. + +```powershell +$cefVer = (Get-Content C:\Projects\keyman\keyman\common\windows\CEF_VERSION.md).Trim() +# e.g. 89.0.18 + +Push-Location $env:KEYMAN_CEF4DELPHI_ROOT +git reset --hard +git clean -fd +git fetch origin "v$cefVer" +git switch "v$cefVer" + +if (-not (Test-Path .\libcef.dll)) { + Get-ChildItem *.zip | ForEach-Object { + & 'C:\Program Files\7-Zip\7z.exe' x -y $_.FullName + Remove-Item $_.FullName + } +} +Pop-Location +``` + +### 2.6 Install Keyman 19 official + +Install Keyman 19.0 from https://keyman.com/desktop. After install verify +`C:\Program Files (x86)\Keyman\Keyman Desktop\kmshell.exe` exists and that +`HKLM\SOFTWARE\WOW6432Node\Keyman\Keyman Engine` is populated. The overlay +workflow in section 7 depends on these. + +### 2.7 Delphi 12 IDE Library Search Paths + +`build.sh` injects `dcc32 -I/-U/-R` flags that the IDE does not see. Without +them, opening any of the Keyman .dproj files in Delphi triggers `F1026 File +not found: jvcl.inc` / `jedi.inc` / `jcl.inc` / etc. + +These paths live in the per-user registry under +`HKCU\Software\Embarcadero\BDS\23.0\Library\Win32` (and `...\Win64`), in the +`Search Path` value, semicolon-separated. **Close Delphi first**; the IDE +caches the value at startup. + +```powershell +# Elevated PowerShell -- back up first +reg export 'HKCU\Software\Embarcadero\BDS\23.0\Library' ` + C:\Projects\keyman\keyman\delphi-library-paths.backup.reg /y + +$key32 = 'HKCU:\Software\Embarcadero\BDS\23.0\Library\Win32' +$key64 = 'HKCU:\Software\Embarcadero\BDS\23.0\Library\Win64' + +$paths = @( + # Keyman common includes (mirrors DELPHIINCLUDES in delphi_flags.inc.sh) + 'C:\Projects\keyman\keyman\common\windows\delphi\ext\cef4delphi\source', + 'C:\Projects\keyman\keyman\common\windows\delphi\ext\dcpcrypt', + 'C:\Projects\keyman\keyman\common\windows\delphi\ext\jwa\Win32API', + 'C:\Projects\keyman\keyman\common\windows\delphi\ext\sentry', + 'C:\Projects\keyman\keyman\developer\src\ext\mbcolor', + 'C:\Projects\keyman\keyman\developer\src\ext\scfontcombobox', + # JCL / JVCL source roots (needed for TIKE) + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jcl\jcl\source\common', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jcl\jcl\source\prototypes', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jcl\jcl\source\vcl', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jcl\jcl\source\windows', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jcl\jcl\source\include', # jcl.inc + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jvcl\jvcl\design', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jvcl\jvcl\run', + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jvcl\jvcl\common', # jvcl.inc + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jvcl\jvcl\resources', # JvConsts.res + 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jedi', # jedi.inc + 'C:\Projects\keyman\keyman\developer\src\ext\jedi' # parent for {$I jedi\jedi.inc} +) + +foreach ($key in @($key32, $key64)) { + $cur = (Get-ItemProperty -Path $key -Name 'Search Path' ` + -ErrorAction SilentlyContinue).'Search Path' + $merged = (@($cur -split ';') + $paths | + Where-Object { $_ } | + Select-Object -Unique) -join ';' + Set-ItemProperty -Path $key -Name 'Search Path' -Value $merged +} +``` + +Restore via `reg import C:\Projects\keyman\keyman\delphi-library-paths.backup.reg`. + +Note: only the **Search Path** value was set, not Browsing Path or Debug DCU +Path. Ctrl+click navigation to JCL/JVCL source and step-into-source debugging +may need the same paths added there manually via the IDE +(Tools > Options > Language > Delphi > Library). + +## 3. Local-only source patches + +These patches are required to compile against Delphi 12 (VER360) but are +**not yet upstreamable** -- they coexist with Delphi 10.3 (VER340) by +extending IFDEF chains rather than replacing the older guard. The patches +must be reverted before any PR. + +> [!NOTE] +> In some branches `windows/src/engine/engine.groupproj` carries a stale +> reference to `inst\insthelper\insthelper.dproj` -- a path that doesn't +> resolve because the real file is at +> `windows\src\engine\insthelper\insthelper.dproj` (no `inst\` parent). +> If your tree has the broken reference, either patch `engine.groupproj` +> to drop the `inst\` segment or move the file. See section 3.3 and the +> `[[delphi-12-local-patches]]` memory. + +### 3.1 Build-script: select Delphi version + +`KEYMAN_DELPHI_VERSION` selects which Delphi installation `build.sh` and the +builder tool-detection look for. It defaults to `20.0` (Delphi 10.3, the CI +target), so unset == existing CI behavior. + +For any Delphi 12 installation (Professional or CE), set it to `23.0`: + +```powershell +# PowerShell (persistent for your user) +[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_VERSION', '23.0', 'User') +``` + +```bash +# Git Bash (per-shell) +export KEYMAN_DELPHI_VERSION=23.0 +``` + +Without this, `build.sh` looks for +`C:\Program Files (x86)\Embarcadero\Studio\20.0\bin\rsvars.bat` and the builder +platform system looks for `Studio\20.0\bin\dcc32.exe` to decide whether to include +`win,delphi`-gated targets -- both fail silently on a Delphi-12-only machine. + +**Delphi 12 Professional** — setting `KEYMAN_DELPHI_VERSION=23.0` is sufficient. +CLI compilation works normally; `build.sh` targets run end-to-end. + +**Delphi 12 Community Edition** — additionally set `KEYMAN_DELPHI_CE=1`. CE blocks +CLI compilation, so `delphi_msbuild` switches to interactive mode: instead of +invoking `msbuild.exe`, the script pauses and prompts: + +``` +Delphi CE: CLI compilation is not available. +Please build in the Delphi IDE now, then press Enter to continue. +``` + +```powershell +[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_CE', '1', 'User') +``` + +```bash +export KEYMAN_DELPHI_CE=1 +``` + +### 3.2 keyman.exe uiAccess strip (LOCAL ONLY, not committed) + +This patch is **deliberately not committed** to this branch — it would +change release-signed-binary semantics in a way upstream CI doesn't want. +Apply it manually as a working-tree-only edit if you need to overlay an +unsigned dev `keyman.exe`. Run after a fresh checkout, before building +keyman.dproj: + +```powershell +$f = 'C:\Projects\keyman\keyman\windows\src\engine\keyman\manifest.in' +(Get-Content $f) -replace 'uiAccess="true"', 'uiAccess="false"' | Set-Content $f -Encoding UTF8 +``` + +Then re-run `build.sh build` for `windows/src/engine/keyman` (the pre-build +`build_manifest.res` step regenerates manifest.xml / manifest.res before prompting +for the IDE build), do a **Clean + Build** of keyman.dproj in Delphi IDE +(see [[delphi-incremental-build-res-cache]] — Build alone won't re-link the +new manifest.res), and run `windows/src/engine/build.sh install` elevated (section 5.3). + +**Why:** Windows refuses to launch any unsigned binary that declares +`uiAccess="true"`, returning `Access is denied` (error 8235). The overlay +workflow (section 7) replaces the signed installed keyman.exe with an +unsigned dev build, so uiAccess must be turned off for the overlaid dev +binary to launch. Trade-off: keyboard injection into elevated apps stops +working until uiAccess is restored and the binary is signed via Keyman's +test-cert pipeline. + +To revert (keep your tree CI-clean): `git checkout -- windows/src/engine/keyman/manifest.in`. + +### 3.3 engine.groupproj / insthelper path ambiguity + +There is a real path ambiguity in the engine tree: + +* The actual `insthelper.dproj` lives at + `windows\src\engine\insthelper\insthelper.dproj`. +* Historical `build.sh` plumbing (and at least one variant of + `engine.groupproj` carried in some branches) refers to it as + `inst\insthelper\insthelper.dproj` -- i.e. with an extra `inst\` + segment that does not resolve. + +In the current tree, `engine.groupproj` does not include an `insthelper` +entry at all (the group only builds keyman, kmcomapi, tsysinfo, +tsysinfox64), so "Build All Projects" on the group works -- but +`insthelper.dproj` must then be opened and built on its own. If you encounter +a tree where the groupproj does carry the broken `inst\insthelper\...` path, +the two clean workarounds are: + +(a) Patch `engine.groupproj` to drop the `inst\` segment -- change every + `inst\insthelper\insthelper.dproj` to `insthelper\insthelper.dproj` and + update the `` MSBuild attribute the same way. + +(b) Move the directory: `git mv windows/src/engine/insthelper + windows/src/engine/inst/insthelper` so the existing groupproj path + resolves. This is invasive and not recommended. + +In either case, also add `insthelper` to the `Build` / `Clean` / `Make` +`CallTarget` lists if you want "Build All Projects" to pick it up. See the +GAP note at the top of section 3 about `[[delphi-12-local-patches]]`. + +### 3.4 JCL Boolean -> BOOL casts, plus JclWin32 -> Winapi.Windows on CreateMutex + +`developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas` + +Delphi 12 dropped the implicit `Boolean` -> `BOOL` conversion. Every Win32 +sync API call must be wrapped: + +```pascal +// - Result := CreateEvent(SecAttr, Manual, Signaled, PChar(Name)); +// + Result := CreateEvent(SecAttr, BOOL(Manual), BOOL(Signaled), PChar(Name)); +``` + +Call sites that need the cast: `CreateEvent`, `OpenEvent`, +`CreateWaitableTimer`, `OpenWaitableTimer`, `OpenSemaphore`, `CreateMutex`, +`OpenMutex`. Other JCL units likely need the same treatment -- patch +reactively. + +The `CreateMutex` site additionally needs a namespace switch -- the BOOL +cast alone is not sufficient. Per `local-delphi-12-patches.patch`: + +```pascal +// - FHandle := JclWin32.CreateMutex(SecAttr, InitialOwner, PChar(Name)); +// + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateMutex( +// + SecAttr, BOOL(InitialOwner), PChar(Name)); +``` + +The reason is that `JclWin32.CreateMutex` is a `JclWin32`-namespaced +re-declaration that Delphi 12 resolves ambiguously against +`Winapi.Windows.CreateMutex`, producing a "Cardinal vs LongBool" type +mismatch that the BOOL cast can't fix. Routing the call through +`Winapi.Windows.CreateMutex` directly bypasses the ambiguity. The +`{$IFDEF HAS_UNITSCOPE}` guard preserves compatibility with older Delphis +that still use the un-namespaced `Windows` unit. + +### 3.5 JVCL `OldCreateOrder` removed in Delphi 11 + +`developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas` (line ~126): + +```pascal +// - if OldCreateOrder then +// - DoCreate; +// + {$IF Defined(VER350) or Defined(VER360)} +// + DoCreate; +// + {$ELSE} +// + if OldCreateOrder then DoCreate; +// + {$IFEND} +``` + +`TCustomForm.OldCreateOrder` was removed in Delphi 11; modern Delphi behaves +as if it were always `True`. + +### 3.6 mbcolor: teach it about VER350/VER360 + +`developer/src/ext/mbcolor/mxs.inc` + +Add two new IFDEF blocks mirroring the existing VER340 block: + +```pascal +{$ifdef VER350} // Delphi 11 +{$define DELPHI_5_UP} +{$define DELPHI_6_UP} +{$define DELPHI_7_UP} +{$define DELPHI_8_UP} +{$define DELPHI_9_UP} +{$define DELPHI_10_UP} +{$endif} + +{$ifdef VER360} // Delphi 12 +{$define DELPHI_5_UP} +{$define DELPHI_6_UP} +{$define DELPHI_7_UP} +{$define DELPHI_8_UP} +{$define DELPHI_9_UP} +{$define DELPHI_10_UP} +{$endif} +``` + +Without this, mbcolor's `{$IFDEF DELPHI_6_UP}, Variants{$ENDIF}` silently +drops `Variants` from the `uses` clause, causing `E2003 Undeclared +identifier: 'null'` at `HTMLColors.pas:290`. + +### 3.7 devtools: map VER350 / VER360 to Studio version + +`common/windows/delphi/tools/devtools/SourceRootPath.pas` + +The constant is named `DelphiMajorVersion` (not `DelphiVersion`). The existing +baseline (not patched by us) maps VER310->'18.0', VER320->'19.0', +VER330->'20.0', VER340->'21.0'. The patch adds the two newer compilers: + +Before (baseline, abridged): + +```pascal +{$IFDEF VER310} const DelphiMajorVersion = '18.0'; +{$ELSE}{$IFDEF VER320} const DelphiMajorVersion = '19.0'; +{$ELSE}{$IFDEF VER330} const DelphiMajorVersion = '20.0'; +{$ELSE}{$IFDEF VER340} const DelphiMajorVersion = '21.0'; +{$ELSE} +ERROR: must define Delphi version +{$ENDIF}{$ENDIF}{$ENDIF}{$ENDIF} +``` + +After (with VER350 / VER360 inserted before the `ERROR:` fallback): + +```pascal +{$IFDEF VER340} const DelphiMajorVersion = '21.0'; +{$ELSE}{$IFDEF VER350} const DelphiMajorVersion = '22.0'; // 11 (NEW) +{$ELSE}{$IFDEF VER360} const DelphiMajorVersion = '23.0'; // 12 (NEW) +{$ELSE} +ERROR: must define Delphi version +{$ENDIF}{$ENDIF}{$ENDIF} +``` + +Note that the VER340->'21.0' baseline reflects upstream's choice to bump the +Studio dir per CE-license rev, not the more conventional 10.3->'20.0' +mapping. Don't "fix" the existing rows -- only append the two new ones. + +Without one of these defined, the `$ELSE` branch hits the `ERROR: must +define Delphi version` literal which manifests as `E2029 Declaration +expected`. + +### 3.8 Indy UTF-8 URL tripwire + +`common/windows/delphi/web/Keyman.System.HttpServer.Base.pas` + +Wrap the `ERROR! Check if this is still needed with Delphi update` tripwire +in nested `{$IFNDEF VER340}{$IFNDEF VER350}{$IFNDEF VER360}`. The Indy URL +encoding workaround is still required on 12; just extending the guard +preserves the workaround. + +### 3.9 FixedTrackbar tripwire + +`common/windows/delphi/components/FixedTrackbar.pas` + +Same pattern -- extend the inner `{$MESSAGE ERROR}` guard with +`{$IFNDEF VER350}{$IFNDEF VER360}`. + +### 3.10 CleartypeDrawCharacter EnumFontFamiliesEx return type + +`common/windows/delphi/general/CleartypeDrawCharacter.pas` (line ~594) + +`Winapi.Windows.EnumFontFamiliesEx` returns `Integer` starting with Delphi +10.4. The existing guard only covered VER340; extend it: + +```pascal +// - {$IFDEF VER340} +// + {$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} + if EnumFontFamiliesEx(...) <> 0 then +// - {$ELSE} +// + {$ELSE} + if EnumFontFamiliesEx(...) then +// - {$ENDIF} +// + {$IFEND} +``` + +### 3.11 JsonUtil ToChars signature change + +`common/windows/delphi/general/JsonUtil.pas` (line ~55) + +Delphi 11 added a required `Options: TJSONOutputOptions` parameter to +`System.JSON.TJSONAncestor.ToChars`: + +```pascal +{$IF Defined(VER350) or Defined(VER360)} + obj.ToChars(builder, []); +{$ELSE} + obj.ToChars(builder); +{$IFEND} +``` + +### 3.12 Generated `.res` files + +The local working tree will also show binary diffs to many `.res` files +under `developer/src/`, `windows/src/desktop/`, `windows/src/engine/`. These +are produced by `rc.exe` during preflight (icons / manifest / version) -- +they are *not* patches but should be excluded from any PR. See section 5. + +## 4. Build order + +Keyman's repo has several cross-project dependencies that are **not** +expressed in `.dproj` or `.groupproj` files -- they're implicit because the +producer is a Delphi tool that emits code consumed by another Delphi +project, or because a Delphi `.res` embeds the output of another Delphi +`.exe`. Doing things in the wrong order produces fatal `dcc32` errors with +non-obvious messages. + +### CLI-buildable on Delphi 12 CE + +These do not need the IDE: + +* **Keyman Core** -- `./core/build.sh` (Rust / C++ via meson+ninja) +* **kmcmplib** -- `./developer/src/kmcmplib/build.sh build` (Rust / C++) +* **TypeScript modules** -- `kmc`, `kmc-kmn`, `kmc-ldml`, `kmc-package`, + `kmc-keyboard-info`, `kmc-analyze`, `kmc-copy`, `kmc-generate`, + `kmc-model`, `kmc-model-info` (each has its own `build.sh`) +* **KeymanWeb** -- `./web/build.sh` (TypeScript / Emscripten) +* **C++ engine pieces** -- `windows/src/engine/{keyman32,kmtip,keymanhp, + kmrefresh,mcompile,testhost}` (msbuild) + +Build these via the normal `build.sh` chain. They have no Delphi codegen +dependency. + +> [!CAUTION] +> `build.sh` cascades dependencies via `--builder-dep-parent`, so even a +> "non-Delphi" CLI build can pull a Delphi tool in -- which then fails on +> CE with `This version of the product does not support command line +> compiling`. Build the tool by hand in the IDE first, or pass `--no-deps`. + +### Delphi-IDE-only on CE + +* **All of `windows/src/engine/`** -- `keyman.dproj`, `kmcomapi.dproj`, + `insthelper.dproj`, `tsysinfo.dproj`, `tsysinfox64.dproj` + (`engine.groupproj`) +* **All of `windows/src/desktop/`** -- kmshell, kmbrowserhost, kmconfig, + insthelp, setup (`desktop.groupproj`) +* **All of `developer/src/`** -- TIKE, kmconvert, setup + (`developer.groupproj`) +* **All Delphi tools in `common/windows/delphi/tools/`** -- devtools, + build_standards_data, certificates, sentrytool, verify_signatures + +### The cross-project dependency graph + +The non-obvious dependencies, in the order they must be satisfied: + +#### (a) devtools.dproj BEFORE keyman.dproj / setup.dproj / kmshell.dproj + +`keyman.dpr` (engine) and `kmshell.dpr` (desktop) both depend on +`MessageIdentifierConsts.pas`, a ~1500-line file generated from +`windows/src/desktop/kmshell/xml/strings.xml` via +`devtools.exe -buildmessageconstants`. The file is `.gitignored` -- it does +not exist in a fresh checkout. Failure mode: + +``` +F1026 File not found: '...\windows\src\global\delphi\cust\MessageIdentifierConsts.pas' +``` + +`setup.dpr` depends on ~32 `Keyman.Setup.System.Locale..pas` files +generated from `windows/src/desktop/setup/locale/*.xml` via +`devtools.exe -buildsetupstrings`. + +#### (b) build_standards_data.dproj BEFORE TIKE.dproj + +`Keyman.System.LanguageCodeUtils` and friends reference five generated +BCP-47 registry units in `common/windows/delphi/standards/`. The biggest is +`Keyman.System.Standards.LangTagsRegistry.pas` (~2.4 MB). All five are +`.gitignored`. Failure mode: + +``` +F1026 File not found: '...\Keyman.System.Standards.BCP47SubtagRegistry.pas' +``` + +#### (c) tsysinfox64.dproj BEFORE tsysinfo.dproj + +`tsysinfo.dproj` is Win32 but embeds the Win64 `tsysinfox64.exe` as a +binary resource (`tsysinfo_x64.res`) so it can spawn the 64-bit helper +process at runtime. The default Build All order in `engine.groupproj` is +**keyman, kmcomapi, tsysinfo, tsysinfox64, insthelper** -- so tsysinfo is +attempted before the .exe it embeds exists. Failure mode: + +``` +F1026 File not found: 'tsysinfo_x64.res' +``` + +**Right order:** + +1. Build `tsysinfox64.dproj` in the IDE (Win64 / Debug). +2. Copy: `Copy-Item windows/src/engine/tsysinfox64/bin/Win64/Debug/tsysinfox64.exe windows/src/engine/tsysinfo/tsysinfox64.bin` +3. From `windows/src/engine/tsysinfo/`: `rc /nologo tsysinfo_x64.rc` +4. Now build `tsysinfo.dproj`. + +#### (d) kmcmplib (CLI build) BEFORE TIKE.dproj at runtime + +TIKE links to `kmcmplib-19.dll` (the .kmn keyboard compiler). It builds via +`./developer/src/kmcmplib/build.sh build` -- no IDE needed. TIKE compiles +fine without it, but throws "kmcmplib-19.dll not found" at runtime on any +compile action. + +#### (e) keyman.dproj BEFORE kmshell.dproj runtime + +`kmshell.exe` is the Desktop UI; the actual engine work happens in +`keyman.exe`, which kmshell spawns via the COM `IKeymanController` CLSID. +kmshell compiles without keyman.exe -- the failure is at runtime: the +dev kmshell shows "Keyman failed to start" when enabling a keyboard. + +#### (f) regsvr32 kmcomapi.dll BEFORE running kmshell + +`kmcomapi.dll` exposes the COM objects kmshell instantiates at startup. +Without registration, `CoCreateInstance` returns `REGDB_E_CLASSNOTREG` +(`Class not registered`) and kmshell dies before its main form appears. +`windows/src/engine/build.sh install` handles this automatically (section 5.3). + +### The canonical build order + +For a clean checkout, this is the order that actually works: + +1. CLI build everything CLI-buildable: `./core/build.sh build`, + `./developer/src/kmcmplib/build.sh build`, TypeScript modules, + KeymanWeb. The C++ engine pieces can wait. +2. Build devtools: `cd common/windows/delphi/tools/devtools && ./build.sh build` + (prompted to build `devtools.dproj` in IDE; press Enter when done). +3. Build build_standards_data: `cd common/windows/delphi/tools/build_standards_data && ./build.sh build` + (prompted to build `build_standards_data.dproj` in IDE; after Enter, the script + automatically runs all five codegen invocations). +4. Build engine components: `cd windows/src/engine && ./build.sh build` + (prompted for each Delphi project in dependency order; `tsysinfo.dproj` is + prompted after `tsysinfox64.dproj` because `build.sh` handles the dependency). +5. Build desktop components: `cd windows/src/desktop && ./build.sh build` + (prompted for each desktop Delphi project). +6. Build developer components: `cd developer/src && ./build.sh build` + (prompted for TIKE, kmconvert, and developer setup). +7. Overlay dev builds (elevated Git Bash): `cd windows/src/engine && ./build.sh install` + then `cd windows/src/desktop && ./build.sh install` (section 5.3). + +## 5. Shell-based CE workflow + +With `KEYMAN_DELPHI_CE=1` set (section 3.1), the existing `build.sh` scripts +handle the CE constraint interactively. At each step that would normally invoke +`msbuild` through Delphi, the script pauses and prints: + +``` +Delphi CE: CLI compilation is not available. +Please build in the Delphi IDE now, then press Enter to continue (or Ctrl-C to abort). +``` + +Pre-build steps (rc compilation, manifest generation) and post-build steps +(binary copying, codegen) run automatically around your IDE builds. + +### 5.1 Pre-build resource compilation + +`build_version.res()` and `build_manifest.res()` use `rc.exe` from the Visual +Studio environment -- they work on CE without modification. They run inside each +project's `build.sh build` before the IDE prompt appears. + +> [!IMPORTANT] +> After building in the IDE following a resource change, use **right-click → +> Clean, then Build** in Delphi. An incremental Build silently embeds the stale +> cached `.res`. + +### 5.2 Codegen (devtools.exe, build_standards_data.exe) + +Build the tools first via their `build.sh` scripts, which prompt for the IDE build +then run codegen automatically: + +```bash +# Git Bash (KEYMAN_DELPHI_CE=1 and KEYMAN_DELPHI_VERSION=23.0 must be exported) + +# Build devtools: prompted to build devtools.dproj in IDE, then exits +cd "$KEYMAN_ROOT/common/windows/delphi/tools/devtools" +./build.sh build + +# Build build_standards_data: prompted for IDE build, then auto-runs all five +# build_standards_data.exe invocations to generate the registry .pas files +cd "$KEYMAN_ROOT/common/windows/delphi/tools/build_standards_data" +./build.sh build +``` + +The `-buildmessageconstants`, `-buildsetupstrings`, and `-buildlocaleindex` codegen +steps run automatically when you build their dependent projects +(`windows/src/global/delphi`, `windows/src/desktop/setup`, `windows/src/desktop/kmshell`). + +All generated `.pas` files are `.gitignored` and must be regenerated after `git clean -fdx`. + +### 5.3 Overlay dev builds + +From an **elevated** Git Bash (right-click Git Bash → Run as administrator): + +```bash +cd "$KEYMAN_ROOT/windows/src/engine" +./build.sh install + +cd "$KEYMAN_ROOT/windows/src/desktop" +./build.sh install +``` + +`windows/src/engine/kmcomapi/build.sh install` re-registers `kmcomapi.dll` via +`regsvr32` automatically. + +After overlay, launch Keyman via the Start Menu (or the installed `kmshell.exe` +path) -- not from the dev tree -- so `TKeymanPaths.KeymanDesktopInstallPath()` +finds the support files. + +> [!NOTE] +> An alternative workflow -- keep `uiAccess="true"` in `manifest.in` (i.e. +> skip the section 3.2 patch) and skip overlaying `keyman.exe` -- was +> tried and abandoned. With `uiAccess="true"`, the unsigned dev +> `keyman.exe` is refused by Windows (`error=8235`) and the engine never +> starts. The patch + overlay path documented here is the only flow that +> works without a signing cert. + +## 6. Running Keyman from the dev tree + +Because of `TKeymanPaths.KeymanDesktopInstallPath()`, the dev workflow is: + +1. Install Keyman 19 official from keyman.com (once). +2. Build dev binaries via `build.sh build` with `KEYMAN_DELPHI_CE=1` (per section 4). +3. From an elevated Git Bash: `cd windows/src/engine && ./build.sh install`, then + `cd windows/src/desktop && ./build.sh install` (section 5.3). +4. Launch Keyman from the Start Menu. + +If launching keyman.exe (the engine) directly from the dev `bin\` folder +returns `Could not find keyman.exe (error=8235)`, check that section 3.2's +uiAccess patch was applied and that the project was Clean + Built (not just +Built) after patching `manifest.in`. + +## 7. Debugging + +### 7.1 Run With Host Application + +For DLLs (kmcomapi, keymanhp) and projects that don't have a main entry +point: in Delphi, **Run > Parameters > Host Application** = +`C:\Program Files (x86)\Keyman\Keyman Desktop\kmshell.exe` (or +`...keyman.exe`). Set breakpoints in the DLL source, then Run (F9). + +### 7.2 Attach to Process + +For long-running processes (an already-launched kmshell, the TSF text +service): **Run > Attach to Process**, pick the process, then set +breakpoints. The dev tree's `.dproj` projects emit `.dcu` files with full +debug info into `bin\Win32\Debug\` and `bin\Win64\Debug\`, so as long as +you've overlaid the binary you'll get proper symbols. + +### 7.3 What you get -- and don't get -- from a vanilla install + +The installed Keyman 19 from keyman.com ships **stripped** PDBs at best; +practically, you'll see only export-table symbols when attaching. The +overlay workflow replaces the installed binaries with dev builds whose +debug info lives next to the source -- after overlay you can step through +Keyman source in the IDE. Without overlay, the IDE will load the source +file when you breakpoint but the line numbers will not match the running +binary. + +For C++ pieces (keyman32, kmtip, mcompile), use Visual Studio's Attach to +Process and load the `.pdb` from `windows/bin/`. + +## 8. Troubleshooting + +### `This version of the product does not support command line compiling` + +You're hitting the CE block on `dcc32`. Don't try to drive Delphi from +`build.sh`; build the relevant `.dproj` in the IDE by hand. If `build.sh` +pulled Delphi in as a transitive dep, pass `--no-deps` or build the +upstream Delphi project first. + +### `F1026 File not found: 'MessageIdentifierConsts.pas'` + +Codegen wasn't run. Build `devtools.dproj` in the IDE, then run section 5.2 +step (a). + +### `F1026 File not found: 'Keyman.Setup.System.Locale..pas'` (one of ~32) + +Codegen wasn't run. Build `devtools.dproj` then run section 5.2 step (b). +Trailing backslashes on the two args matter. + +### `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` + +`build_standards_data.exe` wasn't run. Build the `.dproj` then run section +5.2 (BSD block). + +### `F1026 File not found: 'tsysinfo_x64.res'` + +Group build order issue. Build tsysinfox64 first, copy the exe to +tsysinfo's source dir as `tsysinfox64.bin`, run `rc /nologo tsysinfo_x64.rc`, +then build tsysinfo. See section 4 (c). + +### `F1026 File not found: 'version.res'` or `'manifest.res'` + +Pre-build resource compilation didn't run. Re-run `build.sh build` for the +project (which calls `build_version.res` / `build_manifest.res` before the IDE +prompt), or invoke `rc.exe` manually from the project directory. + +### `F1026 File not found: 'jvcl.inc'` / `'jedi/jedi.inc'` / `'jcl.inc'` + +Library Search Paths not in the registry. See section 2.7. Close and +reopen Delphi after editing the registry; the IDE caches the value at +startup. + +### `E2010 Incompatible types: 'Cardinal' and 'Boolean'` (JCL) + +JCL Boolean -> BOOL casts missing. See section 3.4. + +### `E2003 Undeclared identifier: 'OldCreateOrder'` (JVCL) + +JvComponent.pas not patched. See section 3.5. + +### `E2003 Undeclared identifier: 'null'` (HTMLColors) + +mbcolor's `mxs.inc` not patched for VER350/VER360 -- the `Variants` unit +was silently dropped from the `uses` clause. See section 3.6. + +### `E2029 Declaration expected` near SourceRootPath.pas + +devtools/SourceRootPath.pas hit the `{$ELSE} {$MESSAGE ERROR}` fallback. +See section 3.7. + +### `E2012 Type of expression must be BOOLEAN` near EnumFontFamiliesEx + +CleartypeDrawCharacter.pas guard not extended for VER350/VER360. See +section 3.10. + +### `F2613 Unit 'JvComponentBase' not found` + +JVCL `run/` (or `design/`) is missing from Library Search Path. See +section 2.7. + +### Delphi pops up an "Unsupported CEF version" dialog + +The CEF4Delphi_Binary checkout isn't on the tag in `CEF_VERSION.md`. See +section 2.5. Don't ignore the dialog -- the CEF4Delphi Pascal binding and +the libcef binary must match exactly. + +### `SKApplicationTitle has had a fatal error...` on kmshell launch + +kmshell can't find its strings.xml / locale files because +`KeymanDesktopInstallPath()` is hardcoded to `Program Files`, and Keyman +19 official isn't installed (or its install support files were removed by +an aborted dev experiment). Install Keyman 19 official from keyman.com. + +### `Class not registered ClassID {CF46549D-...}` on kmshell launch + +`kmcomapi.dll` isn't registered. From an elevated Git Bash: + +```bash +cd "$KEYMAN_ROOT/windows/src/engine/kmcomapi" +./build.sh install +``` + +This re-registers the DLL automatically. If you want to run `regsvr32` directly: + +```powershell +& regsvr32 /s 'C:\Program Files (x86)\Common Files\Keyman\Keyman Engine\kmcomapi.dll' +``` + +### `Could not find keyman.exe (error=8235)` + +Windows blocked launch of an unsigned `uiAccess="true"` binary. Patch +`windows/src/engine/keyman/manifest.in` per section 3.2, Clean + rebuild +`keyman.dproj`, then re-run `windows/src/engine/build.sh install` elevated. + +--- + +## Upstream candidates + +If you'd like to PR some of these changes back upstream, the patches split +into two groups based on whether they touch Keyman-owned code or vendored +third-party code. + +**Upstreamable (Keyman-owned, additive, CI-safe with `KEYMAN_DELPHI_VERSION` +unset):** + +* `resources/build/win/configure_environment.inc.sh` + `delphi_environment.inc.sh` + -- `KEYMAN_DELPHI_VERSION` env-var override (default preserves 10.3 behavior). +* `resources/build/win/environment.inc.sh` -- `KEYMAN_DELPHI_CE=1` interactive + mode for `delphi_msbuild` (no-op when unset). +* `common/windows/delphi/tools/devtools/SourceRootPath.pas` -- VER350/VER360 + cases for `DelphiMajorVersion`. +* `common/windows/delphi/web/Keyman.System.HttpServer.Base.pas` -- extend + VER tripwire to accept VER340/VER350/VER360. +* `common/windows/delphi/components/FixedTrackbar.pas` -- same tripwire + pattern. +* `common/windows/delphi/general/CleartypeDrawCharacter.pas` -- integer-return + comparison for `EnumFontFamiliesEx` on VER340+. +* `common/windows/delphi/general/JsonUtil.pas` -- two-arg `ToChars` on + VER350/VER360 (Delphi 11+ RTL change). +* `windows/src/engine/engine.groupproj` -- drop the broken + `inst\insthelper\insthelper.dproj` reference. + +These could ship as a single "Support compiling under Delphi 11/12" PR +without conflicting with the broader Delphi-removal effort +([keymanapp/keyman#4599](https://github.com/keymanapp/keyman/issues/4599)). + +**Local-only (vendored third-party):** + +* `developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas` -- JCL Boolean + -> BOOL casts. The right fix is to refresh the bundled JCL from upstream + (project-jedi.org) when JCL releases a Delphi 12-compatible version. +* `developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas` -- JVCL + OldCreateOrder removal. Same -- pull from upstream JVCL when available. +* `developer/src/ext/mbcolor/mxs.inc` -- third-party mbcolor library. + Upstream maintenance status unclear. + +These should *not* be PR'd upstream-Keyman as-is; the bundled third-party +copies should be refreshed from their respective maintainers instead. + +## Reverting before a PR + +Before opening a PR, revert all section 3 patches and remove the section +2.7 registry entries. The simplest check: + +```bash +git status # should show only intentional changes +git diff -- '*.res' # should be empty (preflight .res are local-only) +``` + +If `git diff` shows any of the files mentioned in section 3, revert them +with `git checkout -- ` before pushing. + +Restore the original Library Search Path values: + +```powershell +reg import C:\Projects\keyman\keyman\delphi-library-paths.backup.reg +``` diff --git a/docs/build/windows.md b/docs/build/windows.md index edc756340ff..a595a29f633 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -257,6 +257,11 @@ of appropriate node versions during builds. for a short time. (We are actively working to remove Delphi dependencies given the licensing issues with using it.) + * If you only have access to **Delphi 12 Community Edition**, see + [windows-d12.md](windows-d12.md) for an IDE-based workflow that papers over + the missing command-line compiler. The patches and helper scripts described + there are local-only and not required for the standard 10.3 / Pro flow. + Start Delphi IDE once after installation as it will create various environment files and take you through required registration. diff --git a/resources/build/win/configure_environment.inc.sh b/resources/build/win/configure_environment.inc.sh index d0bce72a812..1d274c67be1 100644 --- a/resources/build/win/configure_environment.inc.sh +++ b/resources/build/win/configure_environment.inc.sh @@ -65,9 +65,11 @@ _build_vs_environment() { _locate_rsvars() { # - # Delphi Compiler Configuration - Delphi 10.3.2 + # Delphi Compiler Configuration - defaults to Delphi 10.3 (BDS 20.0). + # Override via the KEYMAN_DELPHI_VERSION environment variable to build with + # a newer Delphi installation (e.g., KEYMAN_DELPHI_VERSION=23.0 for Delphi 12). # - DELPHI_VERSION=20.0 + DELPHI_VERSION="${KEYMAN_DELPHI_VERSION:-20.0}" DCC32PATH="$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$DELPHI_VERSION\\bin")" RSVars_path="$DCC32PATH/rsvars.bat" } diff --git a/resources/build/win/delphi_environment.inc.sh b/resources/build/win/delphi_environment.inc.sh index 0465bcb6930..a68b08421c4 100644 --- a/resources/build/win/delphi_environment.inc.sh +++ b/resources/build/win/delphi_environment.inc.sh @@ -13,7 +13,10 @@ DELPHIWARNINGS=(-W-MESSAGE_DIRECTIVE -W-IMPLICIT_STRING_CAST -W-IMPLICIT_STRING_CAST_LOSS -W-EXPLICIT_STRING_CAST -W-EXPLICIT_STRING_CAST_LOSS -W-CVT_WCHAR_TO_ACHAR -W-CVT_NARROWING_STRING_LOST -W-CVT_ACHAR_TO_WCHAR -W-CVT_WIDENING_STRING_LOST -W-UNICODE_TO_LOCALE -W-LOCALE_TO_UNICODE -W-IMPLICIT_VARIANTS -W-IMPLICIT_INTEGER_CAST_LOSS -W-IMPLICIT_CONVERSION_LOSS -W-COMBINING_SIGNED_UNSIGNED64 -W-COMBINING_SIGNED_UNSIGNED64) # !ENDIF -DELPHI_VERSION=20.0 +# DELPHI_VERSION defaults to Delphi 10.3 (BDS 20.0). Override via the +# KEYMAN_DELPHI_VERSION environment variable to build with a newer Delphi +# installation (e.g., KEYMAN_DELPHI_VERSION=23.0 for Delphi 12). +DELPHI_VERSION="${KEYMAN_DELPHI_VERSION:-20.0}" DCC32PATH="$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$DELPHI_VERSION\\bin")" source "$KEYMAN_ROOT/resources/build/win/delphi_environment_generated.inc.sh" diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 9408c6660ef..eed75080f28 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -100,6 +100,15 @@ tds2dbg() { } delphi_msbuild() { + # When KEYMAN_DELPHI_CE=1 the Delphi Community Edition blocks CLI + # compilation. Pause and prompt the developer to build in the IDE. + if [[ "${KEYMAN_DELPHI_CE:-}" == "1" ]]; then + local project="${1:-}" + builder_echo warning "Delphi CE: CLI compilation is not available." + builder_echo warning "Please build ${project} in the Delphi IDE now, then press Enter to continue (or Ctrl-C to abort)." + read -r _ + return 0 + fi run_in_delphi_env msbuild.exe "$@" "$DELPHI_MSBUILD_FLAG_DEBUG" } diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 6ec2fbcebff..7e9ea36a0e4 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -2323,7 +2323,8 @@ builder_describe_platform() { # Detect delphi compiler (see also delphi_environment.inc.sh) if builder_is_windows; then local ProgramFilesx86="$(cygpath -w -F 42)" - if [[ -x "$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\20.0\\bin\\dcc32.exe")" ]]; then + local _delphi_version="${KEYMAN_DELPHI_VERSION:-20.0}" + if [[ -x "$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$_delphi_version\\bin\\dcc32.exe")" ]]; then builder_installed_tools+=(delphi) fi fi diff --git a/windows/src/engine/engine.groupproj b/windows/src/engine/engine.groupproj index bb2548b6103..150ea34d9cf 100644 --- a/windows/src/engine/engine.groupproj +++ b/windows/src/engine/engine.groupproj @@ -15,9 +15,6 @@ - - - Default.Personality.12 @@ -62,23 +59,14 @@ - - - - - - - - - - + - + - +