diff --git a/.gitignore b/.gitignore
index 7b872195880..dd7d41d3709 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
diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas
index 50cb444a55f..9e900c2c0d9 100644
--- a/common/windows/delphi/components/FixedTrackbar.pas
+++ b/common/windows/delphi/components/FixedTrackbar.pas
@@ -64,16 +64,22 @@ procedure TTntFixedDrawGrid.WMEraseBkgnd(var Message: TMessage);
Tested on VER320 (10.2)
Tested on VER330 (10.3) - 29 Oct 2019 - mcdurdin
+ Not yet fully verified against Vcl.Grids.pas in VER350 (11) or VER360 (12);
+ IFNDEF arms added to unblock compilation, override behaviour retained as-is.
}
{$IFNDEF VER340}
{$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/DevIncludePaths.pas b/common/windows/delphi/tools/devtools/DevIncludePaths.pas
index 509045d4d17..d6df62621df 100644
--- a/common/windows/delphi/tools/devtools/DevIncludePaths.pas
+++ b/common/windows/delphi/tools/devtools/DevIncludePaths.pas
@@ -144,6 +144,7 @@ class function TIncludePaths.AddPathToProjectXML(const ProjectXMLFileName, Path:
doc: IXMLDocument;
sn, node: IXMLNode;
IncludePath: string;
+ Condition: string;
I: Integer;
begin
if not FileExists(ProjectXMLFileName) then
@@ -159,16 +160,20 @@ class function TIncludePaths.AddPathToProjectXML(const ProjectXMLFileName, Path:
for I := 0 to node.ChildNodes.Count - 1 do
begin
sn := node.ChildNodes[I];
+ // Delphi 12 EnvOptions.proj emits an empty without a
+ // Condition attribute; convert defensively via VarToStrDef so a Null or
+ // Empty variant returns '' instead of raising EVariantTypeCastError in Pos().
+ Condition := VarToStrDef(sn.Attributes['Condition'], '');
if (sn.NodeName = 'PropertyGroup') and
- not VarIsNull(sn.Attributes['Condition']) and
- ((Pos('Win32', sn.Attributes['Condition']) > 0) or
- (Pos('Win64', sn.Attributes['Condition']) > 0)) then
+ ((Pos('''Win32''', Condition) > 0) or (Pos('''Win64''', Condition) > 0)) then
begin
- IncludePath := sn.ChildNodes['DelphiBrowsingPath'].NodeValue;
+ // Guard against empty child nodes (e.g. ) whose
+ // NodeValue is Null on Delphi 12 and can't coerce to a string directly.
+ IncludePath := VarToStrDef(sn.ChildNodes['DelphiBrowsingPath'].NodeValue, '');
if AddPathToIncludePath(IncludePath, Path) then
sn.ChildNodes['DelphiBrowsingPath'].nodeValue := IncludePath;
- IncludePath := sn.ChildNodes['DelphiLibraryPath'].NodeValue;
+ IncludePath := VarToStrDef(sn.ChildNodes['DelphiLibraryPath'].NodeValue, '');
if AddPathToIncludePath(IncludePath, Path) then
sn.ChildNodes['DelphiLibraryPath'].nodeValue := IncludePath;
end;
@@ -256,6 +261,7 @@ class function TIncludePaths.Reset: Boolean;
doc: IXMLDocument;
node: IXMLNode;
ProjectFileName: string;
+ Condition: string;
I: Integer;
sn: IXMLNode;
begin
@@ -296,10 +302,11 @@ class function TIncludePaths.Reset: Boolean;
for I := 0 to node.ChildNodes.Count - 1 do
begin
sn := node.ChildNodes[I];
+ // See AddPathToProjectXML: guard against Delphi 12's empty
+ // where Attributes['Condition'] returns a Null variant.
+ Condition := VarToStrDef(sn.Attributes['Condition'], '');
if (sn.NodeName = 'PropertyGroup') and
- not VarIsNull(sn.Attributes['Condition']) and
- ((Pos('Win32', sn.Attributes['Condition']) > 0) or
- (Pos('Win64', sn.Attributes['Condition']) > 0)) then
+ ((Pos('''Win32''', Condition) > 0) or (Pos('''Win64''', Condition) > 0)) then
begin
sn.ChildNodes['DelphiBrowsingPath'].NodeValue := SDefault_DelphiBrowsingPath;
sn.ChildNodes['DelphiLibraryPath'].NodeValue := SDefault_DelphiSearchPath;
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..bec398bcbca 100644
--- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas
+++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas
@@ -44,9 +44,19 @@ function CrackUTF8ZeroExtendedString(CommandType: THTTPCommandType; const p: str
end;
// Indy's UTF8 handling of URLs is *completely* broken.
- // We may need to check this with updated versions of Delphi
+ // We may need to check this with updated versions of Delphi.
+ // VER340/VER350/VER360 added to the IFNDEF chain to unblock
+ // Delphi 10.4/11/12 compilation; whether Indy's URL handling was
+ // fixed in those versions has not been re-verified — the
+ // workaround stays applied conservatively.
{$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/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-delphi-ce.md b/docs/build/windows-delphi-ce.md
new file mode 100644
index 00000000000..aaaa6808a1a
--- /dev/null
+++ b/docs/build/windows-delphi-ce.md
@@ -0,0 +1,359 @@
+# Build Keyman on Windows with Delphi Community Edition
+
+Delphi CE (11 and 12) blocks CLI `dcc32`, so `build.sh` cannot drive
+Delphi builds end-to-end. Setting `KEYMAN_DELPHI_CE=1` switches
+`delphi_msbuild` to interactive mode: at each Delphi step the script
+pauses and prompts for an IDE build. The surrounding pre/post-build
+work (`rc.exe`, manifest generation, codegen, binary copies) still
+runs automatically.
+
+The body is written against Delphi 12 Athens CE (currently the only
+free tier). Delphi 11 CE works the same way; only the `Studio\22.0\`
+path differs.
+
+This doc is a delta on top of [windows.md](windows.md) — read that
+first for repository layout, base dependencies, and the standard
+build. Delphi 11/12 also needs a small set of source-compat patches
+under `common/windows/delphi/`; those are documented in
+[windows.md § Delphi requirements](windows.md#windows-platform-dependencies)
+and are separate from the CE-specific work below.
+
+## 1. Prerequisites (delta from windows.md)
+
+* **Delphi 12 Athens Community Edition** from
+ https://www.embarcadero.com/products/delphi/starter/free-download.
+ Installs to `C:\Program Files (x86)\Embarcadero\Studio\23.0\`. During
+ install, select **DUnit Unit Testing Frameworks** (Keyman test
+ projects require it). Launch the IDE once after install so the
+ per-user `BDS\23.0` registry hive gets populated.
+* **Keyman 19 (official release)** from https://keyman.com/windows is
+ strongly recommended — it populates the Keyman install-path registry
+ key that kmshell reads via `TKeymanPaths.KeymanDesktopInstallPath()`,
+ which simplifies the debugging setup and gives you a working system
+ to overlay dev binaries onto. It's possible to debug individual
+ components without an official install, but each component then
+ needs its support files located manually.
+* **Test signing certificates** — one-time setup. Several `build.sh`
+ scripts run `signtool.exe` against test certificates at
+ `common/windows/delphi/tools/certificates/`. If the `.pfx` files
+ don't exist, the `wrap-signcode` step fails with
+ `SignTool Error: File not found: ...keymantest-sha1.pfx`. Generate
+ them once via:
+
+ ```bash
+ ./common/windows/delphi/tools/certificates/build.sh certificates
+ ```
+
+ This runs `makecert` + `pvk2pfx` from the Windows SDK and installs
+ two Keyman test-CA root certificates into your current-user cert
+ store (via `certutil -user -addstore Root`). To clean up later:
+ `certutil -user -delstore Root "Keyman Test CA"` and the SHA1 variant.
+
+## 2. Environment variables
+
+Set both:
+
+```bat
+SETX KEYMAN_DELPHI_VERSION 23.0
+SETX KEYMAN_DELPHI_CE 1
+```
+
+* `KEYMAN_DELPHI_VERSION` (from #16043) tells `build.sh` and the
+ builder platform probe to look at `Studio\23.0\` instead of the
+ default `Studio\20.0\`. Without it, `win,delphi`-gated targets are
+ silently skipped on a Delphi-12-only machine.
+* `KEYMAN_DELPHI_CE=1` (this PR) makes `delphi_msbuild` prompt at
+ each Delphi step:
+
+ ```
+ Delphi CE: CLI compilation is not available.
+ Please build in the Delphi IDE now, then press
+ Enter to continue (or Ctrl-C to abort).
+ ```
+
+`SETX` is persistent but does not affect the current shell — open a
+fresh shell to pick the values up.
+
+> [!CAUTION]
+> **Verify these variables are set in every terminal you build from.**
+> If `KEYMAN_DELPHI_CE` is unset, `delphi_msbuild` falls through to
+> `msbuild.exe` which — on Delphi CE — reports `Build succeeded` with
+> a small `Time Elapsed` line but produces **no output**. Downstream
+> `cp` / `mv` / `sentrytool_delphiprep` / `mt.exe` steps then fail
+> with confusing "file not found" errors on paths where Delphi never
+> actually wrote anything. If you see a suspiciously fast Delphi
+> build followed by a missing-file error, check this first:
+>
+> ```bash
+> echo "KEYMAN_DELPHI_CE='$KEYMAN_DELPHI_CE' KEYMAN_DELPHI_VERSION='$KEYMAN_DELPHI_VERSION'"
+> ```
+>
+> Both must be set. If either is empty, `export` them in the current
+> shell before rerunning any `build.sh`.
+
+## 3. Delphi IDE Library Search Paths
+
+`build.sh` passes `-U/-I/-R` flags to `dcc32` that the IDE never sees.
+Without them, opening any Keyman `.dproj` fails with `F1026 File not
+found: jvcl.inc` / `jedi.inc` / `jcl.inc`. Register the paths once in
+the per-user registry. The first six paths mirror `DELPHIINCLUDES` in
+[`resources/build/win/delphi_flags.inc.sh`](../../resources/build/win/delphi_flags.inc.sh);
+the remaining eleven JCL / JVCL / jedi paths are IDE-only (the CLI
+compiler resolves them via `.dproj` `` and vendored `.inc`
+lookups) and have no equivalent in `delphi_flags.inc.sh`.
+
+**Close Delphi first** — the IDE caches this value at startup.
+
+```powershell
+# Elevated PowerShell — back up first
+reg export 'HKCU\Software\Embarcadero\BDS\23.0\Library' `
+ 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 = @(
+ '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',
+ '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',
+ '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',
+ 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jvcl\jvcl\resources',
+ 'C:\Projects\keyman\keyman\developer\src\ext\jedi\jedi',
+ 'C:\Projects\keyman\keyman\developer\src\ext\jedi'
+)
+
+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 later via `reg import delphi-library-paths.backup.reg`.
+
+Only the **Search Path** is set. Ctrl-click navigation into JCL/JVCL
+source and step-into-source debugging need the same paths added to
+Browsing Path and Debug DCU Path via **Tools → Options → Language →
+Delphi → Library** in the IDE.
+
+## 4. Build order and implicit dependencies
+
+Keyman's repo has several cross-project dependencies not expressed
+in `.dproj` / `.groupproj` files — a Delphi tool emits code consumed
+by another Delphi project, or one project's `.res` embeds another's
+`.exe`. `build.sh` sequences these correctly; under CE you just
+accept the IDE prompts in the order the script issues them. Below is
+a reference of what each prompt is producing for what dependent:
+
+| # | Producer → Consumer | Failure if skipped |
+|---|---------------------|---------------------|
+| a | `windows/src/global/delphi/build.sh` → `devtools -buildmessageconstants` → `MessageIdentifierConsts.pas` consumed by keyman.dproj, kmshell.dproj, plus `keyman_components.bpl` → `windows/lib/` | `F1026 File not found: 'MessageIdentifierConsts.pas'` OR `File ... keyman_components.bpl does not exist` |
+| a' | `windows/src/desktop/setup/build.sh` → `devtools -buildsetupstrings` → ~32 `Keyman.Setup.System.Locale..pas` consumed by setup.dproj | `F1026 File not found: 'Keyman.Setup.System.Locale..pas'` |
+| b | `common/windows/delphi/tools/build_standards_data/build.sh` → 5 BCP-47 registry `.pas` files → TIKE.dproj | `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` |
+| c | `windows/src/engine/tsysinfo/build.sh` → auto-invokes tsysinfox64 publish, copies exe, runs `rc.exe` → `tsysinfo_x64.res` embedded by tsysinfo.dproj | `F1026 File not found: 'tsysinfo_x64.res'` |
+| d | `developer/src/kmcmplib/build.sh build` → `kmcmplib-19.dll` needed by TIKE runtime | `"kmcmplib-19.dll not found"` at runtime |
+| e | keyman.dproj → kmshell.dproj runtime | `"Keyman failed to start"` when launching Keyman |
+| f | `regsvr32 kmcomapi.dll` (both Win32 and Win64 variants) → kmshell startup | `Class not registered {CF46549D-...}` (`REGDB_E_CLASSNOTREG`) |
+
+All generated `.pas` files are `.gitignored` and must be regenerated
+after `git clean -fdx`.
+
+Run `./build.sh build` in each child directory (or at the repo root
+to fan out) with `KEYMAN_DELPHI_CE=1` set — the script drives the
+order and prompts you at each Delphi step. Then, from an **elevated
+Git Bash**: `windows/src/engine/build.sh install` and
+`windows/src/desktop/build.sh install` overlay the dev binaries and
+register `kmcomapi.dll` for dependency (f).
+
+### Multi-platform Delphi packages (kmcomapi)
+
+`kmcomapi.dproj` gets built **twice** by its `build.sh` — once for
+Win32 (`kmcomapi.dll`), once for Win64 (`kmcomapi.x64.dll`, renamed
+post-build). Two consecutive CE prompts fire for the same `.dproj`.
+Between them, **change Delphi's Platform dropdown**: first prompt →
+Win32, second prompt → Win64. If you build both as Win32, the Win64
+output isn't produced and the script fails at `mv: cannot stat
+'bin/Win64/Debug/kmcomapi.dll'`.
+
+### Full clean before rebuild if you see debug-section errors
+
+`sentrytool_delphiprep` can fail on a partially-built `.dll`/`.exe`
+with `ERROR: This executable has a debug section. Not able to update
+this file.` This happens when Delphi produced a partial binary with
+an already-populated debug section (usually from a prior attempt).
+Wipe the project's build state fully:
+
+```bash
+rm -rf windows/src/engine//bin windows/src/engine//obj
+```
+
+Then rerun that project's `build.sh` and rebuild in Delphi from
+scratch.
+
+> [!IMPORTANT]
+> After an IDE build following any `.res`, manifest, version, or icon
+> change: right-click → **Clean, then Build**. An incremental Build
+> silently embeds the stale cached `.res`.
+
+## 5. Local-only: uiAccess strip for overlaid keyman.exe
+
+Windows refuses to launch unsigned binaries declaring
+`uiAccess="true"` (error 8235). To run an unsigned dev `keyman.exe`,
+swap in the pre-existing non-elevated manifest:
+
+```bash
+cd windows/src/engine/keyman
+./build.sh debug-manifest
+```
+
+That copies `debug-manifest.in` over `manifest.in` and regenerates
+`manifest.res`. Then Clean + Build `keyman.dproj` in the IDE and
+re-run `windows/src/engine/build.sh install` elevated.
+
+Trade-off: keyboard injection into elevated apps stops working under
+the debug manifest. Not committed. Revert with
+`git checkout -- windows/src/engine/keyman/manifest.in`.
+
+## 6. Debugging
+
+Nothing here is CE-specific — the same debugging setup applies under
+Pro. Included as a landing point because there's no dedicated doc
+for it yet:
+
+* **DLLs (kmcomapi, keymanhp):** Run → Parameters → **Host
+ Application** = `\kmshell.exe`.
+* **Long-running processes (kmshell, TSF text service):** Run →
+ **Attach to Process**.
+* **C++ engine pieces (keyman32, kmtip, mcompile):** Visual Studio →
+ Attach to Process; load `.pdb` from `windows/bin/`. Stepping
+ between `keyman.exe` (Delphi) and `keyman32.dll` (C++) requires
+ `windbg` — out of scope here.
+
+## 7. Troubleshooting
+
+Source-compilation errors from Delphi 11/12 compat
+(`E2003 Undeclared identifier: 'null'`, `E2029 Declaration expected`,
+`E2012 Type of expression must be BOOLEAN`) are addressed by the
+source-compat patches described in
+[windows.md § Delphi requirements](windows.md#windows-platform-dependencies).
+
+### `This version of the product does not support command line compiling`
+
+CE block on `dcc32`. Set `KEYMAN_DELPHI_CE=1` (§2). If `build.sh`
+still tries to invoke Delphi as a transitive dep, build the upstream
+tool first or pass `--no-deps`.
+
+### `File ...\windows\lib\keyman_components.bpl does not exist` / `F1026 File not found: MessageIdentifierConsts.pas`
+
+Codegen (a). Run `./windows/src/global/delphi/build.sh build` — that
+script builds `keyman_components.dproj` (produces `keyman_components.bpl`
+at `windows/lib/`) AND runs `devtools -buildmessageconstants` to
+regenerate `MessageIdentifierConsts.pas`. Neither is produced by
+`devtools/build.sh` on its own.
+
+### `F1026 File not found: Keyman.Setup.System.Locale..pas`
+
+Codegen (a'). Run `./windows/src/desktop/setup/build.sh build` —
+that script's post-build runs `devtools -buildsetupstrings` to
+regenerate the ~32 locale `.pas` files.
+
+### `F1026 File not found: Keyman.System.Standards.BCP47SubtagRegistry.pas`
+
+Codegen (b). Rerun
+`common/windows/delphi/tools/build_standards_data/build.sh build`.
+
+### `F1026 File not found: tsysinfo_x64.res`
+
+Dependency (c). Let `windows/src/engine/build.sh` sequence
+`tsysinfox64` before `tsysinfo`; don't skip prompts.
+
+### `F1026 File not found: version.res` or `manifest.res`
+
+Pre-build resource compilation didn't run. Re-invoke `build.sh build`
+for the project — it runs `build_version.res` / `build_manifest.res`
+before the IDE prompt.
+
+### `F1026 File not found: jvcl.inc` / `jedi/jedi.inc` / `jcl.inc`
+
+Library Search Paths not registered. See §3. Close and reopen Delphi
+after the registry edit.
+
+### `F2613 Unit 'JvComponentBase' not found`
+
+JVCL `run/` (or `design/`) missing from Library Search Path. See §3.
+
+### `SKApplicationTitle has had a fatal error` on kmshell launch
+
+kmshell can't find the Keyman install-path registry key it reads via
+`TKeymanPaths.KeymanDesktopInstallPath()`. Either install Keyman 19
+from https://keyman.com/windows so the key is populated, or set up
+the required support files manually alongside the dev build.
+
+### `Class not registered {CF46549D-...}` on kmshell launch
+
+`kmcomapi.dll` not registered — dependency (f). From an elevated Git
+Bash: `cd windows/src/engine/kmcomapi && ./build.sh install`.
+
+### `Could not find keyman.exe (error=8235)`
+
+Windows blocked an unsigned `uiAccess="true"` binary. See §5.
+
+### Delphi pops up an "Unsupported CEF version" dialog
+
+CEF4Delphi_Binary checkout doesn't match `common/windows/CEF_VERSION.md`.
+Not CE-specific — fix per
+[windows.md § KEYMAN_CEF4DELPHI_ROOT](windows.md#keyman_cef4delphi_root).
+
+### `Build succeeded... Time Elapsed 00:00:0X.XX` in terminal, then `cp: cannot stat '...'` / `File ... does not exist`
+
+`KEYMAN_DELPHI_CE` isn't set in the shell that ran `build.sh`, so
+`delphi_msbuild` invoked `msbuild.exe` directly. On CE that produces
+a fake success with no output. See §2 for the shell-verification
+step — this is the single most common failure signature in the whole
+CE workflow.
+
+### `SignTool Error: File not found: ...keymantest-sha1.pfx`
+
+Test-signing certificates never generated. Run once:
+`./common/windows/delphi/tools/certificates/build.sh certificates`
+(see §1).
+
+### `ERROR: This executable has a debug section. Not able to update this file.`
+
+`sentrytool_delphiprep` complaining about a residual debug section
+from a partial prior build. Fully wipe the project's `bin/` and
+`obj/` and rebuild from scratch (see §4 sub-section).
+
+### `mv: cannot stat 'bin/Win64/Debug/.dll'`
+
+You built kmcomapi (or another multi-platform Delphi package) with
+Delphi's Platform dropdown set to Win32 for both CE prompts. The
+second prompt is for Win64 — toggle the dropdown between the two
+prompts. See §4 "Multi-platform Delphi packages".
+
+### `EVariantTypeCastError: Could not convert variant of type (Null)` in `devtools -ai`
+
+Pre-Delphi-12-fix. Ensure you have the source-compat patches from
+[PR #16043](https://github.com/keymanapp/keyman/pull/16043) —
+specifically `common/windows/delphi/tools/devtools/DevIncludePaths.pas`
+— which handles Delphi 12's empty `` in `EnvOptions.proj`.
+Rebuild devtools in Delphi 12 CE after applying.
+
+### `E2010 Incompatible types: 'TCustFile' and 'TObject'` in `CustomisationStorage.pas`
+
+Delphi 12 Win64 (`dcc64`) specific — same PR #16043 also patches
+`windows/src/global/delphi/cust/CustomisationStorage.pas` for this.
+Only surfaces when building `kmcomapi.dproj` for Win64.
diff --git a/docs/build/windows.md b/docs/build/windows.md
index edc756340ff..b6e2abd4fcb 100644
--- a/docs/build/windows.md
+++ b/docs/build/windows.md
@@ -184,8 +184,8 @@ In bash, run the following commands:
cd /c/Projects/keyman
git clone https://github.com/emscripten-core/emsdk
cd emsdk
-emsdk install 3.1.58
-emsdk activate 3.1.58
+emsdk install 3.1.64
+emsdk activate 3.1.64
cd upstream/emscripten
npm install
```
@@ -195,8 +195,8 @@ If you are updating an existing install of Emscripten:
```bash
cd emsdk
git pull
-emsdk install 3.1.58
-emsdk activate 3.1.58
+emsdk install 3.1.64
+emsdk activate 3.1.64
cd upstream/emscripten
npm install
```
@@ -257,6 +257,17 @@ 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 have Delphi 11 or 12 with a CLI-capable license (Professional or
+ higher), set `KEYMAN_DELPHI_VERSION` to the Studio path — `22.0` for
+ Delphi 11, `23.0` for Delphi 12 — before running `build.sh`. The default
+ (`20.0`, Delphi 10.3) is preserved when the variable is unset.
+
+ * If you only have access to a **Delphi Community Edition** (e.g. Delphi 12 CE),
+ see [windows-delphi-ce.md](windows-delphi-ce.md) for an IDE-based workflow
+ that papers over the missing command-line compiler. The shell-script knob
+ (`KEYMAN_DELPHI_CE=1`) described there is 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..4f5363b060a 100644
--- a/resources/build/win/environment.inc.sh
+++ b/resources/build/win/environment.inc.sh
@@ -99,7 +99,44 @@ tds2dbg() {
builder_if_release_build_level "$TDS2DBG" "$@"
}
+# Detect Delphi Community Edition by probing dcc32. Cached per shell.
+# Sets _KEYMAN_DELPHI_IS_CE=1 if the installed Delphi refuses CLI compilation,
+# leaves it empty otherwise (Pro/Enterprise, or no Delphi at all).
+_KEYMAN_DELPHI_CE_PROBED=""
+_KEYMAN_DELPHI_IS_CE=""
+_probe_delphi_ce() {
+ [[ -n "$_KEYMAN_DELPHI_CE_PROBED" ]] && return
+ _KEYMAN_DELPHI_CE_PROBED=1
+ local version dcc32 output
+ version="${KEYMAN_DELPHI_VERSION:-20.0}"
+ dcc32="$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$version\\bin\\dcc32.exe")"
+ [[ -x "$dcc32" ]] || return
+ # dcc32.exe on CE prints "This version of the product does not support command
+ # line compiling." and exits non-zero. On Pro/Enterprise it accepts args.
+ output="$("$dcc32" 2>&1 || true)"
+ if [[ "$output" == *"does not support command line compiling"* ]]; then
+ _KEYMAN_DELPHI_IS_CE=1
+ fi
+}
+
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
+ # Guard: if the developer is on CE but forgot to set KEYMAN_DELPHI_CE=1,
+ # msbuild.exe below silently reports "Build succeeded" without producing any
+ # output, and downstream cp/mv/mt.exe/sentrytool then fail with confusing
+ # "file not found" errors. Detect and fail loudly instead.
+ _probe_delphi_ce
+ if [[ "$_KEYMAN_DELPHI_IS_CE" == "1" ]]; then
+ builder_die "Delphi Community Edition detected at Studio\\${KEYMAN_DELPHI_VERSION:-20.0}\\ but KEYMAN_DELPHI_CE is not set. CE blocks CLI compilation; msbuild would fake a success with no output. Run: export KEYMAN_DELPHI_CE=1 (see docs/build/windows-delphi-ce.md)."
+ 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 @@
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
diff --git a/windows/src/global/delphi/cust/CustomisationStorage.pas b/windows/src/global/delphi/cust/CustomisationStorage.pas
index db4e5c018fa..1af7f2a8a5e 100644
--- a/windows/src/global/delphi/cust/CustomisationStorage.pas
+++ b/windows/src/global/delphi/cust/CustomisationStorage.pas
@@ -157,8 +157,13 @@ function TCustomisationStorage.GetFileOfType(FileType: TCustFileType; StartIndex
{ TCustFileList }
function TCustFileList.AddCustFile: TCustFile;
+var
+ NewObj: TObject;
begin
- Result := Items[inherited Add(FObjectClass.Create)];
+ // Delphi 12 dcc64 rejects the inline form with E2010 'TCustFile' and 'TObject';
+ // splitting via a local TObject makes the widening explicit.
+ NewObj := FObjectClass.Create;
+ Result := Items[inherited Add(NewObj)];
end;
constructor TCustFileList.Create(AObjectClass: TCustFileClass);