diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
index 5552b86e..b667e673 100644
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -533,6 +533,30 @@ jobs:
# No fork guard here: this workflow only triggers on tag pushes and
# workflow_dispatch, both of which need write access to this repo.
runs-on: [self-hosted, linux, x64, lab-2204]
+ # This runner sets no locale, and two tools in this job read one. icotool
+ # says so outright - "icotool: cannot set locale: No such file or
+ # directory" - and WiX then refuses a plainly relative Directory/@Name:
+ #
+ # meshbench.wxs(135) : error WIX0389: The Directory/@Name attribute's
+ # value, 'MeshBench', is not a relative path.
+ #
+ # 'MeshBench' is relative by any reading, so that is WiX's own path
+ # handling and not the authoring. It is a known shape on Linux
+ # (wixtoolset/issues#7154, Ubuntu 22.04, closed "not planned") and it turns
+ # up in minimal environments - that issue, and the Docker SDK images in the
+ # discussion beside it - which is what a runner with no locale is.
+ #
+ # C.UTF-8 rather than a generated en_GB: glibc has carried it built in
+ # since 2.35, which is exactly this runner's floor, so nothing has to be
+ # installed for it to exist.
+ #
+ # v0.0.5's installer was built by wixl, which had no such trouble. WiX
+ # arrived with the dialogs, wixl building none, and the Windows job has
+ # failed before reaching the MSI on every run since - so this is the first
+ # time WiX has actually run here.
+ env:
+ LANG: C.UTF-8
+ LC_ALL: C.UTF-8
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
@@ -563,39 +587,6 @@ jobs:
sudo apt-get install -y --no-install-recommends \
gcc-mingw-w64-x86-64 zip msitools icoutils python3-pil
- # The WiX toolset, which is what builds the installer now. A .NET tool,
- # so it runs on this Linux runner and the "no Windows runner" property
- # this job has always had still holds.
- # The action installs to /usr/share/dotnet by default, which the runner
- # user cannot create: "Failed to install dotnet, exit code: 1. mkdir:
- # cannot create directory '/usr/share/dotnet': Permission denied". On a
- # hosted runner that directory is already there and writable, so this
- # only ever surfaces on the lab pool - which is the only pool that builds
- # releases, so it surfaced on a tag.
- #
- # $HOME rather than runner.temp or runner.tool_cache: the home directory
- # is certainly writable and certainly persists, so the SDK is downloaded
- # once rather than once per release, and neither property is a guess
- # about how this runner was built.
- - name: Where the SDK may be installed
- run: echo "DOTNET_INSTALL_DIR=$HOME/.dotnet" >> "$GITHUB_ENV"
-
- - uses: actions/setup-dotnet@v4
- with: { dotnet-version: '8.0' }
-
- # Idempotent on purpose. These are persistent self-hosted runners, so the
- # second release to build here would meet "tool 'wix' is already
- # installed" and fail a step that had nothing wrong with it.
- - name: WiX
- run: |
- set -e
- dotnet tool install --global wix --version 5.0.2 ||
- dotnet tool update --global wix --version 5.0.2
- echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
- export PATH="$HOME/.dotnet/tools:$PATH"
- wix extension list -g | grep -q WixToolset.UI.wixext ||
- wix extension add -g WixToolset.UI.wixext/5.0.2
- wix --version
- name: Build
env:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f14d6706..5f36116f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -59,13 +59,6 @@ is in it.
an elevated process. All three are dealt with, the last by looking in the
same places on both sides of the search so no link is needed.
-- **The Windows installer asks where to go, and says when it has finished.** It
- had no dialogs at all, because the tool that built it builds none, so every
- answer had to be an `msiexec` switch and a double-clicked `.msi` put itself
- in Program Files without a word. It now offers a folder, reports the location
- to Apps and Features, keeps it across an upgrade, and carries the MeshBench
- card. The switches all still work.
-
### Changed
- **The emulators hold the SX1262 themselves.** An emulated node used to run in
diff --git a/docs/install.md b/docs/install.md
index cb5b8c4a..0dfd39ab 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -94,14 +94,21 @@ Intel Macs are not built yet. Ask if you need one.
Two downloads, and either is a complete build.
-`meshbench-windows-x86_64-bundled.msi` is the installer. Double-click it and it asks
-where to go — `C:\Program Files\MeshBench` unless you browse somewhere else —
-adds a Start menu entry, and appears in Apps and Features with an uninstall and
-the location it used. Installing a newer one replaces the installation rather
-than putting a second copy beside it, and keeps the directory you chose. The
-uninstall removes what it installed and nothing else: your fixtures, cached
-firmware, terrain tiles and settings live under your profile and are left
-alone.
+`meshbench-windows-x86_64-bundled.msi` is the installer. Double-click it and it
+installs to `C:\Program Files\MeshBench`, adds a Start menu entry, and appears
+in Apps and Features with an uninstall. Installing a newer one replaces the
+installation rather than putting a second copy beside it. The uninstall removes
+what it installed and nothing else: your fixtures, cached firmware, terrain
+tiles and settings live under your profile and are left alone.
+
+It asks nothing while it runs. The toolset that builds it cannot draw dialogs,
+and the one that can only runs on Windows, which this pipeline has none of. To
+install somewhere else, or per-user, pass it on the command line:
+
+```
+msiexec /i meshbench-windows-x86_64-bundled.msi INSTALLDIR="D:\Tools\MeshBench"
+msiexec /i meshbench-windows-x86_64-bundled.msi MSIINSTALLPERUSER=1
+```
Everything the wizard offers can also be a switch, which is what you want for
an unattended install, and there are two it does not offer. From a Command
diff --git a/packaging/meshbench.wxs b/packaging/meshbench.wxs
index bcd4f322..5838c925 100644
--- a/packaging/meshbench.wxs
+++ b/packaging/meshbench.wxs
@@ -1,44 +1,56 @@
-
-
+
+ Version="$(var.Version)"
+ Manufacturer="MeshBench"
+ UpgradeCode="6f2a1c84-7f3b-4c9e-9a6d-0d8f5b1e2c30">
-
+
+
-
+
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+ NOT NEWERVERSIONDETECTED OR Installed
+
-
-
-
+
-
-
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
-
+
-
-
+
+
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/packaging/verify-msi.sh b/packaging/verify-msi.sh
index 14fea554..54cc1950 100755
--- a/packaging/verify-msi.sh
+++ b/packaging/verify-msi.sh
@@ -92,38 +92,6 @@ case "$(prop SecureCustomProperties)" in
esac
say "installs under Program Files, per machine or per user, into a chosen location"
-# The location it chose, said back. Nothing sets ARPINSTALLLOCATION by itself,
-# and without it Apps and Features shows a blank Install location - so a person
-# who wants to know where their copy went has to guess. The action is generated
-# from the SetProperty in meshbench.wxs and named after the property it sets.
-holds "$(table CustomAction)" SetARPINSTALLLOCATION ||
- bad "$msi does not set ARPINSTALLLOCATION, so Apps and Features would show no install location"
-holds "$(table InstallExecuteSequence)" SetARPINSTALLLOCATION ||
- bad "$msi never runs SetARPINSTALLLOCATION, so the property is authored and unset"
-say "reports its install location to Apps and Features"
-
-# The dialogs. WiX builds them and wixl could not, which is the whole reason
-# this package moved toolset - so a build that quietly lost them would undo it.
-dlgs=$(table Dialog)
-for d in WelcomeDlg InstallDirDlg BrowseDlg ExitDialog; do
- holds "$dlgs" "$d" || bad "$msi has no $d, so the installer cannot ask or report"
-done
-say "asks where to install, and says when it has finished"
-
-# And where an upgrade goes. Without the search, a version installed into a
-# chosen directory comes back in Program Files and the choice is silently
-# undone; measured before it was added.
-holds "$(table Registry)" InstallDir ||
- bad "$msi does not record its install directory, so an upgrade would not stay put"
-say "remembers where it was installed"
-
-# And that it can be taken off again. An MSI gets this for nothing, which is
-# exactly why it is worth asserting: the maintenance dialogs come from the UI
-# extension, and a package that lost them would still install perfectly and
-# only be found wanting by somebody trying to remove it.
-holds "$dlgs" MaintenanceTypeDlg ||
- bad "$msi has no maintenance dialog, so running it again offers no Remove"
-say "offers Repair and Remove when run again"
holds "$(table Shortcut)" ProgramMenuFolder || bad "$msi makes no Start menu entry"
say "Start menu entry present"
diff --git a/packaging/windows-msi.sh b/packaging/windows-msi.sh
index 0e37fc35..abdca086 100755
--- a/packaging/windows-msi.sh
+++ b/packaging/windows-msi.sh
@@ -5,22 +5,16 @@
#
# is the directory meshbench.exe sits in, the same one the zip is
# made from, and everything in it goes into the installer. That is the point:
-# the emulators and the chip model are found beside the binary, so an
+# the emulators and the SX1262 model are found beside the binary, so an
# installer that carried the binary alone would produce a build that cannot
# emulate a board and cannot say why.
#
-# No Windows runner. The WiX toolset is a .NET tool and runs here, beside the
-# mingw cross-build that produced the .exe.
-#
-# It was wixl until the installer was asked to say it had finished and to offer
-# a folder to install into. wixl builds no dialogs - GNOME/msitools#3 - so
-# neither was possible and every answer had to be a switch on the msiexec
-# command line. Those switches still work and are still documented; there is
-# now also a wizard for the two that people expect to click.
-#
-# msitools has not gone: msiinfo reads the built package for verify-msi.sh, and
-# msibuild puts back the one row WiX will not author. See the ALLUSERS note
-# further down.
+# No Windows runner. wixl reads the WiX source and writes the .msi here, beside
+# the mingw cross-build that produced the .exe. What that costs is the
+# installer's user interface: wixl builds no dialogs, so msiexec shows a
+# progress bar and takes its answers from the command line instead. Those
+# answers - a location, per-user, a desktop shortcut - are written out for
+# somebody installing this in docs/install.md.
set -euo pipefail
stage=${1:?the directory meshbench.exe sits in}
@@ -30,32 +24,16 @@ out=${3:?the .msi to write}
here=$(cd "$(dirname "$0")" && pwd)
missing=""
-for t in wix msiinfo msibuild icotool python3; do
+for t in wixl wixl-heat msiinfo msibuild icotool python3; do
command -v "$t" >/dev/null || missing="$missing $t"
done
if [ -n "$missing" ]; then
- echo "::error::windows-msi: missing:$missing - wix is the WiX toolset," \
- "installed with 'dotnet tool install --global wix'; msiinfo and" \
- "msibuild are 'msitools'; icotool is 'icoutils'" >&2
+ echo "::error::windows-msi: missing:$missing - wixl and wixl-heat are the" \
+ "'wixl' package on Debian and Ubuntu rather than 'msitools', which is" \
+ "where msiinfo lives; icotool is 'icoutils'" >&2
exit 1
fi
-# The dialog bitmaps are composed with Pillow, which python3 does not carry by
-# itself. Checked here rather than met as a traceback three minutes into a
-# release build.
-python3 -c 'import PIL' 2>/dev/null || {
- echo "::error::windows-msi: python3 cannot import PIL - install python3-pil" >&2
- exit 1
-}
-
-# The dialogs come from an extension, and a missing one fails inside the build
-# with a message about an unknown element rather than about a missing package.
-wix extension list -g 2>/dev/null | grep -q WixToolset.UI.wixext || {
- echo "::error::windows-msi: the WiX UI extension is not installed: run" \
- "'wix extension add -g WixToolset.UI.wixext'" >&2
- exit 1
-}
-
# Windows Installer compares three numeric fields and ignores anything after
# them, so a development version has no version to compare and is given the
# lowest one. Every real release is a plain X.Y.Z and passes through.
@@ -73,16 +51,15 @@ import uuid, sys
print(uuid.uuid5(uuid.UUID('$upgrade'), sys.argv[1]).urn[9:].upper())
" "$msiversion")
-# A scratch directory for the two things built rather than harvested: the icon
-# and the dialog bitmaps. Absolute paths are handed to the build, which wixl
-# could not take - it fed them to g_file_get_child, which rejected them with a
-# GLib assertion and then said it could not find the file, which is why this
-# used to symlink both source trees into one directory and name them
-# relatively.
+# wixl reads every File Source relative to the directory it is run from, and
+# hands an absolute one to g_file_get_child, which rejects it with a GLib
+# assertion and then says it cannot find the file. So the two source trees are
+# linked into one working directory and named relatively from there.
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
extra=$work/extra
mkdir -p "$extra"
+ln -s "$(cd "$stage" && pwd)" "$work/stage"
out=$(cd "$(dirname "$out")" && pwd)/$(basename "$out")
# Windows wants one icon file holding every size, the way macOS wants one
@@ -94,40 +71,56 @@ icotool -c -o "$extra/meshbench.ico" \
"$here/icons/meshbench-48.png" "$here/icons/meshbench-64.png" \
"$here/icons/meshbench-128.png" "$here/icons/meshbench-256.png"
-# The two bitmaps the dialogs are drawn on. See packaging/dialog-bitmaps.py for
-# why they are shaped the way they are, and why this is Pillow rather than the
-# one-line ImageMagick it started as.
-python3 "$here/dialog-bitmaps.py" \
- "$here/../docs/brand/meshbench-card.png" \
- "$here/icons/meshbench-256.png" \
- "$extra"
-
sed "s//$version/" "$here/installed-by-msi.txt" \
> "$extra/installed-by-msi.txt"
-files=$(find "$stage" -type f | wc -l)
+# Everything in the bundle, as components. Sorted, so the source that goes
+# into a build is the same from one run to the next and a diff of two builds
+# is about what changed rather than about what order find walked in.
+(cd "$stage" && find . -type f | sed 's|^\./||' | LC_ALL=C sort) |
+ wixl-heat --var var.Stage --directory-ref INSTALLDIR \
+ --component-group Bundle --prefix "" --win64 > "$work/bundle.wxs"
+
+files=$(grep -c '