Ship the WSL2 runtime in a Squirrel Windows .exe
The Windows installer now bundles the WSL2 engine runtime so the installed app works out of the box. - forge.config.js: configure the Squirrel maker (setupExe name, setup icon) and require the rootfs for any Windows build, throwing a clear error if packaging/wsl2/out/ is empty so the .exe can never ship without its engine. - wsl-runtime.js: rootfsCandidates now checks the flat resources/ path first, which is where forge's extraResource copies the archive (the wsl-runtime/ subdirectory and the dev-checkout path remain as fallbacks). - package.json: add package:win / make:win scripts that build the static engine, the rootfs, then package/make for win32. - tests: cover the flat-first rootfs lookup. - README/CHANGELOG: document the .exe build, its Wine+Mono cross-build prerequisite on Linux, and the required-rootfs guard.
This commit is contained in:
1 parent
0c8e8f2e03
commit
b3e75b2300
6 files changed
+87
-12
No files matched your search
@@ -10,6 +10,9 @@
|
||||
on first run, and is launched via `wsl.exe` with Windows paths translated to
|
||||
their `/mnt/<drive>` form. A runtime gate waits for the import before the
|
||||
first run, and progress is reported in the status bar.
|
||||
- A Squirrel Windows installer (`npm run make:win`,
|
||||
`TRIMSP-NL-Workbench-Setup.exe`) that bundles the WSL2 runtime; a Windows
|
||||
build refuses to proceed without it.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
+31
-2
@@ -20,12 +20,39 @@ const includeFlatpak = process.env.FORGE_FLATPAK === '1';
|
||||
|
||||
// Windows cannot run the bundled Linux engine natively. The Windows build also
|
||||
// ships the engine as a WSL2 rootfs archive (built by packaging/wsl2) which the
|
||||
// app imports as a private distribution on first run. Only add the archive when
|
||||
// it has actually been built, so Linux/macOS packaging is unaffected.
|
||||
// app imports as a private distribution on first run.
|
||||
const wsl2Rootfs = path.resolve(__dirname, 'packaging/wsl2/out/trimspNL-runtime-rootfs.tar.gz');
|
||||
|
||||
// Detect a Windows build so we can (a) include the rootfs and (b) refuse to
|
||||
// produce a Windows installer that would be missing its engine. Forge is asked
|
||||
// for a target platform either as `--platform win32` / `-p win32` (make/package)
|
||||
// or implicitly when running on Windows itself.
|
||||
function wantsWindowsTarget() {
|
||||
if (process.platform === 'win32') return true;
|
||||
const argv = process.argv.slice(2);
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg === '--platform' || arg === '-p') {
|
||||
if ((argv[i + 1] || '').toLowerCase() === 'win32') return true;
|
||||
}
|
||||
for (const prefix of ['--platform=', '-p=']) {
|
||||
if (arg.toLowerCase().startsWith(prefix)
|
||||
&& arg.slice(prefix.length).toLowerCase() === 'win32') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const windowsBuild = wantsWindowsTarget();
|
||||
const extraResource = ['resources/bin'];
|
||||
if (fs.existsSync(wsl2Rootfs)) {
|
||||
extraResource.push(wsl2Rootfs);
|
||||
} else if (windowsBuild) {
|
||||
throw new Error(
|
||||
'Windows build requires the WSL2 engine runtime, but\n ' + wsl2Rootfs
|
||||
+ '\nis missing. Build it first from Linux/WSL with:\n npm run build:wsl2');
|
||||
}
|
||||
|
||||
// The upstream RPM maker assumes Fedora-style package names. Mageia provides
|
||||
@@ -44,6 +71,8 @@ const makers = [
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {
|
||||
name: 'TrimSP',
|
||||
setupExe: 'TRIMSP-NL-Workbench-Setup.exe',
|
||||
setupIcon: path.resolve(__dirname, 'icon.ico'),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"make:flatpak": "npm run build:fortran && FORGE_FLATPAK=1 electron-forge make --targets=@electron-forge/maker-flatpak",
|
||||
"build:wsl2": "bash packaging/wsl2/build-wsl2.sh",
|
||||
"test:wsl-runtime": "node tests/wsl-runtime.test.js && node tests/runtime-gate.test.js",
|
||||
"package:win": "npm run build:fortran && npm run build:wsl2 && electron-forge package --platform=win32",
|
||||
"make:win": "npm run build:fortran && npm run build:wsl2 && electron-forge make --platform=win32 --targets=@electron-forge/maker-squirrel",
|
||||
"checksums:md5": "sh -c 'for d in out/make/deb/x64 out/make/rpm/x64 out/make/flatpak/x86_64; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] || continue; case \"$f\" in *.deb|*.rpm|*.flatpak) base=$(basename \"$f\"); (cd \"$d\" && md5sum \"$base\" > \"$base.md5\");; esac; done; done'",
|
||||
"checksums:sha256": "sh -c 'for d in out/make/deb/x64 out/make/rpm/x64 out/make/flatpak/x86_64; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] || continue; case \"$f\" in *.deb|*.rpm|*.flatpak) base=$(basename \"$f\"); (cd \"$d\" && sha256sum \"$base\" > \"$base.sha256\");; esac; done; done'",
|
||||
"checksums": "npm run checksums:md5 && npm run checksums:sha256"
|
||||
|
||||
@@ -70,9 +70,38 @@ wsl -d trimspNL-runtime -- /opt/trimsp-runtime/bin/trimspNL
|
||||
## Packaging
|
||||
|
||||
The Windows build ships the rootfs through electron-forge. `forge.config.js`
|
||||
adds the archive to `packagerConfig.extraResource` when it exists, landing it at
|
||||
`resources/wsl-runtime/`, while the engine marker at `resources/bin/trimspNL`
|
||||
lets the renderer detect the bundled runtime and route through WSL.
|
||||
adds the archive to `packagerConfig.extraResource` when it exists (copied flat
|
||||
into `resources/`), while the engine marker at `resources/bin/trimspNL` lets the
|
||||
renderer detect the bundled runtime and route through WSL.
|
||||
|
||||
Build the Squirrel `Setup.exe`, which contains the app **and** the WSL2 rootfs:
|
||||
|
||||
```sh
|
||||
npm run make:win
|
||||
```
|
||||
|
||||
This runs the whole chain: it compiles the static engine, builds the rootfs, then
|
||||
packages and makes the installer for `win32`. A Windows build **requires** the
|
||||
rootfs — `forge.config.js` throws if `packaging/wsl2/out/` is empty, so the
|
||||
`.exe` can never silently ship without its engine. Build from Linux/WSL first
|
||||
(Windows cannot compile the Fortran engine or run the rootfs builder).
|
||||
|
||||
The installer is written to `out/make/squirrel.windows/x64/` as
|
||||
`TRIMSP-NL-Workbench-Setup.exe`.
|
||||
|
||||
### Build-host prerequisites
|
||||
|
||||
- Building the `.exe` **on Windows**: nothing extra.
|
||||
- Building the `.exe` **on Linux** (cross-build): `electron-winstaller` needs
|
||||
**both Wine and Mono** installed, otherwise it fails with
|
||||
`You must install both Mono and Wine on non-Windows`. Install e.g.
|
||||
`wine` and `mono-complete` (or `mono-core`) first.
|
||||
|
||||
### Verifying the shipped .exe
|
||||
|
||||
The `TrimSP.exe` inside the install loads the engine only through WSL, so the
|
||||
same manual remote-session check below applies after installing the `.exe`.
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -115,3 +144,5 @@ workbench is often driven remotely. After the first-run import, verify:
|
||||
- `wsl.exe` availability is assumed; older Windows builds without WSL2 must
|
||||
install it (`wsl --install`) first.
|
||||
- The macOS runtime is not built.
|
||||
- Cross-building the `.exe` from Linux additionally needs Wine and Mono (see
|
||||
above); building on Windows needs neither.
|
||||
@@ -72,11 +72,19 @@ function testArgumentBuilders() {
|
||||
|
||||
function testFindRootfsTarball() {
|
||||
assert.strictEqual(wsl.findRootfsTarball('/nonexistent'), '');
|
||||
// forge copies extraResource entries flat into resources/, so the flat path
|
||||
// is the primary candidate.
|
||||
const flat = fs.mkdtempSync(path.join(os.tmpdir(), 'trimsp-flat-'));
|
||||
fs.writeFileSync(path.join(flat, wsl.ROOTFS_BASENAME), 'rootfs');
|
||||
assert.strictEqual(wsl.findRootfsTarball(flat), path.join(flat, wsl.ROOTFS_BASENAME));
|
||||
|
||||
// A wsl-runtime/ subdirectory is also honoured.
|
||||
const { dir, file } = makeRootfs();
|
||||
const primary = path.join(dir, 'wsl-runtime');
|
||||
fs.mkdirSync(primary);
|
||||
fs.renameSync(file, path.join(primary, wsl.ROOTFS_BASENAME));
|
||||
assert.strictEqual(wsl.findRootfsTarball(dir), path.join(primary, wsl.ROOTFS_BASENAME));
|
||||
const sub = path.join(dir, 'wsl-runtime');
|
||||
fs.mkdirSync(sub);
|
||||
fs.renameSync(file, path.join(sub, wsl.ROOTFS_BASENAME));
|
||||
assert.strictEqual(wsl.findRootfsTarball(dir), path.join(sub, wsl.ROOTFS_BASENAME));
|
||||
|
||||
assert.ok(wsl.rootfsCandidates('/x')[0].endsWith(wsl.ROOTFS_BASENAME));
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -23,13 +23,15 @@ const DEFAULT_DISTRO = "trimspNL-runtime";
|
||||
const DEFAULT_PREFIX = "/opt/trimsp-runtime";
|
||||
const ROOTFS_BASENAME = "trimspNL-runtime-rootfs.tar.gz";
|
||||
|
||||
// Candidate locations for the shipped rootfs tarball, in priority order. The
|
||||
// first is the packaged layout (packagerConfig.extraResource maps
|
||||
// packaging/wsl2/out -> wsl-runtime); the second tolerates running from a
|
||||
// Candidate locations for the shipped rootfs tarball, in priority order. A
|
||||
// forge/electron-packager `extraResource` entry is copied *flat* into the
|
||||
// resources directory (the first candidate); the `wsl-runtime/` subdirectory is
|
||||
// kept for an explicit directory layout; the last tolerates running from a
|
||||
// development checkout without repackaging.
|
||||
function rootfsCandidates(resourcesPath) {
|
||||
const base = resourcesPath || ".";
|
||||
return [
|
||||
path.join(base, ROOTFS_BASENAME),
|
||||
path.join(base, "wsl-runtime", ROOTFS_BASENAME),
|
||||
path.join(base, "packaging", "wsl2", "out", ROOTFS_BASENAME)
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user