diff --git a/app/igneum-app/Cargo.toml b/app/igneum-app/Cargo.toml index cc6803fd5..b8fd8004e 100644 --- a/app/igneum-app/Cargo.toml +++ b/app/igneum-app/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" description = "Igneum Miner engine: supervises the node, the miner and the GPU workers, and serves the dashboard on 127.0.0.1" license = "MIT" publish = false +build = "build.rs" # Windows targets: links resources/igneum-app.rc (coin icon, version block) into the exe [[bin]] name = "igneum-app" diff --git a/app/igneum-app/build.rs b/app/igneum-app/build.rs new file mode 100644 index 000000000..5e55e38dc --- /dev/null +++ b/app/igneum-app/build.rs @@ -0,0 +1,80 @@ +// Build script for igneum-app. On a Windows target it compiles resources/igneum-app.rc (the coin icon Explorer shows +// and the version block under Properties > Details) with windres and links the object into igneum-app.exe. Other +// targets: nothing. Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the +// Mac app and DMG. No crate dependency: windres is called directly (x86_64-w64-mingw32-windres from Homebrew mingw-w64 +// on the Mac, windres from MSYS2 on a PC; IGNEUM_WINDRES names another one). +use std::env; +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + let rc = manifest.join("resources").join("igneum-app.rc"); + let icons = manifest.join("..").join("..").join("brand").join("icons"); + let ico = icons.join("igneum.ico"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed={}", rc.display()); + println!("cargo:rerun-if-changed={}", ico.display()); + println!("cargo:rerun-if-env-changed=IGNEUM_WINDRES"); + if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + // The .rc carries the version by hand; it must agree with Cargo.toml so Properties shows the number the app reports. + let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION"); + let text = fs::read_to_string(&rc).unwrap_or_else(|e| panic!("cannot read {}: {e}", rc.display())); + let numeric = format!("{},0", version.replace('.', ",")); + if !text.contains(&format!("\"{version}\"")) || !text.contains(&numeric) { + panic!( + "{} must carry version {version} (FILEVERSION/PRODUCTVERSION {numeric}, FileVersion/ProductVersion \"{version}\")", + rc.display() + ); + } + if !ico.is_file() { + panic!("no {}; run python3 brand/icons/make-icons.py first", ico.display()); + } + let windres = match find_windres() { + Some(w) => w, + None => { + if env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc") { + println!("cargo:warning=no windres found: igneum-app.exe is built without the coin icon and version block (set IGNEUM_WINDRES)"); + return; + } + panic!("no windres found (brew install mingw-w64, or set IGNEUM_WINDRES): igneum-app.exe must carry the coin icon and version block"); + } + }; + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("igneum-app.res.o"); + let status = Command::new(&windres) + .arg("-I").arg(&icons) + .arg("-i").arg(&rc) + .arg("-O").arg("coff") + .arg("-o").arg(&out) + .status() + .unwrap_or_else(|e| panic!("cannot run {windres}: {e}")); + if !status.success() { + panic!("{windres} failed on {} ({status})", rc.display()); + } + println!("cargo:rustc-link-arg-bins={}", out.display()); +} + +fn find_windres() -> Option { + let mut candidates = Vec::new(); + if let Ok(w) = env::var("IGNEUM_WINDRES") { + if !w.is_empty() { + candidates.push(w); + } + } + let prefix = match env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default().as_str() { + "x86_64" => Some("x86_64-w64-mingw32"), + "x86" => Some("i686-w64-mingw32"), + "aarch64" => Some("aarch64-w64-mingw32"), + _ => None, + }; + if let Some(p) = prefix { + candidates.push(format!("{p}-windres")); + } + candidates.push("windres".to_string()); + candidates + .into_iter() + .find(|c| Command::new(c).arg("--version").output().map(|o| o.status.success()).unwrap_or(false)) +} diff --git a/app/igneum-app/resources/igneum-app.rc b/app/igneum-app/resources/igneum-app.rc new file mode 100644 index 000000000..3a37b08aa --- /dev/null +++ b/app/igneum-app/resources/igneum-app.rc @@ -0,0 +1,35 @@ +// Windows resources for igneum-app.exe: the coin icon Explorer shows and the version block under Properties > Details. +// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I). +// Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG. +#include + +1 ICON "igneum.ico" + +1 VERSIONINFO +FILEVERSION 0,3,0,0 +PRODUCTVERSION 0,3,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Igneum" + VALUE "FileDescription", "Igneum Miner engine" + VALUE "FileVersion", "0.3.0" + VALUE "InternalName", "igneum-app" + VALUE "LegalCopyright", "Igneum contributors" + VALUE "OriginalFilename", "igneum-app.exe" + VALUE "ProductName", "Igneum Miner" + VALUE "ProductVersion", "0.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/packaging/windows/embed-resources.sh b/packaging/windows/embed-resources.sh index 5544c9aef..d64195ab1 100755 --- a/packaging/windows/embed-resources.sh +++ b/packaging/windows/embed-resources.sh @@ -1,61 +1,107 @@ #!/usr/bin/env bash -# Gives igneumd.exe and igneum-miner.exe the coin icon and a version block (CompanyName Igneum, ProductName -# Igneum Miner, 0.1.0). Two steps, both on the Mac: +# Gives igneumd.exe and igneum-miner.exe the coin icon and the version block Explorer shows under Properties > Details +# (CompanyName Igneum, ProductName Igneum Miner, 0.3.0). Josh's rule (4 October 2026): every shipped exe carries the +# coin icon and a version block, like the Mac app and DMG. Two steps, both on the Mac: # 1. windres compiles resources/igneumd.rc and resources/igneum-miner.rc into COFF objects (cheap, always done). -# 2. --relink runs `cargo rustc` for each binary with `-C link-arg=`: only the final crate is recompiled and -# relinked, every dependency comes from the cache, so it costs a few minutes after a normal cross-build. The -# objects are linked in as extra inputs, nothing in vendor/ changes. Without --relink the exact commands are -# printed, and build-installer.ps1 stamps the resources with rcedit on the PC instead. -# The resulting exes land where proto-cuda/windows-app/make-package.sh already picks them up -# (vendor/igneum-node-win/target/x86_64-pc-windows-gnu/release/). +# 2. --relink runs the same cargo build as proto-cuda/windows-node/cross-build.sh (same packages, features and +# environment, so every cached dependency is reused) with a linker shim first in PATH: a script named +# x86_64-w64-mingw32-gcc (the linker rustc picks for x86_64-pc-windows-gnu) that runs the real one and appends +# the matching object whenever the output being linked is igneumd-.exe or igneum-miner-.exe. The two +# exes are removed from deps/ first so cargo relinks them even when nothing changed. Nothing in vendor/ changes. +# Why not `cargo rustc -- -C link-arg=`: it takes one package, and a one-package selection unifies features +# differently from the two-package cross-build (300 crates differ, checked 4 Oct 2026), so it would rebuild half +# the tree and ship a differently-featured miner. Why PATH and not CARGO_TARGET_..._LINKER: cargo puts the +# configured linker in its fingerprints, so that rebuilds every crate (checked on app/igneum-app the same day). +# The exes land in /x86_64-pc-windows-gnu/release/, where proto-cuda/windows-app/make-package.sh and +# packaging/windows/make-payload.sh pick them up. Without --relink only the objects are compiled and the command is +# printed (build-installer.ps1 can stamp the resources with rcedit on the PC instead). # -# packaging/windows/embed-resources.sh compile the objects, print the relink commands -# packaging/windows/embed-resources.sh --relink also relink both exes (needs a completed cross-build in the cache) +# Usage: packaging/windows/embed-resources.sh [--relink] [worktree] [target dir] +# worktree the node checkout to build, default vendor/igneum-node-v4 (or NODE_WORKTREE) +# target dir CARGO_TARGET_DIR, default vendor/igneum-node/target-integration (or CARGO_TARGET_DIR); relative paths +# are taken from the repository root +# JOBS=n cargo jobs, default 4 set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$HERE/../.." && pwd)" ICONS="$ROOT/brand/icons" OUT="$HERE/build/res" -NODE="${NODE_WORKTREE:-$ROOT/vendor/igneum-node-win}" +VERIFY="$HERE/resources/verify-exe.py" MINGW="/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32" +RELINK=0 +if [ "${1:-}" = "--relink" ]; then RELINK=1; shift; fi +NODE="${1:-${NODE_WORKTREE:-$ROOT/vendor/igneum-node-v4}}" +TD="${2:-${CARGO_TARGET_DIR:-$ROOT/vendor/igneum-node/target-integration}}" +case "$NODE" in /*) ;; *) NODE="$ROOT/$NODE" ;; esac +case "$TD" in /*) ;; *) TD="$ROOT/$TD" ;; esac +JOBS="${JOBS:-4}" command -v x86_64-w64-mingw32-windres >/dev/null || { echo "x86_64-w64-mingw32-windres not found (brew install mingw-w64)" >&2; exit 1; } -[ -f "$ICONS/igneum.ico" ] || { echo "no $ICONS/igneum.ico; run python3 brand/icons/make-icons.py first" >&2; exit 1; } +[ -f "$ICONS/igneum.ico" ] || { echo "no $ICONS/igneum.ico, making the icons"; python3 "$ICONS/make-icons.py"; } +[ -d "$NODE" ] || { echo "no worktree at $NODE" >&2; exit 1; } mkdir -p "$OUT" for name in igneumd igneum-miner; do x86_64-w64-mingw32-windres -I "$ICONS" -i "$HERE/resources/$name.rc" -O coff -o "$OUT/$name.res.o" echo "compiled $OUT/$name.res.o ($(stat -f %z "$OUT/$name.res.o") bytes)" done +# The linker shim: the real x86_64-w64-mingw32-gcc plus the resource object for the exe being linked +REAL_GCC="$(command -v x86_64-w64-mingw32-gcc)" || { echo "x86_64-w64-mingw32-gcc not found" >&2; exit 1; } +SHIM="$HERE/build/linker-shim" +rm -rf "$SHIM" && mkdir -p "$SHIM" +cat > "$SHIM/x86_64-w64-mingw32-gcc" <.exe or igneum-miner-.exe, found after -o, also inside a @response file). +REAL="$REAL_GCC" +RES="$OUT" +out="" +prev="" +for a in "\$@"; do + case "\$a" in @*) if [ -f "\${a#@}" ]; then + p2=""; for w in \$(cat "\${a#@}"); do if [ "\$p2" = "-o" ]; then out="\$w"; fi; p2="\$w"; done + fi ;; esac + if [ "\$prev" = "-o" ]; then out="\$a"; fi + prev="\$a" +done +case "\$(basename "\$out")" in + igneumd-*.exe|igneumd.exe) exec "\$REAL" "\$@" "\$RES/igneumd.res.o" ;; + igneum-miner-*.exe|igneum_miner-*.exe|igneum-miner.exe) exec "\$REAL" "\$@" "\$RES/igneum-miner.res.o" ;; +esac +exec "\$REAL" "\$@" +SHIM_SH +chmod +x "$SHIM/x86_64-w64-mingw32-gcc" + # The same environment as proto-cuda/windows-node/cross-build.sh, so the cached dependencies are reused. export PATH="$HOME/.cargo/bin:/opt/homebrew/bin:$PATH" -export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target}" +export CARGO_TARGET_DIR="$TD" 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" export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-arg=-static -C link-arg=-static-libgcc" +REL="$TD/x86_64-pc-windows-gnu/release" +BUILD="nice -n 19 cargo build --release -j $JOBS -p kaspad -p igneum-miner --features igneum-pow --target x86_64-pc-windows-gnu" -relink_cmd() { # $1 package, $2 bin, $3 object - printf 'nice -n 19 cargo rustc --release -j 2 -p %s --bin %s --features igneum-pow --target x86_64-pc-windows-gnu -- -C link-arg=%q\n' "$1" "$2" "$3" -} -if [ "${1:-}" = "--relink" ]; then +if [ "$RELINK" = 1 ]; then cd "$NODE" - for spec in "kaspad igneumd" "igneum-miner igneum-miner"; do - set -- $spec - echo "relinking $2.exe" - eval "$(relink_cmd "$1" "$2" "$OUT/$2.res.o")" - exe="$CARGO_TARGET_DIR/x86_64-pc-windows-gnu/release/$2.exe" - x86_64-w64-mingw32-objdump -h "$exe" | grep -q '\.rsrc' && echo "$exe: .rsrc section present ($(stat -f %z "$exe") bytes)" || { echo "$exe has no .rsrc section" >&2; exit 1; } + echo "worktree $NODE at $(git rev-parse --short HEAD) ($(git status --porcelain | grep -v '^??' | wc -l | tr -d ' ') modified files), target dir $TD" + rm -f "$REL"/deps/igneumd-*.exe "$REL"/deps/igneum-miner-*.exe "$REL"/deps/igneum_miner-*.exe + PATH="$SHIM:$PATH" $BUILD + for exe in igneumd igneum-miner; do + f="$REL/$exe.exe" + x86_64-w64-mingw32-objdump -h "$f" | grep -q '\.rsrc' || { echo "$f has no .rsrc section" >&2; exit 1; } + 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' ' ')" done + python3 "$VERIFY" --version 0.3.0 "$REL/igneumd.exe" "$REL/igneum-miner.exe" else cat < Details. -// Compiled by embed-resources.sh with x86_64-w64-mingw32-windres (icon path relative to brand/icons, passed with -I). -#include - -1 ICON "igneum.ico" - -1 VERSIONINFO -FILEVERSION 0,1,0,0 -PRODUCTVERSION 0,1,0,0 -FILEFLAGSMASK 0x3fL -FILEFLAGS 0x0L -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_APP -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "CompanyName", "Igneum" - VALUE "FileDescription", "Igneum Miner" - VALUE "FileVersion", "0.1.0" - VALUE "InternalName", "igneum-miner" - VALUE "LegalCopyright", "Igneum. Nothing is bought or sold." - VALUE "OriginalFilename", "igneum-miner.exe" - VALUE "ProductName", "Igneum Miner" - VALUE "ProductVersion", "0.1.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END +// Windows resources for igneum-miner.exe: the coin icon Explorer shows and the version block under Properties > Details. +// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I). +// Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG. +#include + +1 ICON "igneum.ico" + +1 VERSIONINFO +FILEVERSION 0,3,0,0 +PRODUCTVERSION 0,3,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Igneum" + VALUE "FileDescription", "Igneum Miner lottery miner" + VALUE "FileVersion", "0.3.0" + VALUE "InternalName", "igneum-miner" + VALUE "LegalCopyright", "Igneum contributors" + VALUE "OriginalFilename", "igneum-miner.exe" + VALUE "ProductName", "Igneum Miner" + VALUE "ProductVersion", "0.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/packaging/windows/resources/igneumd.rc b/packaging/windows/resources/igneumd.rc index 08fe8e9ab..152ffb5b5 100644 --- a/packaging/windows/resources/igneumd.rc +++ b/packaging/windows/resources/igneumd.rc @@ -1,34 +1,35 @@ -// Windows resources for igneumd.exe: the coin icon and the version block Explorer shows under Properties > Details. -// Compiled by embed-resources.sh with x86_64-w64-mingw32-windres (icon path relative to brand/icons, passed with -I). -#include - -1 ICON "igneum.ico" - -1 VERSIONINFO -FILEVERSION 0,1,0,0 -PRODUCTVERSION 0,1,0,0 -FILEFLAGSMASK 0x3fL -FILEFLAGS 0x0L -FILEOS VOS_NT_WINDOWS32 -FILETYPE VFT_APP -FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "CompanyName", "Igneum" - VALUE "FileDescription", "Igneum Miner node" - VALUE "FileVersion", "0.1.0" - VALUE "InternalName", "igneumd" - VALUE "LegalCopyright", "Igneum. Nothing is bought or sold." - VALUE "OriginalFilename", "igneumd.exe" - VALUE "ProductName", "Igneum Miner" - VALUE "ProductVersion", "0.1.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END +// Windows resources for igneumd.exe: the coin icon Explorer shows and the version block under Properties > Details. +// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I). +// Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG. +#include + +1 ICON "igneum.ico" + +1 VERSIONINFO +FILEVERSION 0,3,0,0 +PRODUCTVERSION 0,3,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Igneum" + VALUE "FileDescription", "Igneum Miner node" + VALUE "FileVersion", "0.3.0" + VALUE "InternalName", "igneumd" + VALUE "LegalCopyright", "Igneum contributors" + VALUE "OriginalFilename", "igneumd.exe" + VALUE "ProductName", "Igneum Miner" + VALUE "ProductVersion", "0.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/packaging/windows/resources/verify-exe.py b/packaging/windows/resources/verify-exe.py new file mode 100755 index 000000000..feb7057b2 --- /dev/null +++ b/packaging/windows/resources/verify-exe.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Checks that a Windows exe carries the Igneum resources: a .rsrc section with an icon group and a version block whose +CompanyName is Igneum and ProductName is Igneum Miner. Prints what it finds. Small PE parser, no dependencies, runs on +the Mac where rcedit and Explorer are not available. + python3 verify-exe.py [--version 0.3.0] exe [exe ...] exit 1 if any exe fails""" +import struct, sys + +RT_ICON, RT_GROUP_ICON, RT_VERSION = 3, 14, 16 +REQUIRED = ("CompanyName", "ProductName", "FileDescription", "FileVersion", "ProductVersion", "LegalCopyright") + + +def sections_of(data): + pe = struct.unpack_from("= 52 and struct.unpack_from("> 16, ms & 0xFFFF, ls >> 16, ls & 0xFFFF, pms >> 16, pms & 0xFFFF, pls >> 16, pls & 0xFFFF) + pos = (val + vlen + 3) & ~3 + while pos < off + length: + clen, _, _, ckey, cval = block(pos) + if ckey == "StringFileInfo": + tpos = cval + while tpos < pos + clen: + tlen, _, _, _, tval = block(tpos) + spos = tval + while spos < tpos + tlen: + slen, svlen, _, skey, sval = block(spos) + if slen == 0: + break + raw = data[sval:sval + 2 * svlen] + strings[skey] = raw.decode("utf-16-le").rstrip("\0") + spos = (spos + slen + 3) & ~3 + tpos = (tpos + tlen + 3) & ~3 + if clen == 0: + break + pos = (pos + clen + 3) & ~3 + return fixed, strings + + +def check(path, want_version): + data = open(path, "rb").read() + secs, res_rva, res_size = sections_of(data) + names = [s[0] for s in secs] + problems = [] + if ".rsrc" not in names: + problems.append("no .rsrc section") + if not res_rva: + problems.append("resource table empty in the PE data directory") + icons = groups = 0 + fixed, strings = None, {} + if res_rva: + base = rva_to_off(secs, res_rva) + found = [] + walk(data, base, 0, 0, found) + for p, rva, size in found: + if p[0] == RT_ICON: + icons += 1 + elif p[0] == RT_GROUP_ICON: + groups += 1 + elif p[0] == RT_VERSION and fixed is None: + fixed, strings = version_strings(data, rva_to_off(secs, rva), size) + if not groups: + problems.append("no icon group (RT_GROUP_ICON)") + if fixed is None: + problems.append("no version block (RT_VERSION)") + for k in REQUIRED: + if not strings.get(k): + problems.append("missing " + k) + if strings.get("CompanyName") not in (None, "Igneum"): + problems.append("CompanyName is %r, not Igneum" % strings["CompanyName"]) + if strings.get("ProductName") not in (None, "Igneum Miner"): + problems.append("ProductName is %r, not Igneum Miner" % strings["ProductName"]) + if want_version: + for k in ("FileVersion", "ProductVersion"): + if strings.get(k) and strings[k] != want_version: + problems.append("%s is %s, not %s" % (k, strings[k], want_version)) + if fixed and ".".join(map(str, fixed[:3])) != want_version: + problems.append("FILEVERSION is %s, not %s" % (".".join(map(str, fixed[:4])), want_version)) + print("%s: %d bytes, sections %s" % (path, len(data), " ".join(names))) + print(" icon images %d, icon groups %d, FILEVERSION %s" % (icons, groups, ".".join(map(str, fixed[:4])) if fixed else "none")) + for k in REQUIRED + ("InternalName", "OriginalFilename"): + if k in strings: + print(" %-17s %s" % (k, strings[k])) + for p in problems: + print(" FAIL: " + p) + return not problems + + +if __name__ == "__main__": + args = sys.argv[1:] + want = None + if args[:1] == ["--version"]: + want, args = args[1], args[2:] + if not args: + sys.exit(__doc__) + ok = all([check(p, want) for p in args]) + print("all %d exe(s) carry the Igneum resources" % len(args) if ok else "FAILED") + sys.exit(0 if ok else 1) diff --git a/proto-cuda/nvrtc/build-windows.sh b/proto-cuda/nvrtc/build-windows.sh index 0cad55641..4e9639484 100755 --- a/proto-cuda/nvrtc/build-windows.sh +++ b/proto-cuda/nvrtc/build-windows.sh @@ -5,27 +5,43 @@ # proto-opencl/igneum-worker-opencl.exe host.c with IGNEUM_CL_DYNAMIC (OpenCL.dll loaded at run time) and the generic # --pack serve mode; the pack it is built against is only a placeholder # Static libgcc/libstdc++/winpthread, so each exe needs only KERNEL32 and the Universal CRT (Windows 10 and 11 have it). +# Each exe carries the coin icon and a version block (igneum-worker-cuda.rc, igneum-worker-opencl.rc, compiled with windres +# and linked in; Josh's rule, 4 October 2026: every shipped exe shows the coin in Explorer and a publisher in Properties). # Usage: build-windows.sh (then windows-app/make-package.sh ships them with the NVRTC DLLs) set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$HERE/../.." && pwd)" RED="$HERE/redist" +ICONS="$ROOT/brand/icons" [ -f "$RED/include/cuda.h" ] && [ -f "$RED/include/nvrtc.h" ] && [ -f "$RED/include/CL/cl.h" ] || { echo "run $HERE/fetch-redist.sh first" >&2; exit 1; } CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc STRIP=x86_64-w64-mingw32-strip +WINDRES=x86_64-w64-mingw32-windres +command -v "$WINDRES" >/dev/null || { echo "$WINDRES not found (brew install mingw-w64)" >&2; exit 1; } PLACEHOLDER="$ROOT/proto-cuda/packs/igneum-devnet-v4-epoch0" +VERIFY="$ROOT/packaging/windows/resources/verify-exe.py" + +# The coin icon and the version blocks, as COFF objects the linker takes like any other input +[ -f "$ICONS/igneum.ico" ] || { echo "== no $ICONS/igneum.ico, making the icons"; python3 "$ICONS/make-icons.py"; } +RES="$(mktemp -d)" +for w in cuda opencl; do + "$WINDRES" -I "$ICONS" -i "$HERE/igneum-worker-$w.rc" -O coff -o "$RES/igneum-worker-$w.res.o" +done echo "== igneum-worker-cuda.exe" -"$CXX" -std=c++17 -O2 -Wall -Wextra -static -I "$HERE" -I "$RED/include" -o "$HERE/igneum-worker-cuda.exe" "$HERE/worker.cpp" +"$CXX" -std=c++17 -O2 -Wall -Wextra -static -I "$HERE" -I "$RED/include" -o "$HERE/igneum-worker-cuda.exe" "$HERE/worker.cpp" "$RES/igneum-worker-cuda.res.o" "$STRIP" "$HERE/igneum-worker-cuda.exe" echo "== igneum-worker-opencl.exe" "$CC" -std=c99 -O2 -Wall -Wextra -Wno-stringop-truncation -Wno-format-truncation -static -DIGNEUM_CL_DYNAMIC -DCL_TARGET_OPENCL_VERSION=120 \ -I "$RED/include" -I "$PLACEHOLDER" -DIGNEUM_KERNEL_PATH='"kernel_bound.cl"' \ - -o "$ROOT/proto-opencl/igneum-worker-opencl.exe" "$ROOT/proto-opencl/host.c" + -o "$ROOT/proto-opencl/igneum-worker-opencl.exe" "$ROOT/proto-opencl/host.c" "$RES/igneum-worker-opencl.res.o" "$STRIP" "$ROOT/proto-opencl/igneum-worker-opencl.exe" +rm -rf "$RES" for exe in "$HERE/igneum-worker-cuda.exe" "$ROOT/proto-opencl/igneum-worker-opencl.exe"; do printf '%s: %d bytes, imports:' "$(basename "$exe")" "$(stat -f %z "$exe")" x86_64-w64-mingw32-objdump -p "$exe" | sed -n 's/^[[:space:]]*DLL Name: //p' | tr '\n' ' ' echo done +# the icon and version block survived the strip (strip keeps .rsrc; this proves it) +python3 "$VERIFY" --version 0.3.0 "$HERE/igneum-worker-cuda.exe" "$ROOT/proto-opencl/igneum-worker-opencl.exe" diff --git a/proto-cuda/nvrtc/igneum-worker-cuda.rc b/proto-cuda/nvrtc/igneum-worker-cuda.rc new file mode 100644 index 000000000..c7670d5c5 --- /dev/null +++ b/proto-cuda/nvrtc/igneum-worker-cuda.rc @@ -0,0 +1,35 @@ +// Windows resources for igneum-worker-cuda.exe: the coin icon Explorer shows and the version block under Properties > Details. +// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I). +// Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG. +#include + +1 ICON "igneum.ico" + +1 VERSIONINFO +FILEVERSION 0,3,0,0 +PRODUCTVERSION 0,3,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Igneum" + VALUE "FileDescription", "Igneum Miner CUDA worker" + VALUE "FileVersion", "0.3.0" + VALUE "InternalName", "igneum-worker-cuda" + VALUE "LegalCopyright", "Igneum contributors" + VALUE "OriginalFilename", "igneum-worker-cuda.exe" + VALUE "ProductName", "Igneum Miner" + VALUE "ProductVersion", "0.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/proto-cuda/nvrtc/igneum-worker-opencl.rc b/proto-cuda/nvrtc/igneum-worker-opencl.rc new file mode 100644 index 000000000..402d72d58 --- /dev/null +++ b/proto-cuda/nvrtc/igneum-worker-opencl.rc @@ -0,0 +1,35 @@ +// Windows resources for igneum-worker-opencl.exe: the coin icon Explorer shows and the version block under Properties > Details. +// Compiled with x86_64-w64-mingw32-windres (the icon path is relative to brand/icons, passed with -I). +// Josh's rule (4 October 2026): every shipped exe carries the coin icon and a version block, like the Mac app and DMG. +#include + +1 ICON "igneum.ico" + +1 VERSIONINFO +FILEVERSION 0,3,0,0 +PRODUCTVERSION 0,3,0,0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Igneum" + VALUE "FileDescription", "Igneum Miner OpenCL worker" + VALUE "FileVersion", "0.3.0" + VALUE "InternalName", "igneum-worker-opencl" + VALUE "LegalCopyright", "Igneum contributors" + VALUE "OriginalFilename", "igneum-worker-opencl.exe" + VALUE "ProductName", "Igneum Miner" + VALUE "ProductVersion", "0.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/proto-opencl/cl_dynamic.h b/proto-opencl/cl_dynamic.h index c146499fc..7544326f9 100644 --- a/proto-opencl/cl_dynamic.h +++ b/proto-opencl/cl_dynamic.h @@ -19,7 +19,7 @@ X(clCreateCommandQueue) X(clCreateProgramWithSource) X(clBuildProgram) X(clGetProgramBuildInfo) X(clCreateKernel) \ X(clSetKernelArg) X(clEnqueueNDRangeKernel) X(clEnqueueReadBuffer) X(clEnqueueWriteBuffer) X(clCreateBuffer) \ X(clReleaseMemObject) X(clReleaseKernel) X(clReleaseProgram) X(clReleaseCommandQueue) X(clReleaseContext) \ - X(clFinish) X(clWaitForEvents) X(clReleaseEvent) X(clGetEventProfilingInfo) X(clGetKernelWorkGroupInfo) \ + X(clFinish) X(clWaitForEvents) X(clReleaseEvent) X(clGetEventInfo) X(clGetEventProfilingInfo) X(clGetKernelWorkGroupInfo) \ X(clGetExtensionFunctionAddressForPlatform) /* One pointer per entry point, typed from the header's declaration (so a signature drift is a compile error). */ @@ -72,6 +72,7 @@ static int ig_cl_load(void) { #define clFinish ig_clFinish #define clWaitForEvents ig_clWaitForEvents #define clReleaseEvent ig_clReleaseEvent +#define clGetEventInfo ig_clGetEventInfo #define clGetEventProfilingInfo ig_clGetEventProfilingInfo #define clGetKernelWorkGroupInfo ig_clGetKernelWorkGroupInfo #define clGetExtensionFunctionAddressForPlatform ig_clGetExtensionFunctionAddressForPlatform