igneum/proto-cuda/windows-node/build-node.ps1
igneum-labs 976c14ae1f Windows node package: igneumd cross-compiled for the PC, START-NODE and BUILD-NODE launchers
proto-cuda/windows-node/: START-NODE.bat and start-node.ps1 (igneumd
--devnet with --addpeer to the Mac, status line every 30 s through
igneum-miner watch, keep-awake, random-delay restart, Ctrl+C),
BUILD-NODE.bat and build-node.ps1 (builds on the PC from the src.zip
snapshot; winget for Rustup, LLVM and protoc; MSVC default toolset),
ALLOW-FIREWALL.bat and allow-firewall.ps1, README.txt, cross-build.sh
(the mingw-w64 recipe that built igneumd.exe in 8 min 25 s; the exe
ships with the three mingw runtime DLLs) and make-package.sh.
WINDOWS-MINER.md gains "Run your own node"; bench-log records the
cross-compile and the two-peer sync test on the Mac (ports 27000 and
27010, live node untouched). The mining launcher's NODE_HOST=auto
edit stays uncommitted for the agent that owns start-mining.ps1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 20:54:27 +00:00

212 lines
12 KiB
PowerShell

# Builds igneumd.exe (the Igneum node) on this PC from the source snapshot in src.zip. Started by BUILD-NODE.bat.
# 3 October 2026.
#
# Steps:
# 1. Unpacks src.zip into src\ (src\vendor\igneum-node is the node fork, src\igneum-pow the lottery-hash crate the fork
# depends on by relative path; the layout must stay as it is). Nothing is cloned: the snapshot is a `git archive`
# of the fork's HEAD made on the Mac, so no GitHub access is needed and the repository stays private.
# 2. Makes sure the tools are present, installing the missing ones with winget (silent):
# Rust Rustlang.Rustup (stable, MSVC host). cargo lands in %USERPROFILE%\.cargo\bin.
# LLVM LLVM.LLVM, for libclang.dll: the rocksdb crate generates its bindings with bindgen (LIBCLANG_PATH).
# protoc Google.Protobuf, or a direct download of the protoc release zip: the gRPC and p2p crates run protoc
# from their build scripts (prost-build, no vendored protoc).
# MSVC the toolset of the installed Visual Studio, through vcvarsall x64 (the default 14.5x is fine for
# Rust and for the C and C++ crates: rocksdb, zstd, lz4, bzip2, zlib, secp256k1, mimalloc). The CUDA
# 14.30 trick of the mining launcher is not used here.
# 3. Runs `cargo build --release -p kaspad --features igneum-pow` in src\vendor\igneum-node (the package keeps its
# upstream name kaspad; the binary is igneumd.exe; the feature selects the real lottery-hash engine, without it the
# node would check blocks with the kHeavyHash stub and reject every devnet block). A progress line prints every
# minute: elapsed time, crates compiled, the crate being compiled.
# 4. Copies target\release\igneumd.exe next to BUILD-NODE.bat (and igneum-miner.exe if it was missing).
#
# Time: approximate 15 to 25 minutes on a Ryzen 7 9800X3D for the first build (the rocksdb C++ sources and about 500
# Rust crates; thin LTO at the end), under a minute for a rebuild with nothing changed. Windows Defender's real-time
# scan of target\ can add a few minutes. Extract the zip to a short path (C:\igneum-node) so no build path reaches the
# 260-character limit.
$ProgressPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Continue'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $root
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$buildLog = Join-Path $root "build-$stamp.log"
$started = Get-Date
$vcvars = if ($env:VCVARS) { $env:VCVARS } else { 'C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsall.bat' }
function Log([string]$msg) {
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg"
Write-Host $line
Add-Content -Path $buildLog -Value $line
}
function Refresh-Path {
$m = [Environment]::GetEnvironmentVariable('PATH', 'Machine')
$u = [Environment]::GetEnvironmentVariable('PATH', 'User')
$env:PATH = "$env:PATH;$m;$u"
}
function Invoke-Winget([string]$id) {
if (-not (Get-Command winget.exe -ErrorAction SilentlyContinue)) { Log "winget is not available, cannot install $id by itself"; return }
Log "installing $id with winget (silent)"
$out = & winget.exe install --id $id -e --silent --accept-package-agreements --accept-source-agreements 2>&1
$out | ForEach-Object { Add-Content -Path $buildLog -Value " winget: $_" }
Refresh-Path
}
Log "igneum node build on $env:COMPUTERNAME (run $stamp), folder $root"
if ($root.Length -gt 80) { Log "WARNING: this folder path is $($root.Length) characters long; cargo's build paths may pass the 260-character limit. C:\igneum-node is a safer place." }
# ---- 1. the source -----------------------------------------------------------------------------------------------------
$src = Join-Path $root 'src'
$nodeDir = Join-Path $src 'vendor\igneum-node'
$srcZip = Join-Path $root 'src.zip'
if (-not (Test-Path (Join-Path $nodeDir 'Cargo.toml'))) {
if (-not (Test-Path $srcZip)) { Log 'neither src\vendor\igneum-node nor src.zip is here. Extract the whole package again.'; exit 1 }
Log "unpacking src.zip into $src"
try { Expand-Archive -Path $srcZip -DestinationPath $src -Force } catch { Log "could not unpack src.zip: $_"; exit 1 }
}
if (-not (Test-Path (Join-Path $src 'igneum-pow\Cargo.toml'))) { Log 'src\igneum-pow is missing (the fork depends on it by relative path); the snapshot is incomplete.'; exit 1 }
$snapshotNote = Join-Path $src 'SNAPSHOT.txt'
if (Test-Path $snapshotNote) { Get-Content $snapshotNote | ForEach-Object { Log "source: $_" } }
# ---- 2. tools -------------------------------------------------------------------------------------------------------------
# Rust
$cargoBin = Join-Path $env:USERPROFILE '.cargo\bin'
if (Test-Path $cargoBin) { $env:PATH = "$cargoBin;$env:PATH" }
if (-not (Get-Command cargo.exe -ErrorAction SilentlyContinue)) {
Invoke-Winget 'Rustlang.Rustup'
if (Test-Path $cargoBin) { $env:PATH = "$cargoBin;$env:PATH" }
}
if (-not (Get-Command cargo.exe -ErrorAction SilentlyContinue)) { Log 'cargo is still not found. Install Rust from https://rustup.rs (default options) and run BUILD-NODE.bat again.'; exit 1 }
$toolchains = @(& rustup.exe toolchain list 2>&1)
if (-not ($toolchains | Where-Object { $_ -match '^stable' })) {
Log 'no stable toolchain yet: rustup toolchain install stable'
& rustup.exe toolchain install stable --profile minimal 2>&1 | ForEach-Object { Add-Content -Path $buildLog -Value " rustup: $_" }
& rustup.exe default stable 2>&1 | ForEach-Object { Add-Content -Path $buildLog -Value " rustup: $_" }
}
$active = (& rustup.exe show active-toolchain 2>&1 | Select-Object -First 1)
if ($active -notmatch 'msvc') { Log "the active toolchain is '$active'; the MSVC host toolchain is the tested one (rustup default stable-msvc)" }
Log "rust: $(& rustc.exe --version 2>&1) / $(& cargo.exe --version 2>&1) ($active)"
# LLVM (libclang for bindgen)
$libclang = $null
foreach ($d in @($env:LIBCLANG_PATH, 'C:\Program Files\LLVM\bin', 'C:\Program Files\LLVM\lib')) {
if ($d -and (Test-Path (Join-Path $d 'libclang.dll'))) { $libclang = $d; break }
}
if (-not $libclang) {
Invoke-Winget 'LLVM.LLVM'
foreach ($d in @('C:\Program Files\LLVM\bin', 'C:\Program Files\LLVM\lib')) { if (Test-Path (Join-Path $d 'libclang.dll')) { $libclang = $d; break } }
}
if (-not $libclang) { Log 'libclang.dll not found (LLVM). Install LLVM from https://releases.llvm.org (the Windows installer) and run BUILD-NODE.bat again.'; exit 1 }
$env:LIBCLANG_PATH = $libclang
Log "libclang: $libclang"
# protoc
$protoc = $null
$cmd = Get-Command protoc.exe -ErrorAction SilentlyContinue
if ($cmd) { $protoc = $cmd.Source }
$localProtoc = Join-Path $root 'tools\protoc\bin\protoc.exe'
if (-not $protoc -and (Test-Path $localProtoc)) { $protoc = $localProtoc }
if (-not $protoc) {
Invoke-Winget 'Google.Protobuf'
$cmd = Get-Command protoc.exe -ErrorAction SilentlyContinue
if ($cmd) { $protoc = $cmd.Source }
}
if (-not $protoc) {
# Fallback: the release zip from the protobuf project (public), unpacked under tools\protoc.
$ver = '33.1'
$url = "https://github.com/protocolbuffers/protobuf/releases/download/v$ver/protoc-$ver-win64.zip"
$zip = Join-Path $root 'tools\protoc.zip'
Log "protoc not found: downloading $url"
try {
New-Item -ItemType Directory -Force -Path (Join-Path $root 'tools') | Out-Null
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath (Join-Path $root 'tools\protoc') -Force
} catch { Log "download failed: $_" }
if (Test-Path $localProtoc) { $protoc = $localProtoc }
}
if (-not $protoc) { Log 'protoc is still missing. Install it (winget install Google.Protobuf, or the protoc-*-win64.zip from the protobuf releases page, unpacked to tools\protoc) and run BUILD-NODE.bat again.'; exit 1 }
$env:PROTOC = $protoc
Log "protoc: $protoc ($(& $protoc --version 2>&1))"
# MSVC: import the toolset environment (default version). rustc and the cc crate find MSVC through vswhere by themselves,
# so a missing vcvarsall is a warning, not a stop.
function Find-VcVarsAll {
if ($vcvars -and (Test-Path $vcvars)) { return $vcvars }
foreach ($pf in @('C:\Program Files', 'C:\Program Files (x86)')) {
$hits = Get-ChildItem -Path (Join-Path $pf 'Microsoft Visual Studio') -Recurse -Filter 'vcvarsall.bat' -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\VC\\Auxiliary\\Build\\vcvarsall\.bat$' } | Sort-Object FullName -Descending
if ($hits) { return $hits[0].FullName }
}
return $null
}
$bat = Find-VcVarsAll
if ($bat) {
Log "importing the MSVC environment: `"$bat`" x64"
$out = cmd /s /c "`"$bat`" x64 >nul 2>&1 && set" 2>$null
foreach ($l in @($out)) {
$i = $l.IndexOf('=')
if ($i -gt 0) { [Environment]::SetEnvironmentVariable($l.Substring(0, $i), $l.Substring($i + 1), 'Process') }
}
}
$cl = Get-Command cl.exe -ErrorAction SilentlyContinue
if ($cl) { Log "MSVC: $($cl.Source)" } else { Log 'cl.exe is not on PATH (no vcvarsall found); cargo will look for MSVC by itself. If the build stops with "linker link.exe not found", install the "Desktop development with C++" workload in the Visual Studio Installer.' }
# ---- 3. the build ------------------------------------------------------------------------------------------------------
$needMiner = -not (Test-Path (Join-Path $root 'igneum-miner.exe'))
$cargoArgs = @('build', '--release', '-p', 'kaspad', '--features', 'igneum-pow')
if ($needMiner) { $cargoArgs += @('-p', 'igneum-miner') }
$cargoOut = Join-Path $root "build-$stamp.cargo.log"
Log "cargo $($cargoArgs -join ' ') in $nodeDir (cargo's own output: $cargoOut)"
Log 'this takes about 15 to 25 minutes the first time; a line prints every minute'
$t0 = Get-Date
$proc = Start-Process -FilePath (Get-Command cargo.exe).Source -ArgumentList $cargoArgs -WorkingDirectory $nodeDir -RedirectStandardOutput ($cargoOut + '.out') -RedirectStandardError $cargoOut -NoNewWindow -PassThru
if ($proc) { $null = $proc.Handle }
function Get-CargoProgress {
# cargo prints " Compiling name vX.Y.Z" lines to stderr; count them and show the last one.
try {
$fs = New-Object IO.FileStream($cargoOut, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
try { $sr = New-Object IO.StreamReader($fs); $text = $sr.ReadToEnd(); $sr.Close() } finally { $fs.Close() }
} catch { return 'waiting for cargo' }
$lines = @($text -split "`n" | ForEach-Object { $_.TrimEnd("`r") })
$compiling = @($lines | Where-Object { $_ -match '^\s*Compiling ' })
$last = if ($compiling.Count -gt 0) { ($compiling[-1] -replace '^\s*Compiling ', '') -replace ' \(.*\)$', '' } else { 'resolving and downloading crates' }
$errors = @($lines | Where-Object { $_ -match '^error' }).Count
$txt = "$($compiling.Count) crates compiled, now $last"
if ($errors -gt 0) { $txt += ", $errors error line(s) so far" }
return $txt
}
$lastLine = Get-Date
while (-not $proc.HasExited) {
Start-Sleep -Seconds 5
if (((Get-Date) - $lastLine).TotalSeconds -ge 60) {
$lastLine = Get-Date
Log (" {0:N0} min: {1}" -f ((Get-Date) - $t0).TotalMinutes, (Get-CargoProgress))
}
}
$took = (Get-Date) - $t0
$rc = $proc.ExitCode
Log ("cargo finished with exit code {0} after {1} ({2})" -f $rc, $took.ToString('hh\:mm\:ss'), (Get-CargoProgress))
# ---- 4. the result ----------------------------------------------------------------------------------------------------------
$built = Join-Path $nodeDir 'target\release\igneumd.exe'
if ($rc -ne 0 -or -not (Test-Path $built)) {
Log "the build FAILED. The last error lines from $cargoOut :"
try { Get-Content $cargoOut -ErrorAction SilentlyContinue | Where-Object { $_ -match '^(error|warning: unused| = note: )|LNK|link\.exe|protoc|libclang|bindgen' } | Select-Object -Last 15 | ForEach-Object { Log " $_" } } catch { }
Log 'Common causes: no MSVC (install the Desktop development with C++ workload), protoc or libclang not found (see the lines above), a path over 260 characters (move the folder to C:\igneum-node), Defender holding a file (run it again).'
exit 1
}
Copy-Item $built (Join-Path $root 'igneumd.exe') -Force
$size = (Get-Item (Join-Path $root 'igneumd.exe')).Length
Log ("igneumd.exe copied next to BUILD-NODE.bat ({0:N1} MB)" -f ($size / 1MB))
if ($needMiner) {
$m = Join-Path $nodeDir 'target\release\igneum-miner.exe'
if (Test-Path $m) { Copy-Item $m (Join-Path $root 'igneum-miner.exe') -Force; Log 'igneum-miner.exe copied too (the status reader of START-NODE.bat)' }
}
Log ("done in {0}. Now run START-NODE.bat." -f ((Get-Date) - $started).ToString('hh\:mm\:ss'))