|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Plan-level check for dist-wix's `msi` and `setup` formats (mcpp#634, B4). |
| 3 | +# |
| 4 | +# The bundle is built on the Windows runner (`check-setup.sh`). This script runs |
| 5 | +# on any host: it runs the compiled build program under the environment the |
| 6 | +# engine sets for `mcpp pack --format msi` and `--format setup` on a Windows |
| 7 | +# target, with a fabricated `xim:wix` directory that holds the two files the |
| 8 | +# member looks for, and reads what it plans and writes. |
| 9 | +# |
| 10 | +# 1. `--format setup` plans the MSI and a bundle action whose inputs include |
| 11 | +# the MSI, so the bundle is the only terminal artifact, and whose command |
| 12 | +# loads the extension and names the MSI as the variable `Msi`. |
| 13 | +# 2. Both generated documents are well-formed XML. `wix build` refused a |
| 14 | +# bundle document whose comment held `--` (WIX0104, windows-2022), and |
| 15 | +# an XML parser refuses the same document on every host. |
| 16 | +# 3. `--format msi` plans no bundle. |
| 17 | +# 4. A bundle named `setup.exe` is refused before `wix` runs, as a warning. |
| 18 | +# |
| 19 | +# Usage: MCPP=<mcpp> ./check-wix-plan.sh (run from this directory) |
| 20 | +set -eu |
| 21 | + |
| 22 | +MCPP="${MCPP:-mcpp}" |
| 23 | +fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; } |
| 24 | +command -v python3 > /dev/null || fail "python3 is required to read the planned actions" |
| 25 | + |
| 26 | +rm -rf target |
| 27 | +"$MCPP" build > wix-plan-build.log 2>&1 || fail "the host build failed to compile build.mcpp" wix-plan-build.log |
| 28 | +BIN=target/.build-mcpp/build.mcpp.bin |
| 29 | +[ -x "$BIN" ] || fail "no compiled build.mcpp.bin at $BIN" wix-plan-build.log |
| 30 | + |
| 31 | +work=$(mktemp -d) |
| 32 | +wix="$work/xpkg-wix" |
| 33 | +mkdir -p "$wix/tool/tools/net6.0/any" "$wix/bal/wixext5" |
| 34 | +: > "$wix/tool/tools/net6.0/any/wix.exe" |
| 35 | +: > "$wix/bal/wixext5/WixToolset.BootstrapperApplications.wixext.dll" |
| 36 | + |
| 37 | +# run_program <format> <out dir> <log> [bundle output] |
| 38 | +run_program() { |
| 39 | + mkdir -p "$2" |
| 40 | + env -i PATH="$PATH" \ |
| 41 | + MCPP_PACK_FORMAT="$1" MCPP_TARGET_OS=windows MCPP_TARGET_ARCH=x86_64 \ |
| 42 | + MCPP_TARGET_ENV=msvc MCPP_PACK_STAGE_DIR="" MCPP_MANIFEST_DIR="$PWD" \ |
| 43 | + MCPP_PKG_NAME=msi-consumer MCPP_PKG_NAMESPACE= MCPP_PKG_VERSION=0.3.0 \ |
| 44 | + MCPP_PKG_AUTHORS=mcpp-community MCPP_OUT_DIR="$2" \ |
| 45 | + MCPP_XPKG_XIM_WIX_DIR="$wix" MSI_CONSUMER_BUNDLE_OUTPUT="${4:-}" \ |
| 46 | + "$BIN" > "$3" 2>&1 || true |
| 47 | +} |
| 48 | + |
| 49 | +# check <log> <python assertions over `actions` (id -> action), `lines`, `out`> |
| 50 | +check() { |
| 51 | + python3 - "$1" "$2" "$3" <<'PY' || exit 1 |
| 52 | +import json, sys |
| 53 | +log, out, code = sys.argv[1], sys.argv[2], sys.argv[3] |
| 54 | +lines = open(log).read().splitlines() |
| 55 | +actions = {} |
| 56 | +for l in lines: |
| 57 | + if l.startswith("mcpp:action="): |
| 58 | + a = json.loads(l[len("mcpp:action="):]) |
| 59 | + actions[a["id"]] = a |
| 60 | +def fail(msg): |
| 61 | + print("FAIL: " + msg) |
| 62 | + print("--- " + log + " ---") |
| 63 | + print("\n".join(lines)) |
| 64 | + sys.exit(1) |
| 65 | +exec(code) |
| 66 | +PY |
| 67 | +} |
| 68 | + |
| 69 | +echo "== 1, 2. --format setup ==" |
| 70 | +run_program setup "$work/setup" "$work/setup.log" |
| 71 | +check "$work/setup.log" "$work/setup" ' |
| 72 | +import xml.dom.minidom, os |
| 73 | +msi = actions.get("mcpp.dist.wix") |
| 74 | +bundle = actions.get("mcpp.dist.wix.bundle") |
| 75 | +if not msi or not bundle: fail("the MSI and the bundle are not both planned: " + repr(sorted(actions))) |
| 76 | +if msi["outputs"] != [out + "/MsiConsumer-x64.msi"]: fail("the MSI is not MsiConsumer-x64.msi: " + repr(msi["outputs"])) |
| 77 | +if bundle["outputs"] != [out + "/MsiConsumer-x64.exe"]: fail("the bundle is not MsiConsumer-x64.exe: " + repr(bundle["outputs"])) |
| 78 | +if msi["outputs"][0] not in bundle["inputs"]: fail("the bundle does not take the MSI as an input") |
| 79 | +cmd = bundle["command"] |
| 80 | +ext = "WixToolset.BootstrapperApplications.wixext.dll" |
| 81 | +if "-ext" not in cmd or not cmd[cmd.index("-ext") + 1].endswith(ext): fail("the bundle command loads no extension: " + repr(cmd)) |
| 82 | +if "-d" not in cmd or cmd[cmd.index("-d") + 1] != "Msi=" + msi["outputs"][0]: fail("the bundle command does not name the MSI as Msi: " + repr(cmd)) |
| 83 | +consumed = set(i for a in actions.values() for i in a["inputs"]) |
| 84 | +terminals = [a["id"] for a in actions.values() if not set(a["outputs"]) & consumed] |
| 85 | +if terminals != ["mcpp.dist.wix.bundle"]: fail("the bundle is not the sole terminal artifact: " + repr(terminals)) |
| 86 | +for name in ("MsiConsumer.wxs", "MsiConsumer-bundle.wxs"): |
| 87 | + path = os.path.join(out, name) |
| 88 | + if not os.path.isfile(path): fail("no generated " + name) |
| 89 | + try: |
| 90 | + doc = xml.dom.minidom.parse(path) |
| 91 | + except Exception as e: |
| 92 | + fail(name + " is not well-formed XML: " + str(e)) |
| 93 | +bundle_doc = open(os.path.join(out, "MsiConsumer-bundle.wxs")).read() |
| 94 | +if "bal:WixStandardBootstrapperApplication" not in bundle_doc or "<MsiPackage SourceFile=\"$(Msi)\" />" not in bundle_doc: |
| 95 | + fail("the bundle document does not chain $(Msi) under the stock bootstrapper application") |
| 96 | +' |
| 97 | +echo "ok: the bundle follows the MSI and is the sole terminal artifact, and both documents are well-formed XML" |
| 98 | + |
| 99 | +echo "== 3. --format msi ==" |
| 100 | +run_program msi "$work/msi" "$work/msi.log" |
| 101 | +check "$work/msi.log" "$work/msi" ' |
| 102 | +if "mcpp.dist.wix" not in actions: fail("--format msi planned no MSI") |
| 103 | +if "mcpp.dist.wix.bundle" in actions: fail("--format msi planned a bundle") |
| 104 | +' |
| 105 | +echo "ok: no bundle under --format msi" |
| 106 | + |
| 107 | +echo "== 4. a bundle named setup.exe ==" |
| 108 | +run_program setup "$work/refused" "$work/refused.log" "$work/refused/SETUP.exe" |
| 109 | +check "$work/refused.log" "$work/refused" ' |
| 110 | +if actions: fail("a bundle named setup.exe planned actions: " + repr(sorted(actions))) |
| 111 | +if not any(l.startswith("mcpp:warning=") and "WIX0388" in l for l in lines): |
| 112 | + fail("the refusal is not a warning naming WIX0388") |
| 113 | +' |
| 114 | +echo "ok: refused as a warning, before wix runs" |
| 115 | + |
| 116 | +echo "PASS: dist-wix plans the MSI and the bundle, and writes well-formed documents" |
0 commit comments