#!/usr/bin/env bash # The Windows cross-build on igneum-build-1: what proto-cuda/windows-node/cross-build.sh does on the Mac (Homebrew mingw-w64) # and what the PC build job does in WSL2 (app/igneum-app/src/jobbuild.rs: Ubuntu 24.04 mingw-w64 posix threads, static # libgcc), run on the box with sccache and -j 90 (sources synced and re-stamped with touch by lib.sh's bs_overlay_dir, the # copied-sources rule). Run from a fork worktree (igneumd.exe, igneum-miner.exe) or from # app/igneum-app (igneum-app.exe, igneum-ota-sign.exe, igneum-prove-verify.exe). # # tools/cross-remote.sh the default command for this crate # tools/cross-remote.sh --compare target-integration/x86_64-pc-windows-gnu/release sha256 against the Mac's exes # tools/cross-remote.sh -- build --release -p kaspad --features igneum-pow --target x86_64-pc-windows-gnu # tools/cross-remote.sh --jobs 48 --target-dir target-win # # Output: /target-remote/x86_64-pc-windows-gnu/release/.exe, one line each with size, sha256 and the DLL import # list (x86_64-w64-mingw32-objdump -p on the box, as the Mac script prints it). With --compare , the Mac's exe of the same # name is hashed too and the line says identical or differs. # # Reproducible (main's decision, 6 October 2026): -Wl,--no-insert-timestamp zeroes the PE header timestamp the mingw linker # writes, so two builds of one tree give one hash (verified 6 Oct: two runs, both exes identical); the Mac's cross-build.sh and # the PC job (jobbuild.rs) carry the same flag. # Byte identity (6 October 2026): the same rustc (1.99.0 both sides, refused otherwise) and the same flags give the same Rust # code, but two things still differ between the Mac's exe and the box's: the C and C++ objects (rocksdb, snappy, zstd, lz4, # secp256k1, mimalloc) come from Homebrew's mingw gcc on the Mac and Ubuntu's gcc 13 here, and source paths embedded by # rustc (panic locations, /Users/joshm/... against /srv/builds/...) differ unless both sides pass --remap-path-prefix, which # the Mac script does not. So: identical bytes build to build ON THE BOX (sccache does not change output), a different hash # from the Mac's exe is expected, and the number that matters is the DLL list (must be none since the fork's database/build.rs # links libstdc++ statically) and that the exe runs on the PC. The same holds for the PC-built exes today. # # Environment of the build (the PC recipe; the Mac's -static-libstdc++ added, harmless since housekeeping made the C++ runtime # static in the fork): CC/CXX/AR_x86_64_pc_windows_gnu = the posix mingw compilers, the linker the same gcc, RUSTFLAGS # -static -static-libgcc -static-libstdc++, LIBCLANG_PATH = Ubuntu's llvm lib dir (bindgen for librocksdb-sys), # BINDGEN_EXTRA_CLANG_ARGS pointed at /usr/x86_64-w64-mingw32, IGNEUM_WINDRES for the app's .rc. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC2034 # shared with lib.sh BS_TOOL=cross-remote # shellcheck source=../infra/build-server/lib.sh . "$HERE/../infra/build-server/lib.sh" { # whole-body: bash parses this block entirely before running a line of it, so an edit to this file while a run is in # flight cannot reach the running copy (7 Oct 2026: build-remote.sh was edited mid-run and died on shifted bytes after a 4-min build) TARGET=x86_64-pc-windows-gnu # JOBS empty = the box decides: 90 alone, 45 beside another slot holder (remote-run.sh, main's ruling 6 Oct 2026). One line, no # trailing comment: a `#` on this line once swallowed every assignment after it, CARGO_ARGS was never declared and the default # cross-build died with "unbound variable" under the Mac's bash 3.2 (the shipper, 6 Oct 2026, 21:xx UTC) JOBS="${JOBS:-}"; OUT=""; COMPARE=""; TARGET_DIR="target"; CARGO_ARGS=() while [ $# -gt 0 ]; do case "$1" in --jobs) JOBS="$2"; shift 2 ;; --out) OUT="$2"; shift 2 ;; --compare) COMPARE="$2"; shift 2 ;; --target-dir) TARGET_DIR="$2"; shift 2 ;; --) shift; CARGO_ARGS=("$@"); break ;; -h|--help) sed -n '2,30p' "$0"; exit 0 ;; *) CARGO_ARGS=("$@"); break ;; esac done [ "${CARGO_ARGS[0]:-}" = cargo ] && CARGO_ARGS=("${CARGO_ARGS[@]:1}") bs_host bs_context case "$BS_KIND:$BS_CRATE_REL" in node:*) [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release -p kaspad -p igneum-miner --features igneum-pow --target "$TARGET") EXES="igneumd.exe igneum-miner.exe" ;; repo:app/igneum-app) [ -n "${CARGO_ARGS[*]:-}" ] || CARGO_ARGS=(build --release --target "$TARGET") EXES="igneum-app.exe igneum-ota-sign.exe igneum-prove-verify.exe" ;; *) bs_die "cross-remote builds the fork (run from a vendor/igneum-node* worktree) or app/igneum-app, not $BS_CRATE_REL" ;; esac [ -n "$OUT" ] || OUT="$BS_CRATE/target-remote" [ -z "$COMPARE" ] && [ -d "$BS_CRATE/target-integration/$TARGET/release" ] && COMPARE="$BS_CRATE/target-integration/$TARGET/release" bs_log "$BS_KIND crate $BS_WT/$BS_CRATE_REL at $BS_SHA ($BS_BRANCH) -> $BS_HOST:$BS_REMOTE_CRATE; cargo ${CARGO_ARGS[*]} -j ${JOBS:-auto}; target dir $TARGET_DIR" bs_toolchain_check t_sync0=$(date +%s) bs_sync_sources bs_log "sources in place after $(( $(date +%s) - t_sync0 )) s" # the build environment, as one shell string for the remote runner (every value is a literal; nothing from the Mac's env) env_block='LLVM_LIB=$(ls -d /usr/lib/llvm-*/lib 2>/dev/null | sort -V | tail -1); [ -n "$LLVM_LIB" ] || { echo "no /usr/lib/llvm-*/lib on the box (apt clang libclang-dev)"; exit 2; } export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc-posix CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++-posix AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc-posix 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" export IGNEUM_WINDRES=x86_64-w64-mingw32-windres LIBCLANG_PATH="$LLVM_LIB" export BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--target=x86_64-w64-mingw32 --sysroot=/usr/x86_64-w64-mingw32 -I/usr/x86_64-w64-mingw32/include" echo "cross-remote: $(x86_64-w64-mingw32-gcc-posix --version | head -1); libclang $LLVM_LIB"' pre="" # the same kaspa-build-info clean on a new commit as build-remote.sh (the Windows target has its own fingerprint) [ "$BS_KIND" = node ] && pre="[ \"\$(cat '.cross-remote-sha-$TARGET_DIR' 2>/dev/null)\" = '$BS_SHA' ] || CARGO_TARGET_DIR='$TARGET_DIR' cargo clean -q --release -p kaspa-build-info --target $TARGET 2>/dev/null; " cmd="$(bs_repro_env)$env_block ${pre}CARGO_TARGET_DIR='$TARGET_DIR' cargo $(printf '%q ' "${CARGO_ARGS[@]}")${JOBS:+-j $JOBS} 2>&1 | tee -a '$BS_REMOTE_WT/.cross-remote.log'; rc=\${PIPESTATUS[0]}; [ \$rc = 0 ] && echo '$BS_SHA' > '.cross-remote-sha-$TARGET_DIR' for exe in $EXES; do f='$TARGET_DIR/$TARGET/release/'\$exe; [ -f \"\$f\" ] && echo \"cross-remote: DLLS \$exe: \$(x86_64-w64-mingw32-objdump -p \"\$f\" | awk '/DLL Name/ { print \$3 }' | sort -u | tr '\n' ' ')\"; done ( exit \$rc )" # a subshell exit: the runner reads \$? and still prints its RESULT line label="$BS_WT/$BS_CRATE_REL cross $TARGET" BR_KIND=$(bs_kind cross-remote "${CARGO_ARGS[0]}"); BR_COMMAND="cargo ${CARGO_ARGS[*]}"; BR_TARGET="$TARGET" BR_ARTEFACTS=""; for exe in $EXES; do BR_ARTEFACTS="$BR_ARTEFACTS $TARGET_DIR/$TARGET/release/$exe"; done export BR_KIND BR_COMMAND BR_TARGET BR_ARTEFACTS t0=$(date +%s) set +e bs_remote_run "$BS_REMOTE_CRATE" "$label" "$cmd" 2>&1 | tee "/tmp/cross-remote-$$.log" rc=${PIPESTATUS[0]} set -e secs=$(( $(date +%s) - t0 )) result=$(grep -m1 '^build-remote: RESULT' "/tmp/cross-remote-$$.log" || true) dlls=$(grep '^cross-remote: DLLS' "/tmp/cross-remote-$$.log" || true); rm -f "/tmp/cross-remote-$$.log" if [ "$rc" != 0 ]; then bs_die "remote cross-build failed (rc $rc) after $(bs_fmt_secs "$secs"); $result"; fi bs_log "remote cross-build done in $(bs_fmt_secs "$secs") wall from the Mac; ${result#build-remote: RESULT }" mkdir -p "$OUT/$TARGET/release" for exe in $EXES; do dest="$OUT/$TARGET/release/$exe" bs_rsync -p "$BS_HOST:$BS_REMOTE_CRATE/$TARGET_DIR/$TARGET/release/$exe" "$dest" || bs_die "no $exe on the box after the build" sum=$(bs_sha256 "$dest"); line="$exe: $(bs_size "$dest") bytes, sha256 $sum" d=$(printf '%s\n' "$dlls" | grep "DLLS $exe:" | sed 's/.*DLLS [^:]*: *//'); line="$line, DLLs: ${d:-none}" if [ -n "$COMPARE" ] && [ -f "$COMPARE/$exe" ]; then msum=$(bs_sha256 "$COMPARE/$exe") if [ "$msum" = "$sum" ]; then line="$line; IDENTICAL to $COMPARE/$exe"; else line="$line; differs from $COMPARE/$exe ($(bs_size "$COMPARE/$exe") bytes, sha256 ${msum:0:16}...; expected, see the header)"; fi fi bs_log "$line" # the commit-string gate (rule of 6 October 2026): a node exe without its commit in its strings fails the run case "$BS_KIND:$exe" in node:igneumd.exe) "$HERE/ci/commit-string-check.sh" "$dest" "$BS_SHA" || bs_die "commit-string gate failed for $exe" ;; esac # only kaspad depends on kaspa-build-info done # an exe that imports libstdc++-6.dll (a fork before the housekeeping commit that made the C++ runtime static) needs the # three runtime DLLs OF THIS TOOLCHAIN beside it (the PC job does the same; push-inputs.sh takes them from next to the exes) if printf '%s\n' "$dlls" | grep -q 'libstdc++-6.dll'; then bs_ssh 'd=$(dirname "$(x86_64-w64-mingw32-gcc-posix -print-file-name=libstdc++-6.dll)"); mkdir -p /tmp/igneum-mingw-dlls; cp "$d/libstdc++-6.dll" "$d/libgcc_s_seh-1.dll" /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll /tmp/igneum-mingw-dlls/ && ls /tmp/igneum-mingw-dlls' >/dev/null \ && bs_rsync -p "$BS_HOST:/tmp/igneum-mingw-dlls/*.dll" "$OUT/$TARGET/release/" || bs_die "could not fetch the mingw runtime DLLs" for dll in libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll; do bs_log "runtime $dll: $(bs_size "$OUT/$TARGET/release/$dll") bytes, sha256 $(bs_sha256 "$OUT/$TARGET/release/$dll") (GCC 13 posix, beside the exes)"; done fi bs_log "exes in $OUT/$TARGET/release" bs_wt_unlock exit 0 }