Skip to content

feat(windows): Delphi CE interactive build workflow#16044

Open
MattGyverLee wants to merge 1 commit into
keymanapp:masterfrom
MattGyverLee:feat/windows/delphi-ce-workflow
Open

feat(windows): Delphi CE interactive build workflow#16044
MattGyverLee wants to merge 1 commit into
keymanapp:masterfrom
MattGyverLee:feat/windows/delphi-ce-workflow

Conversation

@MattGyverLee
Copy link
Copy Markdown
Contributor

Summary

Lets developers on the free Delphi Community Edition tier build the still-Delphi parts of the tree, even though CE 11+ deliberately blocks the dcc32 CLI that build.sh normally drives. None of the compiler / runtime stack moves; this is purely build-environment work plus docs.

CI is unchanged. KEYMAN_DELPHI_CE is unset by default, so build.sh keeps invoking msbuild.exe exactly as before.

Split off from #16039 per @mcdurdin's review — that PR bundled the CE workflow with the Delphi 11/12 source-compat patches. This PR is the CE half; the compat half is #16043.

Depends on #16043 for the source-compat patches and the KEYMAN_DELPHI_VERSION knob that lets the IDE find Delphi 11/12.

Why this matters for the Delphi removal roadmap

Delphi 10.3 Pro is no longer obtainable, and CE 10.3 has been removed from Embarcadero's downloads. New contributors who want to help finish #4599 currently hit a wall: they can install CE 12, but CE blocks dcc32 and build.sh falls over before reaching any Delphi project. This PR fixes the wall — without committing to keep Delphi alive.

What's in the change

1. CE-aware delphi_msbuild

resources/build/win/environment.inc.sh: delphi_msbuild() now checks KEYMAN_DELPHI_CE. When =1, it prints an IDE-build prompt naming the .dproj and waits for Enter instead of invoking msbuild.exe. The pre-build (rc.exe, manifest gen) and post-build (binary copy, codegen) steps in build.sh run normally around the prompt, so the developer only has to drive the Delphi compile by hand.

This is the approach @mcdurdin proposed in #16039 (comment) — gate by version in the existing shell script rather than parallel PowerShell helpers.

2. Docs

  • docs/build/windows.md: one-line pointer to windows-delphi-ce.md from the existing "Delphi 10.3 CE no longer available" warning, so future CE users find the workaround instead of dead-ending.
  • docs/build/windows-delphi-ce.md: ~940-line guide — prerequisites, registry library-path setup, source patches required on chore(windows): Delphi 11/12 source compatibility #16043, the canonical build order (including the tsysinfox64 -> .bin -> tsysinfo_x64.res chicken-and-egg), install-and-overlay workflow, debugging, troubleshooting catalog, and a section labeling which patches are safe to upstream. Written against Delphi 12 Athens CE specifically (currently the only free tier); Delphi 11 CE works with the same approach modulo a couple of paths.

3. engine.groupproj fix

windows/src/engine/engine.groupproj: drop the inst\insthelper reference. The path doesn't resolve — insthelper.dproj lives at windows/src/engine/insthelper, not .../inst/insthelper — and IDE "Build All" was failing on it. build.sh-based builds were never affected because they don't walk the groupproj.

Technically independent of the CE work, but bundled here because IDE "Build All" is the path CE users actually exercise. Happy to split into its own PR if reviewers prefer.

4. .gitignore

Patterns for the per-script log files that the CE workflow writes (configure.log, core-build.log, etc.).

Reviewer notes

  • The PR's behaviour-changing surface is exactly one if [[ "${KEYMAN_DELPHI_CE:-}" == "1" ]] branch in delphi_msbuild(). Everything else is docs / .gitignore / the orthogonal engine.groupproj cleanup.
  • The PowerShell helper scripts from the original chore(windows): support Delphi 11/12 compilation + Delphi 12 CE dev workflow #16039 are gone in this revision; the workflow runs entirely through KEYMAN_DELPHI_CE=1 ./build.sh.
  • The doc file is named windows-delphi-ce.md (not windows-d12.md) so the same workflow guide applies if/when a future Delphi 11 CE user shows up; the body is Delphi 12-specific because that's what's been actually tested.

User Testing

User testing not required: build-environment / docs change with no runtime behaviour modification. CI will exercise the non-CE default path (KEYMAN_DELPHI_CE unset); the CE flow is manually validated via the steps in docs/build/windows-delphi-ce.md.

Relates-to

Relates-to: #4599
Depends-on: #16043
Replaces: #16039 (split into #16043 + this PR)

Lets developers on the free Delphi Community Edition tier build the
still-Delphi parts of the tree, even though CE 11+ blocks the dcc32
CLI that build.sh normally drives. None of the compiler / runtime
stack moves; this is purely build-environment work plus docs.

CI is unchanged. KEYMAN_DELPHI_CE is unset by default, so build.sh
keeps invoking msbuild.exe exactly as before.

Depends on the Delphi 11/12 source-compat work in a sibling PR — that
PR adds the VER350/VER360 IFDEF arms and KEYMAN_DELPHI_VERSION knob
needed for the IDE to actually compile.

Build-script knob:
* resources/build/win/environment.inc.sh: delphi_msbuild() now checks
  KEYMAN_DELPHI_CE=1. When set, it prints an IDE-build prompt naming
  the .dproj and waits for Enter instead of running msbuild.exe. The
  pre-build (rc.exe) and post-build (binary copy, codegen) steps in
  build.sh run normally around the prompt, so the developer only has
  to drive the Delphi compile by hand.

Docs:
* docs/build/windows.md: one-line pointer to windows-delphi-ce.md from
  the existing "Delphi 10.3 CE no longer available" warning, so future
  CE users find the workaround instead of dead-ending.
* docs/build/windows-delphi-ce.md: ~940-line guide -- prerequisites,
  registry library-path setup, source patches required on the sibling
  branch, the canonical build order (including the tsysinfox64 ->
  .bin -> tsysinfo_x64.res chicken-and-egg), install-and-overlay
  workflow, debugging, and a troubleshooting catalog. Written against
  Delphi 12 Athens CE specifically (currently the only free tier);
  Delphi 11 CE works with the same approach modulo a couple of paths.

engine.groupproj fix (independent of the Delphi work but unblocks IDE
"Build All", which CE users rely on):
* windows/src/engine/engine.groupproj: drop the inst\insthelper
  reference. The path doesn't resolve -- insthelper.dproj lives at
  windows/src/engine/insthelper, not .../inst/insthelper -- and IDE
  "Build All" was failing on it. build.sh-based builds were never
  affected because they don't walk the groupproj.

.gitignore: add patterns for the per-script log files that the CE
workflow writes (configure.log, core-build.log, etc.).

Relates-to: keymanapp#4599
@github-project-automation github-project-automation Bot moved this to Todo in Keyman Jun 2, 2026
@keymanapp-test-bot keymanapp-test-bot Bot added has-user-test user-test-missing User tests have not yet been defined for the PR labels Jun 2, 2026
@keymanapp-test-bot
Copy link
Copy Markdown

User Test Results

Test specification and instructions

ERROR: user tests have not yet been defined

@keymanapp-test-bot keymanapp-test-bot Bot added this to the A19S30 milestone Jun 2, 2026
@keyman-server
Copy link
Copy Markdown
Collaborator

This pull request is from an external repo and will not automatically be built. The build must still be passed before it can be merged. Ask one of the team members to make a manual build of this PR.

@MattGyverLee
Copy link
Copy Markdown
Contributor Author

Took a hard look at the groupproj-batching path. Sharing the analysis so we can pick the cheapest workable design.

Coverage and order — what each groupproj would buy us on CE

Groupproj Delphi sub-projects in dir In groupproj Missing Internal order
engine.groupproj keyman, kmcomapi, tsysinfo, tsysinfox64, insthelper (5) 4 insthelper WRONG: keyman;kmcomapi;tsysinfo;tsysinfox64tsysinfo is attempted before the tsysinfox64.exe it embeds exists
desktop.groupproj kmshell, kmbrowserhost, setup, kmconfig, insthelp (5) 5 OK internally
developer.groupproj TIKE, setup, kmconvert, kmdbrowserhost + ext BPLs 3 kmdbrowserhost + design-time BPLs OK internally

So before any other consideration, adopting engine.groupproj would need a one-line reorder (tsysinfox64 before tsysinfo) plus restoring insthelper at its real path. That's a change to the non-CE flow we'd like to avoid here.

What build.sh actually does between Delphi compiles

The reason per-.dproj prompts fit cleanly is that the build chain interleaves a lot of work that the groupproj knows nothing about. Per child build.sh:

rc.exe (version.rc, manifest.rc, project-specific .rc)
   → manifest.in → manifest.xml generation
   → delphi_msbuild <project>.dproj    ← CE prompt currently fires here
   → sentrytool_delphiprep (symbol-upload prep, per-arch)
   → tds2dbg (TDS → PDB conversion)
   → copy outputs into install layout

Plus the cross-project hand-offs that aren't in any .dproj or .groupproj:

  • (a) devtools → keyman/setup/kmshell. devtools.exe -buildmessageconstants emits MessageIdentifierConsts.pas (~1500 lines) from windows/src/desktop/kmshell/xml/strings.xml. devtools.exe -buildsetupstrings emits ~32 Keyman.Setup.System.Locale.<bcp47>.pas files from windows/src/desktop/setup/locale/*.xml. All .gitignored. Skipping this gives F1026 File not found: '...MessageIdentifierConsts.pas'.
  • (b) build_standards_data → TIKE. Emits five BCP-47 registry .pas files in common/windows/delphi/standards/ (the largest is LangTagsRegistry.pas at ~2.4 MB). All .gitignored. Skipping gives F1026 File not found: '...BCP47SubtagRegistry.pas'.
  • (c) tsysinfox64 → tsysinfo. tsysinfo.dproj (Win32) embeds the Win64 helper tsysinfox64.exe as tsysinfo_x64.res. After building tsysinfox64.dproj, build.sh copies bin/Win64/Debug/tsysinfox64.exe → tsysinfo/tsysinfox64.bin, then runs rc tsysinfo_x64.rc in windows/src/engine/tsysinfo/ to produce the .res, then builds tsysinfo.dproj. Skipping any step gives F1026 File not found: 'tsysinfo_x64.res'.
  • (d) kmcmplib CLI → TIKE runtime. TIKE links to kmcmplib-19.dll, which is built CLI via ./developer/src/kmcmplib/build.sh build — no IDE involvement, but a clean CE build that skips it produces a TIKE that throws "kmcmplib-19.dll not found" on any compile action.
  • (e) keyman.dproj → kmshell runtime. kmshell compiles without keyman.exe — failure is at runtime: kmshell shows "Keyman failed to start" when enabling a keyboard.
  • (f) regsvr32 kmcomapi.dll → kmshell startup. windows/src/engine/build.sh install does this; without it, CoCreateInstance returns REGDB_E_CLASSNOTREG.

(c), (d), and the codegen halves of (a) and (b) are exactly the "post-Delphi hand-offs" build.sh interjects between Delphi compiles. The groupproj has no equivalent and can't fire rc.exe, devtools.exe, or shell copies.

What "use the groupproj" would actually require

To get one prompt per group, we'd have to restructure each child build.sh so that under CE:

  1. All children's pre-build (rc.exe runs, manifest.in.xml) fire first across the whole group.
  2. ONE IDE prompt fires for the groupproj.
  3. All children's post-build (sentrytool_delphiprep, tds2dbg, copies, codegen hand-off (c)) fire across the whole group.

That's a CE-only fork of how every child build.sh is structured — and the engine group still needs the tsysinfox64 → tsysinfo reorder (or it fails inside the IDE the same way) plus an insthelper add-back. Per-dproj prompts side-step both: the existing dependency order in build.sh already gets it right, including the (c) hand-off as a post-build step on tsysinfox64.

Proposal

Ship as-is. The PR-as-it-stands is a one-if-branch CE-only change in delphi_msbuild that fits between the existing rc.exe and sentrytool/tds2dbg calls in every child. ~30 prompts on a top-level clean build, but:

  • no reorder of engine.groupproj (no non-CE flow change),
  • no fork of any child build.sh (no non-CE flow change),
  • chicken-and-egg (a)–(f) all keep working because the existing build.sh order is what handles them,
  • design-time BPLs / insthelper / kmdbrowserhost don't need a special case — they're driven by their own build.sh and get the same per-dproj prompt.

Happy to revisit if there's a tighter design I'm missing, but under the "minimal CE-only changes" constraint, that's where I land. Also happy to add this analysis as a section to docs/build/windows-delphi-ce.md so the next CE contributor doesn't re-litigate it.

@MattGyverLee
Copy link
Copy Markdown
Contributor Author

I had Claude plow through the build and tell me what to do every time I hit an error until I could compile and run everything. I hit all those A-F chicken/egg compile problems above trying to follow the build. I wonder if the CLI version would hit some of the same blockers if it was put onto a fresh new machine, but I can't test that (and don't want to start from scratch again while in "hackathon" mode. I only know it worked on my machine after all the intervening workarounds.

Copy link
Copy Markdown
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of redundant and incorrect information in the docs. I think this needs a thorough rework

Comment on lines +70 to +76
* **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).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right; TKeymanPaths.KeymanDesktopInstallPath() does not hard code this path as it is read from the registry.

To run keyman.exe properly so that it can interact with elevated UI processes (uiAccess=true in the manifest), it needs to be signed with a valid cert, and be installed in a hardened path such as Program Files. But for debugging, you can run keyman.exe unsigned with uiAccess=false, in non-elevated processes. All other components should be runnable outside of Program Files, although there are libraries that have to be located.

installed Developer support files.
* **7-Zip** (`choco install 7zip`) for extracting the CEF libcef payload.

## 2. One-time setup
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this section should be in windows.md and if that file is not correct, should be corrected; it's best not to duplicate.

Comment on lines +133 to +136
`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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? This doesn't require any Delphi bits in order to be checked out.

Comment on lines +157 to +162
### 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not right. keyman.com/windows. s/overlay/debugging. The reason for installing Keyman is to simplify the install environment but it is possible to debug without Keyman installed.

Comment on lines +175 to +213
```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
}
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fragile because if we make changes to the project the docs will need to be updated so we don't get out of sync. I don't like having this kind of thing twice really.

Comment on lines +815 to +836
### `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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should not be necessary

Comment on lines +849 to +854
### `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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm don't think this is correct diagnosis


---

## Upstream candidates
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed

Comment thread .gitignore
Comment on lines +190 to +198
# Local artifacts from the Delphi CE dev workflow (see docs/build/windows-delphi-ce.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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't know where these files come from. They do not seem to be generated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat has-user-test user-test-missing User tests have not yet been defined for the PR windows/

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants