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 4d090204864fae54b9f2a159176c89a47717ac19 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 15:04:12 -0500 Subject: [PATCH 09/14] =?UTF-8?q?Reapply=20JclSynch.pas=20BOOL=20casts=20?= =?UTF-8?q?=E2=80=94=20needed=20by=20TIKE=20compile=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the earlier revert (dcef4c29cf). @mcdurdin's original empirical test — building without the JclSynch patches to prove they were unnecessary — was correct for the projects he compiled, but it did not include TIKE. TIKE pulls in JCL through JVCL's dependency graph and instantiates TJclMutex.Create (JclSynch.pas:1078). On Delphi 12, that hits: [dcc32 Error] JclSynch.pas(1078): E2010 Incompatible types: 'Cardinal' and 'Boolean' [dcc32 Fatal Error] JclSysUtils.pas(2685): F2063 Could not compile used unit 'JclSynch.pas' The BOOL() casts on CreateEvent/OpenEvent/CreateWaitableTimer/ OpenWaitableTimer/OpenSemaphore/CreateMutex/OpenMutex are what let TIKE compile. Marc's smaller test set was Delphi-12-compatible without them; the fuller Developer tree isn't. Discovered by running the CE build workflow through `developer/src/tike/build.sh` on Delphi 12 CE — TIKE fails at the JclSynch link stage without this patch. Note the CreateMutex site also switches JclWin32.CreateMutex to Winapi.Windows.CreateMutex directly, because JclWin32's own CreateMutex is a re-declaration Delphi 12 resolves ambiguously. Co-Authored-By: Claude Opus 4.7 --- .../jedi/jcl/jcl/source/common/JclSynch.pas | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 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 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; From fc6978675ab9f0f9e9500bb889792e3bfb3c4892 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 3 Jul 2026 15:11:53 -0500 Subject: [PATCH 10/14] =?UTF-8?q?Reapply=20JvComponent.pas=20OldCreateOrde?= =?UTF-8?q?r=20patch=20=E2=80=94=20needed=20by=20TIKE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the earlier revert (d7b3784f07). @mcdurdin's original concern was that the Athens docs seemed to say OldCreateOrder was still present, so the patch's premise was wrong. Empirical Delphi 12 CE contradicts that: [dcc32 Error] JvComponent.pas(126): E2003 Undeclared identifier: 'OldCreateOrder' [dcc32 Fatal Error] JvDockControlForm.pas(2442): F2063 Could not compile used unit 'JvComponent.pas' Line 126 is `if OldCreateOrder then DoCreate;`. Under Delphi 12, `TCustomForm.OldCreateOrder` genuinely no longer exists. The {$IF Defined(VER350) or Defined(VER360)} arm unconditionally calls DoCreate on 11/12 (equivalent to OldCreateOrder=True, which the docs describe as the modern default) and keeps the runtime check on older Delphis. Discovered by running the CE build workflow through `developer/src/tike/build.sh` on Delphi 12 CE — TIKE pulls in JvDockControlForm which uses JvComponent, and TIKE fails without this patch. The docs Marc linked may still be reachable, but the compiler is the source of truth here — VER350+ has removed OldCreateOrder from TCustomForm's published surface. Co-Authored-By: Claude Opus 4.7 --- developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas | 8 ++++++++ 1 file changed, 8 insertions(+) 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; From 7ebe6117fcf86c13fbdd3e098e20f92d288a3488 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 9 Jul 2026 17:58:55 -0500 Subject: [PATCH 11/14] Using the upstream guard for JvComponent.pas Missing this guard means that tike fails to build on a fresh install without the OLDCREATEORDER flag. --- developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas | 10 ++-------- 1 file changed, 2 insertions(+), 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..9c0cd1e9719 100644 --- a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas +++ b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas @@ -123,16 +123,10 @@ 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} + {$IFDEF HAS_PROPERTY_OLDCREATEORDER} if OldCreateOrder then DoCreate; -{$IFEND} + {$ENDIF HAS_PROPERTY_OLDCREATEORDER} end; finally GlobalNameSpace.EndWrite; From 1c32b233ba7fd0c68cfffa62788d4c3e2717f81e Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 9 Jul 2026 21:39:45 -0500 Subject: [PATCH 12/14] fix(windows): align vendored JCL with upstream, apply review suggestions Revert the JclSynch.pas BOOL() cast patch to match project-jedi/jcl upstream verbatim at all 7 call sites (no casts; restore JclWin32.CreateMutex). TIKE builds without it once the JvComponent.pas OldCreateOrder guard (already the upstream guard) is in place. Apply mcdurdin's inline review suggestions on FixedTrackbar.pas and Keyman.System.HttpServer.Base.pas: reorder version guards newest-outermost, consolidate messages, and convert the raw ERROR! line to a {$MESSAGE ERROR} directive. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../delphi/components/FixedTrackbar.pas | 9 ++++----- .../web/Keyman.System.HttpServer.Base.pas | 3 ++- .../jedi/jcl/jcl/source/common/JclSynch.pas | 18 +++++++----------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas index 9e900c2c0d9..9696062a62e 100644 --- a/common/windows/delphi/components/FixedTrackbar.pas +++ b/common/windows/delphi/components/FixedTrackbar.pas @@ -64,16 +64,15 @@ 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. + TODO: Not yet fully verified against Vcl.Grids.pas in VER350 (11) or VER360 (12) } +{$IFNDEF VER360} +{$IFNDEF VER350} {$IFNDEF VER340} -{$MESSAGE WARN 'Not yet checked against Delphi 10.4'} +{$MESSAGE WARN 'TODO: Trackbar scrolling on bottom cell not yet checked against Delphi 10.4, 11.0 or 12.0'} {$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} diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index bec398bcbca..4e28593a7c4 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -51,9 +51,10 @@ function CrackUTF8ZeroExtendedString(CommandType: THTTPCommandType; const p: str // workaround stays applied conservatively. {$IFNDEF VER330} {$IFNDEF VER340} + {$MESSAGE WARN 'TODO: Check if Indy URL UTF-8 handling is still needed with Delphi 10.4/11.0/12.0'} {$IFNDEF VER350} {$IFNDEF VER360} - ERROR! Check if this is still needed with Delphi update + {$MESSAGE ERROR 'Check if Indy URL UTF-8 handling is still needed with Delphi update'} {$ENDIF} {$ENDIF} {$ENDIF} 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 e363227eb056d1a0ce333dec57ae4155979cc245 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 9 Jul 2026 22:13:38 -0500 Subject: [PATCH 13/14] style(windows): use {$IF Defined()} guards instead of nested {$IFNDEF} Collapse the nested {$IFNDEF} version walls in FixedTrackbar.pas and Keyman.System.HttpServer.Base.pas into single {$IF Defined(VERxxx) or ...} / {$ELSEIF} / {$ELSE} blocks, matching the style already used in CleartypeDrawCharacter.pas and JsonUtil.pas. Expressed positively: WARN on the unblocked-but-unverified Delphi 10.4/11/12 (VER340/VER350/VER360), silent on the tested baselines, and ERROR on any unrecognised version. No change to the runtime workarounds. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../delphi/components/FixedTrackbar.pas | 15 +++++-------- .../web/Keyman.System.HttpServer.Base.pas | 21 ++++++++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas index 9696062a62e..096d6340415 100644 --- a/common/windows/delphi/components/FixedTrackbar.pas +++ b/common/windows/delphi/components/FixedTrackbar.pas @@ -67,18 +67,13 @@ procedure TTntFixedDrawGrid.WMEraseBkgnd(var Message: TMessage); TODO: Not yet fully verified against Vcl.Grids.pas in VER350 (11) or VER360 (12) } -{$IFNDEF VER360} -{$IFNDEF VER350} -{$IFNDEF VER340} +{$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} {$MESSAGE WARN 'TODO: Trackbar scrolling on bottom cell not yet checked against Delphi 10.4, 11.0 or 12.0'} -{$IFNDEF VER330} -{$IFNDEF VER320} +{$ELSEIF Defined(VER320) or Defined(VER330)} +// Tested on Delphi 10.2 (VER320) and 10.3 (VER330) +{$ELSE} {$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} +{$IFEND} procedure TTntFixedDrawGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index 4e28593a7c4..fbdb7a0e65b 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -45,20 +45,17 @@ 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. - // 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} + // VER340/VER350/VER360 (Delphi 10.4/11/12) are treated as unblocked- + // but-unverified: whether Indy's URL handling was fixed in those + // versions has not been re-verified — the workaround stays applied + // conservatively. +{$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} {$MESSAGE WARN 'TODO: Check if Indy URL UTF-8 handling is still needed with Delphi 10.4/11.0/12.0'} -{$IFNDEF VER350} -{$IFNDEF VER360} +{$ELSEIF Defined(VER330)} + // Verified against Delphi 10.3 (VER330) +{$ELSE} {$MESSAGE ERROR 'Check if Indy URL UTF-8 handling is still needed with Delphi update'} -{$ENDIF} -{$ENDIF} -{$ENDIF} -{$ENDIF} +{$IFEND} SetLength(s, p.Length); for i := 1 to p.Length do From 90792cfa9c84d29be4095282c9fb12d285b382f7 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Thu, 9 Jul 2026 22:59:02 -0500 Subject: [PATCH 14/14] fix(windows): route JclSynch CreateMutex via RTL for Delphi 12 source build TJclMutex.Create called JclWin32.CreateMutex, whose `external kernel32` redeclaration takes a BOOL param (legacy WinNT4 workaround). Delphi 12's dcc32 rejects the implicit Boolean->BOOL there (E2010), which blocks a from-source build of the vendored JCL. Route through the RTL like the sibling OpenMutex, with an explicit BOOL() cast. Only the CreateMutex site is touched; the other calls already go through the RTL. Builds that link a precompiled JCL .dcu never hit this. Also compact the local-patch comments in mxs.inc and HttpServer.Base.pas. Co-Authored-By: Claude Opus 4.8 (1M context) --- common/windows/delphi/web/Keyman.System.HttpServer.Base.pas | 5 +---- developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas | 5 ++++- developer/src/ext/mbcolor/mxs.inc | 6 ++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index fbdb7a0e65b..9af0ac60e96 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -45,10 +45,7 @@ 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. - // VER340/VER350/VER360 (Delphi 10.4/11/12) are treated as unblocked- - // but-unverified: whether Indy's URL handling was fixed in those - // versions has not been re-verified — the workaround stays applied - // conservatively. + // VER340/VER350/VER360 (10.4/11/12): unblocked but not re-verified; workaround kept. {$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} {$MESSAGE WARN 'TODO: Check if Indy URL UTF-8 handling is still needed with Delphi 10.4/11.0/12.0'} {$ELSEIF Defined(VER330)} 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..c014318f09e 100644 --- a/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas +++ b/developer/src/ext/jedi/jcl/jcl/source/common/JclSynch.pas @@ -1075,7 +1075,10 @@ constructor TJclMutex.Create(SecAttr: PSecurityAttributes; InitialOwner: Boolean begin inherited Create; FName := Name; - FHandle := JclWin32.CreateMutex(SecAttr, InitialOwner, PChar(Name)); + // Keyman patch (D12, source-compiled JCL): JclWin32.CreateMutex is an external + // decl with a BOOL param; D12 rejects Boolean->BOOL there (E2010). Route via the + // RTL like OpenMutex below. Only bites when JCL is built from source. + 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; diff --git a/developer/src/ext/mbcolor/mxs.inc b/developer/src/ext/mbcolor/mxs.inc index 75ca0b34e73..358ee4b84b3 100644 --- a/developer/src/ext/mbcolor/mxs.inc +++ b/developer/src/ext/mbcolor/mxs.inc @@ -7,10 +7,8 @@ {$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. + // Keyman patch (D11/12, vendored mbcolor): define DELPHI_*_UP on VER350/VER360 + // too, else HTMLColors.pas drops "uses Variants" and Null won't resolve. {$ifdef VER350} {$define DELPHI_5_UP} {$define DELPHI_6_UP}