From 4a795986dcae7b8559a0afd4617e05f50eefb170 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Sat, 3 Oct 2026 19:28:21 +0000 Subject: [PATCH] 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 --- proto-cuda/windows-miner/START-MINING.bat | 5 ++ proto-cuda/windows-miner/start-mining.ps1 | 103 ++++++++++++++++++---- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/proto-cuda/windows-miner/START-MINING.bat b/proto-cuda/windows-miner/START-MINING.bat index dae772f4b..f37451ac2 100644 --- a/proto-cuda/windows-miner/START-MINING.bat +++ b/proto-cuda/windows-miner/START-MINING.bat @@ -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. diff --git a/proto-cuda/windows-miner/start-mining.ps1 b/proto-cuda/windows-miner/start-mining.ps1 index ec1c309aa..bc55de86a 100644 --- a/proto-cuda/windows-miner/start-mining.ps1 +++ b/proto-cuda/windows-miner/start-mining.ps1 @@ -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' }