#!/usr/bin/env bash # Cross-compiles igneumd.exe and igneum-miner.exe for x86_64-pc-windows-gnu on the Mac (Homebrew mingw-w64 and llvm). # Worked on 3 October 2026 (8 min 25 s with -j 6 at nice 19 on a loaded M5 Max); see WINDOWS-MINER.md, "Run your own node". # What each variable is for: # CC/CXX/AR_x86_64_pc_windows_gnu the cc crate builds rocksdb (C++), snappy, zstd, lz4, bzip2, zlib, secp256k1 and # mimalloc with the mingw compilers # LIBCLANG_PATH librocksdb-sys runs bindgen on rocksdb/c.h; Homebrew llvm's libclang # BINDGEN_EXTRA_CLANG_ARGS_... bindgen's clang must parse the mingw headers for the Windows target, not the Mac's # CARGO_TARGET_..._RUSTFLAGS -static: ld takes libstdc++.a, libgcc.a and libwinpthread.a instead of the import # libraries, so the exe does not need libstdc++-6.dll, libgcc_s_seh-1.dll or # libwinpthread-1.dll on the PC (the first build linked libstdc++-6.dll because # -static-libstdc++ means nothing to the gcc driver rustc links with) # Usage: windows-node/cross-build.sh [node worktree, default vendor/igneum-node-win] [cargo jobs, default 6] set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$HERE/../.." && pwd)" NODE="${1:-$ROOT/vendor/igneum-node-win}" JOBS="${2:-6}" MINGW="/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32" [ -d "$MINGW" ] || { echo "no mingw-w64 at $MINGW (brew install mingw-w64)" >&2; exit 1; } [ -f /opt/homebrew/opt/llvm/lib/libclang.dylib ] || { echo "no libclang at /opt/homebrew/opt/llvm/lib (brew install llvm)" >&2; exit 1; } export PATH="$HOME/.cargo/bin:/opt/homebrew/bin:$PATH" rustup target list --installed | grep -q x86_64-pc-windows-gnu || rustup target add x86_64-pc-windows-gnu export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target}" export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++ export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar export LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib export BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=$MINGW -I$MINGW/include" # 5 October 2026 (0.3.7): -static-libstdc++ added as a try; -static alone left libstdc++-6.dll in the import table of # every exe so far (0.3.5's and the PC's), which is why the payload must ship the DLLs of the toolchain that linked # the exes (push-inputs.sh takes them from next to the exes first, then from this toolchain) and why push-inputs.sh # refuses an exe that imports a symbol the shipped DLL does not export. # 5 October 2026 (housekeeping, after 0.3.8): the fork's database/build.rs now links libstdc++ statically on this # target (the toolchain's libstdc++.a offered as libstdc++.dll.a in a search dir), so a node built from a fork at or # after that commit imports no mingw DLL and the pairing rule is moot for it; the gate and the DLL copy stay for any # older fork. The PC-built exe (GCC 13) died at its first rocksdb call with the dynamic runtime; see the # release-0.3.6 plan, section 10. # 6 October 2026 (main's decision): -Wl,--no-insert-timestamp makes the exe reproducible (the PE header timestamp was the one # byte-level difference between two builds of one tree on igneum-build-1); the box (tools/cross-remote.sh) and the PC job carry it too export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-arg=-static -C link-arg=-static-libgcc -C link-arg=-static-libstdc++ -C link-arg=-Wl,--no-insert-timestamp" cd "$NODE" # reproducible (main, 6 October 2026, the 0.3.14 repro): the commit's author time for mimalloc's __DATE__/__TIME__, UTC, and ONE # target dir per target (CARGO_TARGET_DIR above is fixed, never a per-run name; prost embeds OUT_DIR) SOURCE_DATE_EPOCH=$(git log -1 --format=%at HEAD); export SOURCE_DATE_EPOCH TZ=UTC # The commit hash (6 October 2026, found on igneum-build-1): kaspa-build-info's build.rs embeds the commit only when .git is # a DIRECTORY and HEAD is a symbolic ref to a branch file, and once it has found nothing it emits no rerun-if-changed, so # cargo never runs it again in this target dir. Two steps: clean that one crate when the commit changed since the last build # here, and refuse a worktree (.git is a file there: the hash would be empty and tools/ci/commit-string-check.sh would fail # the release). Build the Windows exes from the fork's main checkout or through tools/cross-remote.sh (the box checks out a branch). sha=$(git rev-parse HEAD) [ -d .git ] || { echo "$NODE/.git is not a directory (a worktree): kaspa-build-info would embed no commit; use tools/cross-remote.sh or the fork's main checkout" >&2; exit 1; } [ "$(cat ".cross-build-sha-$CARGO_TARGET_DIR" 2>/dev/null)" = "$sha" ] || cargo clean -q --release -p kaspa-build-info --target x86_64-pc-windows-gnu 2>/dev/null || true nice -n 19 cargo build --release -j "$JOBS" -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu echo "$sha" > ".cross-build-sha-$CARGO_TARGET_DIR" for exe in igneumd igneum-miner; do f="$CARGO_TARGET_DIR/x86_64-pc-windows-gnu/release/$exe.exe" echo "$f: $(stat -f %z "$f") bytes; DLLs: $(x86_64-w64-mingw32-objdump -p "$f" | grep 'DLL Name' | awk '{print $3}' | sort -u | tr '\n' ' ')" [ "$exe" = igneumd ] && "$ROOT/tools/ci/commit-string-check.sh" "$f" "$sha" # only kaspad depends on kaspa-build-info done