From ebc6d2a176ca564aaeebb8624f03981b917b25cd Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Tue, 2 Jun 2026 01:04:38 -0500 Subject: [PATCH 01/14] chore(windows): Delphi 11/12 source compatibility Lets the existing tree compile under Delphi 11 (VER350) and Delphi 12 (VER360) without breaking Delphi 10.3 (VER330). CI is unchanged: with KEYMAN_DELPHI_VERSION unset, every build script keeps defaulting to Delphi 10.3 (BDS 20.0) exactly as before. This is a bridge to lower contributor friction on #4599 (deprecate Delphi). The 10.3 build server can also be upgraded to a paid Delphi 11/12 license on top of these patches if/when that's wanted. Build-script knob: * resources/build/win/configure_environment.inc.sh, resources/build/win/delphi_environment.inc.sh: DELPHI_VERSION now reads ${KEYMAN_DELPHI_VERSION:-20.0}. * resources/builder.inc.sh: builder_describe_platform's delphi tool detection respects the same KEYMAN_DELPHI_VERSION default, so machines with only Delphi 12 installed no longer have win,delphi- gated targets silently skipped. Keyman-owned source compat (additive VER350/VER360 arms; VER330 paths untouched): * common/windows/delphi/tools/devtools/SourceRootPath.pas: teach DelphiMajorVersion about BDS 22.0 / 23.0 install dirs. * common/windows/delphi/web/Keyman.System.HttpServer.Base.pas: extend the IFNDEF VER330 tripwire to accept VER340/350/360. * common/windows/delphi/components/FixedTrackbar.pas: same tripwire extension. * common/windows/delphi/general/CleartypeDrawCharacter.pas: EnumFontFamiliesEx integer-return comparison on VER340/350/360 (was VER340-only). * common/windows/delphi/general/JsonUtil.pas: pass [] options arg to TJSONAncestor.ToChars on VER350/360 (Delphi 11+ added the parameter). Vendored third-party patches (each hunk annotated with a "Keyman local patch" comment plus refresh strategy): * developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas: wrap Boolean args to CreateEvent / OpenEvent / CreateWaitableTimer / OpenWaitableTimer / OpenSemaphore / CreateMutex / OpenMutex in explicit BOOL() casts (Delphi 12 tightened implicit Boolean->BOOL at call sites); switch JclWin32.CreateMutex -> Winapi.Windows.CreateMutex to match the file's existing pattern for the other Winapi calls. * developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas: on VER350+, unconditionally call DoCreate (Embarcadero removed the OldCreateOrder property in Delphi 11; modern behavior is equivalent to OldCreateOrder=True). * developer/src/ext/mbcolor/mxs.inc: add VER350/VER360 blocks defining DELPHI_5_UP through DELPHI_10_UP. Without these, HTMLColors.pas silently drops Variants from its uses clause and breaks with "Undeclared identifier: 'null'". .gitignore: add patterns for per-arch version*.res and meson wraplock files. Relates-to: #4599 --- .gitignore | 4 ++++ .../delphi/components/FixedTrackbar.pas | 4 ++++ .../delphi/general/CleartypeDrawCharacter.pas | 4 ++-- common/windows/delphi/general/JsonUtil.pas | 4 ++++ .../delphi/tools/devtools/SourceRootPath.pas | 8 +++++++ .../web/Keyman.System.HttpServer.Base.pas | 6 +++++ .../jedi/jcl/jcl/source/common/JclSynch.pas | 18 +++++++++------ .../ext/jedi/jvcl/jvcl/run/JvComponent.pas | 8 +++++++ developer/src/ext/mbcolor/mxs.inc | 22 +++++++++++++++++++ .../build/win/configure_environment.inc.sh | 6 +++-- resources/build/win/delphi_environment.inc.sh | 5 ++++- resources/builder.inc.sh | 3 ++- 12 files changed, 79 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 7b872195880..cd9499f6c1a 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,6 @@ lcov.info # flag file for build script .configured + +# 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/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/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/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 From a57500f57912c796e6ddae14c40b57fd78eefa53 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 09:58:49 -0500 Subject: [PATCH 02/14] docs(windows): surface KEYMAN_DELPHI_VERSION in windows.md Pro-tier Delphi 11/12 users had no way to discover the env-var knob introduced by this PR - it was documented only in the PR body and in the CE-workflow doc from #16044. Add a short paragraph to the Delphi requirements section pointing at the two Studio path values and reaffirming that the unset default preserves CI behavior. Co-Authored-By: Claude Opus 4.7 --- docs/build/windows.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/build/windows.md b/docs/build/windows.md index edc756340ff..a3b4ce73983 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -257,6 +257,12 @@ 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, so CI is + unaffected. + Start Delphi IDE once after installation as it will create various environment files and take you through required registration. From b611e81d7192905228e1500ff91f6c9178ff7432 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 10:08:34 -0500 Subject: [PATCH 03/14] revert: drop unnecessary JclSynch.pas Boolean/BOOL cast patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per @mcdurdin's review: he built with unmodified JclSynch.pas and verified the Delphi 12 compile succeeds without the BOOL casts. The patches were defensive against a compile failure I never actually observed on this file — I assumed Delphi 12's tightened implicit-Boolean handling required them without empirically checking. Removing. If a real compile failure on JclSynch shows up later, address it then with a specific error to fix rather than a preemptive patch. Co-Authored-By: Claude Opus 4.7 --- .../jedi/jcl/jcl/source/common/JclSynch.pas | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) 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 eb405224249..f73b1722b09 100644 --- a/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas +++ b/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas @@ -937,15 +937,11 @@ 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, BOOL(Manual), BOOL(Signaled), PChar(FName)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateEvent(SecAttr, Manual, Signaled, PChar(FName)); if FHandle = 0 then raise EJclEventError.CreateRes(@RsSynchCreateEvent); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -956,7 +952,7 @@ constructor TJclEvent.Open(Access: Cardinal; Inheritable: Boolean; begin FName := Name; FExisted := True; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenEvent(Access, BOOL(Inheritable), PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenEvent(Access, Inheritable, PChar(Name)); if FHandle = 0 then raise EJclEventError.CreateRes(@RsSynchOpenEvent); end; @@ -984,7 +980,7 @@ constructor TJclWaitableTimer.Create(SecAttr: PSecurityAttributes; begin FName := Name; FResume := False; - FHandle := CreateWaitableTimer(SecAttr, BOOL(Manual), PChar(Name)); + FHandle := CreateWaitableTimer(SecAttr, Manual, PChar(Name)); if FHandle = 0 then raise EJclWaitableTimerError.CreateRes(@RsSynchCreateWaitableTimer); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -1004,7 +1000,7 @@ constructor TJclWaitableTimer.Open(Access: Cardinal; Inheritable: Boolean; FExisted := True; FName := Name; FResume := False; - FHandle := OpenWaitableTimer(Access, BOOL(Inheritable), PChar(Name)); + FHandle := OpenWaitableTimer(Access, Inheritable, PChar(Name)); if FHandle = 0 then raise EJclWaitableTimerError.CreateRes(@RsSynchOpenWaitableTimer); end; @@ -1052,7 +1048,7 @@ constructor TJclSemaphore.Open(Access: Cardinal; Inheritable: Boolean; begin FName := Name; FExisted := True; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenSemaphore(Access, BOOL(Inheritable), PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenSemaphore(Access, Inheritable, PChar(Name)); if FHandle = 0 then raise EJclSemaphoreError.CreateRes(@RsSynchOpenSemaphore); end; @@ -1079,7 +1075,7 @@ constructor TJclMutex.Create(SecAttr: PSecurityAttributes; InitialOwner: Boolean begin inherited Create; FName := Name; - FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CreateMutex(SecAttr, BOOL(InitialOwner), PChar(Name)); + FHandle := JclWin32.CreateMutex(SecAttr, InitialOwner, PChar(Name)); if FHandle = 0 then raise EJclMutexError.CreateRes(@RsSynchCreateMutex); FExisted := GetLastError = ERROR_ALREADY_EXISTS; @@ -1090,7 +1086,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, BOOL(Inheritable), PChar(Name)); + FHandle := {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.OpenMutex(Access, Inheritable, PChar(Name)); if FHandle = 0 then raise EJclMutexError.CreateRes(@RsSynchOpenMutex); end; From d7b3784f0715310a382e0192c1ad73350d18356b Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 10:09:02 -0500 Subject: [PATCH 04/14] revert: drop JvComponent.pas OldCreateOrder patch pending verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per @mcdurdin's review: the OldCreateOrder-was-removed premise disagrees with the Athens documentation he linked (https://docwiki.embarcadero.com/Libraries/Athens/en/Vcl.Forms.TForm.OldCreateOrder). Given JclSynch.pas turned out to be an unnecessary defensive patch, this one deserves the same skepticism. Reverting until a specific Delphi 12 compile failure on JVCL surfaces that motivates a targeted fix. The remaining vendored ext patch (mbcolor/mxs.inc) stays because it produces a concrete E2003 Undeclared identifier: 'null' at HTMLColors.pas:290 on Delphi 12 — reproducible, not defensive. Co-Authored-By: Claude Opus 4.7 --- developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas | 8 -------- 1 file changed, 8 deletions(-) diff --git a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas index fab8609e2c8..1877ee48efe 100644 --- a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas +++ b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas @@ -123,16 +123,8 @@ 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; From 9d53e7715ef4ebf4ced99932b5320b6f22b3135a Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 11:20:02 -0500 Subject: [PATCH 05/14] address remaining review points: tripwire acknowledgements, scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FixedTrackbar.pas: extend the existing 'Tested on' comment block to note VER350/VER360 IFNDEF arms are added without full verification against Vcl.Grids.pas — same self-documentation pattern @mcdurdin used when he added the 10.3 attestation line in 2019. - HttpServer.Base.pas: extend the pre-existing "may need to check" comment to name the newly-silenced tripwire arms and record that Indy's URL handling on 10.4/11/12 has not been re-verified. The workaround stays applied conservatively rather than removed optimistically. - Drop `.wraplock` .gitignore rule — Meson subproject artifact, no connection to Delphi 11/12 source compat. Scope drift. - windows.md: trim the "so CI is unaffected" tail from the KEYMAN_DELPHI_VERSION paragraph. CI default is a separate concern from the env-var default. Motivated by the same "no unverified defensive extensions" concern that led me to revert the JclSynch and JvComponent patches earlier. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 3 --- common/windows/delphi/components/FixedTrackbar.pas | 2 ++ common/windows/delphi/web/Keyman.System.HttpServer.Base.pas | 6 +++++- docs/build/windows.md | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index cd9499f6c1a..dd7d41d3709 100644 --- a/.gitignore +++ b/.gitignore @@ -187,6 +187,3 @@ lcov.info # flag file for build script .configured - -# Meson wrap dependency lock files -**/subprojects/.wraplock diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas index 8d3c525d842..9e900c2c0d9 100644 --- a/common/windows/delphi/components/FixedTrackbar.pas +++ b/common/windows/delphi/components/FixedTrackbar.pas @@ -64,6 +64,8 @@ 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} diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index 8df4e732056..bec398bcbca 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -44,7 +44,11 @@ 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} diff --git a/docs/build/windows.md b/docs/build/windows.md index a3b4ce73983..f11e6ac1e33 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -260,8 +260,7 @@ of appropriate node versions during builds. * 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, so CI is - unaffected. + (`20.0`, Delphi 10.3) is preserved when the variable is unset. Start Delphi IDE once after installation as it will create various environment files and take you through required registration. From 736afd9b56e6d87544f959560f870453b92c85f4 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 23:47:34 -0500 Subject: [PATCH 06/14] docs(windows): sync emsdk install version with the pinned minimum resources/build/minimum-versions.inc.sh pins KEYMAN_MIN_VERSION_EMSCRIPTEN at 3.1.64, but the windows.md walkthrough still told contributors to `emsdk install 3.1.58`. That version now fails to compile core/src/wasm.cpp because a recent Core commit (9909d7d901 "expose km_core_state_options_update to WASM") uses stricter emscripten pointer-binding APIs. Bumping the docs to match the pin. Discovered while running the CE build walkthrough on this branch. Co-Authored-By: Claude Opus 4.7 --- docs/build/windows.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index f11e6ac1e33..9e482a894cc 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 ``` From 9ef5278f19ac80b03f061e11505c65c41d4f257f Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 14:25:19 -0500 Subject: [PATCH 07/14] fix(devtools): tolerate Delphi 12 EnvOptions.proj Null attrs and Win64x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Delphi 12 dev-env issues in devtools' -ai / TIncludePaths.Reset when processing %AppData%\Embarcadero\BDS\23.0\EnvOptions.proj: 1. Delphi 12 emits an empty at the top of the file with no Condition attribute. The pre-existing VarIsNull guard didn't short-circuit reliably under Delphi 12's variant handling, so a Null Attributes['Condition'] hit Pos() and raised EVariantTypeCastError. Replace with VarToStrDef(..., '') for a defensive Variant->string coercion that handles Null and Empty. 2. The Win32/Win64 match was Pos('Win32', ...) — a substring match that also matches 'Win64x' (a Delphi 12 target now present in the file). Building against the Win64x PropertyGroup then hit an empty and again produced Null. Match with the surrounding literal single quotes ('''Win32''' / '''Win64''') so 'Win64x' no longer matches, and additionally wrap the NodeValue reads in VarToStrDef so empty child elements degrade cleanly. Applied to both AddPathToProjectXML (the -ai/-ip path) and Reset (the -ri path) — same code shape in both. Found while running the CE build workflow against Delphi 12 CE — the tripwire fired at cef4delphi's `devtools -ai` step and blocked the engine build entirely until this fix landed. Co-Authored-By: Claude Opus 4.7 --- .../delphi/tools/devtools/DevIncludePaths.pas | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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; From d69d9b7fe3737edc2d6329b98a9b751fb64c0fb8 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 14:25:20 -0500 Subject: [PATCH 08/14] fix(global): let CustomisationStorage compile under Delphi 12 dcc64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCustFileList.AddCustFile used a nested expression the Delphi 12 Win64 compiler rejects with E2010 'Incompatible types: TCustFile and TObject': Result := Items[inherited Add(FObjectClass.Create)]; dcc32 (Win32) accepted the same source, so the file has been building fine for CI's default 10.3 flow. On Delphi 12 dcc64, the inline chain loses the widening from TCustFile (returned by FObjectClass.Create) to TObject (Add's parameter). Splitting the object creation into a local TObject variable lets the widening happen explicitly and both compilers accept the code without any IFDEF. Found while building the Win64 half of kmcomapi.dproj as part of the CE workflow walkthrough — the Win32 half compiled cleanly. Co-Authored-By: Claude Opus 4.7 --- windows/src/global/delphi/cust/CustomisationStorage.pas | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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); From ecc0aa81bb4804adbac08fb7836517e2747a71ac Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Tue, 2 Jun 2026 01:11:51 -0500 Subject: [PATCH 09/14] feat(windows): Delphi CE interactive build workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: #4599 --- .gitignore | 10 + docs/build/windows-delphi-ce.md | 939 +++++++++++++++++++++++++ docs/build/windows.md | 6 + resources/build/win/environment.inc.sh | 9 + windows/src/engine/engine.groupproj | 18 +- 5 files changed, 967 insertions(+), 15 deletions(-) create mode 100644 docs/build/windows-delphi-ce.md diff --git a/.gitignore b/.gitignore index dd7d41d3709..4c84ed419b3 100644 --- a/.gitignore +++ b/.gitignore @@ -187,3 +187,13 @@ lcov.info # flag file for build script .configured + +# 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 diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md new file mode 100644 index 00000000000..1ff993b35fa --- /dev/null +++ b/docs/build/windows-delphi-ce.md @@ -0,0 +1,939 @@ +# Build Keyman on Windows with Delphi Community Edition + +> [!WARNING] +> This is a workaround guide for developers using **Delphi Community Edition +> (CE)** — the free tier that Embarcadero offers in place of the now-retired +> Delphi 10.3 CE. Delphi CE 11 and 12 both deliberately block 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. Setting `KEYMAN_DELPHI_CE=1` makes `delphi_msbuild` pause +> at each Delphi project and prompt the developer to build it in the IDE, +> while the surrounding pre/post-build steps in `build.sh` (rc.exe, codegen, +> binary copies) still run automatically. +> +> The body of this guide is written against Delphi 12 Athens CE specifically, +> since that's the currently-available free tier. Delphi 11 CE works with the +> same approach — only the BDS install path (`Studio\22.0\`) and one or two +> RTL details differ. +> +> 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 9e482a894cc..b6e2abd4fcb 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -262,6 +262,12 @@ of appropriate node versions during builds. 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/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/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 @@ - - - - - - - - - - + - + - + From d36fc00f2e2638c7c5450cb49f671cf52c736449 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 09:49:18 -0500 Subject: [PATCH 10/14] docs: rework windows-delphi-ce.md per review feedback Address @mcdurdin's "redundant and incorrect information" review. Drop 836 lines. Trim scope to what this PR actually introduces (KEYMAN_DELPHI_CE=1 interactive prompt) plus the CE-specific prerequisites the standard windows.md doesn't cover: - Remove content that belongs in PR #16043 (the source-compat patches for JCL, JVCL, mbcolor, SourceRootPath, HttpServer, FixedTrackbar, CleartypeDraw, JsonUtil). Point readers there. - Delete the pre-PR engine.groupproj / insthelper workaround section - this PR removes that reference, so the workaround no longer applies to trees on this branch. - Drop internal memory-system references ([[double-bracket-wiki]]) that would render as literal text on GitHub. - Fix the VER340->'21.0' factual error (it's Delphi 10.4's real Studio path, not a CE-license rev bump). - Collapse duplicated content: uiAccess/kmshell-path explanation, install-Keyman-19 step, revert-before-PR warning, .res file handling, engine.groupproj discussion. - Fix broken cross-references to "section 5.2 step (a)/(b)" and "BSD block" that had no matching structure. - Consolidate build order and shell workflow into one section. - Convert the a-f cross-project dependency graph into a table for scanability. Doc now ~270 lines vs. 939. Scope matches the PR: what KEYMAN_DELPHI_CE=1 does, plus the CE-only setup (Library Search Paths, Keyman 19 install requirement, dependency ordering under IDE prompts). Co-Authored-By: Claude Opus 4.7 --- docs/build/windows-delphi-ce.md | 1005 ++++++------------------------- 1 file changed, 169 insertions(+), 836 deletions(-) diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md index 1ff993b35fa..c4d85a8f7a6 100644 --- a/docs/build/windows-delphi-ce.md +++ b/docs/build/windows-delphi-ce.md @@ -1,205 +1,100 @@ # Build Keyman on Windows with Delphi Community Edition -> [!WARNING] -> This is a workaround guide for developers using **Delphi Community Edition -> (CE)** — the free tier that Embarcadero offers in place of the now-retired -> Delphi 10.3 CE. Delphi CE 11 and 12 both deliberately block 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. Setting `KEYMAN_DELPHI_CE=1` makes `delphi_msbuild` pause -> at each Delphi project and prompt the developer to build it in the IDE, -> while the surrounding pre/post-build steps in `build.sh` (rc.exe, codegen, -> binary copies) still run automatically. -> -> The body of this guide is written against Delphi 12 Athens CE specifically, -> since that's the currently-available free tier. Delphi 11 CE works with the -> same approach — only the BDS install path (`Studio\22.0\`) and one or two -> RTL details differ. -> -> 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. +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. The source-compat patches Delphi 11/12 need to compile ship +via [PR #16043](https://github.com/keymanapp/keyman/pull/16043); make +sure it's merged or applied before proceeding here. > [!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. +> Registry edits (§3) and the manifest patch (§5) are local-only. +> Revert before opening any PR — CI runs against Delphi 10.3. -```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 -``` +## 1. Prerequisites (delta from windows.md) -### 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" -``` +* **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/desktop. + Required, not optional: `TKeymanPaths.KeymanDesktopInstallPath()` + runtime-checks `C:\Program Files (x86)\Keyman\Keyman Desktop\` for + support files. Without an install, kmshell dies at startup with + `SKApplicationTitle has had a fatal error` before its main form + appears. -`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. Environment variables -### 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. +Set both, persistently: ```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 +[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_VERSION', '23.0', 'User') +[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_CE', '1', 'User') ``` -### 2.6 Install Keyman 19 official +* `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: + + ``` + Delphi CE: CLI compilation is not available. + Please build in the Delphi IDE now, then press + Enter to continue (or Ctrl-C to abort). + ``` -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. +Open a fresh shell after `SetEnvironmentVariable` for the values to +be visible. -### 2.7 Delphi 12 IDE Library Search Paths +## 3. Delphi 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. +`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. -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. +**Close Delphi first** — the IDE caches this value at startup. ```powershell -# Elevated PowerShell -- back up first +# Elevated PowerShell — back up first reg export 'HKCU\Software\Embarcadero\BDS\23.0\Library' ` - C:\Projects\keyman\keyman\delphi-library-paths.backup.reg /y + 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\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', # 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} + '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)) { @@ -212,728 +107,166 @@ foreach ($key in @($key32, $key64)) { } ``` -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. +Restore later via `reg import delphi-library-paths.backup.reg`. -> [!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. +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. -### 3.1 Build-script: select Delphi version +## 4. Build order and implicit dependencies -`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. +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`. Under the standard flow `build.sh` sequences these +invisibly; under CE you must respect them at IDE-prompt time. -For any Delphi 12 installation (Professional or CE), set it to `23.0`: +| # | Producer → Consumer | Failure if skipped | +|---|---------------------|---------------------| +| a | `devtools.exe -buildmessageconstants` → keyman.dproj, kmshell.dproj | `F1026 File not found: 'MessageIdentifierConsts.pas'` | +| a | `devtools.exe -buildsetupstrings` → setup.dproj | `F1026 File not found: 'Keyman.Setup.System.Locale..pas'` | +| b | `build_standards_data.exe` → TIKE.dproj | `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` | +| c | `tsysinfox64.exe` → `tsysinfo_x64.res` → tsysinfo.dproj | `F1026 File not found: 'tsysinfo_x64.res'` | +| d | `kmcmplib` (CLI build) → TIKE runtime | `"kmcmplib-19.dll not found"` at runtime | +| e | keyman.dproj → kmshell.dproj runtime | `"Keyman failed to start"` when enabling a keyboard | +| f | `regsvr32 kmcomapi.dll` → kmshell startup | `Class not registered {CF46549D-...}` (`REGDB_E_CLASSNOTREG`) | -```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 -``` +All generated `.pas` files are `.gitignored` and must be regenerated +after `git clean -fdx`. -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. +The canonical CE build order: -**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') -``` +1. CLI-build everything CLI-buildable: `./core/build.sh build`, + `./developer/src/kmcmplib/build.sh build`, TypeScript modules, + `./web/build.sh`. C++ engine pieces can wait. +2. `cd common/windows/delphi/tools/devtools && ./build.sh build` + (prompted for IDE build; codegen (a) runs after Enter). +3. `cd common/windows/delphi/tools/build_standards_data && ./build.sh build` + (prompted for IDE; codegen (b) runs after Enter). +4. `cd windows/src/engine && ./build.sh build` — `build.sh` handles + dependency (c) by building `tsysinfox64.dproj` before + `tsysinfo.dproj`; just accept prompts in order. +5. `cd windows/src/desktop && ./build.sh build`. +6. `cd developer/src && ./build.sh build`. +7. **Elevated Git Bash:** `cd windows/src/engine && ./build.sh install`, + then `cd windows/src/desktop && ./build.sh install`. Handles (f) + automatically. -```bash -export KEYMAN_DELPHI_CE=1 -``` +> [!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`. -### 3.2 keyman.exe uiAccess strip (LOCAL ONLY, not committed) +## 5. Local-only: uiAccess strip for overlaid keyman.exe -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: +Windows refuses to launch unsigned binaries declaring +`uiAccess="true"` (error 8235). The overlay workflow (§4 step 7) +replaces the installed signed `keyman.exe` with an unsigned dev +build, so uiAccess must be turned off for the overlay to launch. +**Do not commit:** ```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). +Then Clean + Build `keyman.dproj` and re-run +`windows/src/engine/build.sh install` elevated. -**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. +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`. +Revert with `git checkout -- windows/src/engine/keyman/manifest.in`. -### 3.3 engine.groupproj / insthelper path ambiguity +## 6. Debugging -There is a real path ambiguity in the engine tree: +* **DLLs (kmcomapi, keymanhp) and library projects:** Run → + Parameters → **Host Application** = + `C:\Program Files (x86)\Keyman\Keyman Desktop\kmshell.exe`. +* **Long-running processes (kmshell, TSF text service):** Run → + **Attach to Process**. Full symbols work only after overlay (§4 + step 7); without overlay the IDE loads source but line numbers + won't match. +* **C++ engine pieces (keyman32, kmtip, mcompile):** Visual Studio → + Attach to Process; load `.pdb` from `windows/bin/`. -* 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. +## 7. Troubleshooting -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 +Source-compilation errors (`E2010 Cardinal/Boolean`, `E2003 Undeclared +identifier 'OldCreateOrder' / 'null'`, `E2029 Declaration expected`, +`E2012 Type of expression must be BOOLEAN`) are handled by +[PR #16043](https://github.com/keymanapp/keyman/pull/16043) — apply +that PR first. ### `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). +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`. -### `F1026 File not found: 'version.res'` or `'manifest.res'` +### `F1026 File not found: MessageIdentifierConsts.pas` / `Keyman.Setup.System.Locale..pas` -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. +Codegen (a). Ensure devtools was built via `build.sh build` (not just +opened in the IDE) — the wrapper is what runs codegen after the IDE +prompt. -### `F1026 File not found: 'jvcl.inc'` / `'jedi/jedi.inc'` / `'jcl.inc'` +### `F1026 File not found: Keyman.System.Standards.BCP47SubtagRegistry.pas` -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. +Codegen (b). Rerun +`common/windows/delphi/tools/build_standards_data/build.sh build`. -### `E2010 Incompatible types: 'Cardinal' and 'Boolean'` (JCL) +### `F1026 File not found: tsysinfo_x64.res` -JCL Boolean -> BOOL casts missing. See section 3.4. +Dependency (c). Let `windows/src/engine/build.sh` sequence +`tsysinfox64` before `tsysinfo`; don't skip prompts. -### `E2003 Undeclared identifier: 'OldCreateOrder'` (JVCL) +### `F1026 File not found: version.res` or `manifest.res` -JvComponent.pas not patched. See section 3.5. +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. -### `E2003 Undeclared identifier: 'null'` (HTMLColors) +### `F1026 File not found: jvcl.inc` / `jedi/jedi.inc` / `jcl.inc` -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. +Library Search Paths not registered. See §3. Close and reopen Delphi +after the registry edit. ### `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. +JVCL `run/` (or `design/`) missing from Library Search Path. See §3. -### `Class not registered ClassID {CF46549D-...}` on kmshell launch +### `SKApplicationTitle has had a fatal error` on kmshell launch -`kmcomapi.dll` isn't registered. From an elevated Git Bash: - -```bash -cd "$KEYMAN_ROOT/windows/src/engine/kmcomapi" -./build.sh install -``` +Keyman 19 official isn't installed. Install from +https://keyman.com/desktop. -This re-registers the DLL automatically. If you want to run `regsvr32` directly: +### `Class not registered {CF46549D-...}` on kmshell launch -```powershell -& regsvr32 /s 'C:\Program Files (x86)\Common Files\Keyman\Keyman Engine\kmcomapi.dll' -``` +`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 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):** +Windows blocked an unsigned `uiAccess="true"` binary. See §5. -* `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. +### Delphi pops up an "Unsupported CEF version" dialog -## Reverting before a PR +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). -Before opening a PR, revert all section 3 patches and remove the section -2.7 registry entries. The simplest check: +## 8. Reverting before a PR ```bash -git status # should show only intentional changes -git diff -- '*.res' # should be empty (preflight .res are local-only) +git status # only intentional changes +git diff -- '*.res' # empty (preflight .res are local) ``` -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: +Restore the manifest and Library Search Paths: ```powershell -reg import C:\Projects\keyman\keyman\delphi-library-paths.backup.reg +git checkout -- windows/src/engine/keyman/manifest.in +reg import delphi-library-paths.backup.reg ``` From 83a61b8b0c6b252c14ba9bbc48e6cc018cc361f8 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 10:11:43 -0500 Subject: [PATCH 11/14] docs: address @mcdurdin's remaining review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Working through the inline comments that the initial rework didn't already resolve: - Fix TKeymanPaths.KeymanDesktopInstallPath() explanation: the path is read from a registry key the installer populates, not hardcoded. Reword the prereq accordingly and the corresponding troubleshooting entry. - Soften "Keyman 19 required" to "strongly recommended". It is possible to debug without an official install; the install just simplifies the setup by giving you a working system to overlay onto. Also fix the URL (keyman.com/windows, not /desktop). - Switch env-var setup to SETX to match the style used throughout windows.md. - Replace the manual PowerShell manifest.in patch with `./build.sh debug-manifest`, which swaps in the pre-existing debug-manifest.in template that already exists for this purpose. - Fix dependency (e) failure description: the runtime failure appears when launching Keyman, not on enable-keyboard. - Trim the debugging section: drop the incorrect claim about symbol behavior and note that C++/Delphi cross-stepping is out of scope (windbg territory). - Drop the canonical numbered build order - the script prompts the developer in the right order already, the list was redundant. - Drop §8 "reverting before a PR" - covered by the §5 revert note and §3 backup restore. - Remove the local-workflow entries from .gitignore (delphi-library-paths.backup*.reg, local-delphi-12-patches.patch, the six *.log entries). These files aren't generated by any repo script - they were leftovers from my local build workflow. Doc now ~245 lines vs. 272 in the first rework, ~939 originally. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 10 --- docs/build/windows-delphi-ce.md | 134 +++++++++++++------------------- 2 files changed, 54 insertions(+), 90 deletions(-) diff --git a/.gitignore b/.gitignore index 4c84ed419b3..dd7d41d3709 100644 --- a/.gitignore +++ b/.gitignore @@ -187,13 +187,3 @@ lcov.info # flag file for build script .configured - -# 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 diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md index c4d85a8f7a6..6b14a5a0c0e 100644 --- a/docs/build/windows-delphi-ce.md +++ b/docs/build/windows-delphi-ce.md @@ -17,10 +17,6 @@ build. The source-compat patches Delphi 11/12 need to compile ship via [PR #16043](https://github.com/keymanapp/keyman/pull/16043); make sure it's merged or applied before proceeding here. -> [!IMPORTANT] -> Registry edits (§3) and the manifest patch (§5) are local-only. -> Revert before opening any PR — CI runs against Delphi 10.3. - ## 1. Prerequisites (delta from windows.md) * **Delphi 12 Athens Community Edition** from @@ -29,27 +25,29 @@ sure it's merged or applied before proceeding here. 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/desktop. - Required, not optional: `TKeymanPaths.KeymanDesktopInstallPath()` - runtime-checks `C:\Program Files (x86)\Keyman\Keyman Desktop\` for - support files. Without an install, kmshell dies at startup with - `SKApplicationTitle has had a fatal error` before its main form - appears. +* **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. ## 2. Environment variables -Set both, persistently: +Set both: -```powershell -[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_VERSION', '23.0', 'User') -[Environment]::SetEnvironmentVariable('KEYMAN_DELPHI_CE', '1', 'User') +```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: +* `KEYMAN_DELPHI_CE=1` (this PR) makes `delphi_msbuild` prompt at + each Delphi step: ``` Delphi CE: CLI compilation is not available. @@ -57,15 +55,17 @@ Set both, persistently: Enter to continue (or Ctrl-C to abort). ``` -Open a fresh shell after `SetEnvironmentVariable` for the values to -be visible. +`SETX` is persistent but does not affect the current shell — open a +fresh shell to pick the values up. ## 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 per-user registry — they mirror `DELPHIINCLUDES` in +[`resources/build/win/delphi_flags.inc.sh`](../../resources/build/win/delphi_flags.inc.sh), +so if that file gains or drops paths, update this recipe to match. **Close Delphi first** — the IDE caches this value at startup. @@ -119,8 +119,9 @@ Delphi → Library** in the IDE. 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`. Under the standard flow `build.sh` sequences these -invisibly; under CE you must respect them at IDE-prompt time. +`.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 | |---|---------------------|---------------------| @@ -129,29 +130,18 @@ invisibly; under CE you must respect them at IDE-prompt time. | b | `build_standards_data.exe` → TIKE.dproj | `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` | | c | `tsysinfox64.exe` → `tsysinfo_x64.res` → tsysinfo.dproj | `F1026 File not found: 'tsysinfo_x64.res'` | | d | `kmcmplib` (CLI build) → TIKE runtime | `"kmcmplib-19.dll not found"` at runtime | -| e | keyman.dproj → kmshell.dproj runtime | `"Keyman failed to start"` when enabling a keyboard | +| e | keyman.dproj → kmshell.dproj runtime | `"Keyman failed to start"` when launching Keyman | | f | `regsvr32 kmcomapi.dll` → kmshell startup | `Class not registered {CF46549D-...}` (`REGDB_E_CLASSNOTREG`) | All generated `.pas` files are `.gitignored` and must be regenerated after `git clean -fdx`. -The canonical CE build order: - -1. CLI-build everything CLI-buildable: `./core/build.sh build`, - `./developer/src/kmcmplib/build.sh build`, TypeScript modules, - `./web/build.sh`. C++ engine pieces can wait. -2. `cd common/windows/delphi/tools/devtools && ./build.sh build` - (prompted for IDE build; codegen (a) runs after Enter). -3. `cd common/windows/delphi/tools/build_standards_data && ./build.sh build` - (prompted for IDE; codegen (b) runs after Enter). -4. `cd windows/src/engine && ./build.sh build` — `build.sh` handles - dependency (c) by building `tsysinfox64.dproj` before - `tsysinfo.dproj`; just accept prompts in order. -5. `cd windows/src/desktop && ./build.sh build`. -6. `cd developer/src && ./build.sh build`. -7. **Elevated Git Bash:** `cd windows/src/engine && ./build.sh install`, - then `cd windows/src/desktop && ./build.sh install`. Handles (f) - automatically. +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). > [!IMPORTANT] > After an IDE build following any `.res`, manifest, version, or icon @@ -161,44 +151,40 @@ The canonical CE build order: ## 5. Local-only: uiAccess strip for overlaid keyman.exe Windows refuses to launch unsigned binaries declaring -`uiAccess="true"` (error 8235). The overlay workflow (§4 step 7) -replaces the installed signed `keyman.exe` with an unsigned dev -build, so uiAccess must be turned off for the overlay to launch. -**Do not commit:** +`uiAccess="true"` (error 8235). To run an unsigned dev `keyman.exe`, +swap in the pre-existing non-elevated manifest: -```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 +```bash +cd windows/src/engine/keyman +./build.sh debug-manifest ``` -Then Clean + Build `keyman.dproj` and re-run -`windows/src/engine/build.sh install` elevated. - -Trade-off: keyboard injection into elevated apps stops working until -uiAccess is restored and the binary is signed via Keyman's test-cert -pipeline. +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. -Revert with `git checkout -- windows/src/engine/keyman/manifest.in`. +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 -* **DLLs (kmcomapi, keymanhp) and library projects:** Run → - Parameters → **Host Application** = - `C:\Program Files (x86)\Keyman\Keyman Desktop\kmshell.exe`. +* **DLLs (kmcomapi, keymanhp):** Run → Parameters → **Host + Application** = `\kmshell.exe`. * **Long-running processes (kmshell, TSF text service):** Run → - **Attach to Process**. Full symbols work only after overlay (§4 - step 7); without overlay the IDE loads source but line numbers - won't match. + **Attach to Process**. * **C++ engine pieces (keyman32, kmtip, mcompile):** Visual Studio → - Attach to Process; load `.pdb` from `windows/bin/`. + Attach to Process; load `.pdb` from `windows/bin/`. Note that + stepping between `keyman.exe` (Delphi) and `keyman32.dll` (C++) is + not straightforward — `windbg` can bridge but is out of scope here. ## 7. Troubleshooting -Source-compilation errors (`E2010 Cardinal/Boolean`, `E2003 Undeclared -identifier 'OldCreateOrder' / 'null'`, `E2029 Declaration expected`, -`E2012 Type of expression must be BOOLEAN`) are handled by -[PR #16043](https://github.com/keymanapp/keyman/pull/16043) — apply -that PR first. +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 +[PR #16043](https://github.com/keymanapp/keyman/pull/16043) — make +sure that PR is applied. ### `This version of the product does not support command line compiling` @@ -239,8 +225,10 @@ JVCL `run/` (or `design/`) missing from Library Search Path. See §3. ### `SKApplicationTitle has had a fatal error` on kmshell launch -Keyman 19 official isn't installed. Install from -https://keyman.com/desktop. +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 @@ -256,17 +244,3 @@ Windows blocked an unsigned `uiAccess="true"` binary. See §5. 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). - -## 8. Reverting before a PR - -```bash -git status # only intentional changes -git diff -- '*.res' # empty (preflight .res are local) -``` - -Restore the manifest and Library Search Paths: - -```powershell -git checkout -- windows/src/engine/keyman/manifest.in -reg import delphi-library-paths.backup.reg -``` From 9603b1b56234760bbbcddf6e61b0579f5cf346fb Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 2 Jul 2026 11:08:18 -0500 Subject: [PATCH 12/14] docs: correct DELPHIINCLUDES mirroring claim, tighten cross-refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §3 registry recipe: only the first six paths mirror DELPHIINCLUDES in delphi_flags.inc.sh (verified: that variable defines exactly those six). The remaining eleven JCL/JVCL/jedi paths are IDE-only and have no equivalent in the CLI build path; my earlier claim that all seventeen mirror DELPHIINCLUDES was wrong. - Replace two "see PR #16043" references with pointers to windows.md's Delphi requirements section, which will remain the canonical entry point after 16043 merges (PR-number cross-refs age badly). - §6 debugging: add one-line acknowledgment that the content isn't CE-specific but has no other current home, per review feedback that the section drifts from the doc's stated scope. Co-Authored-By: Claude Opus 4.7 --- docs/build/windows-delphi-ce.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md index 6b14a5a0c0e..5c08cf9d1b0 100644 --- a/docs/build/windows-delphi-ce.md +++ b/docs/build/windows-delphi-ce.md @@ -13,9 +13,10 @@ 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. The source-compat patches Delphi 11/12 need to compile ship -via [PR #16043](https://github.com/keymanapp/keyman/pull/16043); make -sure it's merged or applied before proceeding here. +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) @@ -63,9 +64,11 @@ fresh shell to pick the values up. `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 — they mirror `DELPHIINCLUDES` in -[`resources/build/win/delphi_flags.inc.sh`](../../resources/build/win/delphi_flags.inc.sh), -so if that file gains or drops paths, update this recipe to match. +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. @@ -169,22 +172,26 @@ the debug manifest. Not committed. Revert with ## 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/`. Note that - stepping between `keyman.exe` (Delphi) and `keyman32.dll` (C++) is - not straightforward — `windbg` can bridge but is out of scope here. + 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 -[PR #16043](https://github.com/keymanapp/keyman/pull/16043) — make -sure that PR is applied. +`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` From b6b9b7ec1e87af202fc587355a020d404c17b427 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 14:31:42 -0500 Subject: [PATCH 13/14] docs: harvest CE workflow gaps discovered during real build walkthrough MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran the full CE workflow end-to-end against Delphi 12 CE and hit six distinct failure modes not previously covered in the doc. Adding each as either a §1/§2/§4 setup step or a §7 troubleshooting entry so the next contributor doesn't rediscover them. Setup steps added to §1 Prerequisites: - Test-signing certificates one-time setup via common/windows/delphi/tools/certificates/build.sh certificates. Skipping this makes every wrap-signcode call fail with 'SignTool Error: File not found: keymantest-sha1.pfx'. Prominent CAUTION added to §2 Environment variables: - If KEYMAN_DELPHI_CE isn't set in the current shell, delphi_msbuild falls through to msbuild.exe, which on CE produces a fake 'Build succeeded... Time Elapsed 00:00:0X.XX' with no output. The downstream cp/mv/mt.exe/sentrytool then fail on paths where Delphi never actually wrote anything. This is by far the most confusing failure mode in the whole workflow. Document the verification command and require it in every fresh shell. §4 build order fixes: - Row (a) was incomplete: MessageIdentifierConsts.pas is not produced by devtools/build.sh, it's produced by windows/src/global/delphi/build.sh, which also produces keyman_components.bpl. Rewrote the row to name the actual producer script and split the setup-locale codegen into row (a'). - Row (c): named tsysinfo/build.sh as the orchestrator (the .dproj and .groupproj don't do the tsysinfox64->exe->res chain; the build.sh do_build does). - New sub-section: multi-platform Delphi packages (kmcomapi builds once for Win32 and once for Win64 - toggle Delphi's Platform dropdown between the two consecutive CE prompts). - New sub-section: full-clean bin/obj when sentrytool_delphiprep complains about residual debug sections. §7 troubleshooting additions covering: - 'Build succeeded... Time Elapsed' + downstream missing-file (the env-var-lost signature) - 'SignTool Error: keymantest-sha1.pfx' (certificates missing) - 'This executable has a debug section. Not able to update' (partial prior build) - 'mv: cannot stat bin/Win64/Debug/...dll' (Platform toggle missed) - 'EVariantTypeCastError' and 'E2010 TCustFile and TObject' pointing to the two Delphi-12-specific source-compat patches now in #16043. Doc grew 253 -> 359 lines. All additions are grounded in real failures observed in the walkthrough, not speculation. Co-Authored-By: Claude Opus 4.7 --- docs/build/windows-delphi-ce.md | 126 +++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 10 deletions(-) diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md index 5c08cf9d1b0..aaaa6808a1a 100644 --- a/docs/build/windows-delphi-ce.md +++ b/docs/build/windows-delphi-ce.md @@ -33,6 +33,21 @@ and are separate from the CE-specific work below. 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 @@ -59,6 +74,23 @@ SETX KEYMAN_DELPHI_CE 1 `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. @@ -128,13 +160,13 @@ a reference of what each prompt is producing for what dependent: | # | Producer → Consumer | Failure if skipped | |---|---------------------|---------------------| -| a | `devtools.exe -buildmessageconstants` → keyman.dproj, kmshell.dproj | `F1026 File not found: 'MessageIdentifierConsts.pas'` | -| a | `devtools.exe -buildsetupstrings` → setup.dproj | `F1026 File not found: 'Keyman.Setup.System.Locale..pas'` | -| b | `build_standards_data.exe` → TIKE.dproj | `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` | -| c | `tsysinfox64.exe` → `tsysinfo_x64.res` → tsysinfo.dproj | `F1026 File not found: 'tsysinfo_x64.res'` | -| d | `kmcmplib` (CLI build) → TIKE runtime | `"kmcmplib-19.dll not found"` at runtime | +| 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` → kmshell startup | `Class not registered {CF46549D-...}` (`REGDB_E_CLASSNOTREG`) | +| 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`. @@ -146,6 +178,31 @@ 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 @@ -199,11 +256,19 @@ 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`. -### `F1026 File not found: MessageIdentifierConsts.pas` / `Keyman.Setup.System.Locale..pas` +### `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). Ensure devtools was built via `build.sh build` (not just -opened in the IDE) — the wrapper is what runs codegen after the IDE -prompt. +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` @@ -251,3 +316,44 @@ Windows blocked an unsigned `uiAccess="true"` binary. See §5. 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. From 52ba14e474c26ed71b2e00b35cc9ef4b8b5f566d Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 14:37:46 -0500 Subject: [PATCH 14/14] feat(windows): guard delphi_msbuild against silent CE build failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a developer is on Delphi Community Edition but forgets to set KEYMAN_DELPHI_CE=1, `delphi_msbuild` falls through to msbuild.exe, which — on CE — reports "Build succeeded" and a small Time Elapsed line but produces no output. Downstream cp/mv/mt.exe/sentrytool then fail on paths where Delphi never actually wrote anything, and diagnosing the root cause takes ~30 minutes of confused debugging. Detect CE via a one-time dcc32.exe probe (dcc32 on CE prints "This version of the product does not support command line compiling" and exits non-zero; on Pro it accepts args). If CE is detected and KEYMAN_DELPHI_CE isn't set, `builder_die` with a clear message pointing at docs/build/windows-delphi-ce.md. Result is cached in shell-local vars so subsequent delphi_msbuild calls don't re-spawn dcc32. CI unchanged: CI runs against Delphi 10.3 Pro where the probe returns non-CE and delphi_msbuild proceeds normally. Co-Authored-By: Claude Opus 4.7 --- resources/build/win/environment.inc.sh | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index eed75080f28..4f5363b060a 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -99,6 +99,26 @@ 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. @@ -109,6 +129,14 @@ delphi_msbuild() { 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" }