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