diff --git a/.gitignore b/.gitignore index 7b872195880..dd7d41d3709 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ /windows/src/**/*.identcache /windows/src/**/*.vcxproj.user /windows/src/**/version.res +/windows/src/**/version*.res /windows/src/**/*.pch /windows/src/**/*.wixobj /windows/src/**/*.sbr diff --git a/common/windows/delphi/components/FixedTrackbar.pas b/common/windows/delphi/components/FixedTrackbar.pas index 50cb444a55f..096d6340415 100644 --- a/common/windows/delphi/components/FixedTrackbar.pas +++ b/common/windows/delphi/components/FixedTrackbar.pas @@ -64,16 +64,16 @@ procedure TTntFixedDrawGrid.WMEraseBkgnd(var Message: TMessage); Tested on VER320 (10.2) Tested on VER330 (10.3) - 29 Oct 2019 - mcdurdin + TODO: Not yet fully verified against Vcl.Grids.pas in VER350 (11) or VER360 (12) } -{$IFNDEF VER340} -{$MESSAGE WARN 'Not yet checked against Delphi 10.4'} -{$IFNDEF VER330} -{$IFNDEF VER320} +{$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'} +{$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} +{$IFEND} procedure TTntFixedDrawGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); diff --git a/common/windows/delphi/general/CleartypeDrawCharacter.pas b/common/windows/delphi/general/CleartypeDrawCharacter.pas index 6d1a51976de..44562012ed2 100644 --- a/common/windows/delphi/general/CleartypeDrawCharacter.pas +++ b/common/windows/delphi/general/CleartypeDrawCharacter.pas @@ -591,11 +591,11 @@ function TestFont(FFontName: string): Boolean; StrPCopy(lf.lfFaceName, FFontName); //'Code2000'); hdc := GetDC(0); //FPlane0FontName := 'Code2000'; -{$IFDEF VER340} +{$IF Defined(VER340) or Defined(VER350) or Defined(VER360)} if EnumFontFamiliesEx(hdc, lf, @EnumFallbackFonts, 0, 0) <> 0 then {$ELSE} if EnumFontFamiliesEx(hdc, lf, @EnumFallbackFonts, 0, 0) then -{$ENDIF} +{$IFEND} begin FPlane0FontName := FFontName; Result := True; diff --git a/common/windows/delphi/general/JsonUtil.pas b/common/windows/delphi/general/JsonUtil.pas index ae2b9b0e9c6..16a90bc729d 100644 --- a/common/windows/delphi/general/JsonUtil.pas +++ b/common/windows/delphi/general/JsonUtil.pas @@ -52,7 +52,11 @@ function JSONToString(obj: TJSONAncestor; ReplaceSlashes: Boolean = False): stri begin builder := TStringBuilder.Create; try +{$IF Defined(VER350) or Defined(VER360)} + obj.ToChars(builder, []); +{$ELSE} obj.ToChars(builder); +{$IFEND} Result := builder.ToString; finally builder.Free; diff --git a/common/windows/delphi/tools/devtools/DevIncludePaths.pas b/common/windows/delphi/tools/devtools/DevIncludePaths.pas index 509045d4d17..d6df62621df 100644 --- a/common/windows/delphi/tools/devtools/DevIncludePaths.pas +++ b/common/windows/delphi/tools/devtools/DevIncludePaths.pas @@ -144,6 +144,7 @@ class function TIncludePaths.AddPathToProjectXML(const ProjectXMLFileName, Path: doc: IXMLDocument; sn, node: IXMLNode; IncludePath: string; + Condition: string; I: Integer; begin if not FileExists(ProjectXMLFileName) then @@ -159,16 +160,20 @@ class function TIncludePaths.AddPathToProjectXML(const ProjectXMLFileName, Path: for I := 0 to node.ChildNodes.Count - 1 do begin sn := node.ChildNodes[I]; + // Delphi 12 EnvOptions.proj emits an empty without a + // Condition attribute; convert defensively via VarToStrDef so a Null or + // Empty variant returns '' instead of raising EVariantTypeCastError in Pos(). + Condition := VarToStrDef(sn.Attributes['Condition'], ''); if (sn.NodeName = 'PropertyGroup') and - not VarIsNull(sn.Attributes['Condition']) and - ((Pos('Win32', sn.Attributes['Condition']) > 0) or - (Pos('Win64', sn.Attributes['Condition']) > 0)) then + ((Pos('''Win32''', Condition) > 0) or (Pos('''Win64''', Condition) > 0)) then begin - IncludePath := sn.ChildNodes['DelphiBrowsingPath'].NodeValue; + // Guard against empty child nodes (e.g. ) whose + // NodeValue is Null on Delphi 12 and can't coerce to a string directly. + IncludePath := VarToStrDef(sn.ChildNodes['DelphiBrowsingPath'].NodeValue, ''); if AddPathToIncludePath(IncludePath, Path) then sn.ChildNodes['DelphiBrowsingPath'].nodeValue := IncludePath; - IncludePath := sn.ChildNodes['DelphiLibraryPath'].NodeValue; + IncludePath := VarToStrDef(sn.ChildNodes['DelphiLibraryPath'].NodeValue, ''); if AddPathToIncludePath(IncludePath, Path) then sn.ChildNodes['DelphiLibraryPath'].nodeValue := IncludePath; end; @@ -256,6 +261,7 @@ class function TIncludePaths.Reset: Boolean; doc: IXMLDocument; node: IXMLNode; ProjectFileName: string; + Condition: string; I: Integer; sn: IXMLNode; begin @@ -296,10 +302,11 @@ class function TIncludePaths.Reset: Boolean; for I := 0 to node.ChildNodes.Count - 1 do begin sn := node.ChildNodes[I]; + // See AddPathToProjectXML: guard against Delphi 12's empty + // where Attributes['Condition'] returns a Null variant. + Condition := VarToStrDef(sn.Attributes['Condition'], ''); if (sn.NodeName = 'PropertyGroup') and - not VarIsNull(sn.Attributes['Condition']) and - ((Pos('Win32', sn.Attributes['Condition']) > 0) or - (Pos('Win64', sn.Attributes['Condition']) > 0)) then + ((Pos('''Win32''', Condition) > 0) or (Pos('''Win64''', Condition) > 0)) then begin sn.ChildNodes['DelphiBrowsingPath'].NodeValue := SDefault_DelphiBrowsingPath; sn.ChildNodes['DelphiLibraryPath'].NodeValue := SDefault_DelphiSearchPath; diff --git a/common/windows/delphi/tools/devtools/SourceRootPath.pas b/common/windows/delphi/tools/devtools/SourceRootPath.pas index 360477641e7..c8372c3c6e2 100644 --- a/common/windows/delphi/tools/devtools/SourceRootPath.pas +++ b/common/windows/delphi/tools/devtools/SourceRootPath.pas @@ -14,11 +14,19 @@ interface {$IFDEF VER340} const DelphiMajorVersion = '21.0'; {$ELSE} +{$IFDEF VER350} +const DelphiMajorVersion = '22.0'; +{$ELSE} +{$IFDEF VER360} +const DelphiMajorVersion = '23.0'; +{$ELSE} ERROR: must define Delphi version {$ENDIF} {$ENDIF} {$ENDIF} {$ENDIF} +{$ENDIF} +{$ENDIF} const DelphiBasePath = 'C:\Program Files (x86)\Embarcadero\Studio\' + DelphiMajorVersion + '\'; diff --git a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas index d1daf087c39..9af0ac60e96 100644 --- a/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas +++ b/common/windows/delphi/web/Keyman.System.HttpServer.Base.pas @@ -44,10 +44,15 @@ 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 -{$IFNDEF VER330} - ERROR! Check if this is still needed with Delphi update -{$ENDIF} + // We may need to check this with updated versions of Delphi. + // 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)} + // Verified against Delphi 10.3 (VER330) +{$ELSE} + {$MESSAGE ERROR 'Check if Indy URL UTF-8 handling is still needed with Delphi update'} +{$IFEND} SetLength(s, p.Length); for i := 1 to p.Length do 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/jedi/jvcl/jvcl/run/JvComponent.pas b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas index 1877ee48efe..9c0cd1e9719 100644 --- a/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas +++ b/developer/src/ext/jedi/jvcl/jvcl/run/JvComponent.pas @@ -123,8 +123,10 @@ constructor TJvForm.Create(AOwner: TComponent); finally Exclude(FFormState, fsCreating); end; + {$IFDEF HAS_PROPERTY_OLDCREATEORDER} if OldCreateOrder then DoCreate; + {$ENDIF HAS_PROPERTY_OLDCREATEORDER} end; finally GlobalNameSpace.EndWrite; diff --git a/developer/src/ext/mbcolor/mxs.inc b/developer/src/ext/mbcolor/mxs.inc index 41b82ad96e5..358ee4b84b3 100644 --- a/developer/src/ext/mbcolor/mxs.inc +++ b/developer/src/ext/mbcolor/mxs.inc @@ -7,6 +7,26 @@ {$define DELPHI_10_UP} {$endif} + // 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} + {$define DELPHI_7_UP} + {$define DELPHI_8_UP} + {$define DELPHI_9_UP} + {$define DELPHI_10_UP} + {$endif} + + {$ifdef VER360} + {$define DELPHI_5_UP} + {$define DELPHI_6_UP} + {$define DELPHI_7_UP} + {$define DELPHI_8_UP} + {$define DELPHI_9_UP} + {$define DELPHI_10_UP} + {$endif} + {$ifdef VER330} {$define DELPHI_5_UP} {$define DELPHI_6_UP} diff --git a/docs/build/windows-delphi-ce.md b/docs/build/windows-delphi-ce.md new file mode 100644 index 00000000000..0636503b59a --- /dev/null +++ b/docs/build/windows-delphi-ce.md @@ -0,0 +1,412 @@ +# Build Keyman on Windows with Delphi Community Edition + +Delphi CE (11 and 12) blocks CLI `dcc32`, so `build.sh` cannot drive +Delphi builds end-to-end. Setting `KEYMAN_DELPHI_CE=1` switches +`delphi_msbuild` to interactive mode: at each Delphi step the script +pauses and prompts for an IDE build. The surrounding pre/post-build +work (`rc.exe`, manifest generation, codegen, binary copies) still +runs automatically. + +The body is written against Delphi 12 Athens CE (currently the only +free tier). Delphi 11 CE works the same way; only the `Studio\22.0\` +path differs. + +This doc is a delta on top of [windows.md](windows.md) — read that +first for repository layout, base dependencies, and the standard +build. + +## 1. Prerequisites (delta from windows.md) + +* **Delphi 12 Athens Community Edition** from + https://www.embarcadero.com/products/delphi/starter/free-download. + Installs to `C:\Program Files (x86)\Embarcadero\Studio\23.0\`. During + install, select **DUnit Unit Testing Frameworks** (Keyman test + projects require it). Launch the IDE once after install so the + per-user `BDS\23.0` registry hive gets populated. +* **Keyman 19 (official release)** from https://keyman.com/windows is + strongly recommended — it populates the Keyman install-path registry + key that kmshell reads via `TKeymanPaths.KeymanDesktopInstallPath()`, + which simplifies the debugging setup and gives you a working system + to overlay dev binaries onto. It's possible to debug individual + components without an official install, but each component then + needs its support files located manually. +* **Test signing certificates** — one-time setup. Several `build.sh` + scripts run `signtool.exe` against test certificates at + `common/windows/delphi/tools/certificates/`. If the `.pfx` files + don't exist, the `wrap-signcode` step fails with + `SignTool Error: File not found: ...keymantest-sha1.pfx`. Generate + them once via: + + ```bash + ./common/windows/delphi/tools/certificates/build.sh certificates + ``` + + This runs `makecert` + `pvk2pfx` from the Windows SDK and installs + two Keyman test-CA root certificates into your current-user cert + store (via `certutil -user -addstore Root`). To clean up later: + `certutil -user -delstore Root "Keyman Test CA"` and the SHA1 variant. + +## 2. Environment variables + +Set both: + +```bat +SETX KEYMAN_DELPHI_VERSION 23.0 +SETX KEYMAN_DELPHI_CE 1 +``` + +* `KEYMAN_DELPHI_VERSION` tells builder which version of Delphi to target + (defaults to `20.0`). Without it, Delphi targets are silently skipped + if Delphi 10.3 is not installed. +* `KEYMAN_DELPHI_CE=1` makes `delphi_msbuild` prompt at each Delphi step: + + ``` + Delphi CE: CLI compilation is not available. + Please build in the Delphi IDE now, then press + Enter to continue (or Ctrl-C to abort). + ``` + +`SETX` is persistent but does not affect the current shell — open a +fresh shell to pick the values up. + +> [!CAUTION] +> **Verify these variables are set in every terminal you build from.** +> If `KEYMAN_DELPHI_CE` is unset, `delphi_msbuild` falls through to +> `msbuild.exe` which — on Delphi CE — reports `Build succeeded` with +> a small `Time Elapsed` line but produces **no output**. Downstream +> `cp` / `mv` / `sentrytool_delphiprep` / `mt.exe` steps then fail +> with confusing "file not found" errors on paths where Delphi never +> actually wrote anything. If you see a suspiciously fast Delphi +> build followed by a missing-file error, check this first: +> +> ```bash +> echo "KEYMAN_DELPHI_CE='$KEYMAN_DELPHI_CE' KEYMAN_DELPHI_VERSION='$KEYMAN_DELPHI_VERSION'" +> ``` +> +> Both must be set. If either is empty, `export` them in the current +> shell before rerunning any `build.sh`. + +## 3. Delphi IDE Library Search Paths + +`build.sh` passes `-U/-I/-R` flags to `dcc32` that the IDE never sees. +Without them, opening any Keyman `.dproj` fails with `F1026 File not +found: jvcl.inc` / `jedi.inc` / `jcl.inc`. + +Register them with `devtools -ai`, which writes the IDE **Search** and +**Browsing** paths for both Win32 and Win64 (registry + `EnvOptions.proj`) +in one shot — no manual **Tools → Options** step needed. `devtools` has no +JCL/JVCL dependencies, so it builds before the paths exist: build it first +(one of the CE prompts under `windows/src/global/delphi/build.sh build`), +**close Delphi** (it caches library paths at startup), then register each +path (`-ai` takes one absolute path per call): + +```bash +DEVTOOLS="$KEYMAN_ROOT/common/windows/delphi/tools/devtools/bin/Win32/Debug/devtools.exe" +register() { "$DEVTOOLS" -ai "$(cygpath -w "$1")"; } + +# The six CLI include paths, read from their single source of truth: +eval "$(grep '^DELPHIINCLUDES=' "$KEYMAN_ROOT/resources/build/win/delphi_flags.inc.sh")" +IFS=';'; for p in $DELPHIINCLUDES; do register "$p"; done; unset IFS + +# The IDE-only JCL/JVCL/jedi paths (no build-side equivalent): +for p in \ + developer/src/ext/jedi/jcl/jcl/source/common \ + developer/src/ext/jedi/jcl/jcl/source/prototypes \ + developer/src/ext/jedi/jcl/jcl/source/vcl \ + developer/src/ext/jedi/jcl/jcl/source/windows \ + developer/src/ext/jedi/jcl/jcl/source/include \ + developer/src/ext/jedi/jvcl/jvcl/design \ + developer/src/ext/jedi/jvcl/jvcl/run \ + developer/src/ext/jedi/jvcl/jvcl/common \ + developer/src/ext/jedi/jvcl/jvcl/resources \ + developer/src/ext/jedi/jedi \ + developer/src/ext/jedi ; do + register "$KEYMAN_ROOT/$p" +done +``` + +The first block reads the six CLI include paths straight from `DELPHIINCLUDES` +in [`delphi_flags.inc.sh`](../../resources/build/win/delphi_flags.inc.sh), so +they can't drift from the build. The eleven JCL / JVCL / jedi paths are +IDE-only with no build-side equivalent, so they stay listed explicitly. +`devtools` targets the `BDS\` hive matching the Delphi it was built +with (`23.0` under Delphi 12, per `SourceRootPath.pas`), so build it under the +same Delphi you'll open the projects in. + +## 4. Building the Delphi projects under CE + +The implicit cross-project **build-order dependencies** (codegen `.pas` +files, embedded `.res`, COM registration) are general to all Windows +builds, not CE-specific, so they now live in +[windows.md § Delphi build-order dependencies](windows.md#delphi-build-order-dependencies). +They bite harder under CE because you build each project by hand: accept +the IDE prompts in the order `build.sh` issues them and let none get +skipped. + +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. + +**Install (a separate step).** Once built, overlay the dev binaries and +register `kmcomapi.dll` from an **elevated Git Bash**: + +```bash +windows/src/engine/build.sh install +windows/src/desktop/build.sh install +``` + +### Faster iteration: script + IDE Build All (warm-state only) + +`delphi_msbuild` under `KEYMAN_DELPHI_CE=1` **auto-skips its prompt** +when the expected output at `bin//Debug/.{exe,dll,bpl}` +is newer than the corresponding `.dpr` / `.dpk` source. On subsequent +builds — where the previous build's `.res` files, codegen `.pas` +files, and vendored `.bpl`s are all still on disk — this enables: + +1. From a terminal, kick off the shell build script for a subsystem + (e.g. `./windows/src/engine/build.sh build`). The first CE prompt + fires and blocks. +2. Alt-Tab to Delphi 12 CE. Open the relevant project group: + * `windows/src/engine/engine.groupproj` — engine children (keyman, + kmcomapi, tsysinfo, tsysinfox64). Post-PR #16044 `insthelper` + is not in the group and needs to be opened on its own. + * `windows/src/desktop/desktop.groupproj` — desktop children + (kmshell, kmbrowserhost, kmconfig, insthelp, setup). + * `developer/src/developer.groupproj` — TIKE, kmconvert, + developer/setup. +3. Check Delphi's Config = **Debug** and Platform = **Win32** (or + Win64 for the 64-bit half of kmcomapi and for tsysinfox64), then + **Build All**. Every project in the group compiles once. +4. Alt-Tab back to the terminal, press Enter to release the first + prompt. +5. Each subsequent `delphi_msbuild` call detects its output is fresh + and skips the prompt automatically. Post-build steps + (`sentrytool_delphiprep`, `tds2dbg`, staging `cp`) run against the + pre-built binaries. + +**This flow only works when all Delphi inputs already exist on disk.** +Delphi Build All can't fabricate the inputs the shell scripts +generate: + +* `MessageIdentifierConsts.pas` (from `devtools -buildmessageconstants` + in `windows/src/global/delphi/build.sh`) +* `Keyman.Setup.System.Locale.*.pas` (from `devtools -buildsetupstrings` + in `windows/src/desktop/setup/build.sh`) +* `Keyman.System.Standards.BCP47*.pas` (from `build_standards_data/build.sh`) +* `keyman_components.bpl`, `common_components.bpl`, `CEF4Delphi.bpl` + (from their respective `build.sh` scripts) +* `tsysinfo_x64.res` (from tsysinfo/build.sh's tsysinfox64 → copy → + rc.exe chain) +* `kbd_noicon.res` + `kmcomapi.tlb` (from kmcomapi/build.sh do_build) +* Per-project `version.res` / `manifest.res` (from each `build.sh`'s + pre-build) + +**Fresh clone or after `git clean -fdx`**: you must walk the CE +prompts one-by-one at least once. Each project's `build.sh` fires the +pre-build steps that generate the inputs above; then the CE prompt +fires; you Build in Delphi; script proceeds. **On the next build** +(codegen outputs still present) you can switch to the Build All flow. + +If a project's output is missing or older than the source (you +edited a `.dpr`, or Delphi built to Release instead of Debug), the +CE prompt still fires for that project — no risk of silent +stale-output failures. + +**Caveat on engine.groupproj even for warm builds**: the group +currently orders `tsysinfo` before `tsysinfox64`, so a bare Build All +on the engine group fails on tsysinfo (missing `tsysinfo_x64.res`) +unless `windows/src/engine/tsysinfo/build.sh` was run at least once +first (which produces the `.res`). +[#16192](https://github.com/keymanapp/keyman/issues/16192) proposes +a reorder + pre-build event that would remove this caveat. + +If you prefer per-project prompts (e.g. debugging a single project +in isolation, or a truly fresh clone), skip step 3 above — the +script will prompt for each `.dproj` in dep order and you can Build +them one at a time. + +### Verify Configuration + Platform before every IDE build + +Delphi's active Configuration (top-of-IDE dropdown) and Platform +persist across projects — if you switched to Release or Win64 while +inspecting an earlier `.dproj`, Delphi opens the next one with those +same settings. The `.dproj`'s `DCC_ExeOutput` interpolates +`$(Platform)` and `$(Config)`, so a Release/Win32 build lands at +`bin/Win32/Release/.exe` — not the `bin/Win32/Debug/` +location the `build.sh` post-build step expects. + +Symptom: Delphi's Messages pane reports `Build succeeded`, but the +`build.sh` fails with +`EFOpenError: Cannot open file "...\bin\Win32\Debug\.exe". +The system cannot find the path specified.` + +Before Building each project via the CE prompt, confirm the top-of-IDE +dropdown reads **Debug** and **Win32** (or **Win64** for tsysinfox64 +and the Win64 half of kmcomapi). + +### Multi-platform Delphi packages (kmcomapi) + +`kmcomapi.dproj` gets built **twice** by its `build.sh` — once for +Win32 (`kmcomapi.dll`), once for Win64 (`kmcomapi.x64.dll`, renamed +post-build). Two consecutive CE prompts fire for the same `.dproj`. +Between them, **change Delphi's Platform dropdown**: first prompt → +Win32, second prompt → Win64. If you build both as Win32, the Win64 +output isn't produced and the script fails at `mv: cannot stat +'bin/Win64/Debug/kmcomapi.dll'`. + +### Full clean before rebuild if you see debug-section errors + +`sentrytool_delphiprep` can fail on a partially-built `.dll`/`.exe` +with `ERROR: This executable has a debug section. Not able to update +this file.` This happens when Delphi produced a partial binary with +an already-populated debug section (usually from a prior attempt). +Wipe the project's build state fully: + +```bash +rm -rf windows/src/engine//bin windows/src/engine//obj +``` + +Then rerun that project's `build.sh` and rebuild in Delphi from +scratch. + +> [!IMPORTANT] +> After an IDE build following any `.res`, manifest, version, or icon +> change: right-click → **Clean, then Build**. An incremental Build +> silently embeds the stale cached `.res`. + +## 5. Local-only: uiAccess strip for overlaid keyman.exe + +Windows refuses to launch unsigned binaries declaring +`uiAccess="true"` (error 8235). To run an unsigned dev `keyman.exe`, +swap in the pre-existing non-elevated manifest: + +```bash +cd windows/src/engine/keyman +./build.sh debug-manifest +``` + +That copies `debug-manifest.in` over `manifest.in` and regenerates +`manifest.res`. Then Clean + Build `keyman.dproj` in the IDE and +re-run `windows/src/engine/build.sh install` elevated. + +Trade-off: keyboard injection into elevated apps stops working under +the debug manifest. Not committed. Revert with +`git checkout -- windows/src/engine/keyman/manifest.in`. + +## 6. Debugging + +Nothing here is CE-specific — the same debugging setup applies under +Pro. + +* **DLLs (kmcomapi, keymanhp):** Run → Parameters → **Host + Application** = `\kmshell.exe`. +* **Long-running processes (kmshell, TSF text service):** Run → + **Attach to Process**. +* **C++ engine pieces (keyman32, kmtip, mcompile):** Visual Studio → + Attach to Process; load `.pdb` from `windows/bin/`. Stepping + between `keyman.exe` (Delphi) and `keyman32.dll` (C++) requires + `windbg` — out of scope here. + +## 7. Troubleshooting + +### `This version of the product does not support command line compiling` + +CE block on `dcc32`. Set `KEYMAN_DELPHI_CE=1` (§2). If `build.sh` +still tries to invoke Delphi as a transitive dep, build the upstream +tool first or pass `--no-deps`. + +### `File ...\windows\lib\keyman_components.bpl does not exist` / `F1026 File not found: MessageIdentifierConsts.pas` + +Codegen (a). Run `./windows/src/global/delphi/build.sh build` — that +script builds `keyman_components.dproj` (produces `keyman_components.bpl` +at `windows/lib/`) AND runs `devtools -buildmessageconstants` to +regenerate `MessageIdentifierConsts.pas`. Neither is produced by +`devtools/build.sh` on its own. + +### `F1026 File not found: Keyman.Setup.System.Locale..pas` + +Codegen (a'). Run `./windows/src/desktop/setup/build.sh build` — +that script's post-build runs `devtools -buildsetupstrings` to +regenerate the ~32 locale `.pas` files. + +### `F1026 File not found: Keyman.System.Standards.BCP47SubtagRegistry.pas` + +Codegen (b). Rerun +`common/windows/delphi/tools/build_standards_data/build.sh build`. + +### `F1026 File not found: tsysinfo_x64.res` + +Dependency (c). Let `windows/src/engine/build.sh` sequence +`tsysinfox64` before `tsysinfo`; don't skip prompts. + +### `F1026 File not found: version.res` or `manifest.res` + +Pre-build resource compilation didn't run. Re-invoke `build.sh build` +for the project — it runs `build_version.res` / `build_manifest.res` +before the IDE prompt. + +### `F1026 File not found: jvcl.inc` / `jedi/jedi.inc` / `jcl.inc` + +Library Search Paths not registered. See §3. Close and reopen Delphi +after the registry edit. + +### `F2613 Unit 'JvComponentBase' not found` + +JVCL `run/` (or `design/`) missing from Library Search Path. See §3. + +### `SKApplicationTitle has had a fatal error` on kmshell launch + +kmshell can't find the Keyman install-path registry key it reads via +`TKeymanPaths.KeymanDesktopInstallPath()`. Either install Keyman 19 +from https://keyman.com/windows so the key is populated, or set up +the required support files manually alongside the dev build. + +### `Class not registered {CF46549D-...}` on kmshell launch + +`kmcomapi.dll` not registered. From an elevated Git +Bash: `windows/src/engine/kmcomapi/build.sh install`. + +### `Could not find keyman.exe (error=8235)` + +Windows blocked an unsigned `uiAccess="true"` binary. See §5. + +### Delphi pops up an "Unsupported CEF version" dialog + +CEF4Delphi_Binary checkout doesn't match `common/windows/CEF_VERSION.md`. +Not CE-specific — fix per +[windows.md § KEYMAN_CEF4DELPHI_ROOT](windows.md#keyman_cef4delphi_root). + +### `Build succeeded... Time Elapsed 00:00:0X.XX` in terminal, then `cp: cannot stat '...'` / `File ... does not exist` + +`KEYMAN_DELPHI_CE` isn't set in the shell that ran `build.sh`, so +`delphi_msbuild` invoked `msbuild.exe` directly. On CE that produces +a fake success with no output. See §2 for the shell-verification +step — this is the single most common failure signature in the whole +CE workflow. + +### `SignTool Error: File not found: ...keymantest-sha1.pfx` + +Test-signing certificates never generated. Run once: +`./common/windows/delphi/tools/certificates/build.sh certificates` +(see §1). + +### `ERROR: This executable has a debug section. Not able to update this file.` + +`sentrytool_delphiprep` complaining about a residual debug section +from a partial prior build. Fully wipe the project's `bin/` and +`obj/` and rebuild from scratch (see §4 sub-section). + +### `EFOpenError: Cannot open file "...\bin\Win32\Debug\.exe". The system cannot find the path specified.` + +Delphi's Config dropdown was on **Release** (or the Platform on +**Win64**) when you Built, so the output landed at +`bin/Win32/Release/.exe` and `sentrytool_delphiprep` +can't find the Debug path. Switch Config to **Debug**, Platform to +**Win32**, Clean + Build. See §4 "Verify Configuration + Platform". + +### `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". diff --git a/docs/build/windows.md b/docs/build/windows.md index edc756340ff..0452d2fa078 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -184,8 +184,8 @@ In bash, run the following commands: cd /c/Projects/keyman git clone https://github.com/emscripten-core/emsdk cd emsdk -emsdk install 3.1.58 -emsdk activate 3.1.58 +emsdk install 3.1.64 +emsdk activate 3.1.64 cd upstream/emscripten npm install ``` @@ -195,8 +195,8 @@ If you are updating an existing install of Emscripten: ```bash cd emsdk git pull -emsdk install 3.1.58 -emsdk activate 3.1.58 +emsdk install 3.1.64 +emsdk activate 3.1.64 cd upstream/emscripten npm install ``` @@ -257,6 +257,17 @@ of appropriate node versions during builds. for a short time. (We are actively working to remove Delphi dependencies given the licensing issues with using it.) + * If you have Delphi 11 or 12 with a CLI-capable license (Professional or + higher), set `KEYMAN_DELPHI_VERSION` to the Studio path — `22.0` for + Delphi 11, `23.0` for Delphi 12 — before running `build.sh`. The default + (`20.0`, Delphi 10.3) is preserved when the variable is unset. + + * If you only have access to a **Delphi Community Edition** (e.g. Delphi 12 CE), + see [windows-delphi-ce.md](windows-delphi-ce.md) for an IDE-based workflow + that papers over the missing command-line compiler. The shell-script knob + (`KEYMAN_DELPHI_CE=1`) described there is local-only and not required for + the standard 10.3 / Pro flow. + Start Delphi IDE once after installation as it will create various environment files and take you through required registration. @@ -313,6 +324,31 @@ of appropriate node versions during builds. repository to your `PATH` environment variable. This is required for Keyman's design-time packages to load in Delphi. +### Delphi build-order dependencies + +Several Keyman projects have cross-project dependencies not expressed in +`.dproj` / `.groupproj` files — a Delphi tool emits code consumed by another +Delphi project, or one project's `.res` embeds another's `.exe`. `build.sh` +sequences these correctly; they matter if you build projects individually or +out of order (for example in the IDE). What each build step produces, and for +which dependent: + +| # | Producer → Consumer | Failure if skipped | +|---|---------------------|---------------------| +| a | `windows/src/global/delphi/build.sh` → `devtools -buildmessageconstants` → `MessageIdentifierConsts.pas` consumed by keyman.dproj, kmshell.dproj, plus `keyman_components.bpl` → `windows/lib/` | `F1026 File not found: 'MessageIdentifierConsts.pas'` OR `File ... keyman_components.bpl does not exist` | +| a' | `windows/src/desktop/setup/build.sh` → `devtools -buildsetupstrings` → ~32 `Keyman.Setup.System.Locale..pas` consumed by setup.dproj | `F1026 File not found: 'Keyman.Setup.System.Locale..pas'` | +| b | `common/windows/delphi/tools/build_standards_data/build.sh` → 5 BCP-47 registry `.pas` files → TIKE.dproj | `F1026 File not found: 'Keyman.System.Standards.BCP47SubtagRegistry.pas'` | +| c | `windows/src/engine/tsysinfo/build.sh` → auto-invokes tsysinfox64 publish, copies exe, runs `rc.exe` → `tsysinfo_x64.res` embedded by tsysinfo.dproj | `F1026 File not found: 'tsysinfo_x64.res'` | +All generated `.pas` files are `.gitignored` and must be regenerated after +`git clean -fdx`. + +Distinct from build order, a few components must be **installed or registered +to run** (not to compile): `kmcomapi.dll` registered via `regsvr32` (or +`windows/src/engine/kmcomapi/build.sh install`), `kmcmplib-19.dll` present for +Keyman Developer, and `keyman.exe` / `kmshell.exe` able to launch. Missing +these surfaces as **runtime** errors — `Class not registered`, +`kmcmplib-19.dll not found`, `"Keyman failed to start"` — not build errors. + ### KEYMAN_CEF4DELPHI_ROOT Keyman and Keyman Developer use Chromium Embedded Framework. The source repo is diff --git a/resources/build/win/configure_environment.inc.sh b/resources/build/win/configure_environment.inc.sh index d0bce72a812..1d274c67be1 100644 --- a/resources/build/win/configure_environment.inc.sh +++ b/resources/build/win/configure_environment.inc.sh @@ -65,9 +65,11 @@ _build_vs_environment() { _locate_rsvars() { # - # Delphi Compiler Configuration - Delphi 10.3.2 + # Delphi Compiler Configuration - defaults to Delphi 10.3 (BDS 20.0). + # Override via the KEYMAN_DELPHI_VERSION environment variable to build with + # a newer Delphi installation (e.g., KEYMAN_DELPHI_VERSION=23.0 for Delphi 12). # - DELPHI_VERSION=20.0 + DELPHI_VERSION="${KEYMAN_DELPHI_VERSION:-20.0}" DCC32PATH="$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$DELPHI_VERSION\\bin")" RSVars_path="$DCC32PATH/rsvars.bat" } diff --git a/resources/build/win/delphi_environment.inc.sh b/resources/build/win/delphi_environment.inc.sh index 0465bcb6930..a68b08421c4 100644 --- a/resources/build/win/delphi_environment.inc.sh +++ b/resources/build/win/delphi_environment.inc.sh @@ -13,7 +13,10 @@ DELPHIWARNINGS=(-W-MESSAGE_DIRECTIVE -W-IMPLICIT_STRING_CAST -W-IMPLICIT_STRING_CAST_LOSS -W-EXPLICIT_STRING_CAST -W-EXPLICIT_STRING_CAST_LOSS -W-CVT_WCHAR_TO_ACHAR -W-CVT_NARROWING_STRING_LOST -W-CVT_ACHAR_TO_WCHAR -W-CVT_WIDENING_STRING_LOST -W-UNICODE_TO_LOCALE -W-LOCALE_TO_UNICODE -W-IMPLICIT_VARIANTS -W-IMPLICIT_INTEGER_CAST_LOSS -W-IMPLICIT_CONVERSION_LOSS -W-COMBINING_SIGNED_UNSIGNED64 -W-COMBINING_SIGNED_UNSIGNED64) # !ENDIF -DELPHI_VERSION=20.0 +# DELPHI_VERSION defaults to Delphi 10.3 (BDS 20.0). Override via the +# KEYMAN_DELPHI_VERSION environment variable to build with a newer Delphi +# installation (e.g., KEYMAN_DELPHI_VERSION=23.0 for Delphi 12). +DELPHI_VERSION="${KEYMAN_DELPHI_VERSION:-20.0}" DCC32PATH="$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$DELPHI_VERSION\\bin")" source "$KEYMAN_ROOT/resources/build/win/delphi_environment_generated.inc.sh" diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 9408c6660ef..488046b19da 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -99,7 +99,54 @@ tds2dbg() { builder_if_release_build_level "$TDS2DBG" "$@" } +# Check whether the expected Delphi output for a project is already fresh — +# useful when a developer has pre-built everything via a .groupproj Build All +# in the IDE. Compares bin//Debug/.{exe,dll,bpl} mtime against +# the .dpr or .dpk source. Returns 0 (up-to-date, skip prompt) if any match. +_delphi_ce_output_fresh() { + local project="$1" platform="$2" + local base="${project%.dproj}" + local source + if [[ -f "${base}.dpr" ]]; then source="${base}.dpr" + elif [[ -f "${base}.dpk" ]]; then source="${base}.dpk" + else return 1 # can't judge freshness without a source file + fi + local candidate ext + for ext in exe dll bpl; do + candidate="bin/${platform}/Debug/${base}.${ext}" + if [[ -f "$candidate" && "$candidate" -nt "$source" ]]; then + return 0 + fi + done + return 1 +} + 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:-}" + local platform="Win32" + local arg + for arg in "$@"; do + [[ "$arg" == *Platform=Win64* ]] && platform="Win64" + done + + # Skip prompt if the expected Debug output is already fresh — supports the + # "open .groupproj + Build All once, then Enter through each script prompt" + # workflow. Subsequent prompts become no-ops as long as outputs are newer + # than their sources. + if _delphi_ce_output_fresh "$project" "$platform"; then + builder_echo "Delphi CE: $project ($platform) — output is up-to-date, skipping prompt." + return 0 + fi + + builder_echo warning "Delphi CE: CLI compilation is not available." + builder_echo warning "Please build ${project} (Platform: ${platform}, Config: Debug) in the Delphi IDE now, then press Enter to continue (or Ctrl-C to abort)." + builder_echo warning "Tip: opening the parent .groupproj and running Build All pre-builds all sibling projects at once; subsequent CE prompts in this script will auto-skip when their outputs are fresh." + read -r _ + return 0 + fi run_in_delphi_env msbuild.exe "$@" "$DELPHI_MSBUILD_FLAG_DEBUG" } diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 6ec2fbcebff..7e9ea36a0e4 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -2323,7 +2323,8 @@ builder_describe_platform() { # Detect delphi compiler (see also delphi_environment.inc.sh) if builder_is_windows; then local ProgramFilesx86="$(cygpath -w -F 42)" - if [[ -x "$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\20.0\\bin\\dcc32.exe")" ]]; then + local _delphi_version="${KEYMAN_DELPHI_VERSION:-20.0}" + if [[ -x "$(cygpath -u "$ProgramFilesx86\\Embarcadero\\Studio\\$_delphi_version\\bin\\dcc32.exe")" ]]; then builder_installed_tools+=(delphi) fi fi diff --git a/windows/src/engine/engine.groupproj b/windows/src/engine/engine.groupproj index bb2548b6103..79830a476ed 100644 --- a/windows/src/engine/engine.groupproj +++ b/windows/src/engine/engine.groupproj @@ -15,7 +15,7 @@ - + @@ -63,13 +63,13 @@ - + - + - + 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);