#!/bin/bash # Cross-compile JellyTau for Windows from Linux, producing an NSIS installer. # # Uses the OFFICIAL Tauri cross-compile path (https://v2.tauri.app/distribute/ # windows-installer/): the MSVC target driven by cargo-xwin, which downloads the # MSVC CRT/Windows SDK headers and links with lld. This is the target Tauri # officially supports for Windows (the mingw/GNU target is not), and unlike GNU # it can bundle the NSIS installer from a Linux host. # # Playback on Windows: video renders via WebView2; audio via mpv, whose # libmpv-2.dll ships beside jellytau.exe โ€” see docs/build/build-windows.md. # # Requirements (present in the Docker windows-cross target / unified builder): # - rustup target x86_64-pc-windows-msvc # - cargo-xwin (cargo install --locked cargo-xwin) # - lld, llvm (linker + llvm-lib used by cargo-xwin) # - nsis (makensis) (installer generator) # - libmpv for Windows ($LIBMPV_WIN_DIR: libmpv-2.dll + mpv.lib, pinned and # sha256-checked in Dockerfile.builder) # # Usage: # scripts/build-windows-cross.sh # exe + NSIS installer # WIN_BUNDLES=none scripts/build-windows-cross.sh # exe only, skip bundling # OUTPUT_DIR=/app/dist scripts/build-windows-cross.sh set -euo pipefail cd "$(dirname "$0")/.." TARGET="x86_64-pc-windows-msvc" WIN_BUNDLES="${WIN_BUNDLES:-nsis}" echo "๐ŸชŸ Cross-compiling JellyTau for Windows ($TARGET, via cargo-xwin)" echo "================================================================" echo "Video plays via WebView2; audio via mpv (libmpv-2.dll)." echo "Bundles: $WIN_BUNDLES" echo "" # Stage libmpv where build.rs links it from and tauri.windows.conf.json bundles # it from โ€” one directory, so the DLL shipped is the one linked against. # Copied fresh every build: a stale DLL left from an older image would ship # silently. TRACES: UR-004 | DR-237 LIBMPV_WIN_DIR="${LIBMPV_WIN_DIR:-/opt/libmpv-win64}" for f in libmpv-2.dll mpv.lib; do if [[ ! -f "$LIBMPV_WIN_DIR/$f" ]]; then echo "โŒ $LIBMPV_WIN_DIR/$f not found. Build inside the jellytau-builder image," echo " or set LIBMPV_WIN_DIR to a directory holding libmpv-2.dll and mpv.lib." exit 1 fi done rm -rf src-tauri/windows-libs mkdir -p src-tauri/windows-libs cp -v "$LIBMPV_WIN_DIR/libmpv-2.dll" "$LIBMPV_WIN_DIR/mpv.lib" src-tauri/windows-libs/ bun install --frozen-lockfile 2>/dev/null || bun install bun run build # --runner cargo-xwin + the MSVC target is what makes the Tauri CLI treat this as # a real Windows build and enable the nsis/msi bundlers on a Linux host. # # IMPORTANT: do NOT pass `--bundles nsis` here. tauri-cli 2.9.x validates the # `--bundles` flag against a static clap enum gated by the HOST OS (Linux allows # only deb/rpm/appimage) *before* it considers --target/--runner, so `--bundles # nsis` is rejected at arg-parse time. Instead the Windows bundle targets come # from tauri.conf.json (bundle.targets includes "nsis"), which is not subject to # that CLI validation โ€” the bundler then picks nsis once it knows the target is # Windows. # TRACES: | DR-221 # # ๐Ÿ”ด Clear the bundle output before building. # # The bundle directory is not versioned and is never cleaned by cargo, and the # CI runner reuses src-tauri/target between builds. The copy step below globs # `bundle/**/*-setup.exe`, so every stale installer left there was picked up and # attached to the release: v0.8.2 shipped sixteen Windows installers, thirteen # of them from earlier versions, and v0.5.0 offered users a download list going # back to 0.1.0. Every release from v0.1.0 to v0.8.2 did this. It stopped only # because an unrelated change wiped the runner's target dir, so it is dormant # rather than fixed. # # Filtering the copy by version would hide it; removing the directory means a # stale file cannot exist to be copied. scripts/check-release-artifacts.sh is # the backstop if some other path reintroduces one. BUNDLE_DIR="src-tauri/target/$TARGET/release/bundle" if [[ -d "$BUNDLE_DIR" ]]; then echo "๐Ÿงน Clearing previous bundle output at $BUNDLE_DIR" rm -rf "$BUNDLE_DIR" fi if [[ "$WIN_BUNDLES" == "none" ]]; then bun run tauri build --runner cargo-xwin --target "$TARGET" --no-bundle else bun run tauri build --runner cargo-xwin --target "$TARGET" fi BIN_DIR="src-tauri/target/$TARGET/release" echo "" echo "โœ… Built Windows artifacts:" find "$BIN_DIR" -maxdepth 1 -name '*.exe' -print find "$BIN_DIR/bundle" -type f \( -name '*.exe' -o -name '*.msi' \) -print 2>/dev/null || true if [[ -n "${OUTPUT_DIR:-}" ]]; then mkdir -p "$OUTPUT_DIR" find "$BIN_DIR" -maxdepth 1 -name 'jellytau.exe' -exec cp -v {} "$OUTPUT_DIR/" \; # NSIS setup installers land in bundle/nsis/*-setup.exe; MSI in bundle/msi/*.msi. # # The .sig files come along too: when TAURI_SIGNING_PRIVATE_KEY is set the # bundler writes `.sig` beside each installer, and that signature is # what the updater verifies before installing anything. Leaving it behind # produces a release whose manifest references a signature that was never # published, which fails only on the user's machine. find "$BIN_DIR/bundle" -type f \( -name '*-setup.exe' -o -name '*.msi' -o -name '*.sig' \) \ -exec cp -v {} "$OUTPUT_DIR/" \; 2>/dev/null || true echo "" echo "๐Ÿ“ฆ Copied Windows artifacts to $OUTPUT_DIR" fi # Containerised builds run as root against a bind-mounted tree; hand the # artifacts back to the host user. No-op when not root. See DR-213. "$(dirname "$0")/restore-ownership.sh"