windows-miner: import the MSVC environment before every build, start miners without cmd quoting, extract check, first upload at start

First Windows run (3 Oct 2026): nvcc linked without LIB (LNK1104 LIBCMT.lib) because cl was on PATH and the toolset
environment was never imported; the miners started through cmd /c lost the trailing quote of the redirect target
(cmd strips the first and last quote of the whole line) and never ran.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-03 19:28:21 +00:00
parent fabd725bc9
commit c85540bbc1
2 changed files with 89 additions and 19 deletions

View file

@ -28,6 +28,11 @@ set "VCVARS_VER=14.30"
rem -------------------------------------------------------------------------------------------------
cd /d "%~dp0"
if not exist "%~dp0start-mining.ps1" (
echo Please extract the whole zip first (right-click, Extract All), then run START-MINING.bat from the extracted folder.
pause
exit /b 1
)
where powershell >nul 2>nul
if errorlevel 1 (
echo PowerShell was not found. Windows 10 and 11 ship it; nothing else can be done here.

View file

@ -56,9 +56,29 @@ Log ("GPUs found: " + ($(if ($gpuNames.Count) { $gpuNames -join '; ' } else { 'n
$hasNvidia = ($gpuNames | Where-Object { $_ -match 'NVIDIA' }).Count -gt 0
$hasAmd = ($gpuNames | Where-Object { $_ -match 'AMD|Radeon' }).Count -gt 0
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
}
# Imports the MSVC toolset environment (PATH, LIB, INCLUDE, ...) into this process: cmd runs vcvarsall and prints `set`.
# Always done, even when cl.exe is already on PATH: a prompt can have cl without LIB, and then nvcc's link step fails
# with LNK1104 LIBCMT.lib (seen on the first Windows run, 3 Oct 2026).
function Import-VcVars {
if (-not (Test-Path $vcvars)) { return $false }
$out = cmd /c "`"$vcvars`" x64 -vcvars_ver=$vcvarsVer >nul 2>&1 && set" 2>$null
$bat = Find-VcVarsAll
if (-not $bat) { Log "vcvarsall.bat not found under Program Files\Microsoft Visual Studio; looked for $vcvars"; return $false }
Log "importing the MSVC environment: `"$bat`" x64 -vcvars_ver=$vcvarsVer"
# cmd /s: always strip the first and last quote of the /c string, so the quoted path inside is left intact
$out = cmd /s /c "`"$bat`" x64 -vcvars_ver=$vcvarsVer >nul 2>&1 && set" 2>$null
if (-not $out) {
Log "vcvarsall failed for -vcvars_ver=$vcvarsVer; trying without the version (CUDA 12.8 may then refuse the host compiler)"
$out = cmd /s /c "`"$bat`" x64 >nul 2>&1 && set" 2>$null
}
if (-not $out) { return $false }
foreach ($l in $out) {
$i = $l.IndexOf('=')
@ -67,8 +87,36 @@ function Import-VcVars {
return [bool](Get-Command cl.exe -ErrorAction SilentlyContinue)
}
$haveCl = [bool](Get-Command cl.exe -ErrorAction SilentlyContinue)
if (-not $haveCl) { $haveCl = Import-VcVars }
# Fallback when vcvarsall is unusable: LIB and INCLUDE from the newest MSVC toolset and Windows Kits, as proto-opencl/build.bat does.
function Set-LibFallback {
$vsRoots = @()
foreach ($pf in @('C:\Program Files', 'C:\Program Files (x86)')) { $vsRoots += Get-ChildItem -Path (Join-Path $pf 'Microsoft Visual Studio') -Directory -Recurse -Depth 3 -Filter 'MSVC' -ErrorAction SilentlyContinue }
$msvc = $null
foreach ($r in $vsRoots) {
$tools = Get-ChildItem -Path $r.FullName -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending
foreach ($t in $tools) { if (Test-Path (Join-Path $t.FullName 'lib\x64\libcmt.lib')) { $msvc = $t.FullName; break } }
if ($msvc) { break }
}
$kits = 'C:\Program Files (x86)\Windows Kits\10'
$sdk = $null
$vers = Get-ChildItem -Path (Join-Path $kits 'Lib') -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending
foreach ($v in $vers) { if (Test-Path (Join-Path $v.FullName 'ucrt\x64\libucrt.lib')) { $sdk = $v.Name; break } }
if (-not $msvc -or -not $sdk) { Log "fallback: MSVC lib dir $(if ($msvc) { $msvc } else { 'not found' }), Windows SDK $(if ($sdk) { $sdk } else { 'not found' })"; return $false }
$env:LIB = "$msvc\lib\x64;$kits\Lib\$sdk\ucrt\x64;$kits\Lib\$sdk\um\x64;" + $env:LIB
$env:INCLUDE = "$msvc\include;$kits\Include\$sdk\ucrt;$kits\Include\$sdk\um;$kits\Include\$sdk\shared;" + $env:INCLUDE
$env:PATH = "$msvc\bin\Hostx64\x64;" + $env:PATH
Log "fallback MSVC environment set from $msvc and Windows SDK $sdk"
return [bool](Get-Command cl.exe -ErrorAction SilentlyContinue)
}
$haveCl = Import-VcVars
if (-not $haveCl) { $haveCl = Set-LibFallback }
if ($haveCl) {
$libFirst = if ($env:LIB) { ($env:LIB -split ';')[0] } else { 'EMPTY' }
$incFirst = if ($env:INCLUDE) { ($env:INCLUDE -split ';')[0] } else { 'EMPTY' }
Log "MSVC environment: cl at $((Get-Command cl.exe).Source); LIB starts with $libFirst; INCLUDE starts with $incFirst"
if (-not $env:LIB) { Log 'LIB is empty: nvcc link steps will fail (LNK1104); trying the fallback'; [void](Set-LibFallback) }
}
$haveNvcc = [bool](Get-Command nvcc.exe -ErrorAction SilentlyContinue)
$openclSdk = $null
if ($env:OPENCL_SDK -and (Test-Path (Join-Path $env:OPENCL_SDK 'include\CL\cl.h'))) { $openclSdk = $env:OPENCL_SDK }
@ -141,7 +189,7 @@ function Build-Worker([string]$name, [string]$seeds) {
$t = Get-Date
Log "building the $name worker from the pack ($cmd)"
Push-Location $dir
$out = cmd /c $cmd 2>&1
$out = cmd /s /c "$cmd" 2>&1
$rc = $LASTEXITCODE
Pop-Location
$out | ForEach-Object { Add-Content -Path $launcherLog -Value " $name build: $_" }
@ -164,7 +212,7 @@ foreach ($name in $plan) {
$instances = @()
for ($i = 1; $i -le $minersPerVendor; $i++) {
$suffix = if ($minersPerVendor -gt 1) { "-$i" } else { '' }
$instances += @{ vendor = $name; index = $i; label = "$name-$machine$suffix"; log = (Join-Path $root "$name$suffix-$stamp.log"); runId = "$name-$machine$suffix-$stamp"; proc = $null; restarts = 0; startedAt = $null }
$instances += @{ vendor = $name; index = $i; label = "$name-$machine$suffix"; file = "$name$suffix-$stamp"; log = $null; err = $null; runId = "$name-$machine$suffix-$stamp"; proc = $null; restarts = 0; starts = 0; acceptedBefore = 0; startedAt = $null }
}
$vendors[$name] = @{ name = $name; exe = $exe; instances = $instances; rebuilds = 0 }
}
@ -172,24 +220,31 @@ if ($vendors.Count -eq 0) { Log 'no worker could be built; see the launcher log'
Log ("identities per vendor: $minersPerVendor (each identity runs its own worker: about 1.3 GiB of GPU memory per instance)")
function Start-Miner($v, $inst) {
# --worker-args takes ONE argument (quoted); the miner splits it on spaces and turns underscores into spaces
$margs = @('mine', $nodeUrl, '1', '100000000', $inst.label, '--worker', "`"$($v.exe)`"", '--status-secs', '30', '--exit-on-seed-change')
# One log segment per start (Start-Process truncates its redirect target); the status and upload use the latest one
# and the accepted count of earlier segments is carried in acceptedBefore.
if ($inst.log -and (Test-Path $inst.log)) { $inst.acceptedBefore += (Select-String -Path $inst.log -Pattern 'ACCEPTED block').Count }
$inst.starts += 1
$seg = if ($inst.starts -gt 1) { "-r$($inst.starts)" } else { '' }
$inst.log = Join-Path $root "$($inst.file)$seg.log"
$inst.err = $inst.log + '.err'
# --worker-args takes ONE argument (quoted); the miner splits it on spaces and turns underscores into spaces.
# Start-Process joins the list with spaces and does not quote, so paths and the worker-args value are quoted here.
$margs = @('mine', $nodeUrl, '1', '100000000', $inst.label, '--worker', ('"' + $v.exe + '"'), '--status-secs', '30', '--exit-on-seed-change')
if ($payout) { $margs += @('--address', $payout) } else { $margs += @('--payout-label', $inst.label) }
if ($v.name -eq 'nvidia') { $margs += @('--worker-args', "`"--device $nvidiaDevice`"") }
if ($v.name -eq 'nvidia') { $margs += @('--worker-args', ('"--device ' + $nvidiaDevice + '"')) }
else {
if ($amdDevice) { $margs += @('--worker-args', "`"--device $amdDevice`"") }
if ($amdDevice) { $margs += @('--worker-args', ('"--device ' + $amdDevice + '"')) }
else { $margs += @('--worker-args', ('"--vendor ' + $amdVendor.Replace(' ', '_') + '"')) }
}
if (-not (Test-Path $inst.log)) { Set-Content -Path $inst.log -Value "igneum-mine-test $($inst.label) $machine $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') node $nodeUrl run $($inst.runId)" }
$cmdLine = "`"$miner`" " + ($margs -join ' ') + " >> `"$($inst.log)`" 2>&1"
$inst.proc = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', $cmdLine) -NoNewWindow -PassThru
Set-Content -Path $inst.log -Value "igneum-mine-test $($inst.label) $machine $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') node $nodeUrl run $($inst.runId) start $($inst.starts)"
$inst.proc = Start-Process -FilePath $miner -ArgumentList $margs -WorkingDirectory $root -RedirectStandardOutput $inst.log -RedirectStandardError $inst.err -NoNewWindow -PassThru
$inst.startedAt = Get-Date
Log "$($inst.label) started (pid $($inst.proc.Id)): $cmdLine"
Log "$($inst.label) started (pid $($inst.proc.Id)): `"$miner`" $($margs -join ' ') -> $($inst.log)"
}
function Stop-Vendor($v) {
foreach ($inst in $v.instances) {
if ($inst.proc -and -not $inst.proc.HasExited) { cmd /c "taskkill /T /F /PID $($inst.proc.Id) >nul 2>&1" | Out-Null }
if ($inst.proc -and -not $inst.proc.HasExited) { & taskkill.exe /T /F /PID $inst.proc.Id 2>$null | Out-Null }
}
}
@ -199,7 +254,11 @@ Start-Sleep -Seconds 10
foreach ($v in $vendors.Values) {
$first = $v.instances[0]
$ready = Select-String -Path $first.log -Pattern 'worker: ready' | Select-Object -Last 1
if ($ready) { Log "$($v.name): $($ready.Line)" } else { Log "$($v.name): no ready line yet (see $($first.log))" }
if ($ready) { Log "$($v.name): $($ready.Line)" } else {
Log "$($v.name): no ready line yet (see $($first.log))"
if ($first.err -and (Test-Path $first.err)) { Get-Content $first.err -Tail 5 | ForEach-Object { Log "$($first.label) stderr: $_" } }
if ($first.proc.HasExited) { Log "$($first.label) already exited with code $($first.proc.ExitCode)" }
}
}
# ---- 5. loop: status, uploads, restarts, Ctrl+C --------------------------------------------------------------------------
@ -208,15 +267,21 @@ function Upload-Logs {
if (-not (Test-Path $uploader)) { return }
foreach ($v in $vendors.Values) {
foreach ($inst in $v.instances) {
$o = cmd /c "`"$uploader`" `"$($inst.log)`" $($inst.label) $($inst.runId)" 2>&1
if ($inst.err -and (Test-Path $inst.err) -and ((Get-Item $inst.err).Length -gt 0)) {
Get-Content $inst.err -Tail 5 | ForEach-Object { Add-Content -Path $launcherLog -Value (" " + $inst.label + " stderr: " + $_) }
}
$o = cmd /s /c "`"$uploader`" `"$($inst.log)`" $($inst.label) $($inst.runId)" 2>&1
Add-Content -Path $launcherLog -Value (" upload " + $inst.label + ": " + ($o -join ' '))
}
}
$o = cmd /c "`"$uploader`" `"$launcherLog`" launcher-$machine launcher-$machine-$stamp" 2>&1
$o = cmd /s /c "`"$uploader`" `"$launcherLog`" launcher-$machine launcher-$machine-$stamp" 2>&1
}
Upload-Logs # first upload right after the start summary, so a failed start reaches the intake too
Log 'first upload done'
function Instance-Stats($inst) {
$accepted = (Select-String -Path $inst.log -Pattern 'ACCEPTED block').Count
$accepted = $inst.acceptedBefore + (Select-String -Path $inst.log -Pattern 'ACCEPTED block').Count
$last = Select-String -Path $inst.log -Pattern 'STATUS' | Select-Object -Last 1
$wall = 0.0; $age = 'n/a'
if ($last -and $last.Line -match 'hash=([0-9.]+) MH/s wall \(([0-9.]+) MH/s inside jobs\) template_age=([0-9.]+)s') { $wall = [double]$Matches[1]; $age = $Matches[3] + 's' }