feat(windows): build and publish Windows bundles for logoscore and logosctl (#88)

* feat(windows): build and publish Windows bundles for logoscore and logosctl

Neither tool had a downloadable Windows artifact. A release/* push produced
AppImage tarballs per Linux arch and a macOS bundle per tool, and nothing a
Windows user could open.

`build-windows-bundle` is one more leg of the shape build-macos-bundle already
uses -- the same two tools, the same cli-bundle-dir / ctl-bundle-dir outputs,
both verified to resolve for x86_64-windows. It cross-builds on ubuntu-latest,
because nix does not run on Windows.

NO SMOKE TEST HERE, deliberately: a Linux runner cannot execute a PE. Running
the binary is what logos-windows-ci's native-smoke leg does on a real
windows-latest runner. Asserting `--help` under wine would be a weaker claim
wearing the same clothes.

The packaging step asserts instead of trusting its exit code. The bundles' DLLs
are symlinks into the nix store, and an archive of dangling links extracts to a
tree that cannot start -- nix-bundle-lgx shipped exactly that, losing 75% of a
payload while exiting 0. So it copies with -L, compares the zip's entry count
against the bundle's file count and fails on a shortfall, and refuses a bundle
with no .exe. The comparison is `<` rather than `!=` because a zip counts
directory entries too, so a healthy bundle yields MORE entries than files
(measured on a real bundle: 1677 files -> 1768 entries).

One YAML trap worth recording: `files:` in the release job is a LITERAL block
scalar, so every line is a glob handed to the action and a '#' line would be
passed as a pattern rather than ignored. The commentary sits above the key.

* ci: take Nix and the cache from setup-nix-cache-action, drop the disk reclaim

The Windows legs failed after 27 minutes rebuilding boost, libpng, sqlite and
Qt from source. They were not slow -- they were reading the wrong cache. This
file pointed at the `logos-co` CACHIX cache, while the mingw closure is primed
in the Logos ATTIC cache. Measured on the failed run: 0 paths from
cache.nix.logos.co, 9 from cachix, 612 from cache.nixos.org.

All three build jobs now use logos-co/setup-nix-cache-action@v1, which replaces
three different setups (install-nix-action@v27, DeterminateSystems, and
cachix-action). Verified the closure this repo resolves is the one that is
primed: logos-nix 6e0f4a71 and nixpkgs e9f00bd8 are identical to logos-basecamp,
whose Windows job is green off this cache, and the two paths that mattered
resolve to attic/ci HTTP 200:

  boost-x86_64-w64-mingw32-1.89.0   <- the derivation that actually failed
  qtbase-x86_64-w64-mingw32-6.11.1

Reading needs no credentials -- both Attic caches are public to read and the
action configures the substituters unconditionally. Publishing stays inactive
until this repo gets ATTIC_TOKEN_CI; the action skips an empty token rather
than failing.

The disk reclaim is removed as unnecessary rather than kept as insurance. Its
justification cited "~14 GiB free", which is stale -- a real run reports
`/dev/root 145G  37G used  108G avail`. It was deleting ~10 GiB from a disk
with 108 GiB spare, and with the cache substituting instead of building, the
build-time store pressure drops further.

Also documents the Windows leg in the header, which was missed when the job
was added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(windows): stage bundled modules through the install bundler

The shipped Windows bundle declared **Qt6Core.dll** as capability_module's
entry point:

    "main": { "windows-x86_64-dev": "Qt6Core.dll", ... }

That is not cosmetic. The module scanner is manifest-driven -- it resolves
exactly ONE path per module, the one `main` names -- so it opened Qt6Core.dll
because the manifest told it to, failed to parse metadata, and returned before
inserting into the registry. capability_module, the auth handshake every client
needs, was silently dead in every bundle we have shipped.

ROOT CAUSE, and it was upstream of this repo. mkBinWindows hand-copied
`${liblogos}/modules`, whose generator (logos-liblogos/nix/modules.nix) carries
two Unix-shaped assumptions that only combine into a defect on Windows:

  * it globs `${capabilityModule}/lib/*.dll` -- on Unix `*.so`/`*.dylib` match
    only the plugin, but on Windows linkDLLsInfolder has already staged 14
    dependency DLLs into that same lib/ dir, so all 15 were copied;
  * it then picks the entry point by taking the FIRST regular file in the
    directory, which alphabetically is Qt6Core.dll.

Neither is visible natively and the build succeeded either way.

THE FIX is to stop hand-copying and use the mechanism the native builds and
logos-basecamp already use -- installPortable -> nix-bundle-lgx, which derives
`main` from the module's authored metadata.json rather than a directory
listing, and strips the host-provided runtime DLLs that bin/ already ships.
mkBinWindows gains `withPkgModules` to match mkBin/mkBinPortable.

That also ships package_manager and package_downloader on Windows, which the
comment here documented as blocked. The blocker was real and has cleared
upstream: logos-package-downloader-module gained packages.x86_64-windows when
it bumped logos-module-builder 8e4ea1c -> 9d3b7cc. This flake was 3 commits
behind; a single-input re-lock was the whole fix. The comment's stated reason
for not shipping half was also wrong, and is corrected in place.

MEASURED on the builder, x86_64-windows ctl-bundle-dir, now byte-for-byte the
shape logos-basecamp produces:

    modules/capability_module        15 entries, main=Qt6Core.dll
                                  ->  3 entries, main=capability_module_plugin.dll
    modules-pkg/package_manager      absent ->  7 entries
    modules-pkg/package_downloader   absent -> 19 entries (its libcurl closure)

A build-time guard now asserts every manifest's `main` names
<module>_plugin.*, because every structural check we had -- file counts, PE
counts, zip entry counts -- passed on the broken bundle. Verified both ways:
it rejects the exact manifest that shipped and accepts the corrected one, and
appears in the real derivation closure rather than being decorative.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(windows): build the PORTABLE logosctl, matching the portable modules

The previous commit moved the bundled modules onto the install bundler, which
installs them as variant `windows-x86_64`. The binary was still the dev build,
and a dev build accepts only `-dev` variants -- so all three modules were
refused at startup:

    module 'capability_module' ... was installed for variant 'windows-x86_64'
    which is not supported on this platform and will not be loadable:
    supported variants [windows-x86_64-dev].

That skew was introduced by the previous commit: before it, hand-copied dev
modules happened to match a dev binary (while carrying the broken manifest).

`portable` is not only a LAYOUT distinction, which is what the comment in this
file previously claimed when it called portable "redundant" on Windows. It is
also a COMPILE-TIME one: LGPM_PORTABLE_BUILD (logos-package-manager
CMakeLists.txt:58) decides what platformVariantsToTry() returns, appending
"-dev" when absent (package_manager_lib.cpp:1005-1008). The old reasoning was
right that a PE has no rpath and needs no @rpath/$ORIGIN variant, and wrong to
conclude from that that portable had nothing left to mean here.

mkBinWindows now builds from buildPortable/liblogosPortable. buildPortable
needed the two Windows fixes `build` already carried: the Qt host-tool
cmakeFlags, and the !isWindows guard on wrapQtAppsNoGuiHook (which cannot
evaluate for a mingw host).

Also escaped a `${liblogos}` that sat inside a shell COMMENT in a Nix ''
string, where it is still interpolated -- it was pulling the dev liblogos into
the portable bundle's build closure.

MEASURED on the builder:
  * all three modules declare variant `windows-x86_64` (was: refused)
  * the shipped bin/libpackage_manager_lib.dll is byte-identical to the copy in
    logos-liblogos-portable, and DIFFERS from the dev lib output
  * dev liblogos paths in the bundle closure: 0

Two probes were discarded as unsound before landing this, both returning a
zero that meant "cannot see" rather than "not present": `strings | grep
windows-x86_64-dev` reports 0 for dev AND portable builds, because the suffix
is constructed at runtime and never stored; and grepping the derivation closure
for LGPM_PORTABLE_BUILD=ON matches in both cases, because the portable MODULE
is present either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Lipicar
2026-08-18 22:13:57 -03:00
committed by GitHub
co-authored by Claude Opus 5
parent 1a4f56fe2b
commit cbd4c0967b
3 changed files with 83598 additions and 53433 deletions
+97 -14
View File
@@ -24,6 +24,27 @@ name: Build & Release
# macOS (aarch64): build the portable bundle dir (bin/ + lib/),
# strip the Gatekeeper quarantine flag the way
# Homebrew handles casks, wrap in a tar.gz.
# Windows (x86_64): cross-build the bundle dir on a Linux runner and
# zip it. Nix does not run on Windows, so there is
# no native leg here at all.
#
# Nix and the binary cache come from logos-co/setup-nix-cache-action, replacing
# three different setups this file used to carry (install-nix-action@v27,
# DeterminateSystems, and cachix/cachix-action pointed at the `logos-co` cachix).
#
# The swap is what makes the Windows leg viable, and it was measured rather than
# assumed. The mingw Qt/boost closure is primed in the Logos Attic cache
# (cache.nix.logos.co), NOT in cachix, so the first Windows run substituted
# nothing from it -- 0 paths from Attic against 612 from cache.nixos.org -- and
# spent 27 minutes rebuilding boost, libpng, sqlite and Qt from source before
# falling over inside boost.
#
# Reading needs no credentials: both Attic caches are public to read, and the
# action configures them as substituters unconditionally, so fork PRs pull too.
# PUBLISHING is separate and currently inactive here -- this repo has neither
# ATTIC_TOKEN_CI nor the public-cache environment that carries
# ATTIC_TOKEN_PUBLIC, and the action skips the push on an empty token rather
# than failing. Adding the repo secret turns publishing on with no edit here.
on:
push:
@@ -51,15 +72,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
- uses: logos-co/setup-nix-cache-action@v1
with:
extra_nix_config: |
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v15
with:
name: logos-co
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
- name: Build ${{ matrix.tool.output }}
run: nix build .#${{ matrix.tool.output }} -L
@@ -98,12 +113,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: cachix/cachix-action@v15
- uses: logos-co/setup-nix-cache-action@v1
with:
name: logos-co
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
- name: Build ${{ matrix.tool.output }}
run: nix build .#${{ matrix.tool.output }} -L
@@ -133,9 +145,72 @@ jobs:
name: macos-${{ matrix.tool.bin }}-aarch64-darwin
path: ${{ matrix.tool.bin }}-aarch64-macos.tar.gz
# ────────────────────────────────────────────────────────────────────────
# Windows. A CROSS build on a Linux runner -- nix does not run on Windows, so
# there is no windows-latest runner here. Executing the result on real Windows
# is covered separately by logos-windows-ci.
#
# Same two tools and the same bundle-dir outputs the macOS job packages, so
# this is one more leg of an established shape rather than a new one.
build-windows-bundle:
strategy:
fail-fast: false
matrix:
tool:
- { bin: logoscore, output: cli-bundle-dir }
- { bin: logosctl, output: ctl-bundle-dir }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: logos-co/setup-nix-cache-action@v1
with:
attic-token-ci: ${{ secrets.ATTIC_TOKEN_CI }}
- name: Build ${{ matrix.tool.output }} for Windows
run: nix build .#packages.x86_64-windows.${{ matrix.tool.output }} -L
# NO smoke test here, and that is deliberate rather than an omission: this
# runner cannot execute a PE. Running the binary is what logos-windows-ci's
# native-smoke leg does, on a real windows-latest runner. Asserting
# `--help` under wine would be a weaker claim dressed as the same one.
- name: Package bundle as a zip
run: |
set -euo pipefail
dst="${{ matrix.tool.bin }}-x86_64-windows"
src=$(readlink -f result)
staged=$(find -L "$src" -type f | wc -l | tr -d ' ')
[ "$staged" -gt 0 ] || { echo "::error::the bundle is empty"; exit 1; }
# -L DEREFERENCES. The bundle's DLLs are symlinks into the nix store,
# and an archive of dangling links extracts to a tree that cannot
# start. nix-bundle-lgx shipped exactly that: `cp -a` implied
# --no-dereference, the payload lost 75% of its files, and the step
# exited 0. The count check below is why that cannot recur quietly.
rm -rf "$dst" && mkdir "$dst"
cp -rL "$src"/. "$dst/"
chmod -R u+w "$dst"
zip -qr "$dst.zip" "$dst"
zipped=$(unzip -l "$dst.zip" | tail -1 | awk '{print $2}')
echo "bundle $staged file(s) -> zip $zipped entr(ies)"
if [ "$zipped" -lt "$staged" ]; then
echo "::error::the zip holds $zipped entries but the bundle has $staged files."
echo "::error::Something was not dereferenced; the extracted tree would be"
echo "::error::missing DLLs and could not start."
exit 1
fi
n_exe=$(find "$dst" -name '*.exe' | wc -l | tr -d ' ')
[ "$n_exe" -gt 0 ] || { echo "::error::no .exe in the bundle"; exit 1; }
echo "exe(s): $n_exe"
- uses: actions/upload-artifact@v4
with:
name: windows-${{ matrix.tool.bin }}-x86_64
path: ${{ matrix.tool.bin }}-x86_64-windows.zip
release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release/')
needs: [build-appimage, build-macos-bundle]
needs: [build-appimage, build-macos-bundle, build-windows-bundle]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -201,5 +276,13 @@ jobs:
```
Now `logoscore` runs from anywhere.
# NOTE: `files:` below is a LITERAL block scalar -- every line in it is
# a glob handed to the action, and a '#' line would be passed as a
# pattern rather than ignored. Commentary belongs out here.
#
# The Windows bundles are .zip because Explorer opens one and nothing
# on Windows opens a .tar.gz. `merge-multiple: true` above flattens
# every artifact into artifacts/, so a depth-1 glob reaches them.
files: |
artifacts/*.tar.gz
artifacts/*-windows.zip
Generated
+83371 -53351
View File
File diff suppressed because it is too large Load Diff
+130 -68
View File
@@ -380,7 +380,7 @@
# unset, every objdump call fails, and the hook reports "Created 0
# DLL link(s)" -- a silent no-op that only shows up as an .exe that
# will not start on the target machine.
mkBinWindows = { binName }: pkgs.stdenv.mkDerivation {
mkBinWindows = { binName, withPkgModules }: pkgs.stdenv.mkDerivation {
pname = "${pname}-${binName}";
inherit version meta;
@@ -393,8 +393,8 @@
# a PE, in opposite directions:
#
# extraDirs: the bundler carries bin/ and lib/ and nothing else
# unless a directory is named here. modules/ is the built-in
# module tree this package stages by hand a few lines down; without
# unless a directory is named here. modules/ (and modules-pkg/ for
# logosctl) is the built-in module tree staged below; without
# this line the bundle is a STRICT SUBSET of the derivation it
# bundles -- no modules/, no message saying so, and a logosctl.exe
# that starts and then reports no modules.
@@ -410,7 +410,7 @@
# the cost is an unused closure path and if it is necessary the
# build says so instead of shipping an .exe that will not start.
passthru = {
extraDirs = [ "modules" ];
extraDirs = [ "modules" ] ++ pkgs.lib.optional withPkgModules "modules-pkg";
# qtbase + qtremoteobjects are what the CLI itself links. The
# other two come from Qt PLUGINS that qtCliApp stages anyway:
# `guiApp = false` suppresses the LAUNCHER, not the plugin scan,
@@ -451,10 +451,10 @@
mkdir -p $out/bin $out/modules
exe="${build}/bin/${binName}${exeExt}"
exe="${buildPortable}/bin/${binName}${exeExt}"
if [ ! -f "$exe" ]; then
echo "Error: $exe not found" >&2
ls -la ${build}/bin >&2 || true
ls -la ${buildPortable}/bin >&2 || true
exit 1
fi
cp "$exe" $out/bin/
@@ -473,14 +473,14 @@
# interpolated literal path survives into the array and the guard
# passes vacuously.
staged=0
for dll in ${liblogosLib}/lib/*.dll; do
for dll in ${liblogosPortable}/lib/*.dll; do
[ -f "$dll" ] || continue
cp -L "$dll" $out/bin/
staged=$((staged + 1))
done
if [ "$staged" -eq 0 ]; then
echo "Error: no DLLs under ${liblogosLib}/lib; ${binName}${exeExt} would start with no output" >&2
ls -la ${liblogosLib}/lib >&2 || true
echo "Error: no DLLs under ${liblogosPortable}/lib; ${binName}${exeExt} would start with no output" >&2
ls -la ${liblogosPortable}/lib >&2 || true
exit 1
fi
@@ -489,27 +489,82 @@
# script; there is no wrapper on a PE, so the host has to sit
# beside the CLI where the default lookup finds it.
hosts=0
for host in ${liblogos}/bin/*.exe; do
for host in ${liblogosPortable}/bin/*.exe; do
[ -f "$host" ] || continue
cp -L "$host" $out/bin/
hosts=$((hosts + 1))
done
if [ "$hosts" -eq 0 ]; then
echo "Error: no module-host .exe under ${liblogos}/bin" >&2
ls -la ${liblogos}/bin >&2 || true
echo "Error: no module-host .exe under ${liblogosPortable}/bin" >&2
ls -la ${liblogosPortable}/bin >&2 || true
exit 1
fi
chmod -R +w $out/bin
# Built-in modules. ${liblogos}/modules already carries
# capability_module with a windows-x86_64-dev manifest key (see
# logos-liblogos/nix/modules.nix, which keys off the TARGET
# platform rather than uname), so this is the same tree the
# native builds get -- no lgx bundling involved.
if [ -d ${liblogos}/modules ]; then
cp -r ${liblogos}/modules/. $out/modules/
chmod -R +w $out/modules
# Built-in modules, from the SAME install-bundler path the native
# builds use (installPortable -> nix-bundle-lgx), NOT a hand copy.
#
# This used to be `cp -r ''${liblogos}/modules/.`, and that is where
# the first shipped Windows bundle got a capability_module whose
# manifest named **Qt6Core.dll** as the plugin. Two Unix-shaped
# assumptions in logos-liblogos/nix/modules.nix combine: it globs
# `lib/*.dll` -- which matches only the plugin on Unix, but on
# Windows linkDLLsInfolder has staged 14 dependency DLLs beside it
# -- and then picks the entry point by taking the FIRST file in
# the directory, alphabetically Qt6Core.dll. Neither is visible
# natively, and the build succeeded either way.
#
# Going through the bundler fixes both at once and is what
# logos-basecamp already does (binBundleDir = dirBundler
# appDistributed -> installPortable): the manifest comes from the
# module's own metadata rather than a directory listing, and
# nix-bundle-lgx's mkWindowsPayload strips the host-provided
# runtime DLLs that bin/ already ships.
modcount=0
for m in ${modulesPortable}/modules/*; do
[ -d "$m" ] || continue
cp -r "$m" $out/modules/
modcount=$((modcount + 1))
done
if [ "$modcount" -eq 0 ]; then
echo "Error: no modules under ${modulesPortable}/modules" >&2
ls -laR ${modulesPortable} >&2 || true
exit 1
fi
${pkgs.lib.optionalString withPkgModules ''
mkdir -p $out/modules-pkg
pkgcount=0
for m in ${modulesPortable}/modules-pkg/*; do
[ -d "$m" ] || continue
cp -r "$m" $out/modules-pkg/
pkgcount=$((pkgcount + 1))
done
if [ "$pkgcount" -eq 0 ]; then
echo "Error: no modules under ${modulesPortable}/modules-pkg;" >&2
echo " ${binName}${exeExt} would start with no package backend" >&2
exit 1
fi
''}
chmod -R +w $out/modules ${pkgs.lib.optionalString withPkgModules "$out/modules-pkg"}
# Guard the defect class the change above removes: a manifest
# whose "main" names something that is not that module's plugin.
# The convention is modules/<name>/<name>_plugin.<ext>, so any
# other value is wrong by construction. This fails the BUILD
# rather than shipping a bundle that starts and then cannot load
# -- which is exactly what happened, because every structural
# check (file counts, PE counts, zip entries) passed on it.
for man in $out/modules/*/manifest.json ${pkgs.lib.optionalString withPkgModules "$out/modules-pkg/*/manifest.json"}; do
[ -f "$man" ] || continue
mod=$(basename "$(dirname "$man")")
bad=$(${pkgs.buildPackages.jq}/bin/jq -r '.main | if type=="object" then to_entries[].value else . end' "$man" \
| sort -u | grep -v "^''${mod}_plugin\." || true)
if [ -n "$bad" ]; then
echo "Error: $man declares an entry point that is not $mod's plugin:" >&2
echo "$bad" >&2
exit 1
fi
done
runHook postInstall
'';
@@ -639,8 +694,12 @@
pkgs.cmake
pkgs.ninja
pkgs.pkg-config
pkgs.qt6.wrapQtAppsNoGuiHook
];
]
# Same two-part guard as `build`: the Qt wrapper hooks cannot even
# evaluate for a mingw host and would skip a PE anyway, and dropping
# the hook alone makes qtbase's setup hook error with "depends on
# qtbase, but no wrapping behavior was specified".
++ pkgs.lib.optional (!isWindows) pkgs.qt6.wrapQtAppsNoGuiHook;
buildInputs = [
# cppSdk propagates Boost, OpenSSL, nlohmann_json (but
@@ -659,7 +718,7 @@
pkgs.spdlog
];
cmakeFlags = [
cmakeFlags = (pkgs.logosQtCrossCmakeFlags or [ ]) ++ [
"-GNinja"
"-DLOGOS_LIBLOGOS_ROOT=${liblogosPortable}"
"-DLOGOS_CPP_SDK_ROOT=${cppSdk}"
@@ -733,65 +792,68 @@
logoscoreCli = pkgs.symlinkJoin { name = pname; paths = [ binLegacy ]; };
logosctlCli = pkgs.symlinkJoin { name = "${pname}-ctl"; paths = [ binCtl ]; };
binLegacyWin = mkBinWindows { binName = "logoscore"; };
binCtlWin = mkBinWindows { binName = "logosctl"; };
binLegacyWin = mkBinWindows { binName = "logoscore"; withPkgModules = false; };
binCtlWin = mkBinWindows { binName = "logosctl"; withPkgModules = true; };
in
if isWindows then {
# Windows ships the two binaries plus a bundled directory for each.
#
# STILL LEFT OUT, and why. Each reason below was re-checked by eval
# against the revs this flake locks, because the list this replaces
# had two entries whose stated reason had expired:
# against the revs this flake locks.
#
# * modules-pkg/ (package_manager + package_downloader), so
# `logosctl package ...` still has no backend on Windows.
# ONE blocker, not the four the old comment implied. Measured:
# - logos-package-manager-module packages.x86_64-windows.lib
# and .lib-portable -- PRESENT
# - logos-capability-module packages.x86_64-windows.lib
# -- PRESENT
# - nix-bundle-logos-module-install
# bundlers.x86_64-windows.{dev,portable} -- PRESENT (it
# takes lgpm from the BUILD system and passes
# `--platform windows-x86_64`, so the cross case is handled)
# - logos-package-downloader-module
# packages.x86_64-windows -- ABSENT
# Root cause of the one that is missing, and it is not in that
# repo's own sources: logos-module-builder gates the
# x86_64-windows target on its `logos-nix` input, and
# package-downloader-module still pins module-builder 8e4ea1c,
# whose lib/common.nix does not mention x86_64-windows at all.
# package-manager-module and capability-module pin 9d3b7cc, which
# does. So the fix is a pin bump in THAT repo, followed by a
# re-lock here.
# modules-pkg/ USED TO BE LISTED HERE and no longer is: package_manager
# and package_downloader now ship on Windows. The blocker was real but
# cleared upstream -- logos-package-downloader-module gained
# packages.x86_64-windows when it bumped logos-module-builder
# 8e4ea1c -> 9d3b7cc (that file's systems list is a hardcoded 4-element
# array with no x86_64-windows at the older rev). This flake was simply
# 3 commits behind; a single-input re-lock was the whole fix.
#
# Why not ship the half that exists. Shipping package_manager
# alone would be worse than shipping neither, not merely
# incomplete: src/daemon/daemon.cpp:241 loads the two names in a
# loop and `return`s on the first failure -- BEFORE the
# setEmbeddedModulesDirectory / setUserModulesDirectory /
# setKeyringDirectory calls below it. package_manager would come
# up loaded but unconfigured, and installs would land wherever its
# unset defaults point, after one warning on stderr.
# Two things the old entry got WRONG, recorded because both were
# believed and acted on:
#
# What package_downloader's arrival does NOT settle: that the
# daemon LOADS either module on Windows (never run anywhere), and
# the size of what it installs -- the modules-pkg path goes
# through .lgx, which has no host-runtime strip on the PE path
# yet, so both packages would carry their own copy of the Qt,
# OpenSSL and libstdc++ DLLs that sit beside logosctl.exe already.
# * Its stated consequence of shipping package_manager alone --
# "installs would land wherever its unset defaults point" -- is
# FALSE. Every one of those directories fails closed when unset.
# The real hazard is narrower and worse: a configured
# `signature_policy: require` is read and then silently DISARMED
# in the half-configured state.
#
# * It framed daemon.cpp's first-failure `return` as a reason not to
# ship half. It is not a Windows fact at all -- the same early
# return leaves the daemon half-configured on every platform, and
# because package_manager is first in the list, its failure means
# package_downloader is never attempted even when it would load.
# That is a product defect with its own fix, not a packaging
# constraint, and it is tracked separately.
#
# * *-appimage. An AppImage is a Linux ELF runtime concatenated with
# a squashfs image; there is no Windows analogue. Confirmed rather
# than assumed: nix-bundle-appimage exposes `lib` for
# aarch64-linux and x86_64-linux only. Do not force this one.
#
# * portable. Not blocked -- redundant. mkBinWindows IS the portable
# shape: win-dll-link.sh stages every dependency beside the .exe
# because a PE import table carries base names and no rpath, so
# there is no @rpath/$ORIGIN variant for a `portable` output to
# differ from. This is the one entry the old comment got right,
# though it gave the reason under the wrong heading.
# * portable. NO LONGER LEFT OUT, and the reason it was is worth
# keeping because it was wrong in an instructive way. The old
# entry argued portable was "redundant" on Windows: a PE import
# table carries base names and no rpath, win-dll-link.sh stages
# every dependency beside the .exe, so there is no @rpath/$ORIGIN
# variant for a portable output to differ from.
#
# All of that is true and none of it is the whole story. `portable`
# is not only a LAYOUT distinction -- it is a compile-time one.
# LGPM_PORTABLE_BUILD (logos-package-manager/CMakeLists.txt:58)
# decides what platformVariantsToTry() returns: without it, the
# binary appends "-dev" and accepts ONLY dev variants
# (package_manager_lib.cpp:1005-1008). So a dev-built logosctl.exe
# beside modules installed as `windows-x86_64` refuses all three
# with "installed for variant 'windows-x86_64' which is not
# supported on this platform". mkBinWindows therefore builds from
# buildPortable/liblogosPortable, matching the installPortable
# modules it stages.
#
# buildPortable needed two Windows fixes to be usable here, both
# of which `build` already had: the Qt host-tool cmakeFlags, and
# the !isWindows guard on wrapQtAppsNoGuiHook.
#
# * tests. Verified here rather than inherited from the old comment.
# CMakeLists.txt:277 skips the whole test block under WIN32, and