From 000ad640f2a857697641fbbc725b2eccf1e578fc Mon Sep 17 00:00:00 2001 From: mmelnich Date: Mon, 10 Aug 2026 09:04:53 -0700 Subject: [PATCH 01/20] Windows: fetch oneMKL through vcpkg manifest mode; CI lane for the VS-bundled vcpkg --- .../setup-randlapack-deps-windows/setup.ps1 | 48 +++++++++++++- .github/workflows/install-script.yaml | 65 +++++++++++++++++++ INSTALL.md | 7 ++ install/install.ps1 | 6 +- 4 files changed, 122 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 174b0d1e..1363ee13 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -125,9 +125,17 @@ if ($MklRoot -ne "") { $found = Get-Command "vcpkg.exe" -ErrorAction SilentlyContinue if ($found) { $VcpkgExecutable = $found.Source } } + if ($VcpkgExecutable -eq "" -and $env:VSINSTALLDIR) { + # Visual Studio 2022 17.6+ bundles vcpkg with the C++ workload; a + # developer prompt exports VSINSTALLDIR but does not always put + # vcpkg.exe on PATH. + $bundled = Join-Path $env:VSINSTALLDIR "VC\vcpkg\vcpkg.exe" + if (Test-Path $bundled) { $VcpkgExecutable = $bundled } + } if ($VcpkgExecutable -eq "") { throw ("Could not locate vcpkg.exe (checked -VcpkgExecutable, VCPKG_INSTALLATION_ROOT, " + - "VCPKG_ROOT, PATH). Alternatively pass -MklRoot pointing at an existing oneMKL install.") + "VCPKG_ROOT, PATH, and the Visual Studio bundled copy under VSINSTALLDIR). " + + "Alternatively pass -MklRoot pointing at an existing oneMKL install.") } $vcpkgInstallRoot = Join-Path $resolvedRoot "vcpkg-installed" @@ -136,8 +144,42 @@ if ($MklRoot -ne "") { if (Test-Path (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")) { Write-Host "Reusing oneMKL at $mklRoot" } else { - Invoke-Checked $VcpkgExecutable @( - "install", "intel-mkl:x64-windows", "--x-install-root=$vcpkgInstallRoot") + # Manifest mode is the only mode every vcpkg distribution supports: + # the copy bundled with Visual Studio has no classic-mode instance, + # so `vcpkg install intel-mkl:x64-windows` fails there outright. + # Generate a minimal manifest and point every scratch tree at + # DependencyRoot -- the bundled vcpkg lives under Program Files, + # where its default scratch locations are not writable. The bundled + # vcpkg additionally requires builtin-baseline; the pin below is + # vcpkg release 2026.07.29 (intel-mkl 2025.2.0), which also makes + # the oneMKL version independent of the vcpkg copy's age. + $vcpkgScratch = Join-Path $resolvedRoot "vcpkg-scratch" + $manifestDir = Join-Path $vcpkgScratch "manifest" + New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null + Set-Content -Path (Join-Path $manifestDir "vcpkg.json") -Encoding ascii -Value @( + '{', + ' "name": "randlapack-windows-deps",', + ' "version-string": "1",', + ' "builtin-baseline": "9e593bb18ea69cc5095e012465dcd675a822ed0d",', + ' "dependencies": [ "intel-mkl" ]', + '}') + Push-Location $manifestDir + try { + Invoke-Checked $VcpkgExecutable @( + "install", + "--triplet", "x64-windows", + "--x-install-root=$vcpkgInstallRoot", + "--downloads-root=$(Join-Path $vcpkgScratch 'downloads')", + "--x-buildtrees-root=$(Join-Path $vcpkgScratch 'buildtrees')", + "--x-packages-root=$(Join-Path $vcpkgScratch 'packages')") + } finally { + Pop-Location + } + # buildtrees/packages hold gigabytes of extracted installer scratch; + # the installed prefix is self-contained. Keep downloads so a re-run + # (or a CI downloads cache) skips the oneMKL fetch. + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ` + (Join-Path $vcpkgScratch "buildtrees"), (Join-Path $vcpkgScratch "packages") } $mklBin = Join-Path $mklRoot "bin" } diff --git a/.github/workflows/install-script.yaml b/.github/workflows/install-script.yaml index 50276d1d..69a051fa 100644 --- a/.github/workflows/install-script.yaml +++ b/.github/workflows/install-script.yaml @@ -142,3 +142,68 @@ jobs: -ProjectDir "$env:GITHUB_WORKSPACE/RandNLA-project" ` -DependencyRoot "$env:GITHUB_WORKSPACE/../windows-deps" ` -SkipTests + + # User-fidelity lane: a plain Visual Studio install has no standalone + # vcpkg -- only the manifest-only copy bundled with the C++ workload. A + # classic-mode `vcpkg install` fails there ("this vcpkg distribution does + # not have a classic mode instance") while staying green on CI runners, + # which ship a classic-capable C:\vcpkg. This job hides the runner's + # standalone vcpkg and provisions oneMKL through the bundled copy. The + # vcpkg-installed tree is deliberately NOT cached so every run exercises + # the manifest-mode fetch; only the installer download is cached. + install-windows-vs-vcpkg: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + with: + path: RandLAPACK + submodules: recursive + + - name: initialize MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: cache oneMKL installer downloads + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\vcpkg-scratch\downloads + key: windows-vsvcpkg-mkl-downloads-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + + - name: cache GoogleTest + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\googletest-install + key: windows-vsvcpkg-googletest-1.17.0-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + + - name: cache Random123 + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\Random123-install + key: windows-vsvcpkg-random123-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + + - name: cache BLAS++ + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\blaspp-install + key: windows-vsvcpkg-blaspp-remove-symv-debug-print-ilp64-sequential-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + + - name: cache LAPACK++ + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\lapackpp-install + key: windows-vsvcpkg-lapackpp-msvc-direct-includes-ilp64-sequential-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + + - name: run the installer with the Visual Studio bundled vcpkg + shell: pwsh + run: | + Remove-Item Env:VCPKG_INSTALLATION_ROOT -ErrorAction SilentlyContinue + Remove-Item Env:VCPKG_ROOT -ErrorAction SilentlyContinue + $bundledVcpkg = Join-Path $env:VSINSTALLDIR "VC\vcpkg\vcpkg.exe" + if (-not (Test-Path $bundledVcpkg)) { + throw "Runner image has no VS-bundled vcpkg at $bundledVcpkg; this lane needs another way to reproduce a manifest-only vcpkg." + } + & "$env:GITHUB_WORKSPACE/RandLAPACK/install/install.ps1" ` + -ProjectDir "$env:GITHUB_WORKSPACE/RandNLA-project" ` + -DependencyRoot "$env:GITHUB_WORKSPACE/../windows-deps-vsvcpkg" ` + -VcpkgExecutable $bundledVcpkg diff --git a/INSTALL.md b/INSTALL.md index 9ec1ee66..ec5cf1b1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -316,6 +316,13 @@ comment in `install.ps1` documents the options. Points worth knowing: +- **Any vcpkg distribution works, including the copy bundled with Visual + Studio.** The installer fetches oneMKL through vcpkg in manifest mode, + which the Visual Studio bundled copy supports (it has no classic-mode + instance, so `vcpkg install ` invocations would fail there). It + auto-detects vcpkg via `VCPKG_INSTALLATION_ROOT`, `VCPKG_ROOT`, `PATH`, + then the bundled copy under `VSINSTALLDIR`; pass `-VcpkgExecutable ` + to pin one explicitly, or `-MklRoot ` to skip vcpkg entirely. - **ILP64 is required.** RandBLAS's MKL sparse backend checks at compile time that its 64-bit indices match `MKL_INT`, so BLAS++ must be configured with `-Dblas_int=ilp64` against `mkl_intel_ilp64_dll.lib` (the installer does diff --git a/install/install.ps1 b/install/install.ps1 index 0921a894..310a59d9 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -16,6 +16,9 @@ # (default: ..\RandNLA-project relative to this script). # -MklRoot Use an existing oneMKL (oneAPI installer layout) # instead of fetching MKL through vcpkg. +# -VcpkgExecutable vcpkg.exe to use for the oneMKL fetch. Default: +# auto-detect (VCPKG_INSTALLATION_ROOT, VCPKG_ROOT, +# PATH, then the Visual Studio bundled copy). # -Fresh Reconfigure RandLAPACK from scratch (dependencies are # always reused when present; delete \install # subdirectories to force dependency rebuilds). @@ -29,6 +32,7 @@ param( [string]$ProjectDir = "", [string]$MklRoot = "", + [string]$VcpkgExecutable = "", # Where the dependency stack lives (default: \install). CI # points this at its shared, cached dependency directory. [string]$DependencyRoot = "", @@ -79,7 +83,7 @@ Write-Host "" # Step 1: dependencies (idempotent; reused when already present). & (Join-Path $sourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` - -DependencyRoot $dependencyRoot -MklRoot $MklRoot + -DependencyRoot $dependencyRoot -MklRoot $MklRoot -VcpkgExecutable $VcpkgExecutable # Step 2: RandLAPACK itself. if ($Fresh -and (Test-Path $buildDir)) { From 6265fac9ccb52ab675ee9b4807074349e5029d07 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Mon, 10 Aug 2026 10:56:19 -0700 Subject: [PATCH 02/20] Replace vcpkg with a direct pinned fetch of Intel's oneMKL NuGet packages Per review feedback on the RandBLAS counterpart (BallisticLA/RandBLAS#185): the manifest-mode machinery fixed the VS-bundled-vcpkg failure but added complexity. vcpkg's only job here was downloading oneMKL, which Intel also publishes as plain zip packages on nuget.org. Downloading those directly (pinned by version and SHA256, arranged into the oneAPI layout) removes vcpkg from the picture entirely: no distribution variance, no discovery, no manifest, no extra CI lane. --- .../setup-randlapack-deps-windows/action.yml | 8 +- .../setup-randlapack-deps-windows/setup.ps1 | 105 +++++++----------- .github/workflows/install-script.yaml | 65 ----------- INSTALL.md | 22 ++-- install/install.ps1 | 13 +-- 5 files changed, 58 insertions(+), 155 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/action.yml b/.github/actions/setup-randlapack-deps-windows/action.yml index 0353a588..764bfcbb 100644 --- a/.github/actions/setup-randlapack-deps-windows/action.yml +++ b/.github/actions/setup-randlapack-deps-windows/action.yml @@ -1,6 +1,6 @@ name: setup-randlapack-deps-windows description: > - Builds/restores RandLAPACK's native Windows dependencies (oneMKL via vcpkg, + Builds/restores RandLAPACK's native Windows dependencies (oneMKL from Intel's NuGet packages, GoogleTest, Random123, BLAS++, LAPACK++) and exports their locations as environment variables (MKLROOT, googletest_PREFIX, Random123_DIR, blaspp_DIR, lapackpp_DIR). Requires an initialized MSVC environment (ilammy/msvc-dev-cmd). @@ -16,11 +16,11 @@ runs: steps: # Caches live beside the workspace, keyed on the setup script so any recipe # change invalidates them. - - name: cache oneMKL (vcpkg) + - name: cache oneMKL (NuGet) uses: actions/cache@v4 with: - path: ${{ github.workspace }}\..\windows-deps\vcpkg-installed - key: windows-vcpkg-intel-mkl-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + path: ${{ github.workspace }}\..\windows-deps\onemkl-2025.2.0.627 + key: windows-nuget-intel-mkl-2025.2.0.627-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - name: cache GoogleTest if: inputs.sanitize-address != 'true' diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 1363ee13..86c97779 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -1,5 +1,5 @@ # Builds and installs RandLAPACK's native Windows dependencies: -# - oneMKL (via vcpkg, ILP64 + sequential DLL set) +# - oneMKL (Intel's NuGet packages, ILP64 + sequential DLL set) # - GoogleTest v1.17.0 # - Random123 (headers only) # - BLAS++ from BallisticLA/blaspp, branch remove-symv-debug-print @@ -18,10 +18,9 @@ param( [Parameter(Mandatory = $true)] [string]$DependencyRoot, - [string]$VcpkgExecutable = "", - # Use an existing oneMKL install (e.g. from the oneAPI installer) instead - # of fetching MKL through vcpkg. Must contain the ILP64 DLL import libs. + # of downloading Intel's NuGet packages. Must contain the ILP64 DLL + # import libs. [string]$MklRoot = "", [switch]$SanitizeAddress @@ -113,75 +112,47 @@ if ($MklRoot -ne "") { if (-not $mklBin) { throw "-MklRoot $MklRoot has no bin\ (or redist\intel64\) DLL directory." } Write-Host "Using existing oneMKL at $mklRoot" } else { - if ($VcpkgExecutable -eq "") { - foreach ($candidateRoot in @($env:VCPKG_INSTALLATION_ROOT, $env:VCPKG_ROOT)) { - if ($candidateRoot -and (Test-Path (Join-Path $candidateRoot "vcpkg.exe"))) { - $VcpkgExecutable = Join-Path $candidateRoot "vcpkg.exe" - break - } - } - } - if ($VcpkgExecutable -eq "") { - $found = Get-Command "vcpkg.exe" -ErrorAction SilentlyContinue - if ($found) { $VcpkgExecutable = $found.Source } - } - if ($VcpkgExecutable -eq "" -and $env:VSINSTALLDIR) { - # Visual Studio 2022 17.6+ bundles vcpkg with the C++ workload; a - # developer prompt exports VSINSTALLDIR but does not always put - # vcpkg.exe on PATH. - $bundled = Join-Path $env:VSINSTALLDIR "VC\vcpkg\vcpkg.exe" - if (Test-Path $bundled) { $VcpkgExecutable = $bundled } - } - if ($VcpkgExecutable -eq "") { - throw ("Could not locate vcpkg.exe (checked -VcpkgExecutable, VCPKG_INSTALLATION_ROOT, " + - "VCPKG_ROOT, PATH, and the Visual Studio bundled copy under VSINSTALLDIR). " + - "Alternatively pass -MklRoot pointing at an existing oneMKL install.") - } - - $vcpkgInstallRoot = Join-Path $resolvedRoot "vcpkg-installed" - $mklRoot = Join-Path $vcpkgInstallRoot "x64-windows" + # oneMKL comes straight from Intel's official NuGet packages -- plain + # zip archives on nuget.org, pinned by version and SHA256. The devel + # package carries the ILP64/sequential import libs and headers, the + # redist package the runtime DLLs. This deliberately avoids vcpkg: its + # Visual Studio-bundled distribution is manifest-only (no classic-mode + # instance), and nothing here needed vcpkg beyond this one download. + # The OpenMP/TBB packages the devel nuspec references are skipped on + # purpose -- RandLAPACK links the sequential MKL DLL set. + $mklVersion = "2025.2.0.627" + $mklPackages = @( + @{ Id = "intelmkl.devel.win-x64" + Sha256 = "988816fb3cdfc5dcfdd42036c28314dcfda22fe47a29056ae455e360a8833ee5" }, + @{ Id = "intelmkl.redist.win-x64" + Sha256 = "42bf35a13581aa03ecbee62e83e2c6397a45f13ae8aa657c1727fd0335e52c9e" }) + $mklRoot = Join-Path $resolvedRoot "onemkl-$mklVersion" $mklLibDir = Join-Path $mklRoot "lib" + $mklBin = Join-Path $mklRoot "bin" if (Test-Path (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")) { Write-Host "Reusing oneMKL at $mklRoot" } else { - # Manifest mode is the only mode every vcpkg distribution supports: - # the copy bundled with Visual Studio has no classic-mode instance, - # so `vcpkg install intel-mkl:x64-windows` fails there outright. - # Generate a minimal manifest and point every scratch tree at - # DependencyRoot -- the bundled vcpkg lives under Program Files, - # where its default scratch locations are not writable. The bundled - # vcpkg additionally requires builtin-baseline; the pin below is - # vcpkg release 2026.07.29 (intel-mkl 2025.2.0), which also makes - # the oneMKL version independent of the vcpkg copy's age. - $vcpkgScratch = Join-Path $resolvedRoot "vcpkg-scratch" - $manifestDir = Join-Path $vcpkgScratch "manifest" - New-Item -ItemType Directory -Force -Path $manifestDir | Out-Null - Set-Content -Path (Join-Path $manifestDir "vcpkg.json") -Encoding ascii -Value @( - '{', - ' "name": "randlapack-windows-deps",', - ' "version-string": "1",', - ' "builtin-baseline": "9e593bb18ea69cc5095e012465dcd675a822ed0d",', - ' "dependencies": [ "intel-mkl" ]', - '}') - Push-Location $manifestDir - try { - Invoke-Checked $VcpkgExecutable @( - "install", - "--triplet", "x64-windows", - "--x-install-root=$vcpkgInstallRoot", - "--downloads-root=$(Join-Path $vcpkgScratch 'downloads')", - "--x-buildtrees-root=$(Join-Path $vcpkgScratch 'buildtrees')", - "--x-packages-root=$(Join-Path $vcpkgScratch 'packages')") - } finally { - Pop-Location + if (Test-Path $mklRoot) { Remove-Item -Recurse -Force $mklRoot } + $extractRoot = Join-Path $mklRoot "extract" + foreach ($package in $mklPackages) { + $archive = Join-Path $resolvedRoot "$($package.Id).$mklVersion.zip" + Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, + "https://api.nuget.org/v3-flatcontainer/$($package.Id)/$mklVersion/$($package.Id).$mklVersion.nupkg") + $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() + if ($actual -ne $package.Sha256) { + throw "$($package.Id) $mklVersion hash mismatch: expected $($package.Sha256), got $actual." + } + Expand-Archive -Path $archive -DestinationPath (Join-Path $extractRoot $package.Id) -Force + Remove-Item $archive } - # buildtrees/packages hold gigabytes of extracted installer scratch; - # the installed prefix is self-contained. Keep downloads so a re-run - # (or a CI downloads cache) skips the oneMKL fetch. - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue ` - (Join-Path $vcpkgScratch "buildtrees"), (Join-Path $vcpkgScratch "packages") + # Arrange the pieces into the oneAPI directory shape (lib\, include\, + # bin\) that the -MklRoot path, the checks below, and RandBLAS's + # MKL_sparse.cmake (MKLROOT/include) already expect. + Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\win-x64") $mklLibDir + Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\include") (Join-Path $mklRoot "include") + Move-Item (Join-Path $extractRoot "intelmkl.redist.win-x64\runtimes\win-x64\native") $mklBin + Remove-Item -Recurse -Force $extractRoot } - $mklBin = Join-Path $mklRoot "bin" } foreach ($required in @( diff --git a/.github/workflows/install-script.yaml b/.github/workflows/install-script.yaml index 69a051fa..50276d1d 100644 --- a/.github/workflows/install-script.yaml +++ b/.github/workflows/install-script.yaml @@ -142,68 +142,3 @@ jobs: -ProjectDir "$env:GITHUB_WORKSPACE/RandNLA-project" ` -DependencyRoot "$env:GITHUB_WORKSPACE/../windows-deps" ` -SkipTests - - # User-fidelity lane: a plain Visual Studio install has no standalone - # vcpkg -- only the manifest-only copy bundled with the C++ workload. A - # classic-mode `vcpkg install` fails there ("this vcpkg distribution does - # not have a classic mode instance") while staying green on CI runners, - # which ship a classic-capable C:\vcpkg. This job hides the runner's - # standalone vcpkg and provisions oneMKL through the bundled copy. The - # vcpkg-installed tree is deliberately NOT cached so every run exercises - # the manifest-mode fetch; only the installer download is cached. - install-windows-vs-vcpkg: - runs-on: windows-2022 - steps: - - uses: actions/checkout@v4 - with: - path: RandLAPACK - submodules: recursive - - - name: initialize MSVC - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 - - - name: cache oneMKL installer downloads - uses: actions/cache@v4 - with: - path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\vcpkg-scratch\downloads - key: windows-vsvcpkg-mkl-downloads-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - - - name: cache GoogleTest - uses: actions/cache@v4 - with: - path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\googletest-install - key: windows-vsvcpkg-googletest-1.17.0-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - - - name: cache Random123 - uses: actions/cache@v4 - with: - path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\Random123-install - key: windows-vsvcpkg-random123-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - - - name: cache BLAS++ - uses: actions/cache@v4 - with: - path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\blaspp-install - key: windows-vsvcpkg-blaspp-remove-symv-debug-print-ilp64-sequential-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - - - name: cache LAPACK++ - uses: actions/cache@v4 - with: - path: ${{ github.workspace }}\..\windows-deps-vsvcpkg\lapackpp-install - key: windows-vsvcpkg-lapackpp-msvc-direct-includes-ilp64-sequential-1-${{ hashFiles('RandLAPACK/.github/actions/setup-randlapack-deps-windows/setup.ps1') }} - - - name: run the installer with the Visual Studio bundled vcpkg - shell: pwsh - run: | - Remove-Item Env:VCPKG_INSTALLATION_ROOT -ErrorAction SilentlyContinue - Remove-Item Env:VCPKG_ROOT -ErrorAction SilentlyContinue - $bundledVcpkg = Join-Path $env:VSINSTALLDIR "VC\vcpkg\vcpkg.exe" - if (-not (Test-Path $bundledVcpkg)) { - throw "Runner image has no VS-bundled vcpkg at $bundledVcpkg; this lane needs another way to reproduce a manifest-only vcpkg." - } - & "$env:GITHUB_WORKSPACE/RandLAPACK/install/install.ps1" ` - -ProjectDir "$env:GITHUB_WORKSPACE/RandNLA-project" ` - -DependencyRoot "$env:GITHUB_WORKSPACE/../windows-deps-vsvcpkg" ` - -VcpkgExecutable $bundledVcpkg diff --git a/INSTALL.md b/INSTALL.md index ec5cf1b1..cd3da40b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -309,20 +309,20 @@ Intel oneMKL using ILP64, sequential linking. The easy path, from a .\install\install.ps1 ``` -This builds all dependencies (oneMKL through vcpkg, or pass -`-MklRoot ` to use an existing oneAPI install), then builds, installs, -and tests RandLAPACK, mirroring `install.sh`'s directory layout. The header -comment in `install.ps1` documents the options. +This builds all dependencies (oneMKL is downloaded from Intel's official +NuGet packages, or pass `-MklRoot ` to use an existing oneAPI +install), then builds, installs, and tests RandLAPACK, mirroring +`install.sh`'s directory layout. The header comment in `install.ps1` +documents the options. Points worth knowing: -- **Any vcpkg distribution works, including the copy bundled with Visual - Studio.** The installer fetches oneMKL through vcpkg in manifest mode, - which the Visual Studio bundled copy supports (it has no classic-mode - instance, so `vcpkg install ` invocations would fail there). It - auto-detects vcpkg via `VCPKG_INSTALLATION_ROOT`, `VCPKG_ROOT`, `PATH`, - then the bundled copy under `VSINSTALLDIR`; pass `-VcpkgExecutable ` - to pin one explicitly, or `-MklRoot ` to skip vcpkg entirely. +- **No vcpkg (or any package manager) is required.** oneMKL comes from + Intel's official NuGet packages (`intelmkl.devel.win-x64` + + `intelmkl.redist.win-x64`), downloaded as plain zip archives from + nuget.org, pinned by version and SHA256, and arranged into the standard + oneAPI layout. Pass `-MklRoot ` to use an existing oneAPI install + instead. - **ILP64 is required.** RandBLAS's MKL sparse backend checks at compile time that its 64-bit indices match `MKL_INT`, so BLAS++ must be configured with `-Dblas_int=ilp64` against `mkl_intel_ilp64_dll.lib` (the installer does diff --git a/install/install.ps1 b/install/install.ps1 index 310a59d9..83911729 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -7,18 +7,16 @@ # # What it does, mirroring install.sh's layout in a sibling RandNLA-project # directory: -# 1. Builds/reuses the dependencies (oneMKL via vcpkg or -MklRoot, -# GoogleTest, Random123, BLAS++, LAPACK++) under \install. +# 1. Builds/reuses the dependencies (oneMKL from Intel's NuGet packages +# or -MklRoot, GoogleTest, Random123, BLAS++, LAPACK++) under +# \install. # 2. Configures, builds, installs, and tests RandLAPACK. # # Options: # -ProjectDir Where dependencies/builds/installs go # (default: ..\RandNLA-project relative to this script). # -MklRoot Use an existing oneMKL (oneAPI installer layout) -# instead of fetching MKL through vcpkg. -# -VcpkgExecutable vcpkg.exe to use for the oneMKL fetch. Default: -# auto-detect (VCPKG_INSTALLATION_ROOT, VCPKG_ROOT, -# PATH, then the Visual Studio bundled copy). +# instead of downloading Intel's NuGet packages. # -Fresh Reconfigure RandLAPACK from scratch (dependencies are # always reused when present; delete \install # subdirectories to force dependency rebuilds). @@ -32,7 +30,6 @@ param( [string]$ProjectDir = "", [string]$MklRoot = "", - [string]$VcpkgExecutable = "", # Where the dependency stack lives (default: \install). CI # points this at its shared, cached dependency directory. [string]$DependencyRoot = "", @@ -83,7 +80,7 @@ Write-Host "" # Step 1: dependencies (idempotent; reused when already present). & (Join-Path $sourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` - -DependencyRoot $dependencyRoot -MklRoot $MklRoot -VcpkgExecutable $VcpkgExecutable + -DependencyRoot $dependencyRoot -MklRoot $MklRoot # Step 2: RandLAPACK itself. if ($Fresh -and (Test-Path $buildDir)) { From 6a0aaa7531d134fbed29d8898cb8f3e02f59994e Mon Sep 17 00:00:00 2001 From: mmelnich Date: Mon, 10 Aug 2026 15:59:13 -0700 Subject: [PATCH 03/20] Windows: backend selection (mkl/openblas/custom), oneAPI auto-discovery, app-local DLL staging setup.ps1 gains -Backend: mkl (default) auto-discovers an installed oneAPI via MKLROOT/ONEAPI_ROOT/the default install path before falling back to the pinned NuGet download; openblas provisions the official 0.3.34 release binaries (SHA256-pinned, link-checked with a dgemm_/dgesv_ conftest, import lib regenerated from the .def if needed -- the DLL is self-contained, importing only kernel32/msvcrt); custom passes user libraries through to BLAS++/LAPACK++ after the same conftest. Defect fixes: all dependency builds move NMake->Ninja, Random123 pinned to v1.14.0, blaspp/lapackpp cache-reuse now checks for the package config file instead of bare directory existence, and action.yml cache keys drop hashFiles() (it silently resolves empty under the install-script workflow's RandLAPACK/ checkout path, splitting caches that claimed to be shared) for manual -rN revisions. rl_runtime_dlls.cmake gains RANDLAPACK_RUNTIME_DLL_DIRS so the BLAS backend's DLLs (invisible to TARGET_RUNTIME_DLLS as raw-path imports) are staged beside executables; the module + configured dirs are exported with the package and the benchmark project stages via the same function; install.ps1 replaces its keep-MKL-on-PATH instruction with staging, gains preflight checks with fix-it commands. core-windows matrix: mkl-ilp64 serial/openmp + openblas-lp64 serial, with a stripped-PATH staging proof step and a temporary OpenMP-flavor diagnostic. --- .../setup-randlapack-deps-windows/action.yml | 47 +- .../setup-randlapack-deps-windows/setup.ps1 | 420 ++++++++++++++---- .github/scripts/windows/run-ci.ps1 | 37 +- .github/workflows/core-windows.yaml | 49 +- CMake/RandLAPACKConfig.cmake.in | 8 + CMake/rl_config.cmake | 2 + CMake/rl_runtime_dlls.cmake | 36 +- benchmark/CMakeLists.txt | 5 + install/install.ps1 | 60 ++- 9 files changed, 539 insertions(+), 125 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/action.yml b/.github/actions/setup-randlapack-deps-windows/action.yml index 764bfcbb..8c211187 100644 --- a/.github/actions/setup-randlapack-deps-windows/action.yml +++ b/.github/actions/setup-randlapack-deps-windows/action.yml @@ -1,11 +1,17 @@ name: setup-randlapack-deps-windows description: > - Builds/restores RandLAPACK's native Windows dependencies (oneMKL from Intel's NuGet packages, - GoogleTest, Random123, BLAS++, LAPACK++) and exports their locations as - environment variables (MKLROOT, googletest_PREFIX, Random123_DIR, blaspp_DIR, - lapackpp_DIR). Requires an initialized MSVC environment (ilammy/msvc-dev-cmd). + Builds/restores RandLAPACK's native Windows dependencies (a BLAS/LAPACK + backend -- oneMKL from Intel's NuGet packages or OpenBLAS release binaries -- + plus GoogleTest, Random123, BLAS++, LAPACK++) and exports their locations as + environment variables (RANDNLA_BLAS_BACKEND, RANDNLA_BLAS_BIN, MKLROOT for + mkl, googletest_PREFIX, Random123_DIR, blaspp_DIR, lapackpp_DIR). Requires an + initialized MSVC environment (ilammy/msvc-dev-cmd). inputs: + blas-backend: + description: "BLAS/LAPACK backend for the dependency stack: mkl or openblas." + required: false + default: "mkl" sanitize-address: description: Build an AddressSanitizer-instrumented GoogleTest. required: false @@ -14,45 +20,57 @@ inputs: runs: using: composite steps: - # Caches live beside the workspace, keyed on the setup script so any recipe - # change invalidates them. + # Cache keys use MANUAL revision literals (-r), bumped here whenever + # the corresponding recipe in setup.ps1 changes. Do NOT key on + # hashFiles(): it resolves paths relative to GITHUB_WORKSPACE, and the + # install-script workflow checks this repo out under RandLAPACK\, where + # the glob matches nothing and hashFiles() silently returns "" -- the two + # workflows then read/write DIFFERENT caches while appearing to share. - name: cache oneMKL (NuGet) + if: inputs.blas-backend == 'mkl' uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\onemkl-2025.2.0.627 - key: windows-nuget-intel-mkl-2025.2.0.627-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + key: windows-nuget-intel-mkl-2025.2.0.627-r1 + + - name: cache OpenBLAS (release binaries) + if: inputs.blas-backend == 'openblas' + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}\..\windows-deps\openblas-0.3.34 + key: windows-openblas-0.3.34-r1 - name: cache GoogleTest if: inputs.sanitize-address != 'true' uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\googletest-install - key: windows-msvc-googletest-1.17.0-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + key: windows-msvc-googletest-1.17.0-ninja-r1 - name: cache GoogleTest (ASan) if: inputs.sanitize-address == 'true' uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\googletest-asan-install - key: windows-msvc-googletest-asan-1.17.0-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + key: windows-msvc-googletest-asan-1.17.0-ninja-r1 - name: cache Random123 uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\Random123-install - key: windows-random123-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + key: windows-random123-1.14.0-r1 - name: cache BLAS++ uses: actions/cache@v4 with: - path: ${{ github.workspace }}\..\windows-deps\blaspp-install - key: windows-msvc-blaspp-remove-symv-debug-print-ilp64-sequential-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + path: ${{ github.workspace }}\..\windows-deps\blaspp-${{ inputs.blas-backend }}-install + key: windows-msvc-blaspp-remove-symv-debug-print-${{ inputs.blas-backend }}-ninja-r1 - name: cache LAPACK++ uses: actions/cache@v4 with: - path: ${{ github.workspace }}\..\windows-deps\lapackpp-install - key: windows-msvc-lapackpp-msvc-direct-includes-ilp64-sequential-1-${{ hashFiles('.github/actions/setup-randlapack-deps-windows/setup.ps1') }} + path: ${{ github.workspace }}\..\windows-deps\lapackpp-${{ inputs.blas-backend }}-install + key: windows-msvc-lapackpp-msvc-direct-includes-${{ inputs.blas-backend }}-ninja-r1 - name: build missing dependencies shell: pwsh @@ -60,4 +78,5 @@ runs: $sanitizeAddress = '${{ inputs.sanitize-address }}' -eq 'true' & "$env:GITHUB_ACTION_PATH/setup.ps1" ` -DependencyRoot "${env:GITHUB_WORKSPACE}\..\windows-deps" ` + -Backend '${{ inputs.blas-backend }}' ` -SanitizeAddress:$sanitizeAddress diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 86c97779..7df67bbb 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -1,5 +1,8 @@ # Builds and installs RandLAPACK's native Windows dependencies: -# - oneMKL (Intel's NuGet packages, ILP64 + sequential DLL set) +# - a BLAS/LAPACK backend (-Backend): oneMKL (default; discovered from an +# installed oneAPI or fetched from Intel's NuGet packages, ILP64 + +# sequential), OpenBLAS (official release binaries, LP64), or +# custom/bring-your-own libraries # - GoogleTest v1.17.0 # - Random123 (headers only) # - BLAS++ from BallisticLA/blaspp, branch remove-symv-debug-print @@ -18,11 +21,31 @@ param( [Parameter(Mandatory = $true)] [string]$DependencyRoot, + # BLAS/LAPACK backend. "mkl" (default): oneMKL, ILP64 + sequential, + # auto-discovered from an installed oneAPI or downloaded from Intel's + # NuGet packages. "openblas": official OpenBLAS release binaries, LP64. + # "custom": bring your own libraries via -BlasLibraries (anything + # BLAS++/LAPACK++ can link, e.g. AMD AOCL). + [ValidateSet("mkl", "openblas", "custom")] + [string]$Backend = "mkl", + # Use an existing oneMKL install (e.g. from the oneAPI installer) instead - # of downloading Intel's NuGet packages. Must contain the ILP64 DLL - # import libs. + # of auto-discovery/download. Must contain the ILP64 DLL import libs. + # Only meaningful with -Backend mkl. [string]$MklRoot = "", + # -Backend custom only: semicolon-separated .lib paths handed verbatim + # to BLAS++ (-BlasLibraries, required), optionally LAPACK++ + # (-LapackLibraries), the DLL directory for runtime staging + # (-BackendBinDir), the BLAS integer size, and blaspp's blas_fortran + # name-mangling hint (e.g. "add"). + [string]$BlasLibraries = "", + [string]$LapackLibraries = "", + [string]$BackendBinDir = "", + [ValidateSet("lp64", "ilp64")] + [string]$BlasInt = "lp64", + [string]$BlasFortran = "", + [switch]$SanitizeAddress ) @@ -79,6 +102,70 @@ function Export-GitHubValue { Write-Host "$Name = $Value" } +function Find-OneMklLayout { + # Probes a oneMKL root for the ILP64 import libs (oneAPI layout: lib\ on + # current releases, lib\intel64 on older ones) and a DLL directory + # (bin\ since oneAPI 2024, redist\intel64 before). + # Returns @{Root; LibDir; BinDir} or $null. + param([string]$Root) + if (-not $Root) { return $null } + $resolved = [System.IO.Path]::GetFullPath($Root) + foreach ($libDir in @((Join-Path $resolved "lib"), (Join-Path $resolved "lib\intel64"))) { + if (-not (Test-Path (Join-Path $libDir "mkl_intel_ilp64_dll.lib"))) { continue } + $binDir = @((Join-Path $resolved "bin"), (Join-Path $resolved "redist\intel64")) | + Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($binDir) { return @{ Root = $resolved; LibDir = $libDir; BinDir = $binDir } } + } + return $null +} + +function Test-BlasLinkage { + # Compiles and runs a minimal dgemm_/dgesv_ caller against the given + # import libraries: one clear pass/fail up front instead of a BLAS++ + # probe cascade later. Int64 selects the integer width (ILP64 vs LP64). + param([string[]]$Libraries, [string]$DllDir, [bool]$Int64, [string]$ScratchDir) + New-Item -ItemType Directory -Force -Path $ScratchDir | Out-Null + $src = Join-Path $ScratchDir "blas_conftest.c" + $intType = if ($Int64) { "long long" } else { "int" } + @( + '#include ', + "typedef $intType blas_int;", + 'extern void dgemm_(const char*, const char*, const blas_int*, const blas_int*,', + ' const blas_int*, const double*, const double*, const blas_int*,', + ' const double*, const blas_int*, const double*, double*,', + ' const blas_int*);', + 'extern void dgesv_(const blas_int*, const blas_int*, double*, const blas_int*,', + ' blas_int*, double*, const blas_int*, blas_int*);', + 'int main(void) {', + ' blas_int n = 2, one = 1, info = -1, ipiv[2];', + ' double A[4] = {3, 1, 1, 2}, b[2] = {9, 8}, C[4], alpha = 1.0, beta = 0.0;', + ' dgemm_("N", "N", &n, &n, &n, &alpha, A, &n, A, &n, &beta, C, &n);', + ' dgesv_(&n, &one, A, &n, ipiv, b, &n, &info);', + ' if (info != 0) { printf("dgesv_ info=%lld\n", (long long)info); return 1; }', + ' if (b[0] < 1.9 || b[0] > 2.1 || b[1] < 2.9 || b[1] > 3.1) {', + ' printf("wrong dgesv_ solution: %f %f\n", b[0], b[1]); return 2;', + ' }', + ' printf("BLAS/LAPACK link check OK\n");', + ' return 0;', + '}') | Set-Content -Path $src -Encoding ascii + $exe = Join-Path $ScratchDir "blas_conftest.exe" + Push-Location $ScratchDir + try { + & cl.exe /nologo $src "/Fe:$exe" /link @($Libraries) | Out-Host + if ($LASTEXITCODE -ne 0) { return $false } + $savedPath = $env:PATH + if ($DllDir) { $env:PATH = "$DllDir;$env:PATH" } + try { + & $exe | Out-Host + return ($LASTEXITCODE -eq 0) + } finally { + $env:PATH = $savedPath + } + } finally { + Pop-Location + } +} + # ---------------------------------------------------------------- guards ---- $resolvedRoot = [System.IO.Path]::GetFullPath($DependencyRoot) @@ -91,78 +178,213 @@ if (-not (Get-Command "cl.exe" -ErrorAction SilentlyContinue)) { throw "cl.exe is not on PATH. Run from an MSVC developer environment (or ilammy/msvc-dev-cmd in CI)." } -# ---------------------------------------------------------------- oneMKL ---- - -if ($MklRoot -ne "") { - # Bring-your-own MKL (oneAPI installer layout: import libs in lib\ on - # current releases, lib\intel64 on older ones). - $mklRoot = [System.IO.Path]::GetFullPath($MklRoot) - $mklLibDir = "" - foreach ($candidate in @((Join-Path $mklRoot "lib"), (Join-Path $mklRoot "lib\intel64"))) { - if (Test-Path (Join-Path $candidate "mkl_intel_ilp64_dll.lib")) { - $mklLibDir = $candidate - break +# ---------------------------------------------------- argument checks ---- + +if ($Backend -ne "mkl" -and $MklRoot -ne "") { + throw "-MklRoot is only meaningful with -Backend mkl." +} +if ($Backend -eq "custom" -and $BlasLibraries -eq "") { + throw "-Backend custom requires -BlasLibraries (semicolon-separated full paths to .lib files)." +} +if ($Backend -ne "custom") { + foreach ($customOnly in @( + @{ Name = "-BlasLibraries"; Value = $BlasLibraries }, + @{ Name = "-LapackLibraries"; Value = $LapackLibraries }, + @{ Name = "-BackendBinDir"; Value = $BackendBinDir }, + @{ Name = "-BlasFortran"; Value = $BlasFortran })) { + if ($customOnly.Value -ne "") { + throw "$($customOnly.Name) is only meaningful with -Backend custom." } } - if ($mklLibDir -eq "") { - throw "-MklRoot $MklRoot does not contain mkl_intel_ilp64_dll.lib under lib\ or lib\intel64\." - } - $mklBin = @((Join-Path $mklRoot "bin"), (Join-Path $mklRoot "redist\intel64")) | - Where-Object { Test-Path $_ } | Select-Object -First 1 - if (-not $mklBin) { throw "-MklRoot $MklRoot has no bin\ (or redist\intel64\) DLL directory." } - Write-Host "Using existing oneMKL at $mklRoot" -} else { - # oneMKL comes straight from Intel's official NuGet packages -- plain - # zip archives on nuget.org, pinned by version and SHA256. The devel - # package carries the ILP64/sequential import libs and headers, the - # redist package the runtime DLLs. This deliberately avoids vcpkg: its - # Visual Studio-bundled distribution is manifest-only (no classic-mode - # instance), and nothing here needed vcpkg beyond this one download. - # The OpenMP/TBB packages the devel nuspec references are skipped on - # purpose -- RandLAPACK links the sequential MKL DLL set. - $mklVersion = "2025.2.0.627" - $mklPackages = @( - @{ Id = "intelmkl.devel.win-x64" - Sha256 = "988816fb3cdfc5dcfdd42036c28314dcfda22fe47a29056ae455e360a8833ee5" }, - @{ Id = "intelmkl.redist.win-x64" - Sha256 = "42bf35a13581aa03ecbee62e83e2c6397a45f13ae8aa657c1727fd0335e52c9e" }) - $mklRoot = Join-Path $resolvedRoot "onemkl-$mklVersion" - $mklLibDir = Join-Path $mklRoot "lib" - $mklBin = Join-Path $mklRoot "bin" - if (Test-Path (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")) { - Write-Host "Reusing oneMKL at $mklRoot" +} + +# ----------------------------------------------- BLAS/LAPACK backend ---- +# Every branch below must define: $backendLibraries (array of cmake-style +# .lib paths for BLAS++), $backendLapackLibraries ("" = let LAPACK++ resolve +# from the BLAS libs), $backendBlasInt, $backendBlasFortran ("" = unset), +# $backendBlasThreaded ("" = unset), and $backendBin (DLL directory; "" only +# for -Backend custom without -BackendBinDir). + +if ($Backend -eq "mkl") { + $mklLayout = $null + if ($MklRoot -ne "") { + $mklLayout = Find-OneMklLayout $MklRoot + if (-not $mklLayout) { + throw ("-MklRoot $MklRoot does not contain mkl_intel_ilp64_dll.lib under lib\ or " + + "lib\intel64\ alongside a bin\ (or redist\intel64\) DLL directory.") + } + Write-Host "Using existing oneMKL at $($mklLayout.Root)" } else { - if (Test-Path $mklRoot) { Remove-Item -Recurse -Force $mklRoot } - $extractRoot = Join-Path $mklRoot "extract" - foreach ($package in $mklPackages) { - $archive = Join-Path $resolvedRoot "$($package.Id).$mklVersion.zip" - Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, - "https://api.nuget.org/v3-flatcontainer/$($package.Id)/$mklVersion/$($package.Id).$mklVersion.nupkg") - $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() - if ($actual -ne $package.Sha256) { - throw "$($package.Id) $mklVersion hash mismatch: expected $($package.Sha256), got $actual." + # Discovery before download: a setvars.bat session exports MKLROOT; + # some oneAPI installers set ONEAPI_ROOT persistently; and the + # installer's default location is stable even when neither survives + # into a fresh shell (modern installers set no persistent env vars). + foreach ($candidate in @( + $env:MKLROOT, + $(if ($env:ONEAPI_ROOT) { Join-Path $env:ONEAPI_ROOT "mkl\latest" }), + "C:\Program Files (x86)\Intel\oneAPI\mkl\latest")) { + $mklLayout = Find-OneMklLayout $candidate + if ($mklLayout) { + Write-Host "Discovered existing oneMKL at $($mklLayout.Root)" + break + } + } + } + if (-not $mklLayout) { + # oneMKL comes straight from Intel's official NuGet packages -- plain + # zip archives on nuget.org, pinned by version and SHA256. The devel + # package carries the ILP64/sequential import libs and headers, the + # redist package the runtime DLLs. This deliberately avoids vcpkg: its + # Visual Studio-bundled distribution is manifest-only (no classic-mode + # instance), and nothing here needed vcpkg beyond this one download. + # The OpenMP/TBB packages the devel nuspec references are skipped on + # purpose -- RandLAPACK links the sequential MKL DLL set. + $mklVersion = "2025.2.0.627" + $mklPackages = @( + @{ Id = "intelmkl.devel.win-x64" + Sha256 = "988816fb3cdfc5dcfdd42036c28314dcfda22fe47a29056ae455e360a8833ee5" }, + @{ Id = "intelmkl.redist.win-x64" + Sha256 = "42bf35a13581aa03ecbee62e83e2c6397a45f13ae8aa657c1727fd0335e52c9e" }) + $mklRoot = Join-Path $resolvedRoot "onemkl-$mklVersion" + $mklLibDir = Join-Path $mklRoot "lib" + $mklBin = Join-Path $mklRoot "bin" + if (Test-Path (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")) { + Write-Host "Reusing oneMKL at $mklRoot" + } else { + if (Test-Path $mklRoot) { Remove-Item -Recurse -Force $mklRoot } + $extractRoot = Join-Path $mklRoot "extract" + foreach ($package in $mklPackages) { + $archive = Join-Path $resolvedRoot "$($package.Id).$mklVersion.zip" + Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, + "https://api.nuget.org/v3-flatcontainer/$($package.Id)/$mklVersion/$($package.Id).$mklVersion.nupkg") + $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() + if ($actual -ne $package.Sha256) { + throw "$($package.Id) $mklVersion hash mismatch: expected $($package.Sha256), got $actual." + } + Expand-Archive -Path $archive -DestinationPath (Join-Path $extractRoot $package.Id) -Force + Remove-Item $archive } - Expand-Archive -Path $archive -DestinationPath (Join-Path $extractRoot $package.Id) -Force - Remove-Item $archive + # Arrange the pieces into the oneAPI directory shape (lib\, + # include\, bin\) that Find-OneMklLayout, the checks below, and + # RandBLAS's MKL_sparse.cmake (MKLROOT/include) already expect. + Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\win-x64") $mklLibDir + Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\include") (Join-Path $mklRoot "include") + Move-Item (Join-Path $extractRoot "intelmkl.redist.win-x64\runtimes\win-x64\native") $mklBin + Remove-Item -Recurse -Force $extractRoot + } + $mklLayout = @{ Root = $mklRoot; LibDir = $mklLibDir; BinDir = $mklBin } + } + $mklRoot = $mklLayout.Root + $mklLibDir = $mklLayout.LibDir + $mklBin = $mklLayout.BinDir + foreach ($required in @( + (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib"), + (Join-Path $mklLibDir "mkl_sequential_dll.lib"), + (Join-Path $mklLibDir "mkl_core_dll.lib"))) { + if (-not (Test-Path $required)) { throw "oneMKL install is missing $required." } + } + $backendLibraries = @( + (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")), + (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_sequential_dll.lib")), + (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_core_dll.lib"))) + $backendLapackLibraries = "" + # ILP64 because RandBLAS's MKL sparse backend static-asserts that its + # int64_t sparse indices match sizeof(MKL_INT). + $backendBlasInt = "ilp64" + $backendBlasFortran = "" + $backendBlasThreaded = "false" + $backendBin = $mklBin +} elseif ($Backend -eq "openblas") { + # Official OpenBLAS release binaries: MinGW-built but self-contained + # (the DLL imports only kernel32/msvcrt -- the MinGW runtimes are linked + # in statically), MSVC-linkable through the shipped import library, and + # full LAPACK is included. LP64: no ILP64 OpenBLAS binaries are + # published for Windows. Without MKL, RandBLAS's MKL sparse + # acceleration stays off and its portable fallbacks take over. + $openblasVersion = "0.3.34" + $openblasSha256 = "e9cb6134541f36c27346d5fc5995652f060fba227cebbbabcbda5a5a44d7c76b" + $openblasRoot = Join-Path $resolvedRoot "openblas-$openblasVersion" + $openblasLib = Join-Path $openblasRoot "lib\libopenblas.lib" + $openblasBin = Join-Path $openblasRoot "bin" + if (Test-Path $openblasLib) { + Write-Host "Reusing OpenBLAS at $openblasRoot" + } else { + if (Test-Path $openblasRoot) { Remove-Item -Recurse -Force $openblasRoot } + $archive = Join-Path $resolvedRoot "OpenBLAS-$openblasVersion-x64.zip" + Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, + "https://github.com/OpenMathLib/OpenBLAS/releases/download/v$openblasVersion/OpenBLAS-$openblasVersion-x64.zip") + $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() + if ($actual -ne $openblasSha256) { + throw "OpenBLAS $openblasVersion hash mismatch: expected $openblasSha256, got $actual." } - # Arrange the pieces into the oneAPI directory shape (lib\, include\, - # bin\) that the -MklRoot path, the checks below, and RandBLAS's - # MKL_sparse.cmake (MKLROOT/include) already expect. - Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\win-x64") $mklLibDir - Move-Item (Join-Path $extractRoot "intelmkl.devel.win-x64\build\native\include") (Join-Path $mklRoot "include") - Move-Item (Join-Path $extractRoot "intelmkl.redist.win-x64\runtimes\win-x64\native") $mklBin - Remove-Item -Recurse -Force $extractRoot + Expand-Archive -Path $archive -DestinationPath $openblasRoot -Force + Remove-Item $archive + } + $openblasConftest = Join-Path $resolvedRoot "conftest-openblas" + if (-not (Test-BlasLinkage -Libraries @($openblasLib) -DllDir $openblasBin ` + -Int64 $false -ScratchDir $openblasConftest)) { + # The shipped import library is occasionally unusable from MSVC; + # regenerate it from the .def and retry once (self-healing when the + # version pin moves). + Write-Host "Shipped libopenblas.lib failed the link check; regenerating from libopenblas.def..." + Invoke-Checked "lib.exe" @("/nologo", "/machine:x64", + "/def:$(Join-Path $openblasRoot 'lib\libopenblas.def')", "/out:$openblasLib") + if (-not (Test-BlasLinkage -Libraries @($openblasLib) -DllDir $openblasBin ` + -Int64 $false -ScratchDir $openblasConftest)) { + throw "OpenBLAS link check failed even after import-library regeneration." + } + } + $backendLibraries = @(Convert-ToCMakePath $openblasLib) + # OpenBLAS bundles LAPACK in the same library. + $backendLapackLibraries = Convert-ToCMakePath $openblasLib + $backendBlasInt = "int32" + $backendBlasFortran = "add" + $backendBlasThreaded = "" + $backendBin = $openblasBin +} else { + # Bring-your-own backend (e.g. AMD AOCL, whose downloads are + # click-through-gated and cannot be fetched here): the libraries are + # handed to BLAS++/LAPACK++ verbatim after one clear preflight check. + # The check calls dgemm_/dgesv_, i.e. it assumes the common + # lowercase-underscore Fortran mangling (OpenBLAS, AOCL, MKL all + # export it). + $customLibs = @($BlasLibraries -split ";" | Where-Object { $_ -ne "" }) + foreach ($lib in $customLibs) { + if (-not (Test-Path $lib)) { throw "-BlasLibraries entry not found: $lib" } + } + if ($BackendBinDir -ne "" -and -not (Test-Path $BackendBinDir)) { + throw "-BackendBinDir not found: $BackendBinDir" + } + if (-not (Test-BlasLinkage -Libraries $customLibs -DllDir $BackendBinDir ` + -Int64 ($BlasInt -eq "ilp64") -ScratchDir (Join-Path $resolvedRoot "conftest-custom"))) { + throw ("The libraries in -BlasLibraries failed a minimal dgemm_/dgesv_ link-and-run " + + "check. Verify the paths, the integer size (-BlasInt), and that their runtime " + + "DLLs are in -BackendBinDir.") + } + $backendLibraries = @($customLibs | ForEach-Object { Convert-ToCMakePath $_ }) + $backendLapackLibraries = (@($LapackLibraries -split ";" | Where-Object { $_ -ne "" } | + ForEach-Object { Convert-ToCMakePath $_ })) -join ";" + $backendBlasInt = if ($BlasInt -eq "ilp64") { "ilp64" } else { "int32" } + $backendBlasFortran = $BlasFortran + $backendBlasThreaded = "" + $backendBin = $BackendBinDir + if ($backendBin -eq "") { + Write-Warning ("No -BackendBinDir given: the custom backend's runtime DLLs will not be " + + "staged next to executables; making them findable at run time is up to you.") } } -foreach ($required in @( - $mklBin, - (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib"), - (Join-Path $mklLibDir "mkl_sequential_dll.lib"), - (Join-Path $mklLibDir "mkl_core_dll.lib"))) { - if (-not (Test-Path $required)) { throw "oneMKL install is missing $required." } +# Distinct BLAS++/LAPACK++ installs per backend; a custom backend is keyed +# by a hash of its library list so switching -BlasLibraries rebuilds. +$backendId = $Backend +if ($Backend -eq "custom") { + $sha = [System.Security.Cryptography.SHA256]::Create() + $hashHex = ($sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($BlasLibraries)) | + ForEach-Object { $_.ToString("x2") }) -join "" + $backendId = "custom-$($hashHex.Substring(0, 8))" } -$env:PATH = "$mklBin;$env:PATH" + +if ($backendBin -ne "") { $env:PATH = "$backendBin;$env:PATH" } # ------------------------------------------------------------- GoogleTest ---- @@ -175,7 +397,7 @@ if (Test-Path (Join-Path $gtestInstall "include\gtest\gtest.h")) { Clone-Head "https://github.com/google/googletest.git" $gtestSrc "v1.17.0" $gtestBuild = Join-Path $resolvedRoot "$gtestVariant-build" $gtestArgs = @( - "-S", $gtestSrc, "-B", $gtestBuild, "-G", "NMake Makefiles", + "-S", $gtestSrc, "-B", $gtestBuild, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=$(Convert-ToCMakePath $gtestInstall)", "-DBUILD_GMOCK=OFF", "-DINSTALL_GTEST=ON") @@ -195,7 +417,7 @@ if (Test-Path (Join-Path $random123Install "include\Random123\philox.h")) { Write-Host "Reusing Random123 at $random123Install" } else { $random123Src = Join-Path $resolvedRoot "Random123-src" - Clone-Head "https://github.com/DEShawResearch/Random123.git" $random123Src + Clone-Head "https://github.com/DEShawResearch/Random123.git" $random123Src "v1.14.0" New-Item -ItemType Directory -Force -Path (Join-Path $random123Install "include") | Out-Null Copy-Item -Recurse -Force (Join-Path $random123Src "include\Random123") ` (Join-Path $random123Install "include\Random123") @@ -203,64 +425,82 @@ if (Test-Path (Join-Path $random123Install "include\Random123\philox.h")) { # ----------------------------------------------------------------- BLAS++ ---- -$blasppInstall = Join-Path $resolvedRoot "blaspp-install" -if (Test-Path $blasppInstall) { +$blasppInstall = Join-Path $resolvedRoot "blaspp-$backendId-install" +# Reuse only on an intact install: a partially restored cache directory must +# trigger a rebuild, not silently skip it. +$blasppReusable = (Test-Path $blasppInstall) -and (Get-ChildItem -Path $blasppInstall -Recurse ` + -Filter "blasppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) +if ($blasppReusable) { Write-Host "Reusing BLAS++ at $blasppInstall" } else { $blasppSrc = Join-Path $resolvedRoot "blaspp-src" Clone-Head "https://github.com/BallisticLA/blaspp.git" $blasppSrc "remove-symv-debug-print" - $blasppBuild = Join-Path $resolvedRoot "blaspp-build" - # ILP64 + sequential is required: RandBLAS's MKL sparse backend static-asserts - # that its int64_t sparse indices match sizeof(MKL_INT). - $mklLibs = @( - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")), - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_sequential_dll.lib")), - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_core_dll.lib"))) -join ';' - Invoke-Checked "cmake" @( - "-S", $blasppSrc, "-B", $blasppBuild, "-G", "NMake Makefiles", + # Never re-configure an existing blaspp build in place: that regenerates + # blas/defines.h without the backend defines. Fresh build tree per run. + $blasppBuild = Join-Path $resolvedRoot "blaspp-$backendId-build" + if (Test-Path $blasppBuild) { Remove-Item -Recurse -Force $blasppBuild } + $blasppArgs = @( + "-S", $blasppSrc, "-B", $blasppBuild, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=$(Convert-ToCMakePath $blasppInstall)", "-DBUILD_SHARED_LIBS=ON", "-Duse_cmake_find_blas=false", - "-DBLAS_LIBRARIES=$mklLibs", - "-Dblas_int=ilp64", - "-Dblas_threaded=false", + "-DBLAS_LIBRARIES=$($backendLibraries -join ';')", + "-Dblas_int=$backendBlasInt", "-Duse_openmp=false", "-Dgpu_backend=none", "-Dbuild_tests=OFF") + if ($backendBlasThreaded -ne "") { $blasppArgs += "-Dblas_threaded=$backendBlasThreaded" } + if ($backendBlasFortran -ne "") { $blasppArgs += "-Dblas_fortran=$backendBlasFortran" } + Invoke-Checked "cmake" $blasppArgs Invoke-Checked "cmake" @("--build", $blasppBuild, "--target", "install") } $blasppDir = Find-PackageConfigDirectory $blasppInstall "blaspp" # --------------------------------------------------------------- LAPACK++ ---- -$lapackppInstall = Join-Path $resolvedRoot "lapackpp-install" -if (Test-Path $lapackppInstall) { +$lapackppInstall = Join-Path $resolvedRoot "lapackpp-$backendId-install" +$lapackppReusable = (Test-Path $lapackppInstall) -and (Get-ChildItem -Path $lapackppInstall -Recurse ` + -Filter "lapackppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) +if ($lapackppReusable) { Write-Host "Reusing LAPACK++ at $lapackppInstall" } else { $lapackppSrc = Join-Path $resolvedRoot "lapackpp-src" Clone-Head "https://github.com/BallisticLA/lapackpp.git" $lapackppSrc "msvc-direct-includes" - $lapackppBuild = Join-Path $resolvedRoot "lapackpp-build" - Invoke-Checked "cmake" @( - "-S", $lapackppSrc, "-B", $lapackppBuild, "-G", "NMake Makefiles", + $lapackppBuild = Join-Path $resolvedRoot "lapackpp-$backendId-build" + if (Test-Path $lapackppBuild) { Remove-Item -Recurse -Force $lapackppBuild } + $lapackppArgs = @( + "-S", $lapackppSrc, "-B", $lapackppBuild, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_INSTALL_PREFIX=$(Convert-ToCMakePath $lapackppInstall)", "-Dblaspp_DIR=$(Convert-ToCMakePath $blasppDir)", "-DBUILD_SHARED_LIBS=ON", "-Dgpu_backend=none", "-Dbuild_tests=OFF") + if ($backendLapackLibraries -ne "") { + $lapackppArgs += "-DLAPACK_LIBRARIES=$backendLapackLibraries" + } + Invoke-Checked "cmake" $lapackppArgs Invoke-Checked "cmake" @("--build", $lapackppBuild, "--target", "install") } $lapackppDir = Find-PackageConfigDirectory $lapackppInstall "lapackpp" # ----------------------------------------------------------------- export ---- -Export-GitHubValue "MKLROOT" (Convert-ToCMakePath $mklRoot) -Export-GitHubValue "MKL_BIN" $mklBin +Export-GitHubValue "RANDNLA_BLAS_BACKEND" $Backend +if ($backendBin -ne "") { Export-GitHubValue "RANDNLA_BLAS_BIN" $backendBin } +if ($Backend -eq "mkl") { + # MKLROOT is what RandBLAS's MKL_sparse.cmake probes for mkl_spblas.h; + # its absence on other backends is what turns the MKL sparse path off. + Export-GitHubValue "MKLROOT" (Convert-ToCMakePath $mklRoot) + Export-GitHubValue "MKL_BIN" $mklBin +} Export-GitHubValue "googletest_PREFIX" (Convert-ToCMakePath $gtestInstall) Export-GitHubValue "Random123_DIR" (Convert-ToCMakePath (Join-Path $random123Install "include")) Export-GitHubValue "blaspp_DIR" (Convert-ToCMakePath $blasppDir) Export-GitHubValue "lapackpp_DIR" (Convert-ToCMakePath $lapackppDir) -if ($env:GITHUB_PATH) { Add-Content -Path $env:GITHUB_PATH -Value $mklBin } +if ($env:GITHUB_PATH -and $backendBin -ne "") { + Add-Content -Path $env:GITHUB_PATH -Value $backendBin +} Write-Host "All native Windows dependencies are ready under $resolvedRoot" diff --git a/.github/scripts/windows/run-ci.ps1 b/.github/scripts/windows/run-ci.ps1 index e192cd9b..d9bb4520 100644 --- a/.github/scripts/windows/run-ci.ps1 +++ b/.github/scripts/windows/run-ci.ps1 @@ -1,5 +1,6 @@ # Configures, builds, installs, and tests RandLAPACK natively on Windows with -# MSVC + oneMKL (ILP64, sequential). Shared between GitHub CI and local runs. +# MSVC and the selected BLAS/LAPACK backend (-Backend: mkl default, openblas, +# custom). Shared between GitHub CI and local runs. # # Local use, from an MSVC developer prompt in the repository root: # .github\scripts\windows\run-ci.ps1 -Task Core -SetupDependencies @@ -25,6 +26,9 @@ param( [string]$DependencyRoot = "", + [ValidateSet("mkl", "openblas", "custom")] + [string]$Backend = "mkl", + [switch]$SetupDependencies, [switch]$OpenMP, @@ -65,21 +69,34 @@ if ($DependencyRoot -eq "") { if ($SetupDependencies) { & (Join-Path $SourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` - -DependencyRoot $DependencyRoot -SanitizeAddress:$SanitizeAddress + -DependencyRoot $DependencyRoot -Backend $Backend -SanitizeAddress:$SanitizeAddress } $blasppDir = Require-EnvironmentVariable "blaspp_DIR" $lapackppDir = Require-EnvironmentVariable "lapackpp_DIR" $random123Dir = Require-EnvironmentVariable "Random123_DIR" $gtestPrefix = Require-EnvironmentVariable "googletest_PREFIX" -$mklRoot = Require-EnvironmentVariable "MKLROOT" -$mklBin = [Environment]::GetEnvironmentVariable("MKL_BIN") -if (-not $mklBin) { $mklBin = "$($mklRoot.Replace('/', '\'))\bin" } +$backendBin = [Environment]::GetEnvironmentVariable("RANDNLA_BLAS_BIN") +if (-not $backendBin) { + # Legacy fallback for environments set up by an older setup.ps1: derive + # the DLL directory from MKLROOT, accepting both oneAPI layouts. + $mklRoot = [Environment]::GetEnvironmentVariable("MKLROOT") + if ($mklRoot) { + $backendBin = @("$($mklRoot.Replace('/', '\'))\bin", + "$($mklRoot.Replace('/', '\'))\redist\intel64") | + Where-Object { Test-Path $_ } | Select-Object -First 1 + } +} +if (-not $backendBin) { + throw "RANDNLA_BLAS_BIN is not set. Run setup.ps1 (or pass -SetupDependencies)." +} -# oneMKL enters through raw library paths recorded by BLAS++, so its DLLs are -# not covered by the TARGET_RUNTIME_DLLS staging and must be on PATH for the -# build-time gtest discovery and for ctest. -$env:PATH = "$mklBin;$env:PATH" +# The BLAS backend enters through raw library paths recorded by BLAS++, so its +# DLLs are not covered by the TARGET_RUNTIME_DLLS staging. RANDLAPACK_RUNTIME_DLL_DIRS +# stages them beside RandLAPACK's executables; the process-PATH prepend below +# additionally covers the RandBLAS submodule's own executables until the +# RandBLAS-side staging lands (planned follow-up). +$env:PATH = "$backendBin;$env:PATH" $buildDir = Join-Path $WorkRoot "RandLAPACK-build" $installDir = Join-Path $WorkRoot "RandLAPACK-install" @@ -93,6 +110,8 @@ $configureArgs = @( "-Dlapackpp_DIR=$lapackppDir", "-DRandom123_DIR=$random123Dir", "-DCMAKE_PREFIX_PATH=$gtestPrefix", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", + "-DRANDLAPACK_RUNTIME_DLL_DIRS=$($backendBin.Replace('\', '/'))", # The RandBLAS submodule's own tests are covered by RandBLAS's CI; # building and running them here roughly doubled the job time. "-DBUILD_TESTS=OFF" diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 60d270db..3e6e7d3c 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -10,16 +10,28 @@ on: jobs: build-windows: - name: windows-msvc-mkl-ilp64-${{ matrix.label }} + name: windows-msvc-${{ matrix.backend }}-${{ matrix.int }}-${{ matrix.label }} runs-on: windows-2022 strategy: fail-fast: false + # Deliberately small matrix (see docs/CI.md): the mkl legs cover the + # default backend serial + OpenMP; ONE openblas leg covers the non-MKL + # provisioning and LP64 build. No openblas-openmp (OpenMP x MSVC is + # backend-orthogonal and already covered) and no Windows ASan lane. matrix: include: - label: serial + backend: mkl + int: ilp64 openmp: false - label: openmp + backend: mkl + int: ilp64 openmp: true + - label: serial + backend: openblas + int: lp64 + openmp: false steps: - uses: actions/checkout@v4 with: @@ -32,6 +44,8 @@ jobs: - name: setup native Windows dependencies uses: ./.github/actions/setup-randlapack-deps-windows + with: + blas-backend: ${{ matrix.backend }} - name: build, install, and test RandLAPACK shell: pwsh @@ -40,4 +54,37 @@ jobs: & "$env:GITHUB_WORKSPACE/.github/scripts/windows/run-ci.ps1" ` -Task Core ` -WorkRoot "$env:GITHUB_WORKSPACE/../RandLAPACK-windows-ci" ` + -Backend '${{ matrix.backend }}' ` -OpenMP:$openmp + + # TEMPORARY (remove after one green run): prove which OpenMP flavor + # reached cl.exe -- RandBLAS selects /openmp:llvm behind a guard whose + # ordering vs the root find_package(OpenMP) is under audit. + - name: report the MSVC OpenMP flavor + if: matrix.openmp + shell: pwsh + run: | + $cc = "$env:GITHUB_WORKSPACE/../RandLAPACK-windows-ci/RandLAPACK-build/compile_commands.json" + if (Test-Path $cc) { + $flags = Select-String -Path $cc -Pattern '[-/]openmp[:\w]*' -AllMatches | + ForEach-Object { $_.Matches.Value } | Sort-Object -Unique + Write-Host "OpenMP flags observed in compile_commands.json: $($flags -join ', ')" + } else { + Write-Host "compile_commands.json not found at $cc" + } + + # Staged executables must run with NO PATH preparation: this is the + # regression gate for app-local DLL staging (Windows searches the exe's + # directory first, so a staged exe needs nothing else). + - name: run a staged test executable with a stripped PATH + shell: pwsh + run: | + $exe = Get-ChildItem "$env:GITHUB_WORKSPACE/../RandLAPACK-windows-ci/RandLAPACK-build" ` + -Recurse -Filter "RandLAPACK_tests.exe" | Select-Object -First 1 + if (-not $exe) { throw "RandLAPACK_tests.exe not found in the build tree." } + $env:PATH = "C:\Windows\System32;C:\Windows" + # Starting the process at all forces the loader to resolve every + # statically imported DLL (BLAS++, LAPACK++, the BLAS backend) from + # the staged copies; listing tests proves it end to end. + & $exe.FullName --gtest_list_tests | Select-Object -First 5 | Out-Host + if ($LASTEXITCODE -ne 0) { throw "Staged executable failed with a stripped PATH (exit $LASTEXITCODE)." } diff --git a/CMake/RandLAPACKConfig.cmake.in b/CMake/RandLAPACKConfig.cmake.in index 96e773ba..8b3436c7 100644 --- a/CMake/RandLAPACKConfig.cmake.in +++ b/CMake/RandLAPACKConfig.cmake.in @@ -25,4 +25,12 @@ if (NOT lapackpp_DIR) endif() find_dependency(lapackpp) +# Runtime-DLL staging for downstream Windows executables: include the helper +# and seed the DLL directories recorded when RandLAPACK was configured, so a +# consumer can simply call randlapack_stage_runtime_dlls(). +if (NOT DEFINED RANDLAPACK_RUNTIME_DLL_DIRS OR RANDLAPACK_RUNTIME_DLL_DIRS STREQUAL "") + set(RANDLAPACK_RUNTIME_DLL_DIRS "@RandLAPACK_configured_runtime_dll_dirs@") +endif() +include("${CMAKE_CURRENT_LIST_DIR}/rl_runtime_dlls.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/RandLAPACKTargets.cmake") diff --git a/CMake/rl_config.cmake b/CMake/rl_config.cmake index 7e43e0d6..1765f392 100644 --- a/CMake/rl_config.cmake +++ b/CMake/rl_config.cmake @@ -3,6 +3,7 @@ # syntax. Native Windows backslashes would otherwise be parsed as escape # sequences when a downstream project loads RandLAPACKConfig.cmake. file(TO_CMAKE_PATH "${RandLAPACK_lapackpp_DIR}" RandLAPACK_lapackpp_DIR) +file(TO_CMAKE_PATH "${RANDLAPACK_RUNTIME_DLL_DIRS}" RandLAPACK_configured_runtime_dll_dirs) configure_file(CMake/RandLAPACKConfig.cmake.in ${CMAKE_INSTALL_LIBDIR}/cmake/RandLAPACK/RandLAPACKConfig.cmake @ONLY) @@ -13,4 +14,5 @@ configure_file(CMake/RandLAPACKConfigVersion.cmake.in install(FILES ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/RandLAPACK/RandLAPACKConfig.cmake ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/RandLAPACK/RandLAPACKConfigVersion.cmake + ${CMAKE_SOURCE_DIR}/CMake/rl_runtime_dlls.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/RandLAPACK) diff --git a/CMake/rl_runtime_dlls.cmake b/CMake/rl_runtime_dlls.cmake index c5d12311..eb687e6f 100644 --- a/CMake/rl_runtime_dlls.cmake +++ b/CMake/rl_runtime_dlls.cmake @@ -1,9 +1,20 @@ -# Native Windows builds use TARGET_RUNTIME_DLLS to stage imported shared-library -# dependencies (BLAS++/LAPACK++ DLLs) beside executables, so that test discovery -# and test runs find them without PATH edits. The generator expression requires -# CMake 3.21, which is already this project's minimum. Note this only covers -# CMake imported targets: oneMKL enters through raw library paths recorded by -# BLAS++, so the MKL bin directory must be on PATH at build and test time. +# Native Windows builds stage every runtime DLL an executable needs beside +# that executable (app-local deployment, the idiomatic Windows layout: the +# exe's own directory is the first place the loader searches). Two sources: +# +# 1. TARGET_RUNTIME_DLLS (CMake >= 3.21, this project's minimum) covers +# imported SHARED targets -- BLAS++/LAPACK++ DLLs. +# 2. RANDLAPACK_RUNTIME_DLL_DIRS covers what the generator expression +# cannot see: the BLAS backend (oneMKL, OpenBLAS, ...) enters BLAS++ as +# raw library paths, i.e. UNKNOWN imported targets, which +# TARGET_RUNTIME_DLLS documentedly ignores. The installer and CI set +# this to the backend's DLL directory; its *.dll contents are staged +# alongside each executable. +# +# With both in place, staged executables run without any PATH preparation. +set(RANDLAPACK_RUNTIME_DLL_DIRS "" CACHE STRING + "Semicolon-separated directories whose DLLs are staged beside RandLAPACK executables on Windows.") + function(randlapack_stage_runtime_dlls target) if (WIN32) add_custom_command( @@ -15,5 +26,18 @@ function(randlapack_stage_runtime_dlls target) COMMAND_EXPAND_LISTS VERBATIM ) + foreach(dll_dir IN LISTS RANDLAPACK_RUNTIME_DLL_DIRS) + file(GLOB dlls "${dll_dir}/*.dll") + if (dlls) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${dlls} + $ + VERBATIM + ) + endif() + endforeach() endif() endfunction() diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 454a3d5c..a6890a4b 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -55,6 +55,11 @@ function(add_benchmark) target_compile_options(${TGT_NAME} PRIVATE $<$:-g>) # Include directories come from target_link_libraries, no need to set manually target_link_libraries(${TGT_NAME} ${TGT_LINK_LIBS}) + # On Windows the RandLAPACK package config provides DLL staging so the + # benchmark runs without PATH preparation. + if (COMMAND randlapack_stage_runtime_dlls) + randlapack_stage_runtime_dlls(${TGT_NAME}) + endif() message(STATUS "RandLAPACK: added ${TGT_NAME} benchmark") endfunction() diff --git a/install/install.ps1 b/install/install.ps1 index 83911729..e6ca49b1 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -15,8 +15,14 @@ # Options: # -ProjectDir Where dependencies/builds/installs go # (default: ..\RandNLA-project relative to this script). -# -MklRoot Use an existing oneMKL (oneAPI installer layout) -# instead of downloading Intel's NuGet packages. +# -Backend BLAS/LAPACK backend: mkl (default; auto-discovers an +# installed oneAPI, else downloads Intel's pinned NuGet +# packages), openblas (official release binaries), or +# custom (bring your own via -BlasLibraries). +# -MklRoot Use this oneMKL install (oneAPI layout) instead of +# auto-discovery/download. Backend mkl only. +# -BlasLibraries / -LapackLibraries / -BackendBinDir / -BlasInt / -BlasFortran +# Backend custom only; see setup.ps1's header. # -Fresh Reconfigure RandLAPACK from scratch (dependencies are # always reused when present; delete \install # subdirectories to force dependency rebuilds). @@ -29,7 +35,15 @@ [CmdletBinding()] param( [string]$ProjectDir = "", + [ValidateSet("mkl", "openblas", "custom")] + [string]$Backend = "mkl", [string]$MklRoot = "", + [string]$BlasLibraries = "", + [string]$LapackLibraries = "", + [string]$BackendBinDir = "", + [ValidateSet("lp64", "ilp64")] + [string]$BlasInt = "lp64", + [string]$BlasFortran = "", # Where the dependency stack lives (default: \install). CI # points this at its shared, cached dependency directory. [string]$DependencyRoot = "", @@ -53,9 +67,35 @@ $sourceRoot = Split-Path $PSScriptRoot -Parent if (-not (Test-Path (Join-Path $sourceRoot "RandLAPACK.hh"))) { throw "install.ps1 must sit in the install\ directory of a RandLAPACK clone." } + +# ------------------------------------------------------ preflight checks ---- +# Catch every missing prerequisite up front, each with its fix, instead of +# failing later with a tool-specific error. +$preflightProblems = @() if (-not (Get-Command "cl.exe" -ErrorAction SilentlyContinue)) { - throw "cl.exe is not on PATH. Run from an MSVC developer prompt (Developer PowerShell for VS 2022)." + $preflightProblems += ("cl.exe (the MSVC compiler) is not on PATH. Open 'Developer PowerShell " + + "for VS 2022' from the Start menu and run this script there. If Visual Studio is not " + + "installed:`n winget install Microsoft.VisualStudio.2022.Community --override " + + '"--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended"') +} +foreach ($tool in @( + @{ Name = "cmake.exe"; Hint = "CMake ships with the Visual Studio C++ workload; a Developer PowerShell puts it on PATH." }, + @{ Name = "ninja.exe"; Hint = "Ninja ships with the Visual Studio C++ workload; a Developer PowerShell puts it on PATH." }, + @{ Name = "git.exe"; Hint = "Install Git:`n winget install Git.Git" }, + @{ Name = "curl.exe"; Hint = "curl.exe ships with Windows 10 (1803+) in System32; check your PATH includes it." })) { + if (-not (Get-Command $tool.Name -ErrorAction SilentlyContinue)) { + $preflightProblems += "$($tool.Name) is not on PATH. $($tool.Hint)" + } +} +if ($preflightProblems.Count -gt 0) { + $preflightProblems | ForEach-Object { Write-Host "PREREQUISITE MISSING: $_`n" } + throw "Missing prerequisites ($($preflightProblems.Count)); see the messages above." } +if ($ProjectDir -ne "" -and $ProjectDir.Length -gt 150) { + Write-Warning ("-ProjectDir is $($ProjectDir.Length) characters long; deep dependency build " + + "paths may exceed Windows' 260-character limit. Prefer a shorter location.") +} + if (-not (Test-Path (Join-Path $sourceRoot "RandBLAS\CMakeLists.txt"))) { Write-Host "Initializing the RandBLAS submodule..." Invoke-Checked "git" @("-C", $sourceRoot, "submodule", "update", "--init", "--recursive") @@ -80,7 +120,9 @@ Write-Host "" # Step 1: dependencies (idempotent; reused when already present). & (Join-Path $sourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` - -DependencyRoot $dependencyRoot -MklRoot $MklRoot + -DependencyRoot $dependencyRoot -Backend $Backend -MklRoot $MklRoot ` + -BlasLibraries $BlasLibraries -LapackLibraries $LapackLibraries ` + -BackendBinDir $BackendBinDir -BlasInt $BlasInt -BlasFortran $BlasFortran # Step 2: RandLAPACK itself. if ($Fresh -and (Test-Path $buildDir)) { @@ -88,6 +130,7 @@ if ($Fresh -and (Test-Path $buildDir)) { Remove-Item -Recurse -Force $buildDir } +$stageDllDirs = if ($env:RANDNLA_BLAS_BIN) { $env:RANDNLA_BLAS_BIN.Replace('\', '/') } else { "" } Invoke-Checked "cmake" @( "-S", $sourceRoot, "-B", $buildDir, "-G", "Ninja", @@ -97,6 +140,7 @@ Invoke-Checked "cmake" @( "-Dlapackpp_DIR=$env:lapackpp_DIR", "-DRandom123_DIR=$env:Random123_DIR", "-DCMAKE_PREFIX_PATH=$env:googletest_PREFIX", + "-DRANDLAPACK_RUNTIME_DLL_DIRS=$stageDllDirs", "-DCMAKE_DISABLE_FIND_PACKAGE_OpenMP=TRUE") Invoke-Checked "cmake" @("--build", $buildDir, "--target", "install") @@ -114,4 +158,10 @@ Write-Host " blaspp_DIR: $env:blaspp_DIR" Write-Host " lapackpp_DIR: $env:lapackpp_DIR" Write-Host " Random123_DIR: $env:Random123_DIR" Write-Host "" -Write-Host "Keep $env:MKL_BIN on PATH when running executables that link MKL." +if ($env:RANDNLA_BLAS_BIN) { + Write-Host "Runtime DLLs from $env:RANDNLA_BLAS_BIN are staged next to RandLAPACK's" + Write-Host "test and benchmark executables automatically -- no PATH changes needed." + Write-Host "For your own executables: find_package(RandLAPACK), then call" + Write-Host "randlapack_stage_runtime_dlls() in your CMakeLists, or copy" + Write-Host "the DLLs from that directory beside your .exe." +} From 07840893a4d02851764f1f8ad2a62999e6e05d11 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Mon, 10 Aug 2026 16:13:15 -0700 Subject: [PATCH 04/20] Windows docs: dedicated INSTALL_WINDOWS.md; INSTALL.md/INSTALL_SCRIPT.md/CI.md refresh INSTALL_WINDOWS.md is a self-contained non-expert guide: quick start (winget + Developer PowerShell), an explicit how-Windows-differs-from-Linux/macOS section (package sourcing, per-session compiler, app-local DLL staging vs RPATH, backend defaults), backend choice table, full install.ps1 reference with worked examples incl. AOCL bring-your-own, runtime-DLL explanation with the downstream staging helper, and troubleshooting. INSTALL.md section 6 shrinks to a pointer and section 0 gains the MSVC/Ninja requirement; INSTALL_SCRIPT.md gets a Windows call-out and the correct CMake floor (3.21); docs/CI.md documents the new Windows matrix, the staging proof step, and the manual cache-revision policy with the hashFiles trap. --- INSTALL.md | 47 +++---------- INSTALL_SCRIPT.md | 8 ++- INSTALL_WINDOWS.md | 167 +++++++++++++++++++++++++++++++++++++++++++++ docs/CI.md | 40 +++++++---- 4 files changed, 213 insertions(+), 49 deletions(-) create mode 100644 INSTALL_WINDOWS.md diff --git a/INSTALL.md b/INSTALL.md index cd3da40b..fef40c4a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -20,6 +20,9 @@ of the corresponding instructions in Section 1. - GCC 11 or higher - Clang 14 or higher (not extensively tested) - Intel ICPX (has known issues, see GitHub issue #91) + - MSVC (Visual Studio 2022) on native Windows, with Ninja as the build + tool (both bundled with the "Desktop development with C++" workload) -- + see [INSTALL_WINDOWS.md](INSTALL_WINDOWS.md) ### GPU Support (Optional) For GPU/CUDA support (enabled with `-DRequireCUDA=ON`), you need: @@ -301,43 +304,15 @@ We do that so that it's easier to infer a valid choice of directory structure fo ## 6. Native Windows (MSVC) -RandLAPACK builds natively on Windows with MSVC (Visual Studio 2022) and -Intel oneMKL using ILP64, sequential linking. The easy path, from a -"Developer PowerShell for VS 2022" prompt in the repository root: +RandLAPACK builds natively on Windows with MSVC (Visual Studio 2022). The +full guide -- prerequisites, the one-command installer, BLAS/LAPACK backend +choice (oneMKL default, OpenBLAS, bring-your-own), runtime-DLL handling, and +how the Windows install differs from Linux/macOS -- lives in +[INSTALL_WINDOWS.md](INSTALL_WINDOWS.md). The short version, from a +"Developer PowerShell for VS 2022" prompt: ```powershell +git clone --recursive https://github.com/BallisticLA/RandLAPACK.git +cd RandLAPACK .\install\install.ps1 ``` - -This builds all dependencies (oneMKL is downloaded from Intel's official -NuGet packages, or pass `-MklRoot ` to use an existing oneAPI -install), then builds, installs, and tests RandLAPACK, mirroring -`install.sh`'s directory layout. The header comment in `install.ps1` -documents the options. - -Points worth knowing: - -- **No vcpkg (or any package manager) is required.** oneMKL comes from - Intel's official NuGet packages (`intelmkl.devel.win-x64` + - `intelmkl.redist.win-x64`), downloaded as plain zip archives from - nuget.org, pinned by version and SHA256, and arranged into the standard - oneAPI layout. Pass `-MklRoot ` to use an existing oneAPI install - instead. -- **ILP64 is required.** RandBLAS's MKL sparse backend checks at compile time - that its 64-bit indices match `MKL_INT`, so BLAS++ must be configured with - `-Dblas_int=ilp64` against `mkl_intel_ilp64_dll.lib` (the installer does - this for you). -- **BLAS++/LAPACK++ come from BallisticLA branches** (`BallisticLA/blaspp@remove-symv-debug-print`, - `BallisticLA/lapackpp@msvc-direct-includes`) until two one-line MSVC fixes - merge upstream (blaspp PR #132, lapackpp PR #87). -- **OpenMP on MSVC uses the `/openmp:llvm` runtime**, selected automatically - by RandBLAS's build system — the only MSVC mode that accepts RandLAPACK's - 64-bit loop indices and `collapse` clauses. Serial builds (OpenMP off) are - also fully functional. -- **GPU support is not available on native Windows.** -- Executables that link MKL need the MKL `bin` directory on `PATH` at run - time; the installer prints the path to keep. - -CI runs this configuration on every pull request (`core-windows` workflow); -`.github/scripts/windows/run-ci.ps1 -Task Core -SetupDependencies` reproduces -that job locally. diff --git a/INSTALL_SCRIPT.md b/INSTALL_SCRIPT.md index 866a602b..15f943f2 100644 --- a/INSTALL_SCRIPT.md +++ b/INSTALL_SCRIPT.md @@ -12,6 +12,11 @@ single command. a quick, streamlined setup process. If you need fine-grained control over dependency configurations, refer to RandLAPACK's `INSTALL.md` instead. +> **Windows users:** this document describes the Linux/macOS installer +> (`install.sh`). The native Windows companion is `install\install.ps1`, +> documented in [INSTALL_WINDOWS.md](INSTALL_WINDOWS.md) -- prerequisites, +> every option, backend selection, and troubleshooting. + ## 0. Software Requirements Before running the install script, ensure you have the following software @@ -19,7 +24,8 @@ available on your system: ### Essential Requirements * **C++ Compiler:** GNU GCC 13.3.0 or higher (required for C++20 features) -* **CMake:** Version 3.27 or higher +* **CMake:** Version 3.21 or higher (the project's CMake floor; recent + releases recommended) * **BLAS/LAPACK Library:** Intel MKL 2022 or higher recommended * **GoogleTest:** (Optional but recommended) For running RandLAPACK tests diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md new file mode 100644 index 00000000..c2c344b0 --- /dev/null +++ b/INSTALL_WINDOWS.md @@ -0,0 +1,167 @@ +# Installing RandLAPACK on native Windows + +This guide covers building RandLAPACK with Microsoft's Visual C++ compiler +(MSVC) on Windows -- no WSL, no Cygwin. It assumes no prior Windows +development experience. If you are on Linux or macOS, use +[INSTALL.md](INSTALL.md) and `install.sh` instead. + +## 1. Quick start + +Install the two prerequisites (skip any you already have), from a regular +PowerShell window: + +```powershell +winget install Git.Git +winget install Microsoft.VisualStudio.2022.Community --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended" +``` + +Then open **"Developer PowerShell for VS 2022"** from the Start menu (this is +important -- see section 3), and run: + +```powershell +git clone --recursive https://github.com/BallisticLA/RandLAPACK.git +cd RandLAPACK +.\install\install.ps1 +``` + +That is the whole install. The script downloads a pinned, checksum-verified +copy of Intel oneMKL, builds the remaining dependencies, builds RandLAPACK, +and runs its test suite. Everything lands in a sibling `RandNLA-project\` +directory; re-running the script reuses what is already built. + +## 2. How this differs from Linux and macOS + +If you are used to Unix systems, four things explain nearly everything this +installer does differently: + +| | Linux / macOS | Windows | +|---|---|---| +| Getting BLAS/LAPACK | one package-manager command (`apt install libopenblas-dev`, `brew install openblas`) | no system package manager for libraries; the installer downloads pinned, SHA256-verified binaries directly from the vendor (Intel's NuGet packages, OpenBLAS's release archives) | +| Where the compiler lives | `gcc`/`clang` always on PATH | MSVC (`cl.exe`) exists only inside a "Developer" shell that Visual Studio sets up per session | +| Finding shared libraries at run time | the binary itself remembers where its libraries are (RPATH), plus system-wide loader paths | executables have no such memory; Windows searches the executable's **own directory first** and PATH **last**, so the installer copies ("stages") every needed DLL next to each executable | +| Default BLAS backend | OpenBLAS (Linux CI), Accelerate (macOS CI) | Intel oneMKL, ILP64 sequential (fastest on typical Windows x64 machines, and enables RandBLAS's MKL-accelerated sparse routines) | + +Two smaller differences: OpenMP with MSVC needs the `/openmp:llvm` runtime +(the only MSVC mode that accepts RandLAPACK's 64-bit loop indices), which the +build selects automatically; and GPU support is not available on native +Windows. The build tool is Ninja everywhere, which Visual Studio bundles. + +The practical consequence of the third row is worth internalizing: **on +Windows you never need to edit PATH for RandLAPACK**. If an executable is +staged, it runs; if you write your own program against RandLAPACK, either +call the provided staging helper (section 6) or copy the DLLs next to your +`.exe`. This "app-local deployment" is the idiomatic Windows layout -- it is +what Visual Studio's own package manager does by default. + +## 3. Prerequisites, in detail + +- **Visual Studio 2022** (Community is free; Build Tools also works) with the + **"Desktop development with C++"** workload. That workload includes MSVC, + the Windows SDK, CMake, and Ninja -- you do not install those separately. +- **Git** (any recent version). +- A network connection for the first run (dependency downloads; roughly + 200 MB for the default backend). + +The installer checks all of this up front and prints a fix-it command for +anything missing. The most common mistake is running from a *regular* +PowerShell: `cl.exe`, `cmake`, and `ninja` are only on PATH inside +**Developer PowerShell for VS 2022** (Start menu, or "Developer Command +Prompt" if you prefer cmd). + +## 4. Choosing a BLAS/LAPACK backend + +RandLAPACK does all its heavy arithmetic through BLAS++/LAPACK++, which sit +on a BLAS/LAPACK library of your choice. On Windows the installer supports: + +| Backend | Flag | What you get | How it is obtained | +|---|---|---|---| +| **oneMKL** (default) | none needed | fastest option on most x64 CPUs; 64-bit integers (ILP64); MKL-accelerated sparse routines in RandBLAS | an already-installed oneAPI is discovered automatically (via `MKLROOT`, `ONEAPI_ROOT`, or the default install location); otherwise Intel's official NuGet packages are downloaded, pinned by version and SHA256 | +| **OpenBLAS** | `-Backend openblas` | solid free backend; 32-bit integers (LP64); RandBLAS's portable sparse fallbacks replace the MKL-only accelerations | official OpenBLAS release binaries, pinned and checksum-verified; the archive is self-contained and includes full LAPACK | +| **Custom / bring-your-own** | `-Backend custom -BlasLibraries ` | anything BLAS++/LAPACK++ can link -- e.g. AMD AOCL, a local ILP64 OpenBLAS build | you provide the import libraries (and their DLL directory via `-BackendBinDir`); the installer verifies them with a small link-and-run check before building anything | + +Notes for the custom path: AMD AOCL downloads sit behind a click-through +license page, so the installer cannot fetch them -- download AOCL yourself, +then point `-BlasLibraries` at its `.lib` files. The custom path is expected +to work with any well-formed backend but is not exercised by our CI; oneMKL +and OpenBLAS are. + +## 5. `install.ps1` reference + +``` +-ProjectDir Where dependencies/builds/installs go + (default: ..\RandNLA-project next to the clone). +-Backend mkl (default) | openblas | custom. +-MklRoot Use this specific oneMKL install (oneAPI layout); + skips discovery and download. Backend mkl only. +-BlasLibraries Backend custom: semicolon-separated .lib paths. +-LapackLibraries

Backend custom: LAPACK .lib paths, if separate from BLAS. +-BackendBinDir Backend custom: directory holding the backend's DLLs. +-BlasInt lp64|ilp64 Backend custom: the library's integer width (default lp64). +-BlasFortran Backend custom: BLAS++ name-mangling hint (e.g. "add"). +-DependencyRoot

Where the dependency stack lives (default: \install). +-Fresh Reconfigure RandLAPACK from scratch (dependencies are + still reused; delete \install subdirectories + to force dependency rebuilds). +-SkipTests Skip the test suite. +``` + +Worked examples: + +```powershell +# Default: oneMKL, discovered or downloaded. +.\install\install.ps1 + +# OpenBLAS instead of MKL. +.\install\install.ps1 -Backend openblas + +# You already installed the oneAPI toolkit somewhere non-standard. +.\install\install.ps1 -MklRoot "D:\intel\oneAPI\mkl\latest" + +# AMD AOCL, downloaded manually beforehand. +.\install\install.ps1 -Backend custom ` + -BlasLibraries "C:\AOCL\lib\AOCL-LibBlis-Win-MT-dll.lib;C:\AOCL\lib\AOCL-LibFlame-Win-MT-dll.lib" ` + -BackendBinDir "C:\AOCL\bin" +``` + +## 6. Runtime DLLs: what "staging" means + +A Windows executable that links a DLL-based library must be able to find +those DLLs when it starts. Windows looks in the executable's own directory +first and PATH last; there is no RPATH. RandLAPACK's build therefore copies +every DLL an executable needs (BLAS++, LAPACK++, and the BLAS backend's +runtime) into the directory of that executable -- tests and benchmarks work +out of the box, from any shell, with no environment preparation. + +For your own project, the installed CMake package carries the same helper: + +```cmake +find_package(RandLAPACK REQUIRED) +add_executable(myprog main.cc) +target_link_libraries(myprog RandLAPACK) +randlapack_stage_runtime_dlls(myprog) # no-op on non-Windows platforms +``` + +If you prefer not to use the helper, copy the DLLs from the backend's `bin` +directory (the installer prints it at the end) next to your `.exe`. + +## 7. Troubleshooting + +- **"cl.exe is not on PATH"**: you are in a regular shell. Open "Developer + PowerShell for VS 2022" and re-run. If Visual Studio is missing entirely, + the preflight message includes the winget install command. +- **A download fails with a hash mismatch**: the pinned artifact changed + upstream or the download was corrupted. Re-run once; if it persists, open + an issue -- do not bypass the check. +- **Corporate proxy**: the installer downloads with `curl.exe`, which honors + the standard `HTTPS_PROXY` environment variable. +- **First build is slow**: Windows Defender real-time scanning inspects + every new object file. The build is CPU-bound regardless; subsequent + reruns reuse everything. +- **Path-length errors deep in dependency builds**: keep the project + directory short (e.g. `C:\s\RandLAPACK`), or enable Windows long paths. +- **Full reset**: delete `\build` and the relevant + `\install\*` subdirectories, then re-run the installer. + +CI runs the exact flows above on every pull request; `docs/CI.md` describes +the lanes, and `.github\scripts\windows\run-ci.ps1 -Task Core +-SetupDependencies [-Backend openblas]` reproduces a CI lane locally. diff --git a/docs/CI.md b/docs/CI.md index cd3bd0ab..b8482d6d 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -12,7 +12,7 @@ the thing we tell users to type is itself under test). | core-linux | `build-asan` | ubuntu-latest | Debug + AddressSanitizer build and test run | no | | core-macos | `build` | macos-latest | same as Linux `build`, on Apple's toolchain | **yes** (`build`) | | core-macos | `build-asan` | macos-latest | Debug + AddressSanitizer build and test run | no | -| core-windows | `build-windows` | windows-2022 | MSVC + oneMKL (ILP64, sequential) build + tests, serial (no OpenMP) | no (new) | +| core-windows | `build-windows` | windows-2022 | MSVC build + tests: oneMKL ILP64 serial, oneMKL ILP64 OpenMP (`/openmp:llvm`), OpenBLAS LP64 serial; each leg ends with a stripped-PATH run of a staged test executable | no (new) | | install-script | `install-linux` | ubuntu-latest | `install.sh`: fresh install, idempotent re-run, dependency-discovery path | **yes** | | install-script | `install-macos` | macos-latest | `install.sh`: fresh install, idempotent re-run | **yes** | | install-script | `install-windows` | windows-2022 | `install/install.ps1`: fresh install, idempotent re-run | no (new) | @@ -57,10 +57,21 @@ candidates once they have a green track record. tested by RandBLAS's CI; rebuilding its ~450 tests in every RandLAPACK job roughly doubled job times. The install-script lanes keep them, preserving exactly what a user's install builds. -- **Windows builds are serial (OpenMP off) for now.** RandLAPACK's OpenMP - loops need MSVC's `/openmp:llvm` runtime (64-bit indices, `collapse`), - which RandBLAS's build system does not select yet; see BallisticLA/RandBLAS#184. - When that lands, core-windows grows an OpenMP leg. +- **The Windows matrix is deliberately small** (CI-cost budget): the two + mkl legs cover the default backend serial + OpenMP, and ONE openblas leg + covers non-MKL provisioning and the LP64 build. There is no + openblas-openmp leg (OpenMP x MSVC is backend-orthogonal and covered by + mkl-openmp), no second install-script backend leg (the installer's + `-Backend` forwarding is thin; provisioning is covered by the openblas + core leg), and no Windows ASan lane. +- **Windows executables are staged, not PATH-dependent.** The BLAS backend + enters BLAS++ as raw library paths, which `TARGET_RUNTIME_DLLS` cannot + see, so `RANDLAPACK_RUNTIME_DLL_DIRS` stages the backend DLLs beside every + test/benchmark executable (app-local deployment). The stripped-PATH CI + step is the regression gate; do not remove it. An internal process-PATH + prepend remains in run-ci.ps1/install.ps1 only for the RandBLAS + submodule's own test executables, until RandBLAS gains the same staging + (planned follow-up). - **BLAS++ and LAPACK++ on Windows come from BallisticLA fork branches** (`BallisticLA/blaspp@remove-symv-debug-print`, `BallisticLA/lapackpp@msvc-direct-includes`) carrying two one-line MSVC @@ -78,11 +89,16 @@ slower than a warm one. |------------------|---------|--------------| | `core-deps--v` | core-linux, core-macos (both jobs) | the workspace (`../*-install`) | | `installer-deps--v` | install-linux | `deps-install/` in the workspace | -| `windows-*-v` (five keys) | core-windows, install-windows | `..\windows-deps` | +| `windows-*-r` (per backend + shared) | core-windows, install-windows | `..\windows-deps` | -To force a rebuild against fresh upstream clones, bump the `-v` suffix in -the relevant key. The Windows keys are additionally salted with a hash of -`setup.ps1`, so editing that script invalidates them automatically. +To force a rebuild against fresh upstream clones, bump the `-v`/`-r` suffix +in the relevant key. The Windows keys use MANUAL revision literals only -- +never `hashFiles()`: that helper resolves paths relative to the workspace +root, and the install-script workflow checks the repo out under +`RandLAPACK\`, where the glob matches nothing and `hashFiles()` silently +returns an empty string. The result was two workflows reading and writing +*different* caches while appearing to share one. When a recipe in +`setup.ps1` changes, bump the `-r` on the affected keys in `action.yml`. BLAS++/LAPACK++ track upstream default branches, so a stale cache also means frozen upstream — bump the suffix when upstream matters. @@ -92,6 +108,6 @@ means frozen upstream — bump the suffix when upstream matters. ordinary `cmake` + `make` invocations. - Installer lanes: `bash install.sh --yes --no-gpu` from a fresh clone. - Windows (from an MSVC developer prompt in the repository root): - `.github\scripts\windows\run-ci.ps1 -Task Core -SetupDependencies` - reproduces core-windows; `.\install\install.ps1` reproduces - install-windows. + `.github\scripts\windows\run-ci.ps1 -Task Core -SetupDependencies + [-Backend openblas]` reproduces a core-windows leg; `.\install\install.ps1` + reproduces install-windows. From 54991c629f30702e32a6f19286e5b16aead60bd9 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Mon, 10 Aug 2026 16:23:18 -0700 Subject: [PATCH 05/20] Windows CI: honor /openmp:llvm in RandLAPACK's own OpenMP lookup; fix the stripped-PATH proof step The temporary CI diagnostic confirmed classic -openmp was reaching cl.exe: the root find_package(OpenMP) runs before RandBLAS's /openmp:llvm guard, so the classic flavor got cached and MSVC silently ignored the collapse clause (warning C4849) in rl_rpchol. Mirror RandBLAS's guard in rl_build_options.cmake and the benchmark project. The stripped-PATH staging proof failed on its own harness: piping the native process into Select-Object -First terminates the pipeline early, killing the process before it exits and leaving LASTEXITCODE unset. Capture the full output, then truncate for display. --- .github/workflows/core-windows.yaml | 12 +++++++++--- CMake/rl_build_options.cmake | 24 ++++++++++++++++++++++++ benchmark/CMakeLists.txt | 15 +++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 3e6e7d3c..21c9a86d 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -85,6 +85,12 @@ jobs: $env:PATH = "C:\Windows\System32;C:\Windows" # Starting the process at all forces the loader to resolve every # statically imported DLL (BLAS++, LAPACK++, the BLAS backend) from - # the staged copies; listing tests proves it end to end. - & $exe.FullName --gtest_list_tests | Select-Object -First 5 | Out-Host - if ($LASTEXITCODE -ne 0) { throw "Staged executable failed with a stripped PATH (exit $LASTEXITCODE)." } + # the staged copies; listing tests proves it end to end. Capture + # the full output first -- an early-terminating pipeline (e.g. + # Select-Object -First) would kill the process before it exits and + # leave $LASTEXITCODE unset. + $output = & $exe.FullName --gtest_list_tests 2>&1 + $exit = $LASTEXITCODE + $output | Select-Object -First 5 | Out-Host + Write-Host "... ($($output.Count) lines total, exit code $exit)" + if ($exit -ne 0) { throw "Staged executable failed with a stripped PATH (exit $exit)." } diff --git a/CMake/rl_build_options.cmake b/CMake/rl_build_options.cmake index 65734973..4f80eae6 100644 --- a/CMake/rl_build_options.cmake +++ b/CMake/rl_build_options.cmake @@ -12,8 +12,32 @@ endif() set(SANITIZE_ADDRESS OFF CACHE BOOL "Add address sanitizer flags to the library") message(STATUS "Checking for OpenMP ... ") + +# This find_package runs BEFORE add_subdirectory(RandBLAS), so the guard in +# RandBLAS/CMake/OpenMP.cmake ("if NOT DEFINED OpenMP_CXX_FLAGS") never +# fires: whatever flavor is cached here is what every target gets. MSVC's +# classic /openmp implements OpenMP 2.0 only and silently *ignores* the +# collapse clause (warning C4849) that rl_rpchol relies on; /openmp:llvm +# supports 64-bit loop indices and collapse. Mirror RandBLAS's guard here. +# Callers can still override with -DOpenMP_CXX_FLAGS=... at configure time. +if (MSVC AND NOT DEFINED OpenMP_CXX_FLAGS) + set(OpenMP_CXX_FLAGS "/openmp:llvm" CACHE STRING + "OpenMP compiler flags for C++") +endif() +if (MSVC) + set(RandLAPACK_OpenMP_MSVC_FLAGS "${OpenMP_CXX_FLAGS}") +endif() + find_package(OpenMP COMPONENTS CXX) +# FindOpenMP may replace OpenMP_CXX_FLAGS while probing the compiler. Ensure +# the imported target carries the requested MSVC mode. +if (MSVC AND OpenMP_CXX_FOUND AND TARGET OpenMP::OpenMP_CXX) + set_property(TARGET OpenMP::OpenMP_CXX PROPERTY + INTERFACE_COMPILE_OPTIONS "${RandLAPACK_OpenMP_MSVC_FLAGS}") + set(OpenMP_CXX_FLAGS "${RandLAPACK_OpenMP_MSVC_FLAGS}") +endif() + set(tmp FALSE) if (OpenMP_CXX_FOUND) set(tmp TRUE) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index a6890a4b..760f276d 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -10,7 +10,22 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(GNUInstallDirs) message(STATUS "Checking for OpenMP ... ") +# Same MSVC OpenMP-flavor guard as CMake/rl_build_options.cmake: classic +# /openmp (OpenMP 2.0) ignores the collapse clause; /openmp:llvm is the +# mode RandLAPACK's loops need. +if (MSVC AND NOT DEFINED OpenMP_CXX_FLAGS) + set(OpenMP_CXX_FLAGS "/openmp:llvm" CACHE STRING + "OpenMP compiler flags for C++") +endif() +if (MSVC) + set(benchmark_OpenMP_MSVC_FLAGS "${OpenMP_CXX_FLAGS}") +endif() find_package(OpenMP COMPONENTS CXX) +if (MSVC AND OpenMP_CXX_FOUND AND TARGET OpenMP::OpenMP_CXX) + set_property(TARGET OpenMP::OpenMP_CXX PROPERTY + INTERFACE_COMPILE_OPTIONS "${benchmark_OpenMP_MSVC_FLAGS}") + set(OpenMP_CXX_FLAGS "${benchmark_OpenMP_MSVC_FLAGS}") +endif() set(tmp FALSE) if (OpenMP_CXX_FOUND) set(tmp TRUE) From 05477329c0983d9e49f08c397f7f0b412e6f2294 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Tue, 11 Aug 2026 08:53:43 -0700 Subject: [PATCH 06/20] Windows CI: drop the temporary OpenMP diagnostic; record its finding in docs/CI.md The diagnostic confirmed classic -openmp was cached before the fix and /openmp:llvm after it; the resolution now lives in rl_build_options.cmake with the rationale documented in docs/CI.md. --- .github/workflows/core-windows.yaml | 16 ---------------- docs/CI.md | 7 +++++++ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 21c9a86d..a4dd82fa 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -57,22 +57,6 @@ jobs: -Backend '${{ matrix.backend }}' ` -OpenMP:$openmp - # TEMPORARY (remove after one green run): prove which OpenMP flavor - # reached cl.exe -- RandBLAS selects /openmp:llvm behind a guard whose - # ordering vs the root find_package(OpenMP) is under audit. - - name: report the MSVC OpenMP flavor - if: matrix.openmp - shell: pwsh - run: | - $cc = "$env:GITHUB_WORKSPACE/../RandLAPACK-windows-ci/RandLAPACK-build/compile_commands.json" - if (Test-Path $cc) { - $flags = Select-String -Path $cc -Pattern '[-/]openmp[:\w]*' -AllMatches | - ForEach-Object { $_.Matches.Value } | Sort-Object -Unique - Write-Host "OpenMP flags observed in compile_commands.json: $($flags -join ', ')" - } else { - Write-Host "compile_commands.json not found at $cc" - } - # Staged executables must run with NO PATH preparation: this is the # regression gate for app-local DLL staging (Windows searches the exe's # directory first, so a staged exe needs nothing else). diff --git a/docs/CI.md b/docs/CI.md index b8482d6d..149f3e9f 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -64,6 +64,13 @@ candidates once they have a green track record. mkl-openmp), no second install-script backend leg (the installer's `-Backend` forwarding is thin; provisioning is covered by the openblas core leg), and no Windows ASan lane. +- **The MSVC OpenMP flavor is forced to `/openmp:llvm` in RandLAPACK's own + CMake** (`CMake/rl_build_options.cmake` and the benchmark project), not + just in RandBLAS. RandLAPACK's `find_package(OpenMP)` runs before the + submodule's guard, and a 2026-08 CI diagnostic proved classic `-openmp` + was being cached -- under which MSVC silently ignores the `collapse` + clause (warning C4849) that `rl_rpchol` relies on, i.e. the "openmp" leg + was not testing what its name claimed. - **Windows executables are staged, not PATH-dependent.** The BLAS backend enters BLAS++ as raw library paths, which `TARGET_RUNTIME_DLLS` cannot see, so `RANDLAPACK_RUNTIME_DLL_DIRS` stages the backend DLLs beside every From e8b817b3e6e0a408ded2fcb30f7899738486651c Mon Sep 17 00:00:00 2001 From: mmelnich Date: Tue, 11 Aug 2026 09:42:52 -0700 Subject: [PATCH 07/20] INSTALL_WINDOWS.md: troubleshooting note on conda DLL shadowing and conda-as-MklRoot Conda environments carry MKL/OpenBLAS DLLs under identical filenames and prepend Library/bin to PATH on activation; staged executables are immune by construction, but downstream programs that skip staging can be shadowed. Also documents the deliberate opt-in route: -MklRoot /Library against conda's mkl-devel. --- INSTALL_WINDOWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index c2c344b0..768b591a 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -157,6 +157,14 @@ directory (the installer prints it at the end) next to your `.exe`. - **First build is slow**: Windows Defender real-time scanning inspects every new object file. The build is CPU-bound regardless; subsequent reruns reuse everything. +- **Wrong results or crashes only inside an activated conda environment**: + conda ships MKL/OpenBLAS DLLs under the *same filenames* as ours and puts + its `Library\bin` on PATH when an environment is active. RandLAPACK's own + staged executables are immune (the exe's folder outranks PATH), but a + program of yours that relies on PATH can silently pick up conda's copies. + Stage your executable (section 6) and the problem disappears. To + deliberately build *against* a conda-provided MKL instead, pass + `-MklRoot "\Library"` (requires conda's `mkl-devel` package). - **Path-length errors deep in dependency builds**: keep the project directory short (e.g. `C:\s\RandLAPACK`), or enable Windows long paths. - **Full reset**: delete `\build` and the relevant From e3d15bb3d5c615f3249219eed35203df4211de64 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 10:42:42 -0700 Subject: [PATCH 08/20] Windows: reject non-x64 toolchains, make backend provisioning explicit A collaborator's install failed with BLAS++ reporting "BLAS library not found" while oneMKL was correctly installed and correctly discovered. The libraries were never the problem: his log shows bin/Hostx86/x86/cl.exe, a 32-bit compiler, and a 32-bit linker cannot use an x64 import library. Our own documentation caused it. INSTALL_WINDOWS.md said to open "Developer PowerShell for VS 2022"; that shortcut targets SysWOW64 (32-bit PowerShell) and Enter-VsDevShell defaults to an x86 toolchain. "Developer Command Prompt for VS 2022" is a 64-bit process that also defaults to x86, so shell bitness is not a usable signal. CI never caught this because every Windows leg initializes MSVC with an explicit arch: x64 under pwsh, so it has never run the shell the docs prescribe. Architecture guard: - install.ps1 preflight and setup.ps1 both reject a non-x64 toolchain, with different messages for x86 (wrong shell, one-command fix) and arm64/arm (unsupported: no oneMKL build exists for them). - Detection reads VSCMD_ARG_TGT_ARCH, then the bin\Host\\ layout, then cl.exe's banner. The banner alone would miss on a localized Visual Studio, and a missed detection fails open, defeating the check. - Test-BlasLinkage now also runs for the mkl backend. It previously covered only openblas and custom, which is why the default backend had no early diagnostic and failed three layers down instead. Backend provisioning is now explicit rather than implicit: - oneMKL is discovered first (-MklRoot, MKLROOT, ONEAPI_ROOT, default oneAPI path). When none is found the installer explains what it looked for and what it would download, then asks before fetching anything. - -NoDownload turns "not found" into an error; -Yes skips questions. - Prompts are gated on stdin being a terminal, mirroring install.sh's INTERACTIVE flag, so a question can never block a CI job. CI also passes -Yes explicitly. - Non-interactive default stays yes, preserving one-command installs. CI coverage for the documented user path: - windows-guard-logic drives the architecture decision over a table (x64/amd64/x86/arm64/arm). This is the only way to cover arm64 and arm, since we cannot build for them and no build leg could ever test them. It also asserts install.ps1's and setup.ps1's duplicated copies agree. - windows-toolchain-guards runs the real installer under a real x86 toolchain and an amd64_arm64 cross-compiling one, which yields an arm64-targeting cl.exe on ordinary x64 runners. Both assert a refusal, so neither builds a dependency and both finish in seconds. Docs: INSTALL_WINDOWS.md names the x64 Native Tools prompt, explains the execution-policy flag (stock Windows is Restricted), and documents the discovery order; docs/CI.md records the rationale for the guard jobs and the provisioning policy. Verified on a Windows 11 machine that began with no Visual Studio, no CMake, no Git and no MKL: full install 745/745 tests passing via both the oneMKL and OpenBLAS backends, the x86 and declined-download paths refused correctly, and a mutation test confirming the guard table catches a deliberately broken copy. --- .../setup-randlapack-deps-windows/action.yml | 4 + .../setup-randlapack-deps-windows/setup.ps1 | 217 +++++++++++++++++- .github/scripts/windows/run-ci.ps1 | 5 +- .../scripts/windows/test-toolchain-guard.ps1 | 114 +++++++++ .github/workflows/core-windows.yaml | 62 +++++ INSTALL.md | 10 +- INSTALL_WINDOWS.md | 116 ++++++++-- docs/CI.md | 51 ++++ install/install.ps1 | 96 +++++++- 9 files changed, 636 insertions(+), 39 deletions(-) create mode 100644 .github/scripts/windows/test-toolchain-guard.ps1 diff --git a/.github/actions/setup-randlapack-deps-windows/action.yml b/.github/actions/setup-randlapack-deps-windows/action.yml index 8c211187..ddba4bb5 100644 --- a/.github/actions/setup-randlapack-deps-windows/action.yml +++ b/.github/actions/setup-randlapack-deps-windows/action.yml @@ -76,7 +76,11 @@ runs: shell: pwsh run: | $sanitizeAddress = '${{ inputs.sanitize-address }}' -eq 'true' + # -Yes: never prompt. Prompts are already skipped when stdin is not + # a terminal, but stating it makes the intent explicit and survives + # any future change to that detection. & "$env:GITHUB_ACTION_PATH/setup.ps1" ` -DependencyRoot "${env:GITHUB_WORKSPACE}\..\windows-deps" ` -Backend '${{ inputs.blas-backend }}' ` + -Yes ` -SanitizeAddress:$sanitizeAddress diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 7df67bbb..832d061e 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -22,13 +22,29 @@ param( [string]$DependencyRoot, # BLAS/LAPACK backend. "mkl" (default): oneMKL, ILP64 + sequential, - # auto-discovered from an installed oneAPI or downloaded from Intel's - # NuGet packages. "openblas": official OpenBLAS release binaries, LP64. + # discovered from an installed oneAPI, else downloaded (see -NoDownload). + # "openblas": official OpenBLAS release binaries, LP64. # "custom": bring your own libraries via -BlasLibraries (anything # BLAS++/LAPACK++ can link, e.g. AMD AOCL). [ValidateSet("mkl", "openblas", "custom")] [string]$Backend = "mkl", + # Refuse to download a backend that was not found locally, and fail + # instead. The default is to download, which is the ordinary Windows + # practice (no system prefix exists for third-party libraries, so + # per-project acquisition via vcpkg/NuGet/release archives is the norm). + # This switch is for users who want the stricter Linux/macOS behaviour, + # where install.sh expects a system BLAS and errors without one. + # Whatever is downloaded lands under : project-local, + # nothing installed system-wide, removed when that directory is deleted. + [switch]$NoDownload, + + # Skip interactive questions and take the documented default for each, + # mirroring install.sh's -y/--yes. Prompts are already skipped whenever + # stdin is not a terminal (CI, piped input), so this is only needed to + # silence them in an interactive session. + [switch]$Yes, + # Use an existing oneMKL install (e.g. from the oneAPI installer) instead # of auto-discovery/download. Must contain the ILP64 DLL import libs. # Only meaningful with -Backend mkl. @@ -65,6 +81,95 @@ function Convert-ToCMakePath { return $Path.Replace('\', '/') } +function Get-ClTargetArchitecture { + # Returns the compiler's TARGET architecture, lowercased ("x64", "x86", + # "arm64", "arm"), or "" if it genuinely cannot be determined. + # + # Three independent signals, most reliable first -- the same + # probe-several-things approach Find-OneMklLayout uses, and for the same + # reason: a missed detection here fails *open*, which defeats the check. + # 1. VSCMD_ARG_TGT_ARCH, exported by vcvarsall.bat / VsDevCmd (and so + # by ilammy/msvc-dev-cmd in CI). Never localized. + # 2. The toolset path: MSVC lays cl.exe out as + # ...\bin\Host\\cl.exe, a stable convention. + # 3. The banner, last, for anything matching neither of the above. + # On its own this would be wrong on a localized Visual Studio, where + # the words around the architecture are translated. + if ($env:VSCMD_ARG_TGT_ARCH) { return $env:VSCMD_ARG_TGT_ARCH.ToLowerInvariant() } + $cl = Get-Command "cl.exe" -ErrorAction SilentlyContinue + if (-not $cl) { return "" } + if ($cl.Source -match '\\bin\\Host[^\\]+\\([^\\]+)\\cl\.exe$') { + return $Matches[1].ToLowerInvariant() + } + # Native stderr merged via 2>&1 becomes ErrorRecords, which would throw + # under $ErrorActionPreference = "Stop"; relax it for this one call. + $previous = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $banner = (& $cl.Source 2>&1 | Out-String) + } finally { + $ErrorActionPreference = $previous + } + if ($banner -match '\bfor\s+(x64|x86|ARM64|ARM)\b') { return $Matches[1].ToLowerInvariant() } + return "" +} + +# Prompts happen only on a terminal and only without -Yes, mirroring +# install.sh's INTERACTIVE flag. Without this gate, a question asked under +# GitHub Actions (or any piped stdin) blocks until the job times out. +$script:Interactive = -not $Yes -and -not [Console]::IsInputRedirected ` + -and [Environment]::UserInteractive + +function Read-YesNo { + # Returns $true/$false. Non-interactive callers get $Default without a + # prompt, so every question must have a defensible unattended answer. + param([string]$Question, [bool]$Default) + if (-not $script:Interactive) { return $Default } + $suffix = if ($Default) { "[Y/n]" } else { "[y/N]" } + while ($true) { + $reply = (Read-Host "$Question $suffix").Trim().ToLowerInvariant() + if ($reply -eq "") { return $Default } + if ($reply -in @("y", "yes")) { return $true } + if ($reply -in @("n", "no")) { return $false } + Write-Host "Please answer y or n." + } +} + +function Get-ToolchainArchitectureProblem { + # Returns a description of why $Arch is unusable, or "" if it is fine. + # x86 and ARM64 fail for completely different reasons and deserve + # different advice: x86 means the wrong shell was opened and is a + # one-command fix, ARM64 means the platform is genuinely unsupported. + # (Mirrored in install.ps1, which performs the same check up front.) + param([string]$Arch) + if ($Arch -eq "" -or $Arch -eq "x64" -or $Arch -eq "amd64") { return "" } + if ($Arch -eq "x86") { + return ("cl.exe targets x86, but RandLAPACK and its BLAS/LAPACK backends are 64-bit " + + "(x64).`n" + + " You are in a 32-bit developer shell. 'Developer PowerShell for VS 2022' and " + + "'Developer Command Prompt for VS 2022' both default to x86.`n" + + " Fix: open 'x64 Native Tools Command Prompt for VS 2022' from the Start menu " + + "(or run: cmd /k `"\VC\Auxiliary\Build\vcvars64.bat`") and re-run.`n" + + " Then delete the RandNLA-project directory before retrying: dependencies already " + + "configured by the x86 compiler are reused as-is and would keep failing.") + } + return ("cl.exe targets $Arch, which this installer does not support: the Windows build " + + "is x64-only.`n" + + " Intel oneMKL publishes no $Arch build, and the OpenBLAS binaries pinned here are " + + "x64. Supplying an $Arch BLAS/LAPACK through -Backend custom is the only route, and " + + "it is untested.`n" + + " If you meant to build x64, open 'x64 Native Tools Command Prompt for VS 2022'.") +} + +function Assert-SupportedToolchain { + # Left unchecked, a non-x64 toolchain surfaces much later as BLAS++ + # reporting "BLAS library not found", which points at the wrong thing + # entirely: the libraries are present and correct, the linker simply + # cannot use them at that architecture. + $problem = Get-ToolchainArchitectureProblem (Get-ClTargetArchitecture) + if ($problem -ne "") { throw $problem } +} + function Find-PackageConfigDirectory { # Install layouts of blaspp/lapackpp vary between revisions; search for the # package config file instead of hardcoding lib/cmake/. @@ -177,6 +282,7 @@ New-Item -ItemType Directory -Force -Path $resolvedRoot | Out-Null if (-not (Get-Command "cl.exe" -ErrorAction SilentlyContinue)) { throw "cl.exe is not on PATH. Run from an MSVC developer environment (or ilammy/msvc-dev-cmd in CI)." } +Assert-SupportedToolchain # ---------------------------------------------------- argument checks ---- @@ -230,6 +336,70 @@ if ($Backend -eq "mkl") { } } } + # Not found: offer to provision it. Auto-provisioning is the default + # answer because Windows has no system prefix for third-party libraries, + # so per-project acquisition is normal practice rather than a workaround, + # and it is what lets a bare machine install in one command. Asking first + # keeps a 155 MB download from being a surprise, and states plainly where + # it goes and that nothing touches the system. + $provisionMkl = $false + if (-not $mklLayout -and -not $NoDownload) { + Write-Host "" + Write-Host "No existing oneMKL found (checked -MklRoot, `$env:MKLROOT, `$env:ONEAPI_ROOT," + Write-Host "and C:\Program Files (x86)\Intel\oneAPI\mkl\latest)." + Write-Host "" + Write-Host "A pinned, checksum-verified copy (~155 MB) can be downloaded into" + Write-Host " $resolvedRoot" + Write-Host "It is used only by this project: nothing is installed system-wide, no PATH" + Write-Host "or registry changes, and deleting that directory removes it completely." + Write-Host "" + # Default yes: the unattended answer must keep CI and a one-command + # install on a bare machine working, and this is the same choice + # install.sh's ask() makes for its own prompts. + $provisionMkl = Read-YesNo "Download oneMKL now?" $true + if (-not $provisionMkl) { Write-Host "" } + } + if (-not $mklLayout -and -not $provisionMkl) { + # Reached two ways: -NoDownload, or the question above answered no. + # Both mean "no backend is available", so both get the same full list + # of ways to supply one; only the reason differs. + # + # Probing the literal oneAPI path (rather than requiring MKLROOT) is + # the polished Windows behaviour: that path IS canonical for oneMKL, + # even though Windows has no general system prefix for third-party + # libraries. See randnla/reference/windows-software-distribution.md. + # + # Details to the console, short throw -- the same shape install.ps1's + # preflight uses. A long multi-line throw message gets echoed twice by + # PowerShell (message, then FullyQualifiedErrorId) and buried in a + # stack trace, which makes actionable guidance harder to read, not + # easier. + $mklRootShown = if ($env:MKLROOT) { $env:MKLROOT } else { "(not set)" } + $oneApiShown = if ($env:ONEAPI_ROOT) { Join-Path $env:ONEAPI_ROOT "mkl\latest" } else { "(not set)" } + Write-Host "" + $why = if ($NoDownload) { "-NoDownload was given" } else { "the download was declined" } + Write-Host "No oneMKL installation found, and $why." + Write-Host "" + Write-Host " Looked in (in order):" + Write-Host " -MklRoot (not given)" + Write-Host " `$env:MKLROOT $mklRootShown" + Write-Host " `$env:ONEAPI_ROOT\mkl\latest $oneApiShown" + Write-Host " C:\Program Files (x86)\Intel\oneAPI\mkl\latest (default oneAPI location)" + Write-Host "" + Write-Host " A directory only counts if it holds mkl_intel_ilp64_dll.lib under lib\ or" + Write-Host " lib\intel64\ alongside a bin\ (or redist\intel64\) DLL directory, so a" + Write-Host " partial install is rejected rather than half-used." + Write-Host "" + Write-Host " Pick one:" + Write-Host " 1. Install oneMKL: winget install --id Intel.oneMKL --exact" + Write-Host " 2. Use a copy you already have: -MklRoot `"`"" + Write-Host " 3. Use OpenBLAS instead: -Backend openblas" + Write-Host " 4. Let the installer fetch a pinned oneMKL (~155 MB into" + Write-Host " $resolvedRoot; nothing installed system-wide):" + Write-Host " re-run and answer yes, or pass -Yes to skip the question." + Write-Host "" + throw "No BLAS/LAPACK backend available; see the options above." + } if (-not $mklLayout) { # oneMKL comes straight from Intel's official NuGet packages -- plain # zip archives on nuget.org, pinned by version and SHA256. The devel @@ -283,10 +453,22 @@ if ($Backend -eq "mkl") { (Join-Path $mklLibDir "mkl_core_dll.lib"))) { if (-not (Test-Path $required)) { throw "oneMKL install is missing $required." } } - $backendLibraries = @( - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib")), - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_sequential_dll.lib")), - (Convert-ToCMakePath (Join-Path $mklLibDir "mkl_core_dll.lib"))) + # Same link-and-run check the other two backends get: one clear pass/fail + # here beats a BLAS++ probe cascade three layers down. It catches an + # incomplete or mismatched oneMKL (and, belt-and-braces after + # Assert-SupportedToolchain, any remaining bitness mismatch) before anything is + # built. + $mklLibs = @( + (Join-Path $mklLibDir "mkl_intel_ilp64_dll.lib"), + (Join-Path $mklLibDir "mkl_sequential_dll.lib"), + (Join-Path $mklLibDir "mkl_core_dll.lib")) + if (-not (Test-BlasLinkage -Libraries $mklLibs -DllDir $mklBin ` + -Int64 $true -ScratchDir (Join-Path $resolvedRoot "conftest-mkl"))) { + throw ("oneMKL at $mklRoot failed a minimal ILP64 dgemm_/dgesv_ link-and-run check. " + + "Verify that the install is complete and that its DLL directory ($mklBin) holds " + + "the matching runtime DLLs.") + } + $backendLibraries = @($mklLibs | ForEach-Object { Convert-ToCMakePath $_ }) $backendLapackLibraries = "" # ILP64 because RandBLAS's MKL sparse backend static-asserts that its # int64_t sparse indices match sizeof(MKL_INT). @@ -301,6 +483,29 @@ if ($Backend -eq "mkl") { # full LAPACK is included. LP64: no ILP64 OpenBLAS binaries are # published for Windows. Without MKL, RandBLAS's MKL sparse # acceleration stays off and its portable fallbacks take over. + # + # Unlike oneMKL there is nothing to auto-discover: OpenBLAS has no + # canonical Windows install location (GitHub release zips, vcpkg, conda + # and MSYS2 all differ, and the release zips even ship CMake config + # files with wrong hardcoded paths). So rather than probe and guess, ask + # -- and only when someone is there to answer. + if ($script:Interactive -and -not $NoDownload) { + if (Read-YesNo "Do you already have OpenBLAS installed?" $false) { + Write-Host "" + Write-Host "OpenBLAS has no standard layout on Windows, so point at the pieces" + Write-Host "directly rather than at a root directory. Re-run with:" + Write-Host "" + Write-Host " -Backend custom ``" + Write-Host " -BlasLibraries `"\libopenblas.lib`" ``" + Write-Host " -BackendBinDir `"`" ``" + Write-Host " -BlasInt lp64 -BlasFortran add" + Write-Host "" + Write-Host "Your libraries are checked with a real dgemm_/dgesv_ link-and-run" + Write-Host "test before anything is built, so a wrong path fails immediately." + Write-Host "" + throw "Re-run with -Backend custom to use your own OpenBLAS (see above)." + } + } $openblasVersion = "0.3.34" $openblasSha256 = "e9cb6134541f36c27346d5fc5995652f060fba227cebbbabcbda5a5a44d7c76b" $openblasRoot = Join-Path $resolvedRoot "openblas-$openblasVersion" diff --git a/.github/scripts/windows/run-ci.ps1 b/.github/scripts/windows/run-ci.ps1 index d9bb4520..0142a844 100644 --- a/.github/scripts/windows/run-ci.ps1 +++ b/.github/scripts/windows/run-ci.ps1 @@ -68,8 +68,11 @@ if ($DependencyRoot -eq "") { } if ($SetupDependencies) { + # -Yes: this path is CI (and local reproduction of a CI leg), so it must + # never stop on a question. & (Join-Path $SourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` - -DependencyRoot $DependencyRoot -Backend $Backend -SanitizeAddress:$SanitizeAddress + -DependencyRoot $DependencyRoot -Backend $Backend -Yes ` + -SanitizeAddress:$SanitizeAddress } $blasppDir = Require-EnvironmentVariable "blaspp_DIR" diff --git a/.github/scripts/windows/test-toolchain-guard.ps1 b/.github/scripts/windows/test-toolchain-guard.ps1 new file mode 100644 index 00000000..e627f4b3 --- /dev/null +++ b/.github/scripts/windows/test-toolchain-guard.ps1 @@ -0,0 +1,114 @@ +# Table test for the toolchain architecture guard. +# +# Why this exists as a separate test rather than relying on the build matrix: +# the guard's whole job is to reject architectures we cannot build for, so +# there is no runner on which "it built fine" demonstrates it works. Driving +# the decision directly covers arm64 and arm on ordinary x64 hardware, and +# covers them in seconds. +# +# It also guards against drift. install.ps1 and setup.ps1 each carry a copy +# of these two functions (they run independently -- CI calls setup.ps1 alone, +# users call install.ps1), so this asserts the two copies still agree. +# +# The integration counterpart lives in core-windows.yaml, which runs the real +# installer under a real x86 and a real cross-compiled arm64 toolchain and +# asserts it refuses. That proves the wiring; this proves the decision. + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..\..") +$sources = @( + (Join-Path $repoRoot "install\install.ps1"), + (Join-Path $repoRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") +) + +# Arch, whether it must be accepted, and a phrase the refusal must contain. +# x86 and arm64 must not merely both fail -- they must fail with *different* +# advice, since one is a wrong-shell mistake and the other is an unsupported +# platform. The Expect phrases pin that distinction. +$cases = @( + @{ Arch = "x64"; ShouldPass = $true; Expect = "" }, + @{ Arch = "amd64"; ShouldPass = $true; Expect = "" }, + @{ Arch = "x86"; ShouldPass = $false; Expect = "32-bit developer shell" }, + @{ Arch = "arm64"; ShouldPass = $false; Expect = "x64-only" }, + @{ Arch = "arm"; ShouldPass = $false; Expect = "x64-only" } +) + +function Get-GuardVerdicts { + # Loads the two functions out of $SourceFile without executing the rest + # of it, then drives them over the case table. + param([string]$SourceFile, [object[]]$Cases) + $ast = [System.Management.Automation.Language.Parser]::ParseInput( + (Get-Content -Raw $SourceFile), [ref]$null, [ref]$null) + $definitions = foreach ($name in @("Get-ClTargetArchitecture", "Get-ToolchainArchitectureProblem")) { + $found = $ast.Find({ + param($node) + $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $name + }, $true) + if (-not $found) { throw "$SourceFile does not define $name." } + $found.Extent.Text + } + $body = @' +param($Cases) +foreach ($case in $Cases) { + $env:VSCMD_ARG_TGT_ARCH = $case.Arch + $detected = Get-ClTargetArchitecture + [pscustomobject]@{ + Arch = $case.Arch + Detected = $detected + Problem = (Get-ToolchainArchitectureProblem $detected) + } +} +'@ + $script = "param(`$Cases)`n" + ($definitions -join "`n") + "`n" + ($body -replace '^param\(\$Cases\)\r?\n', '') + return & ([scriptblock]::Create($script)) $Cases +} + +$savedArch = $env:VSCMD_ARG_TGT_ARCH +$failures = 0 +$allVerdicts = @{} +try { + foreach ($source in $sources) { + $label = Split-Path $source -Leaf + Write-Host "--- $label ---" + $verdicts = @(Get-GuardVerdicts -SourceFile $source -Cases $cases) + $allVerdicts[$label] = $verdicts + for ($i = 0; $i -lt $cases.Count; $i++) { + $case = $cases[$i] + $verdict = $verdicts[$i] + $accepted = ($verdict.Problem -eq "") + $ok = ($accepted -eq $case.ShouldPass) + if ($ok -and $case.Expect -ne "" -and $verdict.Problem -notmatch [regex]::Escape($case.Expect)) { + $ok = $false + Write-Host " (refused, but the message lacked '$($case.Expect)')" + } + if (-not $ok) { $failures++ } + Write-Host ("{0} {1,-6} detected={2,-6} {3}" -f + $(if ($ok) { "OK " } else { "FAIL" }), + $case.Arch, $verdict.Detected, + $(if ($accepted) { "accepted" } else { "refused" })) + } + } +} finally { + $env:VSCMD_ARG_TGT_ARCH = $savedArch +} + +# Drift check: both copies must reach identical verdicts. +Write-Host "--- install.ps1 vs setup.ps1 agreement ---" +$left = $allVerdicts["install.ps1"] +$right = $allVerdicts["setup.ps1"] +for ($i = 0; $i -lt $cases.Count; $i++) { + $leftAccepted = ($left[$i].Problem -eq "") + $rightAccepted = ($right[$i].Problem -eq "") + if ($leftAccepted -ne $rightAccepted) { + $failures++ + Write-Host ("FAIL {0}: install.ps1 and setup.ps1 disagree" -f $cases[$i].Arch) + } else { + Write-Host ("OK {0}: both agree" -f $cases[$i].Arch) + } +} + +Write-Host "" +if ($failures -gt 0) { throw "$failures toolchain-guard assertion(s) failed." } +Write-Host "All toolchain-guard assertions passed." diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index a4dd82fa..495e81ef 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -9,6 +9,68 @@ on: - cqrrp-gpu-benchmarking jobs: + # Decision-level test of the architecture guard. Needs no MSVC and no + # build, so it covers arm64 and arm -- which we cannot build for, and so + # can never cover with a build leg -- in a few seconds on x64 hardware. + # Also asserts install.ps1's and setup.ps1's duplicated copies agree. + windows-guard-logic: + name: windows-guard-logic + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - name: architecture guard decision table + shell: powershell + run: .\.github\scripts\windows\test-toolchain-guard.ps1 + + # Structural gate for the documented *user* path, which the build matrix + # below cannot cover: it initializes MSVC with an explicit arch: x64 and + # runs under pwsh, so it has never exercised the shell the install docs + # tell users to open. That gap is exactly how a 32-bit toolchain reached a + # collaborator and failed three layers down as "BLAS library not found". + # + # These legs assert a *refusal*, so they stop at preflight in seconds and + # never build a dependency: cheap enough to keep permanently. arch x86 is + # the real-world case; amd64_arm64 cross-compiles, which yields an + # ARM64-targeting cl.exe on an ordinary x64 runner and so covers the ARM + # branch of the guard without ARM hardware. + windows-toolchain-guards: + name: windows-guard-${{ matrix.arch }} + runs-on: windows-2022 + strategy: + fail-fast: false + matrix: + include: + - arch: x86 + expect: targets x86 + - arch: amd64_arm64 + expect: targets arm64 + steps: + - uses: actions/checkout@v4 + + - name: initialize MSVC (${{ matrix.arch }}) + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.arch }} + + # Windows PowerShell 5.1, not pwsh: this is what a user actually gets, + # and the launch line is verbatim the one INSTALL_WINDOWS.md prescribes + # (cmd + -ExecutionPolicy Bypass), so the documented invocation itself + # stays under test. + - name: install.ps1 must refuse a ${{ matrix.arch }} toolchain + shell: powershell + run: | + $output = & cmd /c "powershell -ExecutionPolicy Bypass -File .\install\install.ps1 2>&1" + $exit = $LASTEXITCODE + $text = $output -join "`n" + Write-Host $text + if ($exit -eq 0) { + throw "install.ps1 succeeded with a ${{ matrix.arch }} toolchain; the architecture guard did not fire." + } + if ($text -notmatch '${{ matrix.expect }}') { + throw "The guard fired, but no '${{ matrix.expect }}' in its output: the wrong check failed." + } + Write-Host "OK: refused ${{ matrix.arch }} at preflight, with the expected explanation." + build-windows: name: windows-msvc-${{ matrix.backend }}-${{ matrix.int }}-${{ matrix.label }} runs-on: windows-2022 diff --git a/INSTALL.md b/INSTALL.md index fef40c4a..942df8ce 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -308,11 +308,13 @@ RandLAPACK builds natively on Windows with MSVC (Visual Studio 2022). The full guide -- prerequisites, the one-command installer, BLAS/LAPACK backend choice (oneMKL default, OpenBLAS, bring-your-own), runtime-DLL handling, and how the Windows install differs from Linux/macOS -- lives in -[INSTALL_WINDOWS.md](INSTALL_WINDOWS.md). The short version, from a -"Developer PowerShell for VS 2022" prompt: +[INSTALL_WINDOWS.md](INSTALL_WINDOWS.md). The short version, from an +"x64 Native Tools Command Prompt for VS 2022" (the plain "Developer +PowerShell/Command Prompt for VS 2022" entries default to a 32-bit toolchain +and will not link the x64 BLAS libraries): -```powershell +```bat git clone --recursive https://github.com/BallisticLA/RandLAPACK.git cd RandLAPACK -.\install\install.ps1 +powershell -ExecutionPolicy Bypass -File .\install\install.ps1 ``` diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 768b591a..55234aa5 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -15,19 +15,36 @@ winget install Git.Git winget install Microsoft.VisualStudio.2022.Community --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended" ``` -Then open **"Developer PowerShell for VS 2022"** from the Start menu (this is -important -- see section 3), and run: +Then open **"x64 Native Tools Command Prompt for VS 2022"** from the Start +menu (the exact entry matters -- see section 3), and run: -```powershell +```bat git clone --recursive https://github.com/BallisticLA/RandLAPACK.git cd RandLAPACK -.\install\install.ps1 +powershell -ExecutionPolicy Bypass -File .\install\install.ps1 ``` -That is the whole install. The script downloads a pinned, checksum-verified -copy of Intel oneMKL, builds the remaining dependencies, builds RandLAPACK, -and runs its test suite. Everything lands in a sibling `RandNLA-project\` -directory; re-running the script reuses what is already built. +Two details in that last line, both deliberate. It is a `cmd` prompt, so the +PowerShell script is launched rather than typed directly. And Windows blocks +PowerShell scripts by default (`Restricted` policy on a fresh machine), so +`-ExecutionPolicy Bypass` lets this one script run without permanently +loosening a machine-wide security setting. + +That is the whole install. The script uses an Intel oneMKL you already have, +and otherwise downloads a pinned copy for you, then builds the remaining +dependencies, builds RandLAPACK, and runs its test suite. Everything lands in +a sibling `RandNLA-project\` directory; re-running the script reuses what is +already built. + +Unlike Linux and macOS, you are not expected to install a BLAS library first. +Windows has no system location for third-party libraries, so projects acquire +their own -- that is what vcpkg, Conan, and NuGet exist for. Anything the +installer downloads goes inside `RandNLA-project\`, never into your system, +and deleting that directory removes it completely. + +If you would rather supply the library yourself, use `-MklRoot` to point at an +existing oneMKL, or `-NoDownload` to make a missing one an error instead of a +download. See §4. ## 2. How this differs from Linux and macOS @@ -36,7 +53,7 @@ installer does differently: | | Linux / macOS | Windows | |---|---|---| -| Getting BLAS/LAPACK | one package-manager command (`apt install libopenblas-dev`, `brew install openblas`) | no system package manager for libraries; the installer downloads pinned, SHA256-verified binaries directly from the vendor (Intel's NuGet packages, OpenBLAS's release archives) | +| Getting BLAS/LAPACK | you install one first (`apt install libopenblas-dev`, `brew install openblas`); the installer errors without it | no system location for libraries exists, so the installer discovers an existing oneMKL and otherwise fetches a pinned copy into the project directory. Pass `-NoDownload` for the Linux/macOS behavior | | Where the compiler lives | `gcc`/`clang` always on PATH | MSVC (`cl.exe`) exists only inside a "Developer" shell that Visual Studio sets up per session | | Finding shared libraries at run time | the binary itself remembers where its libraries are (RPATH), plus system-wide loader paths | executables have no such memory; Windows searches the executable's **own directory first** and PATH **last**, so the installer copies ("stages") every needed DLL next to each executable | | Default BLAS backend | OpenBLAS (Linux CI), Accelerate (macOS CI) | Intel oneMKL, ILP64 sequential (fastest on typical Windows x64 machines, and enables RandBLAS's MKL-accelerated sparse routines) | @@ -63,10 +80,51 @@ what Visual Studio's own package manager does by default. 200 MB for the default backend). The installer checks all of this up front and prints a fix-it command for -anything missing. The most common mistake is running from a *regular* -PowerShell: `cl.exe`, `cmake`, and `ninja` are only on PATH inside -**Developer PowerShell for VS 2022** (Start menu, or "Developer Command -Prompt" if you prefer cmd). +anything missing. + +Two mistakes account for nearly every failed Windows install, and both are +about *which shell you start from*: + +1. **A regular PowerShell.** `cl.exe`, `cmake`, and `ninja` are on PATH only + inside a Visual Studio "developer" shell, which sets them up per session. +2. **A developer shell of the wrong architecture.** This one is easy to hit + because the obvious Start-menu entries are the wrong ones: **"Developer + PowerShell for VS 2022"** and **"Developer Command Prompt for VS 2022"** + both default to a **32-bit (x86)** toolchain. RandLAPACK and every BLAS + backend here are 64-bit, and a 32-bit linker cannot use an x64 import + library. Use **"x64 Native Tools Command Prompt for VS 2022"** instead. + +You can confirm you are in the right place with: + +``` +cl +``` + +The banner must end in `for x64`. If it says `for x86`, you are in a 32-bit +shell. The installer's preflight check will also stop you with this same +explanation, so you cannot get far down the wrong path. + +If you prefer PowerShell to `cmd`, there is no x64 PowerShell entry in the +Start menu, so ask for the architecture explicitly: + +```powershell +Import-Module "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" +Enter-VsDevShell -VsInstallPath "C:\Program Files\Microsoft Visual Studio\2022\Community" -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64" +``` + +### Script execution policy + +Windows refuses to run PowerShell scripts at all under its default +`Restricted` policy, with "running scripts is disabled on this system". The +quick start sidesteps this per-invocation with `-ExecutionPolicy Bypass`, +which is the smallest hammer: it applies to that one process and changes +nothing about the machine. If you would rather allow local scripts +permanently, this is the conventional setting, and it affects only your own +account: + +```powershell +Set-ExecutionPolicy -Scope CurrentUser RemoteSigned +``` ## 4. Choosing a BLAS/LAPACK backend @@ -75,7 +133,7 @@ on a BLAS/LAPACK library of your choice. On Windows the installer supports: | Backend | Flag | What you get | How it is obtained | |---|---|---|---| -| **oneMKL** (default) | none needed | fastest option on most x64 CPUs; 64-bit integers (ILP64); MKL-accelerated sparse routines in RandBLAS | an already-installed oneAPI is discovered automatically (via `MKLROOT`, `ONEAPI_ROOT`, or the default install location); otherwise Intel's official NuGet packages are downloaded, pinned by version and SHA256 | +| **oneMKL** (default) | none needed | fastest option on most x64 CPUs; 64-bit integers (ILP64); MKL-accelerated sparse routines in RandBLAS | discovered from an existing install via `-MklRoot`, `MKLROOT`, `ONEAPI_ROOT`, or the default oneAPI location; otherwise Intel's official NuGet packages are downloaded, pinned by version and SHA256. `-NoDownload` turns "not found" into an error | | **OpenBLAS** | `-Backend openblas` | solid free backend; 32-bit integers (LP64); RandBLAS's portable sparse fallbacks replace the MKL-only accelerations | official OpenBLAS release binaries, pinned and checksum-verified; the archive is self-contained and includes full LAPACK | | **Custom / bring-your-own** | `-Backend custom -BlasLibraries ` | anything BLAS++/LAPACK++ can link -- e.g. AMD AOCL, a local ILP64 OpenBLAS build | you provide the import libraries (and their DLL directory via `-BackendBinDir`); the installer verifies them with a small link-and-run check before building anything | @@ -92,7 +150,15 @@ and OpenBLAS are. (default: ..\RandNLA-project next to the clone). -Backend mkl (default) | openblas | custom. -MklRoot Use this specific oneMKL install (oneAPI layout); - skips discovery and download. Backend mkl only. + skips discovery. Backend mkl only. Invalid paths are + an error, never a silent fallback to something else. +-NoDownload Fail instead of downloading a backend that was not + found locally. The default fetches one into + ; nothing is installed system-wide, and + deleting removes it. +-Yes Skip interactive questions, taking each documented + default. Questions are already skipped when stdin is + not a terminal, so CI never needs this. -BlasLibraries Backend custom: semicolon-separated .lib paths. -LapackLibraries

Backend custom: LAPACK .lib paths, if separate from BLAS. -BackendBinDir Backend custom: directory holding the backend's DLLs. @@ -146,9 +212,23 @@ directory (the installer prints it at the end) next to your `.exe`. ## 7. Troubleshooting -- **"cl.exe is not on PATH"**: you are in a regular shell. Open "Developer - PowerShell for VS 2022" and re-run. If Visual Studio is missing entirely, - the preflight message includes the winget install command. +- **"running scripts is disabled on this system"**: Windows' default + PowerShell execution policy. Launch it as the quick start does + (`powershell -ExecutionPolicy Bypass -File .\install\install.ps1`), or see + "Script execution policy" in section 3. +- **"cl.exe is not on PATH"**: you are in a regular shell. Open "x64 Native + Tools Command Prompt for VS 2022" and re-run. If Visual Studio is missing + entirely, the preflight message includes the winget install command. +- **"cl.exe targets x86"**: you are in a developer shell of the wrong + architecture (see section 3). Open "x64 Native Tools Command Prompt for VS + 2022" instead. If an earlier run already got as far as building + dependencies, delete the `RandNLA-project` directory before retrying: + dependencies are reused when present, and the ones configured by the 32-bit + compiler will keep failing no matter which shell you re-run from. +- **"BLAS library not found" from BLAS++, with oneMKL clearly installed**: + almost always the 32-bit shell above, on a version of the installer that + predates the preflight check. The x86 linker rejects the x64 import + library, and BLAS++ can only report that its probe did not link. - **A download fails with a hash mismatch**: the pinned artifact changed upstream or the download was corrupted. Re-run once; if it persists, open an issue -- do not bypass the check. diff --git a/docs/CI.md b/docs/CI.md index 149f3e9f..4513452d 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -12,6 +12,8 @@ the thing we tell users to type is itself under test). | core-linux | `build-asan` | ubuntu-latest | Debug + AddressSanitizer build and test run | no | | core-macos | `build` | macos-latest | same as Linux `build`, on Apple's toolchain | **yes** (`build`) | | core-macos | `build-asan` | macos-latest | Debug + AddressSanitizer build and test run | no | +| core-windows | `windows-guard-logic` | windows-2022 | Architecture-guard decision table (`test-toolchain-guard.ps1`); no MSVC, no build, seconds | no (new) | +| core-windows | `windows-toolchain-guards` | windows-2022 | Asserts `install.ps1` *refuses* a real x86 and a cross-compiled arm64 toolchain, launched exactly as the docs prescribe | no (new) | | core-windows | `build-windows` | windows-2022 | MSVC build + tests: oneMKL ILP64 serial, oneMKL ILP64 OpenMP (`/openmp:llvm`), OpenBLAS LP64 serial; each leg ends with a stripped-PATH run of a staged test executable | no (new) | | install-script | `install-linux` | ubuntu-latest | `install.sh`: fresh install, idempotent re-run, dependency-discovery path | **yes** | | install-script | `install-macos` | macos-latest | `install.sh`: fresh install, idempotent re-run | **yes** | @@ -64,6 +66,55 @@ candidates once they have a green track record. mkl-openmp), no second install-script backend leg (the installer's `-Backend` forwarding is thin; provisioning is covered by the openblas core leg), and no Windows ASan lane. +- **The two Windows guard jobs test the documented *user* path, which the + build matrix structurally cannot.** Every build leg initializes MSVC with + an explicit `arch: x64` under `pwsh`, so none of them exercises the shell + the install docs actually tell users to open. That gap is precisely how a + 32-bit toolchain reached a collaborator in 2026-08 and failed three layers + down as BLAS++ reporting "BLAS library not found" (the libraries were fine; + an x86 linker simply cannot use an x64 import library). The guards close it + from two directions and are cheap because both assert a *refusal*, so + neither builds a dependency: + - `windows-guard-logic` drives the decision over a table of + architectures. This is the only way to cover **arm64** and **arm**: we + cannot build for them, so no build leg can ever test them. It also + asserts that `install.ps1`'s and `setup.ps1`'s duplicated copies of the + check still agree, since they run independently. + - `windows-toolchain-guards` runs the real installer under a real x86 + toolchain and an `amd64_arm64` cross-compiling one (which yields an + arm64-targeting `cl.exe` on ordinary x64 hardware, so no ARM runner is + needed). It launches via `cmd` + `-ExecutionPolicy Bypass` under + Windows PowerShell 5.1, so the documented invocation stays under test + too, not just the guard. + x86 and arm64 must fail with *different* messages: x86 is a wrong-shell + mistake with a one-command fix, arm64 is an unsupported platform (no + oneMKL build exists for it). The tests pin that distinction. +- **CI passes `-Yes`, and prompts are TTY-gated regardless.** `setup.ps1` can + ask a question (currently: whether you already have OpenBLAS, since unlike + oneMKL it has no canonical Windows location to probe). Any prompt reaching a + runner would block until the job times out, so `$script:Interactive` is false + whenever stdin is redirected or the session is non-interactive, and every + question must have a defensible unattended default. `action.yml` and + `run-ci.ps1` additionally pass `-Yes` so the intent survives any future + change to that detection. This mirrors `install.sh`, which computes + `INTERACTIVE` from `[[ -t 0 ]]` plus `--yes` for the same reason. +- **Backends are auto-provisioned by default; `-NoDownload` opts out.** + Windows has no system prefix for third-party libraries, so per-project + acquisition (what vcpkg, Conan, and NuGet exist for) is the ordinary + practice rather than a workaround, and it is what makes a one-command + install possible on a bare machine. oneMKL is still *discovered* first + (`-MklRoot`, `MKLROOT`, `ONEAPI_ROOT`, default oneAPI path) and the download + is announced rather than silent. `-NoDownload` gives the stricter + Linux/macOS behavior, where `install.sh` expects a system BLAS and errors + without one. Everything downloaded lands under the dependency root, so a + runner's cache and a user's project directory stay self-contained. +- **The architecture check reads several signals, not just `cl.exe`'s + banner.** It prefers `VSCMD_ARG_TGT_ARCH`, then the + `bin\Host\\cl.exe` path convention, and only then the banner. + The banner alone would be wrong on a localized Visual Studio, where the + words around the architecture are translated -- and a missed detection here + fails *open*, silently allowing the exact configuration the check exists to + reject. - **The MSVC OpenMP flavor is forced to `/openmp:llvm` in RandLAPACK's own CMake** (`CMake/rl_build_options.cmake` and the benchmark project), not just in RandBLAS. RandLAPACK's `find_package(OpenMP)` runs before the diff --git a/install/install.ps1 b/install/install.ps1 index e6ca49b1..18290fd0 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -1,9 +1,17 @@ # RandLAPACK native Windows installer -- the companion to install.sh. # -# Run from an MSVC developer prompt (or "Developer PowerShell for VS") in the -# repository root: +# Run from an "x64 Native Tools Command Prompt for VS 2022" in the repository +# root: # -# .\install\install.ps1 +# powershell -ExecutionPolicy Bypass -File .\install\install.ps1 +# +# (That prompt is cmd, hence the explicit launch; the policy flag is because +# Windows blocks PowerShell scripts by default on a fresh machine.) +# +# The architecture matters: the plain "Developer PowerShell/Command Prompt for +# VS 2022" entries default to a 32-bit (x86) toolchain, which cannot link the +# x64 BLAS/LAPACK libraries this installer provisions. Preflight rejects that +# case with an explanation rather than letting it fail deep in the build. # # What it does, mirroring install.sh's layout in a sibling RandNLA-project # directory: @@ -20,7 +28,14 @@ # packages), openblas (official release binaries), or # custom (bring your own via -BlasLibraries). # -MklRoot Use this oneMKL install (oneAPI layout) instead of -# auto-discovery/download. Backend mkl only. +# auto-discovery. Backend mkl only. +# -NoDownload Fail instead of downloading a backend that was not +# found locally. The default is to fetch one into +# (project-local; nothing is installed +# system-wide), which is ordinary Windows practice. +# -Yes Skip interactive questions, taking each documented +# default. Questions are already skipped when stdin +# is not a terminal. # -BlasLibraries / -LapackLibraries / -BackendBinDir / -BlasInt / -BlasFortran # Backend custom only; see setup.ps1's header. # -Fresh Reconfigure RandLAPACK from scratch (dependencies are @@ -38,6 +53,8 @@ param( [ValidateSet("mkl", "openblas", "custom")] [string]$Backend = "mkl", [string]$MklRoot = "", + [switch]$NoDownload, + [switch]$Yes, [string]$BlasLibraries = "", [string]$LapackLibraries = "", [string]$BackendBinDir = "", @@ -62,6 +79,56 @@ function Invoke-Checked { } } +function Get-ClTargetArchitecture { + # Returns the compiler's TARGET architecture, lowercased ("x64", "x86", + # "arm64", "arm"), or "" if it genuinely cannot be determined. + # Three independent signals, most reliable first; a missed detection here + # fails *open*, which would defeat the check entirely. Parsing the banner + # alone would be wrong on a localized Visual Studio, where the words + # around the architecture are translated. + # (Mirrored in setup.ps1, which is also runnable on its own.) + if ($env:VSCMD_ARG_TGT_ARCH) { return $env:VSCMD_ARG_TGT_ARCH.ToLowerInvariant() } + $cl = Get-Command "cl.exe" -ErrorAction SilentlyContinue + if (-not $cl) { return "" } + if ($cl.Source -match '\\bin\\Host[^\\]+\\([^\\]+)\\cl\.exe$') { + return $Matches[1].ToLowerInvariant() + } + # Native stderr merged via 2>&1 becomes ErrorRecords, which would throw + # under $ErrorActionPreference = "Stop"; relax it for this one call. + $previous = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $banner = (& $cl.Source 2>&1 | Out-String) + } finally { + $ErrorActionPreference = $previous + } + if ($banner -match '\bfor\s+(x64|x86|ARM64|ARM)\b') { return $Matches[1].ToLowerInvariant() } + return "" +} + +function Get-ToolchainArchitectureProblem { + # Returns a description of why $Arch is unusable, or "" if it is fine. + # x86 and ARM64 fail for completely different reasons and deserve + # different advice: x86 means the wrong shell was opened and is a + # one-command fix, ARM64 means the platform is genuinely unsupported. + # (Mirrored in setup.ps1, which is also runnable on its own.) + param([string]$Arch) + if ($Arch -eq "" -or $Arch -eq "x64" -or $Arch -eq "amd64") { return "" } + if ($Arch -eq "x86") { + return ("cl.exe targets x86, but RandLAPACK and its BLAS/LAPACK backends are 64-bit " + + "(x64). You are in a 32-bit developer shell: 'Developer PowerShell for VS 2022' " + + "and 'Developer Command Prompt for VS 2022' both default to x86.`n Open 'x64 " + + "Native Tools Command Prompt for VS 2022' from the Start menu and re-run. If " + + "dependencies were already configured by the x86 compiler, delete the project " + + "directory first -- they are reused as-is and would keep failing.") + } + return ("cl.exe targets $Arch, which this installer does not support: the Windows build " + + "is x64-only. Intel oneMKL publishes no $Arch build, and the OpenBLAS binaries pinned " + + "here are x64. Supplying an $Arch BLAS/LAPACK through -Backend custom is the only " + + "route, and it is untested.`n If you meant to build x64, open 'x64 Native Tools " + + "Command Prompt for VS 2022'.") +} + # This script lives in \install\; the repository root is one level up. $sourceRoot = Split-Path $PSScriptRoot -Parent if (-not (Test-Path (Join-Path $sourceRoot "RandLAPACK.hh"))) { @@ -73,14 +140,22 @@ if (-not (Test-Path (Join-Path $sourceRoot "RandLAPACK.hh"))) { # failing later with a tool-specific error. $preflightProblems = @() if (-not (Get-Command "cl.exe" -ErrorAction SilentlyContinue)) { - $preflightProblems += ("cl.exe (the MSVC compiler) is not on PATH. Open 'Developer PowerShell " + - "for VS 2022' from the Start menu and run this script there. If Visual Studio is not " + - "installed:`n winget install Microsoft.VisualStudio.2022.Community --override " + - '"--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended"') + $preflightProblems += ("cl.exe (the MSVC compiler) is not on PATH. Open 'x64 Native Tools " + + "Command Prompt for VS 2022' from the Start menu and run this script there. If Visual " + + "Studio is not installed:`n winget install Microsoft.VisualStudio.2022.Community " + + '--override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended"') +} else { + # RandLAPACK and every BLAS backend the installer provisions are 64-bit. + # The plain 'Developer PowerShell/Command Prompt for VS 2022' entries + # default to an x86 toolchain, whose linker silently rejects the x64 + # import libraries; the failure then surfaces much later as BLAS++ + # reporting "BLAS library not found", which points at the wrong thing. + $archProblem = Get-ToolchainArchitectureProblem (Get-ClTargetArchitecture) + if ($archProblem -ne "") { $preflightProblems += $archProblem } } foreach ($tool in @( - @{ Name = "cmake.exe"; Hint = "CMake ships with the Visual Studio C++ workload; a Developer PowerShell puts it on PATH." }, - @{ Name = "ninja.exe"; Hint = "Ninja ships with the Visual Studio C++ workload; a Developer PowerShell puts it on PATH." }, + @{ Name = "cmake.exe"; Hint = "CMake ships with the Visual Studio C++ workload; an x64 Native Tools Command Prompt puts it on PATH." }, + @{ Name = "ninja.exe"; Hint = "Ninja ships with the Visual Studio C++ workload; an x64 Native Tools Command Prompt puts it on PATH." }, @{ Name = "git.exe"; Hint = "Install Git:`n winget install Git.Git" }, @{ Name = "curl.exe"; Hint = "curl.exe ships with Windows 10 (1803+) in System32; check your PATH includes it." })) { if (-not (Get-Command $tool.Name -ErrorAction SilentlyContinue)) { @@ -121,6 +196,7 @@ Write-Host "" # Step 1: dependencies (idempotent; reused when already present). & (Join-Path $sourceRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") ` -DependencyRoot $dependencyRoot -Backend $Backend -MklRoot $MklRoot ` + -NoDownload:$NoDownload -Yes:$Yes ` -BlasLibraries $BlasLibraries -LapackLibraries $LapackLibraries ` -BackendBinDir $BackendBinDir -BlasInt $BlasInt -BlasFortran $BlasFortran From 30d9cc4c65350cc846c7e87cacf37a8d84bdd28d Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 10:47:04 -0700 Subject: [PATCH 09/20] Windows CI: fix guard steps' exit code, harden downloads against transient errors Two failures on the first run of the new jobs. The guard steps assert that install.ps1 FAILS, and both correctly reported "OK: refused x86 / amd64_arm64 at preflight" -- then exited 1 anyway. `shell: powershell` exits the step with $LASTEXITCODE, which the intentional failure had left non-zero, so a passing assertion was reported as a failure. Clear it explicitly on the success path. Separately, the openblas leg hit `curl: (52) Empty reply from server` from GitHub's release CDN. `--retry` alone does not cover error 52; it retries only timeouts and 5xx-class responses. Add --retry-all-errors with a delay, and raise the count, on both the OpenBLAS and oneMKL downloads. Note the amd64_arm64 leg did its job on this first run: it exercised a real ARM64-targeting cl.exe on an x64 runner and the guard rejected it with the unsupported-platform message, which could not be verified locally. --- .github/actions/setup-randlapack-deps-windows/setup.ps1 | 6 ++++-- .github/workflows/core-windows.yaml | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 832d061e..9191250c 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -425,7 +425,8 @@ if ($Backend -eq "mkl") { $extractRoot = Join-Path $mklRoot "extract" foreach ($package in $mklPackages) { $archive = Join-Path $resolvedRoot "$($package.Id).$mklVersion.zip" - Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, + Invoke-Checked "curl.exe" @("-fsSL", "--retry", "5", "--retry-all-errors", + "--retry-delay", "3", "-o", $archive, "https://api.nuget.org/v3-flatcontainer/$($package.Id)/$mklVersion/$($package.Id).$mklVersion.nupkg") $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() if ($actual -ne $package.Sha256) { @@ -516,7 +517,8 @@ if ($Backend -eq "mkl") { } else { if (Test-Path $openblasRoot) { Remove-Item -Recurse -Force $openblasRoot } $archive = Join-Path $resolvedRoot "OpenBLAS-$openblasVersion-x64.zip" - Invoke-Checked "curl.exe" @("-fsSL", "--retry", "3", "-o", $archive, + Invoke-Checked "curl.exe" @("-fsSL", "--retry", "5", "--retry-all-errors", + "--retry-delay", "3", "-o", $archive, "https://github.com/OpenMathLib/OpenBLAS/releases/download/v$openblasVersion/OpenBLAS-$openblasVersion-x64.zip") $actual = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant() if ($actual -ne $openblasSha256) { diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 495e81ef..1d5e22c8 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -70,6 +70,11 @@ jobs: throw "The guard fired, but no '${{ matrix.expect }}' in its output: the wrong check failed." } Write-Host "OK: refused ${{ matrix.arch }} at preflight, with the expected explanation." + # This step succeeds when the installer FAILS, so the non-zero + # $LASTEXITCODE left by that intentional failure has to be cleared: + # `shell: powershell` exits the step with whatever it holds, which + # would report a pass as a failure. + exit 0 build-windows: name: windows-msvc-${{ matrix.backend }}-${{ matrix.int }}-${{ matrix.label }} From f42060ecccf56f026add0b193aa421b5eec60ad7 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 11:32:09 -0700 Subject: [PATCH 10/20] Windows: audit fixes -- honor -NoDownload everywhere, enable OpenMP, share the arch check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-audit of this PR turned up two bugs, three stale docs, one functional gap, and two structural cleanups. Bugs: - -NoDownload was silently ignored by -Backend openblas: only the prompt was gated on it, and the download ran regardless. A flag that says "never download" must never download, so this is now a clear error pointing at -Backend custom, which is the actual way to supply your own OpenBLAS. - install.ps1's -Backend help still described the oneMKL fallback as an automatic download; it asks first, and the -NoDownload entry three lines below contradicted it. Functional gap: install.ps1 passed -DCMAKE_DISABLE_FIND_PACKAGE_OpenMP=TRUE unconditionally, so users got a serial build even though this PR fixed MSVC OpenMP and core-windows exercises /openmp:llvm on every run. OpenMP is now on by default with -NoOpenMP to opt out. Verified locally: 749/749 tests pass with /openmp:llvm selected (four more tests than the serial build's 745). Docs: - INSTALL_WINDOWS.md verified the shell with bare `cl`, which is silent in both failing cases (no compiler at all, or a 32-bit one) -- neither reads as "wrong shell". Now uses `where cl`, which names the toolchain either way. - INSTALL_WINDOWS.md hardcoded a VS 2022 Community path for the PowerShell route, which is wrong for Build Tools (different directory, and under Program Files (x86)), for other editions, and for other versions. Replaced with a vswhere one-liner that works for any install. Note -products *: without it vswhere reports only full editions and finds nothing on Build Tools, which is exactly the configuration that hit this. - The interactive oneMKL prompt was undocumented; users now see it in §1. Structure: - Get-ClTargetArchitecture and Get-ToolchainArchitectureProblem were copy-pasted into install.ps1 and setup.ps1, with a test asserting the copies agreed. They now live in .github/scripts/windows/toolchain-arch.ps1, dot-sourced by both, which removes the duplication and the need for that test. - The three guard jobs became one. Every check in them asserts a refusal or runs pure logic, so each takes seconds while runner start-up dominates; core-windows drops from 6 Windows runners per event back to 4. --- .../setup-randlapack-deps-windows/setup.ps1 | 89 ++++++------------- .../windows/assert-toolchain-refused.ps1 | 34 +++++++ .../scripts/windows/test-toolchain-guard.ps1 | 59 ++++-------- .github/scripts/windows/toolchain-arch.ps1 | 75 ++++++++++++++++ .github/workflows/core-windows.yaml | 86 +++++++----------- INSTALL_WINDOWS.md | 56 +++++++++--- docs/CI.md | 40 +++++---- install/install.ps1 | 89 ++++++------------- 8 files changed, 283 insertions(+), 245 deletions(-) create mode 100644 .github/scripts/windows/assert-toolchain-refused.ps1 create mode 100644 .github/scripts/windows/toolchain-arch.ps1 diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 9191250c..6e640b1e 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -37,6 +37,9 @@ param( # where install.sh expects a system BLAS and errors without one. # Whatever is downloaded lands under : project-local, # nothing installed system-wide, removed when that directory is deleted. + # With -Backend openblas this always fails: OpenBLAS has no canonical + # Windows location to discover, so there is nothing to fall back to and + # the honest answer is to direct the user at -Backend custom. [switch]$NoDownload, # Skip interactive questions and take the documented default for each, @@ -76,44 +79,20 @@ function Invoke-Checked { } } +# Architecture detection is shared with install/install.ps1 rather than +# duplicated; see that file's note. $PSScriptRoot is +# \.github\actions\setup-randlapack-deps-windows. +$archHelper = Join-Path $PSScriptRoot "..\..\scripts\windows\toolchain-arch.ps1" +if (-not (Test-Path $archHelper)) { + throw "Missing $archHelper. This clone looks incomplete." +} +. $archHelper + function Convert-ToCMakePath { param([string]$Path) return $Path.Replace('\', '/') } -function Get-ClTargetArchitecture { - # Returns the compiler's TARGET architecture, lowercased ("x64", "x86", - # "arm64", "arm"), or "" if it genuinely cannot be determined. - # - # Three independent signals, most reliable first -- the same - # probe-several-things approach Find-OneMklLayout uses, and for the same - # reason: a missed detection here fails *open*, which defeats the check. - # 1. VSCMD_ARG_TGT_ARCH, exported by vcvarsall.bat / VsDevCmd (and so - # by ilammy/msvc-dev-cmd in CI). Never localized. - # 2. The toolset path: MSVC lays cl.exe out as - # ...\bin\Host\\cl.exe, a stable convention. - # 3. The banner, last, for anything matching neither of the above. - # On its own this would be wrong on a localized Visual Studio, where - # the words around the architecture are translated. - if ($env:VSCMD_ARG_TGT_ARCH) { return $env:VSCMD_ARG_TGT_ARCH.ToLowerInvariant() } - $cl = Get-Command "cl.exe" -ErrorAction SilentlyContinue - if (-not $cl) { return "" } - if ($cl.Source -match '\\bin\\Host[^\\]+\\([^\\]+)\\cl\.exe$') { - return $Matches[1].ToLowerInvariant() - } - # Native stderr merged via 2>&1 becomes ErrorRecords, which would throw - # under $ErrorActionPreference = "Stop"; relax it for this one call. - $previous = $ErrorActionPreference - $ErrorActionPreference = "Continue" - try { - $banner = (& $cl.Source 2>&1 | Out-String) - } finally { - $ErrorActionPreference = $previous - } - if ($banner -match '\bfor\s+(x64|x86|ARM64|ARM)\b') { return $Matches[1].ToLowerInvariant() } - return "" -} - # Prompts happen only on a terminal and only without -Yes, mirroring # install.sh's INTERACTIVE flag. Without this gate, a question asked under # GitHub Actions (or any piped stdin) blocks until the job times out. @@ -135,32 +114,6 @@ function Read-YesNo { } } -function Get-ToolchainArchitectureProblem { - # Returns a description of why $Arch is unusable, or "" if it is fine. - # x86 and ARM64 fail for completely different reasons and deserve - # different advice: x86 means the wrong shell was opened and is a - # one-command fix, ARM64 means the platform is genuinely unsupported. - # (Mirrored in install.ps1, which performs the same check up front.) - param([string]$Arch) - if ($Arch -eq "" -or $Arch -eq "x64" -or $Arch -eq "amd64") { return "" } - if ($Arch -eq "x86") { - return ("cl.exe targets x86, but RandLAPACK and its BLAS/LAPACK backends are 64-bit " + - "(x64).`n" + - " You are in a 32-bit developer shell. 'Developer PowerShell for VS 2022' and " + - "'Developer Command Prompt for VS 2022' both default to x86.`n" + - " Fix: open 'x64 Native Tools Command Prompt for VS 2022' from the Start menu " + - "(or run: cmd /k `"\VC\Auxiliary\Build\vcvars64.bat`") and re-run.`n" + - " Then delete the RandNLA-project directory before retrying: dependencies already " + - "configured by the x86 compiler are reused as-is and would keep failing.") - } - return ("cl.exe targets $Arch, which this installer does not support: the Windows build " + - "is x64-only.`n" + - " Intel oneMKL publishes no $Arch build, and the OpenBLAS binaries pinned here are " + - "x64. Supplying an $Arch BLAS/LAPACK through -Backend custom is the only route, and " + - "it is untested.`n" + - " If you meant to build x64, open 'x64 Native Tools Command Prompt for VS 2022'.") -} - function Assert-SupportedToolchain { # Left unchecked, a non-x64 toolchain surfaces much later as BLAS++ # reporting "BLAS library not found", which points at the wrong thing @@ -490,7 +443,23 @@ if ($Backend -eq "mkl") { # and MSYS2 all differ, and the release zips even ship CMake config # files with wrong hardcoded paths). So rather than probe and guess, ask # -- and only when someone is there to answer. - if ($script:Interactive -and -not $NoDownload) { + if ($NoDownload) { + # -NoDownload must mean what it says for every backend. There is no + # OpenBLAS to discover, so the only honest outcome is to stop and + # point at the backend that takes user-supplied libraries. + Write-Host "" + Write-Host "-Backend openblas needs to download OpenBLAS, and -NoDownload forbids that." + Write-Host "" + Write-Host " OpenBLAS has no canonical install location on Windows, so unlike oneMKL" + Write-Host " there is nothing to discover. To use a copy you already have:" + Write-Host "" + Write-Host " -Backend custom -BlasLibraries `"\libopenblas.lib`" ``" + Write-Host " -BackendBinDir `"`" ``" + Write-Host " -BlasInt lp64 -BlasFortran add" + Write-Host "" + throw "-Backend openblas requires a download; use -Backend custom or drop -NoDownload." + } + if ($script:Interactive) { if (Read-YesNo "Do you already have OpenBLAS installed?" $false) { Write-Host "" Write-Host "OpenBLAS has no standard layout on Windows, so point at the pieces" diff --git a/.github/scripts/windows/assert-toolchain-refused.ps1 b/.github/scripts/windows/assert-toolchain-refused.ps1 new file mode 100644 index 00000000..6fd8384b --- /dev/null +++ b/.github/scripts/windows/assert-toolchain-refused.ps1 @@ -0,0 +1,34 @@ +# Asserts that install.ps1 REFUSES the toolchain currently on PATH, with a +# message containing -Expect. Used by core-windows.yaml's guard job to prove +# the architecture check is wired end to end, not just correct in isolation. +# +# The installer is launched exactly as INSTALL_WINDOWS.md prescribes -- via +# cmd with -ExecutionPolicy Bypass -- so the documented invocation stays under +# test alongside the guard itself. + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$Expect +) + +$ErrorActionPreference = "Stop" + +$output = & cmd /c "powershell -ExecutionPolicy Bypass -File .\install\install.ps1 2>&1" +$installerExit = $LASTEXITCODE +$text = $output -join "`n" +Write-Host $text + +if ($installerExit -eq 0) { + throw "install.ps1 succeeded; the architecture guard did not fire." +} +if ($text -notmatch [regex]::Escape($Expect)) { + throw "The guard fired, but its output contained no '$Expect': the wrong check failed." +} +Write-Host "OK: refused at preflight with the expected explanation ('$Expect')." + +# This script succeeds when the installer FAILS, so the non-zero exit code +# that failure left behind has to be cleared: `shell: powershell` exits the +# step with whatever $LASTEXITCODE holds, which would report a pass as a +# failure. +exit 0 diff --git a/.github/scripts/windows/test-toolchain-guard.ps1 b/.github/scripts/windows/test-toolchain-guard.ps1 index e627f4b3..db6271e0 100644 --- a/.github/scripts/windows/test-toolchain-guard.ps1 +++ b/.github/scripts/windows/test-toolchain-guard.ps1 @@ -6,9 +6,6 @@ # the decision directly covers arm64 and arm on ordinary x64 hardware, and # covers them in seconds. # -# It also guards against drift. install.ps1 and setup.ps1 each carry a copy -# of these two functions (they run independently -- CI calls setup.ps1 alone, -# users call install.ps1), so this asserts the two copies still agree. # # The integration counterpart lives in core-windows.yaml, which runs the real # installer under a real x86 and a real cross-compiled arm64 toolchain and @@ -17,11 +14,8 @@ $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest -$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..\..") -$sources = @( - (Join-Path $repoRoot "install\install.ps1"), - (Join-Path $repoRoot ".github\actions\setup-randlapack-deps-windows\setup.ps1") -) +# Single shared implementation, dot-sourced by both install.ps1 and setup.ps1. +$source = Join-Path $PSScriptRoot "toolchain-arch.ps1" # Arch, whether it must be accepted, and a phrase the refusal must contain. # x86 and arm64 must not merely both fail -- they must fail with *different* @@ -67,48 +61,27 @@ foreach ($case in $Cases) { $savedArch = $env:VSCMD_ARG_TGT_ARCH $failures = 0 -$allVerdicts = @{} try { - foreach ($source in $sources) { - $label = Split-Path $source -Leaf - Write-Host "--- $label ---" - $verdicts = @(Get-GuardVerdicts -SourceFile $source -Cases $cases) - $allVerdicts[$label] = $verdicts - for ($i = 0; $i -lt $cases.Count; $i++) { - $case = $cases[$i] - $verdict = $verdicts[$i] - $accepted = ($verdict.Problem -eq "") - $ok = ($accepted -eq $case.ShouldPass) - if ($ok -and $case.Expect -ne "" -and $verdict.Problem -notmatch [regex]::Escape($case.Expect)) { - $ok = $false - Write-Host " (refused, but the message lacked '$($case.Expect)')" - } - if (-not $ok) { $failures++ } - Write-Host ("{0} {1,-6} detected={2,-6} {3}" -f - $(if ($ok) { "OK " } else { "FAIL" }), - $case.Arch, $verdict.Detected, - $(if ($accepted) { "accepted" } else { "refused" })) + $verdicts = @(Get-GuardVerdicts -SourceFile $source -Cases $cases) + for ($i = 0; $i -lt $cases.Count; $i++) { + $case = $cases[$i] + $verdict = $verdicts[$i] + $accepted = ($verdict.Problem -eq "") + $ok = ($accepted -eq $case.ShouldPass) + if ($ok -and $case.Expect -ne "" -and $verdict.Problem -notmatch [regex]::Escape($case.Expect)) { + $ok = $false + Write-Host " (refused, but the message lacked '$($case.Expect)')" } + if (-not $ok) { $failures++ } + Write-Host ("{0} {1,-6} detected={2,-6} {3}" -f + $(if ($ok) { "OK " } else { "FAIL" }), + $case.Arch, $verdict.Detected, + $(if ($accepted) { "accepted" } else { "refused" })) } } finally { $env:VSCMD_ARG_TGT_ARCH = $savedArch } -# Drift check: both copies must reach identical verdicts. -Write-Host "--- install.ps1 vs setup.ps1 agreement ---" -$left = $allVerdicts["install.ps1"] -$right = $allVerdicts["setup.ps1"] -for ($i = 0; $i -lt $cases.Count; $i++) { - $leftAccepted = ($left[$i].Problem -eq "") - $rightAccepted = ($right[$i].Problem -eq "") - if ($leftAccepted -ne $rightAccepted) { - $failures++ - Write-Host ("FAIL {0}: install.ps1 and setup.ps1 disagree" -f $cases[$i].Arch) - } else { - Write-Host ("OK {0}: both agree" -f $cases[$i].Arch) - } -} - Write-Host "" if ($failures -gt 0) { throw "$failures toolchain-guard assertion(s) failed." } Write-Host "All toolchain-guard assertions passed." diff --git a/.github/scripts/windows/toolchain-arch.ps1 b/.github/scripts/windows/toolchain-arch.ps1 new file mode 100644 index 00000000..42cb8d0d --- /dev/null +++ b/.github/scripts/windows/toolchain-arch.ps1 @@ -0,0 +1,75 @@ +# Toolchain architecture detection, shared by install/install.ps1 (user-facing +# preflight) and .github/actions/setup-randlapack-deps-windows/setup.ps1 (which +# also runs standalone in CI). Dot-source it; it defines functions only. +# +# Why this check exists: RandLAPACK and every BLAS backend the installer +# provisions are 64-bit, but the "Developer PowerShell for VS" and "Developer +# Command Prompt for VS" Start-menu entries both default to an *x86* toolchain. +# An x86 linker cannot use an x64 import library, and the failure surfaces +# three layers down as BLAS++ reporting "BLAS library not found" -- which +# blames the libraries when the compiler is at fault. Note that the shell's own +# bitness is not a usable signal: the Developer Command Prompt is a 64-bit +# process that still selects x86 tools. + +function Get-ClTargetArchitecture { + # Returns the compiler's TARGET architecture, lowercased ("x64", "x86", + # "arm64", "arm"), or "" if it genuinely cannot be determined. + # + # Three independent signals, most reliable first -- the same + # probe-several-things approach Find-OneMklLayout uses, and for the same + # reason: a missed detection here fails *open*, which defeats the check. + # 1. VSCMD_ARG_TGT_ARCH, exported by vcvarsall.bat / VsDevCmd (and so + # by ilammy/msvc-dev-cmd in CI). Never localized. + # 2. The toolset path: MSVC lays cl.exe out as + # ...\bin\Host\\cl.exe, a stable convention. + # 3. The banner, last, for anything matching neither of the above. + # On its own this would be wrong on a localized Visual Studio, where + # the words around the architecture are translated. + if ($env:VSCMD_ARG_TGT_ARCH) { return $env:VSCMD_ARG_TGT_ARCH.ToLowerInvariant() } + $cl = Get-Command "cl.exe" -ErrorAction SilentlyContinue + if (-not $cl) { return "" } + if ($cl.Source -match '\\bin\\Host[^\\]+\\([^\\]+)\\cl\.exe$') { + return $Matches[1].ToLowerInvariant() + } + # Native stderr merged via 2>&1 becomes ErrorRecords, which would throw + # under $ErrorActionPreference = "Stop"; relax it for this one call. + $previous = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $banner = (& $cl.Source 2>&1 | Out-String) + } finally { + $ErrorActionPreference = $previous + } + if ($banner -match '\bfor\s+(x64|x86|ARM64|ARM)\b') { return $Matches[1].ToLowerInvariant() } + return "" +} + +function Get-ToolchainArchitectureProblem { + # Returns a description of why $Arch is unusable, or "" if it is fine. + # x86 and ARM64 fail for completely different reasons and deserve + # different advice: x86 means the wrong shell was opened and is a + # one-command fix, ARM64 means the platform is genuinely unsupported. + param([string]$Arch) + if ($Arch -eq "" -or $Arch -eq "x64" -or $Arch -eq "amd64") { return "" } + if ($Arch -eq "x86") { + # Single-quoted: the cmd one-liner contains both double quotes and + # backticks, which are literal here but would need escaping in a + # double-quoted PowerShell string. + $vcvarsHint = 'for /f "usebackq delims=" %i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do call "%i\VC\Auxiliary\Build\vcvars64.bat"' + return ("cl.exe targets x86, but RandLAPACK and its BLAS/LAPACK backends are 64-bit " + + "(x64).`n" + + " You are in a 32-bit developer shell. 'Developer PowerShell for VS 2022' and " + + "'Developer Command Prompt for VS 2022' both default to x86.`n" + + " Fix: open 'x64 Native Tools Command Prompt for VS 2022' from the Start menu, " + + "or run this in any Command Prompt (any edition or version):`n" + + " $vcvarsHint`n" + + " Then delete the RandNLA-project directory before retrying: dependencies already " + + "configured by the x86 compiler are reused as-is and would keep failing.") + } + return ("cl.exe targets $Arch, which this installer does not support: the Windows build " + + "is x64-only.`n" + + " Intel oneMKL publishes no $Arch build, and the OpenBLAS binaries pinned here are " + + "x64. Supplying an $Arch BLAS/LAPACK through -Backend custom is the only route, and " + + "it is untested.`n" + + " If you meant to build x64, open 'x64 Native Tools Command Prompt for VS 2022'.") +} diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 1d5e22c8..1e24928a 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -9,72 +9,50 @@ on: - cqrrp-gpu-benchmarking jobs: - # Decision-level test of the architecture guard. Needs no MSVC and no - # build, so it covers arm64 and arm -- which we cannot build for, and so - # can never cover with a build leg -- in a few seconds on x64 hardware. - # Also asserts install.ps1's and setup.ps1's duplicated copies agree. - windows-guard-logic: - name: windows-guard-logic - runs-on: windows-2022 - steps: - - uses: actions/checkout@v4 - - name: architecture guard decision table - shell: powershell - run: .\.github\scripts\windows\test-toolchain-guard.ps1 - # Structural gate for the documented *user* path, which the build matrix - # below cannot cover: it initializes MSVC with an explicit arch: x64 and - # runs under pwsh, so it has never exercised the shell the install docs - # tell users to open. That gap is exactly how a 32-bit toolchain reached a - # collaborator and failed three layers down as "BLAS library not found". + # below cannot cover: it initializes MSVC with an explicit arch: x64 under + # pwsh, so it has never exercised the shell the install docs tell users to + # open. That gap is exactly how a 32-bit toolchain reached a collaborator and + # failed three layers down as "BLAS library not found". # - # These legs assert a *refusal*, so they stop at preflight in seconds and - # never build a dependency: cheap enough to keep permanently. arch x86 is - # the real-world case; amd64_arm64 cross-compiles, which yields an - # ARM64-targeting cl.exe on an ordinary x64 runner and so covers the ARM - # branch of the guard without ARM hardware. + # One job, not three: every check here asserts a *refusal* or runs pure + # logic, so each takes seconds and runner start-up dominates. Splitting them + # would triple the Windows runner count for no extra coverage. windows-toolchain-guards: - name: windows-guard-${{ matrix.arch }} + name: windows-toolchain-guards runs-on: windows-2022 - strategy: - fail-fast: false - matrix: - include: - - arch: x86 - expect: targets x86 - - arch: amd64_arm64 - expect: targets arm64 steps: - uses: actions/checkout@v4 - - name: initialize MSVC (${{ matrix.arch }}) + # Pure logic, no MSVC needed. This is the only way to cover arm64 and + # arm: we cannot build for them, so no build leg could ever test them. + - name: architecture guard decision table + shell: powershell + run: .\.github\scripts\windows\test-toolchain-guard.ps1 + + # Integration counterparts. x86 is the real-world case; amd64_arm64 + # cross-compiles, yielding an arm64-targeting cl.exe on an ordinary x64 + # runner, so the arm branch is exercised without ARM hardware. + - name: initialize MSVC (x86) uses: ilammy/msvc-dev-cmd@v1 with: - arch: ${{ matrix.arch }} + arch: x86 # Windows PowerShell 5.1, not pwsh: this is what a user actually gets, - # and the launch line is verbatim the one INSTALL_WINDOWS.md prescribes - # (cmd + -ExecutionPolicy Bypass), so the documented invocation itself - # stays under test. - - name: install.ps1 must refuse a ${{ matrix.arch }} toolchain + # and the launch line is verbatim the one INSTALL_WINDOWS.md prescribes, + # so the documented invocation itself stays under test. + - name: install.ps1 must refuse an x86 toolchain shell: powershell - run: | - $output = & cmd /c "powershell -ExecutionPolicy Bypass -File .\install\install.ps1 2>&1" - $exit = $LASTEXITCODE - $text = $output -join "`n" - Write-Host $text - if ($exit -eq 0) { - throw "install.ps1 succeeded with a ${{ matrix.arch }} toolchain; the architecture guard did not fire." - } - if ($text -notmatch '${{ matrix.expect }}') { - throw "The guard fired, but no '${{ matrix.expect }}' in its output: the wrong check failed." - } - Write-Host "OK: refused ${{ matrix.arch }} at preflight, with the expected explanation." - # This step succeeds when the installer FAILS, so the non-zero - # $LASTEXITCODE left by that intentional failure has to be cleared: - # `shell: powershell` exits the step with whatever it holds, which - # would report a pass as a failure. - exit 0 + run: .\.github\scripts\windows\assert-toolchain-refused.ps1 -Expect "targets x86" + + - name: initialize MSVC (amd64_arm64) + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64_arm64 + + - name: install.ps1 must refuse an arm64 toolchain + shell: powershell + run: .\.github\scripts\windows\assert-toolchain-refused.ps1 -Expect "targets arm64" build-windows: name: windows-msvc-${{ matrix.backend }}-${{ matrix.int }}-${{ matrix.label }} diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 55234aa5..e2787f5c 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -42,6 +42,25 @@ their own -- that is what vcpkg, Conan, and NuGet exist for. Anything the installer downloads goes inside `RandNLA-project\`, never into your system, and deleting that directory removes it completely. +If no oneMKL is found, the installer explains what it searched and asks before +downloading anything: + +``` +No existing oneMKL found (checked -MklRoot, $env:MKLROOT, $env:ONEAPI_ROOT, +and C:\Program Files (x86)\Intel\oneAPI\mkl\latest). + +A pinned, checksum-verified copy (~155 MB) can be downloaded into + ...\RandNLA-project\install +It is used only by this project: nothing is installed system-wide, no PATH +or registry changes, and deleting that directory removes it completely. + +Download oneMKL now? [Y/n] +``` + +Answering no prints the alternatives and stops. Questions are skipped when the +installer is not attached to a terminal (a script, a pipeline, CI), taking the +default shown in brackets; `-Yes` skips them in an interactive session too. + If you would rather supply the library yourself, use `-MklRoot` to point at an existing oneMKL, or `-NoDownload` to make a missing one an error instead of a download. See §4. @@ -94,24 +113,32 @@ about *which shell you start from*: backend here are 64-bit, and a 32-bit linker cannot use an x64 import library. Use **"x64 Native Tools Command Prompt for VS 2022"** instead. -You can confirm you are in the right place with: +Confirm you are in the right place with: ``` -cl +where cl ``` -The banner must end in `for x64`. If it says `for x86`, you are in a 32-bit -shell. The installer's preflight check will also stop you with this same -explanation, so you cannot get far down the wrong path. +The path it prints must contain `Hostx64\x64`. If it prints +`INFO: Could not find files for the given pattern(s)`, you are in a plain +shell with no compiler; if it contains `Hostx86\x86`, you are in a 32-bit +developer shell. Use `where cl` rather than running `cl` alone: in the failing +cases `cl` either is not found or prints a usage banner, and neither reads as +"wrong shell". The installer's preflight check catches this too, so you cannot +get far down the wrong path. -If you prefer PowerShell to `cmd`, there is no x64 PowerShell entry in the -Start menu, so ask for the architecture explicitly: +**If the Start-menu entry is missing or named differently** (Build Tools, a +different edition, or a Visual Studio version other than 2022), this works +from any Command Prompt regardless of edition or version -- it asks Visual +Studio's own locator where it is installed: -```powershell -Import-Module "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" -Enter-VsDevShell -VsInstallPath "C:\Program Files\Microsoft Visual Studio\2022\Community" -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64" +```bat +for /f "usebackq delims=" %i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do call "%i\VC\Auxiliary\Build\vcvars64.bat" ``` +The `-products *` matters: without it `vswhere` reports only full Visual +Studio editions and silently finds nothing on a Build Tools install. + ### Script execution policy Windows refuses to run PowerShell scripts at all under its default @@ -155,7 +182,14 @@ and OpenBLAS are. -NoDownload Fail instead of downloading a backend that was not found locally. The default fetches one into ; nothing is installed system-wide, and - deleting removes it. + deleting removes it. With -Backend + openblas this always fails, because OpenBLAS has no + canonical Windows location to discover -- use + -Backend custom to supply your own. +-NoOpenMP Build serially. The default enables OpenMP through + MSVC's /openmp:llvm runtime, the only mode that + accepts RandLAPACK's 64-bit loop indices; a serial + build is fully functional too. -Yes Skip interactive questions, taking each documented default. Questions are already skipped when stdin is not a terminal, so CI never needs this. diff --git a/docs/CI.md b/docs/CI.md index 4513452d..e51b1490 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -12,8 +12,7 @@ the thing we tell users to type is itself under test). | core-linux | `build-asan` | ubuntu-latest | Debug + AddressSanitizer build and test run | no | | core-macos | `build` | macos-latest | same as Linux `build`, on Apple's toolchain | **yes** (`build`) | | core-macos | `build-asan` | macos-latest | Debug + AddressSanitizer build and test run | no | -| core-windows | `windows-guard-logic` | windows-2022 | Architecture-guard decision table (`test-toolchain-guard.ps1`); no MSVC, no build, seconds | no (new) | -| core-windows | `windows-toolchain-guards` | windows-2022 | Asserts `install.ps1` *refuses* a real x86 and a cross-compiled arm64 toolchain, launched exactly as the docs prescribe | no (new) | +| core-windows | `windows-toolchain-guards` | windows-2022 | Architecture-guard decision table, plus assertions that `install.ps1` *refuses* a real x86 and a cross-compiled arm64 toolchain, launched exactly as the docs prescribe | no (new) | | core-windows | `build-windows` | windows-2022 | MSVC build + tests: oneMKL ILP64 serial, oneMKL ILP64 OpenMP (`/openmp:llvm`), OpenBLAS LP64 serial; each leg ends with a stripped-PATH run of a staged test executable | no (new) | | install-script | `install-linux` | ubuntu-latest | `install.sh`: fresh install, idempotent re-run, dependency-discovery path | **yes** | | install-script | `install-macos` | macos-latest | `install.sh`: fresh install, idempotent re-run | **yes** | @@ -72,20 +71,23 @@ candidates once they have a green track record. the install docs actually tell users to open. That gap is precisely how a 32-bit toolchain reached a collaborator in 2026-08 and failed three layers down as BLAS++ reporting "BLAS library not found" (the libraries were fine; - an x86 linker simply cannot use an x64 import library). The guards close it - from two directions and are cheap because both assert a *refusal*, so - neither builds a dependency: - - `windows-guard-logic` drives the decision over a table of - architectures. This is the only way to cover **arm64** and **arm**: we - cannot build for them, so no build leg can ever test them. It also - asserts that `install.ps1`'s and `setup.ps1`'s duplicated copies of the - check still agree, since they run independently. - - `windows-toolchain-guards` runs the real installer under a real x86 - toolchain and an `amd64_arm64` cross-compiling one (which yields an - arm64-targeting `cl.exe` on ordinary x64 hardware, so no ARM runner is - needed). It launches via `cmd` + `-ExecutionPolicy Bypass` under - Windows PowerShell 5.1, so the documented invocation stays under test - too, not just the guard. + an x86 linker simply cannot use an x64 import library). `windows-toolchain-guards` closes it + from two directions in **one** job: every check asserts a *refusal* or runs + pure logic, so each takes seconds and runner start-up dominates -- splitting + them across jobs would multiply the Windows runner count for no coverage. + - The decision table (`test-toolchain-guard.ps1`) drives the guard over + every architecture. This is the only way to cover **arm64** and **arm**: + we cannot build for them, so no build leg can ever test them. + - The integration steps (`assert-toolchain-refused.ps1`) run the real + installer under a real x86 toolchain and an `amd64_arm64` cross-compiling + one, which yields an arm64-targeting `cl.exe` on ordinary x64 hardware, + so no ARM runner is needed. They launch via `cmd` + + `-ExecutionPolicy Bypass` under Windows PowerShell 5.1, so the documented + invocation stays under test too, not just the guard. + - A step asserting a *failure* must reset `$LASTEXITCODE` before returning: + `shell: powershell` exits the step with whatever it holds, so the + intentional non-zero code reports a passing assertion as red. This bit us + on the guards' first run. x86 and arm64 must fail with *different* messages: x86 is a wrong-shell mistake with a one-command fix, arm64 is an unsupported platform (no oneMKL build exists for it). The tests pin that distinction. @@ -108,6 +110,12 @@ candidates once they have a green track record. Linux/macOS behavior, where `install.sh` expects a system BLAS and errors without one. Everything downloaded lands under the dependency root, so a runner's cache and a user's project directory stay self-contained. +- **The architecture check lives in one file, dot-sourced by both callers.** + `.github/scripts/windows/toolchain-arch.ps1` is shared by `install.ps1` and + `setup.ps1`, which run independently (CI calls `setup.ps1` alone, users call + `install.ps1`). It was briefly duplicated, with a test asserting the copies + agreed; sharing the file removes both the duplication and the need for that + test. - **The architecture check reads several signals, not just `cl.exe`'s banner.** It prefers `VSCMD_ARG_TGT_ARCH`, then the `bin\Host\\cl.exe` path convention, and only then the banner. diff --git a/install/install.ps1 b/install/install.ps1 index 18290fd0..b9137e91 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -15,17 +15,17 @@ # # What it does, mirroring install.sh's layout in a sibling RandNLA-project # directory: -# 1. Builds/reuses the dependencies (oneMKL from Intel's NuGet packages -# or -MklRoot, GoogleTest, Random123, BLAS++, LAPACK++) under +# 1. Builds/reuses the dependencies (a BLAS/LAPACK backend, GoogleTest, +# Random123, BLAS++, LAPACK++) under # \install. # 2. Configures, builds, installs, and tests RandLAPACK. # # Options: # -ProjectDir Where dependencies/builds/installs go # (default: ..\RandNLA-project relative to this script). -# -Backend BLAS/LAPACK backend: mkl (default; auto-discovers an -# installed oneAPI, else downloads Intel's pinned NuGet -# packages), openblas (official release binaries), or +# -Backend BLAS/LAPACK backend: mkl (default; discovered from an +# installed oneAPI, otherwise offered as a pinned +# download), openblas (official release binaries), or # custom (bring your own via -BlasLibraries). # -MklRoot Use this oneMKL install (oneAPI layout) instead of # auto-discovery. Backend mkl only. @@ -41,11 +41,13 @@ # -Fresh Reconfigure RandLAPACK from scratch (dependencies are # always reused when present; delete \install # subdirectories to force dependency rebuilds). +# -NoOpenMP Build serially. The default enables OpenMP via MSVC's +# /openmp:llvm runtime (the only mode that accepts +# RandLAPACK's 64-bit loop indices and collapse +# clauses); a serial build is fully functional too. # -SkipTests Do not run the test suite after building. # -# GPU support is not available on native Windows yet. OpenMP is disabled on -# MSVC for now (see .github/scripts/windows/run-ci.ps1 for why); RandLAPACK -# is fully functional serially. +# GPU support is not available on native Windows yet. [CmdletBinding()] param( @@ -55,6 +57,7 @@ param( [string]$MklRoot = "", [switch]$NoDownload, [switch]$Yes, + [switch]$NoOpenMP, [string]$BlasLibraries = "", [string]$LapackLibraries = "", [string]$BackendBinDir = "", @@ -79,62 +82,21 @@ function Invoke-Checked { } } -function Get-ClTargetArchitecture { - # Returns the compiler's TARGET architecture, lowercased ("x64", "x86", - # "arm64", "arm"), or "" if it genuinely cannot be determined. - # Three independent signals, most reliable first; a missed detection here - # fails *open*, which would defeat the check entirely. Parsing the banner - # alone would be wrong on a localized Visual Studio, where the words - # around the architecture are translated. - # (Mirrored in setup.ps1, which is also runnable on its own.) - if ($env:VSCMD_ARG_TGT_ARCH) { return $env:VSCMD_ARG_TGT_ARCH.ToLowerInvariant() } - $cl = Get-Command "cl.exe" -ErrorAction SilentlyContinue - if (-not $cl) { return "" } - if ($cl.Source -match '\\bin\\Host[^\\]+\\([^\\]+)\\cl\.exe$') { - return $Matches[1].ToLowerInvariant() - } - # Native stderr merged via 2>&1 becomes ErrorRecords, which would throw - # under $ErrorActionPreference = "Stop"; relax it for this one call. - $previous = $ErrorActionPreference - $ErrorActionPreference = "Continue" - try { - $banner = (& $cl.Source 2>&1 | Out-String) - } finally { - $ErrorActionPreference = $previous - } - if ($banner -match '\bfor\s+(x64|x86|ARM64|ARM)\b') { return $Matches[1].ToLowerInvariant() } - return "" -} - -function Get-ToolchainArchitectureProblem { - # Returns a description of why $Arch is unusable, or "" if it is fine. - # x86 and ARM64 fail for completely different reasons and deserve - # different advice: x86 means the wrong shell was opened and is a - # one-command fix, ARM64 means the platform is genuinely unsupported. - # (Mirrored in setup.ps1, which is also runnable on its own.) - param([string]$Arch) - if ($Arch -eq "" -or $Arch -eq "x64" -or $Arch -eq "amd64") { return "" } - if ($Arch -eq "x86") { - return ("cl.exe targets x86, but RandLAPACK and its BLAS/LAPACK backends are 64-bit " + - "(x64). You are in a 32-bit developer shell: 'Developer PowerShell for VS 2022' " + - "and 'Developer Command Prompt for VS 2022' both default to x86.`n Open 'x64 " + - "Native Tools Command Prompt for VS 2022' from the Start menu and re-run. If " + - "dependencies were already configured by the x86 compiler, delete the project " + - "directory first -- they are reused as-is and would keep failing.") - } - return ("cl.exe targets $Arch, which this installer does not support: the Windows build " + - "is x64-only. Intel oneMKL publishes no $Arch build, and the OpenBLAS binaries pinned " + - "here are x64. Supplying an $Arch BLAS/LAPACK through -Backend custom is the only " + - "route, and it is untested.`n If you meant to build x64, open 'x64 Native Tools " + - "Command Prompt for VS 2022'.") -} - # This script lives in \install\; the repository root is one level up. $sourceRoot = Split-Path $PSScriptRoot -Parent if (-not (Test-Path (Join-Path $sourceRoot "RandLAPACK.hh"))) { throw "install.ps1 must sit in the install\ directory of a RandLAPACK clone." } +# Architecture detection is shared with setup.ps1 rather than duplicated: the +# two scripts run independently (CI calls setup.ps1 alone, users call this), +# and two copies of a safety check drift. +$archHelper = Join-Path $sourceRoot ".github\scripts\windows\toolchain-arch.ps1" +if (-not (Test-Path $archHelper)) { + throw "Missing $archHelper. This clone looks incomplete." +} +. $archHelper + # ------------------------------------------------------ preflight checks ---- # Catch every missing prerequisite up front, each with its fix, instead of # failing later with a tool-specific error. @@ -207,7 +169,7 @@ if ($Fresh -and (Test-Path $buildDir)) { } $stageDllDirs = if ($env:RANDNLA_BLAS_BIN) { $env:RANDNLA_BLAS_BIN.Replace('\', '/') } else { "" } -Invoke-Checked "cmake" @( +$configureArgs = @( "-S", $sourceRoot, "-B", $buildDir, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release", @@ -216,8 +178,13 @@ Invoke-Checked "cmake" @( "-Dlapackpp_DIR=$env:lapackpp_DIR", "-DRandom123_DIR=$env:Random123_DIR", "-DCMAKE_PREFIX_PATH=$env:googletest_PREFIX", - "-DRANDLAPACK_RUNTIME_DLL_DIRS=$stageDllDirs", - "-DCMAKE_DISABLE_FIND_PACKAGE_OpenMP=TRUE") + "-DRANDLAPACK_RUNTIME_DLL_DIRS=$stageDllDirs") +# OpenMP is ON by default. RandLAPACK's CMake selects MSVC's /openmp:llvm +# runtime, the only mode that accepts its 64-bit loop indices and collapse +# clauses; core-windows exercises that configuration on every run. -NoOpenMP +# builds serially, which is also fully functional. +if ($NoOpenMP) { $configureArgs += "-DCMAKE_DISABLE_FIND_PACKAGE_OpenMP=TRUE" } +Invoke-Checked "cmake" $configureArgs Invoke-Checked "cmake" @("--build", $buildDir, "--target", "install") if (-not $SkipTests) { From d29944dba69e911c612c3f65f7019af34793d16f Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 11:54:29 -0700 Subject: [PATCH 11/20] Windows: fix BLAS++ space-in-path failure; pull BLAS++/LAPACK++ from upstream, pinned Two findings from running every backend path against a real oneMKL install. 1. The architecture fix was necessary but NOT sufficient. With a correct x64 toolchain and our own dgemm_/dgesv_ link check passing, BLAS++ still reported "BLAS library not found". Its try_compile log gives the real reason: ninja: error: 'C:/Program', needed by 'cmTC_x.exe', missing BLAS++ feeds library paths into that probe unquoted, so a path containing a space splits at the space. Intel installs oneMKL to C:\Program Files (x86)\Intel\oneAPI\ by default, so every *discovered* oneMKL hits this, as does any custom backend under Program Files. It was invisible to CI and to earlier local runs because the downloaded oneMKL lands in a space-free directory under the dependency root -- the one layout that avoids it. Proven by copying the identical libraries to a space-free path, after which BLAS++ finds them immediately. Worked around by staging backend import libraries into a space-free directory when, and only when, a path contains a space. Import libraries only name their DLL, which is still resolved at run time from the backend's bin directory, so this is safe. The underlying quoting bug is BLAS++'s and is worth reporting upstream. 2. BLAS++ and LAPACK++ were cloned from BallisticLA forks carrying two one-line MSVC fixes. Both merged upstream on 2026-08-06 (blaspp #132, lapackpp #87), so the forks are obsolete; both now come from icl-utk-edu. Verified the pinned commits carry the fixes. They were also pinned to *branch names*, i.e. a moving tip, inside a cache keyed on this script -- so a cache hit could restore a different revision than a cache miss builds. That is the same defect already fixed here for Random123. Clone-Head is now Clone-Pinned and takes a tag or a commit SHA, so every dependency is pinned to an immutable ref: blaspp 3057185, lapackpp 40b9d0d, GoogleTest v1.17.0, Random123 v1.14.0. Commits rather than tags for the first two only because the latest release of each, v2025.05.28, predates the merges. Cache keys renamed off the fork branches and bumped, so no fork-built artifact can be restored. Verified on Windows against a real oneAPI install at the default (spaced) location: discovery path 749/749 tests, and a full build from the upstream pins 749/749, with both clones confirmed to originate from icl-utk-edu. Also corrects two unverified doc claims: the quick start recommended a VS edition and winget invocation that had never been run (missing --wait, which returns while the installer is still going), and INSTALL_SCRIPT.md suggested cmake@3.27 two paragraphs after stating the floor is 3.21. --- .../setup-randlapack-deps-windows/action.yml | 4 +- .../setup-randlapack-deps-windows/setup.ps1 | 86 ++- INSTALL_SCRIPT.md | 710 +++++++++--------- INSTALL_WINDOWS.md | 15 +- 4 files changed, 441 insertions(+), 374 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/action.yml b/.github/actions/setup-randlapack-deps-windows/action.yml index ddba4bb5..b0a7d64f 100644 --- a/.github/actions/setup-randlapack-deps-windows/action.yml +++ b/.github/actions/setup-randlapack-deps-windows/action.yml @@ -64,13 +64,13 @@ runs: uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\blaspp-${{ inputs.blas-backend }}-install - key: windows-msvc-blaspp-remove-symv-debug-print-${{ inputs.blas-backend }}-ninja-r1 + key: windows-msvc-blaspp-upstream-3057185-${{ inputs.blas-backend }}-ninja-r2 - name: cache LAPACK++ uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\lapackpp-${{ inputs.blas-backend }}-install - key: windows-msvc-lapackpp-msvc-direct-includes-${{ inputs.blas-backend }}-ninja-r1 + key: windows-msvc-lapackpp-upstream-40b9d0d-${{ inputs.blas-backend }}-ninja-r2 - name: build missing dependencies shell: pwsh diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 6e640b1e..9804e7eb 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -5,12 +5,14 @@ # custom/bring-your-own libraries # - GoogleTest v1.17.0 # - Random123 (headers only) -# - BLAS++ from BallisticLA/blaspp, branch remove-symv-debug-print -# - LAPACK++ from BallisticLA/lapackpp, branch msvc-direct-includes +# - BLAS++ from icl-utk-edu/blaspp (upstream), pinned by commit +# - LAPACK++ from icl-utk-edu/lapackpp (upstream), pinned by commit # -# The BallisticLA branches carry the two one-line MSVC fixes that upstream -# icl-utk-edu has not merged yet (blaspp PR #132, lapackpp PR #87). Once those -# merge, both clones below can move back to upstream master. +# Everything is fetched from its canonical upstream and pinned to an immutable +# ref. The two one-line MSVC fixes this build needs merged upstream on +# 2026-08-06 (blaspp PR #132, lapackpp PR #87), so the BallisticLA forks these +# once pointed at are no longer needed. The pins are commits rather than tags +# only because the latest release of each, v2025.05.28, predates those merges. # # Every step is idempotent: work already present under -DependencyRoot (for # example, restored from a CI cache) is left alone. Run from an MSVC developer @@ -135,16 +137,23 @@ function Find-PackageConfigDirectory { return $config.DirectoryName } -function Clone-Head { - param([string]$Url, [string]$Destination, [string]$Branch = "") +function Clone-Pinned { + # -Ref is a tag or a commit SHA. Everything here is pinned to an immutable + # ref on purpose: a branch tip moves, and these clones live inside a cache + # keyed on this script, so a moving tip would silently change what a + # "cache hit" restores. + param([string]$Url, [string]$Destination, [string]$Ref) if (Test-Path $Destination) { Write-Host "Reusing existing clone at $Destination" return } - $cloneArgs = @("clone", "--depth", "1") - if ($Branch -ne "") { $cloneArgs += @("--branch", $Branch) } - $cloneArgs += @($Url, $Destination) - Invoke-Checked "git" $cloneArgs + # A tag can be cloned shallowly by name; a SHA cannot, so fetch it + # directly (GitHub allows fetching a reachable commit by SHA). + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + Invoke-Checked "git" @("-C", $Destination, "init", "--quiet") + Invoke-Checked "git" @("-C", $Destination, "remote", "add", "origin", $Url) + Invoke-Checked "git" @("-C", $Destination, "fetch", "--quiet", "--depth", "1", "origin", $Ref) + Invoke-Checked "git" @("-C", $Destination, "checkout", "--quiet", "FETCH_HEAD") } function Export-GitHubValue { @@ -560,6 +569,46 @@ if ($Backend -eq "custom") { $backendId = "custom-$($hashHex.Substring(0, 8))" } +function Copy-LibrariesToSpaceFreePath { + # BLAS++'s BLASFinder feeds library paths into a try_compile *unquoted*, + # so under the Ninja generator a path containing a space is split at the + # space and the probe dies with, e.g.: + # ninja: error: 'C:/Program', needed by 'cmTC_x.exe', missing + # BLASFinder reports only "BLAS library not found", which points at the + # library rather than at the path that broke. + # + # This is not a corner case: Intel installs oneMKL to + # C:\Program Files (x86)\Intel\oneAPI\ by default, so EVERY discovered + # oneMKL hits it, as does any custom backend under Program Files. It is + # invisible to CI and to the download path because those land in a + # space-free directory under the dependency root. + # + # Import libraries are self-contained (they only name their DLL, which is + # still resolved at run time from $backendBin), so copying them next to + # the rest of the dependency tree is safe. Only done when needed, to keep + # the common case transparent. + param([string[]]$Libraries, [string]$Destination, [string]$Label) + if (-not ($Libraries | Where-Object { $_ -match ' ' })) { return $Libraries } + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + Write-Host ("Staging $Label import libraries into a space-free path " + + "($Destination): BLAS++ cannot probe libraries whose path contains a space.") + return @($Libraries | ForEach-Object { + $leaf = Split-Path $_ -Leaf + $target = Join-Path $Destination $leaf + Copy-Item -LiteralPath $_.Replace('/', '\') -Destination $target -Force + Convert-ToCMakePath $target + }) +} + +$spaceFreeLibDir = Join-Path $resolvedRoot "backend-libs" +$backendLibraries = Copy-LibrariesToSpaceFreePath -Libraries $backendLibraries ` + -Destination $spaceFreeLibDir -Label "BLAS" +if ($backendLapackLibraries -ne "") { + $backendLapackLibraries = (Copy-LibrariesToSpaceFreePath ` + -Libraries @($backendLapackLibraries -split ';') ` + -Destination $spaceFreeLibDir -Label "LAPACK") -join ';' +} + if ($backendBin -ne "") { $env:PATH = "$backendBin;$env:PATH" } # ------------------------------------------------------------- GoogleTest ---- @@ -570,7 +619,7 @@ if (Test-Path (Join-Path $gtestInstall "include\gtest\gtest.h")) { Write-Host "Reusing GoogleTest at $gtestInstall" } else { $gtestSrc = Join-Path $resolvedRoot "$gtestVariant-src" - Clone-Head "https://github.com/google/googletest.git" $gtestSrc "v1.17.0" + Clone-Pinned "https://github.com/google/googletest.git" $gtestSrc "v1.17.0" $gtestBuild = Join-Path $resolvedRoot "$gtestVariant-build" $gtestArgs = @( "-S", $gtestSrc, "-B", $gtestBuild, "-G", "Ninja", @@ -593,7 +642,7 @@ if (Test-Path (Join-Path $random123Install "include\Random123\philox.h")) { Write-Host "Reusing Random123 at $random123Install" } else { $random123Src = Join-Path $resolvedRoot "Random123-src" - Clone-Head "https://github.com/DEShawResearch/Random123.git" $random123Src "v1.14.0" + Clone-Pinned "https://github.com/DEShawResearch/Random123.git" $random123Src "v1.14.0" New-Item -ItemType Directory -Force -Path (Join-Path $random123Install "include") | Out-Null Copy-Item -Recurse -Force (Join-Path $random123Src "include\Random123") ` (Join-Path $random123Install "include\Random123") @@ -610,7 +659,11 @@ if ($blasppReusable) { Write-Host "Reusing BLAS++ at $blasppInstall" } else { $blasppSrc = Join-Path $resolvedRoot "blaspp-src" - Clone-Head "https://github.com/BallisticLA/blaspp.git" $blasppSrc "remove-symv-debug-print" + # Upstream, pinned to the commit that merged the MSVC fix (PR #132, + # 2026-08-06). The fix is not in a release yet: the latest tag, + # v2025.05.28, predates it. Move to a tag once one includes it. + Clone-Pinned "https://github.com/icl-utk-edu/blaspp.git" $blasppSrc ` + "30571853f980d3a2a1737124ea4789e025a5e045" # Never re-configure an existing blaspp build in place: that regenerates # blas/defines.h without the backend defines. Fresh build tree per run. $blasppBuild = Join-Path $resolvedRoot "blaspp-$backendId-build" @@ -642,7 +695,10 @@ if ($lapackppReusable) { Write-Host "Reusing LAPACK++ at $lapackppInstall" } else { $lapackppSrc = Join-Path $resolvedRoot "lapackpp-src" - Clone-Head "https://github.com/BallisticLA/lapackpp.git" $lapackppSrc "msvc-direct-includes" + # Upstream, pinned to the commit that merged the MSVC fix (PR #87, + # 2026-08-06); likewise not yet in a release. + Clone-Pinned "https://github.com/icl-utk-edu/lapackpp.git" $lapackppSrc ` + "40b9d0daf29b6f1f3fa58bc3f22bd6cfb2c67fe4" $lapackppBuild = Join-Path $resolvedRoot "lapackpp-$backendId-build" if (Test-Path $lapackppBuild) { Remove-Item -Recurse -Force $lapackppBuild } $lapackppArgs = @( diff --git a/INSTALL_SCRIPT.md b/INSTALL_SCRIPT.md index 15f943f2..0d348333 100644 --- a/INSTALL_SCRIPT.md +++ b/INSTALL_SCRIPT.md @@ -1,355 +1,355 @@ -# Using RandLAPACK's Automated Install Script - -The installer scripts live in the `install/` directory (`install/install.sh` -for Linux/macOS, `install/install.ps1` for native Windows); a small wrapper is -kept at the repository root so `bash install.sh` keeps working. - -This guide explains how to use the `install.sh` script to automatically install -RandLAPACK and all of its dependencies (BLAS++, LAPACK++, Random123) with a -single command. - -**When to use this guide:** Use this automated installation method if you want -a quick, streamlined setup process. If you need fine-grained control over -dependency configurations, refer to RandLAPACK's `INSTALL.md` instead. - -> **Windows users:** this document describes the Linux/macOS installer -> (`install.sh`). The native Windows companion is `install\install.ps1`, -> documented in [INSTALL_WINDOWS.md](INSTALL_WINDOWS.md) -- prerequisites, -> every option, backend selection, and troubleshooting. - -## 0. Software Requirements - -Before running the install script, ensure you have the following software -available on your system: - -### Essential Requirements -* **C++ Compiler:** GNU GCC 13.3.0 or higher (required for C++20 features) -* **CMake:** Version 3.21 or higher (the project's CMake floor; recent - releases recommended) -* **BLAS/LAPACK Library:** Intel MKL 2022 or higher recommended -* **GoogleTest:** (Optional but recommended) For running RandLAPACK tests - -### GPU Support Requirements (Optional) -* **CUDA Toolkit:** Version 12.4.1 or higher - - **Recommended:** CUDA 12.9.0 + GCC 13.3.0 (verified working as of 2025-11-26) - - **IMPORTANT:** CUDA versions have strict GCC compatibility requirements: - - CUDA 12.9.0: Compatible with GCC 13.x ✓ - - CUDA 12.4.1: Compatible with GCC 13.x ✓ - - CUDA 12.2.1: Requires GCC ≤ 12.x (GCC 13.x will fail with "unsupported GNU version") - - See `INSTALL.md` Section 0 for full compatibility matrix - - Ensure compatible NVIDIA driver (v580+ recommended for CUDA 12.9) -* **CUDA Libraries:** cuBLAS and cuSOLVER (included with CUDA Toolkit) - -### Installing Requirements with Spack - -We strongly recommend using [Spack](https://github.com/spack/spack) to manage -these dependencies. A typical Spack installation would look like: - -```shell -# Step 1: Install the compiler FIRST -spack install gcc@13.3.0 - -# Step 2: Register the new compiler with Spack -spack compiler find - -# Step 3: Load the compiler -spack load gcc@13.3.0 - -# Step 4: Install all other dependencies using the new compiler -spack install cmake@3.27 -spack install intel-oneapi-mkl -spack install googletest - -# For GPU support -spack install cuda@12.9.0 -``` - -**IMPORTANT:** The compiler must be installed, registered with `spack compiler find`, -and loaded *before* installing other dependencies. This ensures all packages are -built with the correct compiler version. Spack will automatically use the loaded -compiler for subsequent package installations. - -After installation, load the environment: -```shell -spack load gcc@13.3.0 -spack load cmake -spack load intel-oneapi-mkl -spack load googletest -spack load cuda@12.9.0 # If GPU support needed -``` - -**Pro tip:** Add the spack load commands to your `~/.bashrc` to automatically -load the environment in every shell session. Make sure to load the compiler first -in your `.bashrc`. - -## 1. Preparing for Installation - -### Directory Structure - -The install script expects a specific directory structure: - -``` -~/RandNLA/ -├── RandLAPACK/ # Clone RandLAPACK here (script will move it) -└── RandNLA-project/ # Created automatically by script - ├── lib/ - │ ├── blaspp/ # Built by script - │ ├── lapackpp/ # Built by script - │ ├── random123/ # Built by script - │ └── RandLAPACK/ # Moved here by script - └── build/ # Build artifacts -``` - -### Initial Setup - -1. Create the base directory: - ```shell - mkdir -p ~/RandNLA - cd ~/RandNLA - ``` - -2. Clone RandLAPACK repository: - ```shell - git clone --recursive https://github.com/BallisticLA/RandLAPACK.git - cd RandLAPACK - ``` - -3. **(Important)** Switch to the correct development branch if needed: - ```shell - git checkout - ``` - - **Note:** Always verify with the development team which branch to use for - the latest GPU support and stability improvements. - -## 2. Running the Install Script - -### Basic Usage - -From inside the `RandLAPACK` directory: - -```shell -bash install.sh -``` - -The script will: -1. Detect if GPU hardware is available on your system and, on a terminal, - ask whether to build with CUDA support -2. Automatically clone and build all dependencies (or reuse preinstalled - ones, see the discovery variables below) -3. Build RandLAPACK with appropriate configuration -4. Build test and benchmark executables - -Run `bash install.sh --help` for the full option list. The main flags, each -with an environment-variable equivalent: - -``` --y, --yes assume "yes" for every prompt - --gpu / --no-gpu decide GPU support without asking --j, --jobs parallel build jobs (default: number of cores) - --fresh clear build directories first (default: reuse them, - so re-running is an incremental rebuild) - --modify-rc append RANDNLA_PROJECT_DIR/RANDNLA_PROJECT_GPU_AVAIL - exports to your shell config (default: never touch it; - the summary prints the lines to add yourself) - --project-dir place/locate RandNLA-project at D -``` - -### Automated Installation (Non-Interactive) - -Prompts appear only when stdin is a terminal. Piped and CI runs are already -non-interactive with safe defaults (NVIDIA detected: GPU build; AMD or no -GPU: CPU build), so no `yes |` piping is needed: - -```shell -bash install.sh < /dev/null # or simply: bash install.sh --yes -``` - -### Installation Logging - -All compiler output goes to `/install.log` automatically; the -console shows one line per step, and any failure prints the log path plus -the last lines of the log. There is no need to tee the output yourself. - -## 3. What the Script Does - -The `install.sh` script performs the following steps automatically: - -1. **Creates Project Structure** - - Creates `~/RandNLA/RandNLA-project/` directory tree - - Sets up subdirectories for libraries and build artifacts - -2. **Builds BLAS++** - - Clones BLAS++ from official repository - - Configures with appropriate BLAS backend (MKL if available) - - Builds with GPU support if requested - - Installs to `~/RandNLA/RandNLA-project/lib/blaspp/` - -3. **Builds LAPACK++** - - Clones LAPACK++ from official repository - - Configures to use previously built BLAS++ - - Builds with GPU support if requested - - Installs to `~/RandNLA/RandNLA-project/lib/lapackpp/` - -4. **Installs Random123** - - Clones Random123 header-only library - - Installs headers to `~/RandNLA/RandNLA-project/lib/random123/` - -5. **Moves and Builds RandLAPACK** - - Moves `RandLAPACK` directory to `~/RandNLA/RandNLA-project/lib/` - - Configures CMake with all dependency paths - - Builds RandLAPACK library - - Builds test suite and benchmarks - - Creates executables in `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/` - -## 4. Verifying the Installation - -### Running Tests - -After installation completes, verify everything works correctly: - -```shell -cd ~/RandNLA/RandNLA-project/build/RandLAPACK-build -ctest -``` - -This runs the complete test suite (456 tests). Expected output: -``` -99% tests passed, 1 tests failed out of 456 -Total Test time (real) = 124.62 sec -``` - -**Note:** Some test failures are known and acceptable in development branches. -Consult the development team if you see unexpected failures. - -### Running GPU Tests Only - -If you enabled GPU support, test GPU functionality specifically: - -```shell -./bin/RandLAPACK_tests_gpu -``` - -Expected output: 13-14 GPU tests should pass within 15-20 seconds. - -## 5. Working with the Installed Project - -### Key File Locations - -After installation: - -* **RandLAPACK library:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/libRandLAPACK.a` -* **Headers:** `~/RandNLA/RandNLA-project/lib/RandLAPACK/RandLAPACK/` -* **Tests:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/RandLAPACK_tests*` -* **Benchmarks:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/RandLAPACK_bench*` -* **CMake config:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/RandLAPACKConfig.cmake` - -### Recompiling After Code Changes - -If you modify RandLAPACK source code: - -```shell -cd ~/RandNLA/RandNLA-project/build/RandLAPACK-build -source ~/.bashrc # Ensures environment is loaded -make -j -``` - -**Important:** Always source your `.bashrc` (or equivalent environment setup) -before running `make` to ensure CUDA libraries and other dependencies are in -your `LD_LIBRARY_PATH`. - -### Using RandLAPACK in Your Own Projects - -See Section 4 of `INSTALL.md` for details on linking RandLAPACK to external -CMake projects. You'll need to specify: - -```cmake --Dblaspp_DIR=~/RandNLA/RandNLA-project/lib/blaspp/lib/cmake/blaspp --Dlapackpp_DIR=~/RandNLA/RandNLA-project/lib/lapackpp/lib/cmake/lapackpp --DRandBLAS_DIR=~/RandNLA/RandNLA-project/build/RandLAPACK-build/RandBLAS --DRandLAPACK_DIR=~/RandNLA/RandNLA-project/build/RandLAPACK-build -``` - ---- - -## Building and Running GPU Benchmarks - -GPU benchmarks are in the `benchmark/` directory and must be built separately from the main RandLAPACK project. - -### Prerequisites - -- RandLAPACK must already be built and installed with CUDA support (`-DRequireCUDA=ON`) -- CUDA Toolkit must be available on your system -- GPU hardware must be available - -### Building GPU Benchmarks - -Navigate to the benchmark directory and build as a standalone project: - -```shell -cd ~/RandNLA/RandNLA-project/lib/RandLAPACK/benchmark -mkdir -p build -cd build -cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_COMPILER=g++ \ - -DRandLAPACK_DIR=~/RandNLA/RandNLA-project/install/RandLAPACK-install/lib/cmake/RandLAPACK \ - .. -make -j -``` - -**Note:** Adjust the `RandLAPACK_DIR` path to match your installation location. - -### Running GPU Benchmarks - -#### BQRRP GPU Benchmark - -The BQRRP GPU benchmark supports two modes: - -**Block size sweep** (default): -```shell -./BQRRP_GPU_benchmark block_size [matrix_size] [profile_runtime] [run_qrf] -``` - -Examples: -```shell -# Run with default settings (16384x16384 matrix) -./BQRRP_GPU_benchmark block_size - -# Run with 32768x32768 matrix -./BQRRP_GPU_benchmark block_size 32768 - -# Run with profiling enabled and QRF comparison -./BQRRP_GPU_benchmark block_size 16384 1 1 -``` - -**Matrix size sweep**: -```shell -./BQRRP_GPU_benchmark mat_size [profile_runtime] [run_qrf] -``` - -Examples: -```shell -# Run with default settings -./BQRRP_GPU_benchmark mat_size - -# Run with profiling disabled but QRF comparison enabled -./BQRRP_GPU_benchmark mat_size 0 1 -``` - -### Output Files - -The benchmarks generate text files with timing results in the current directory: - -- `_BQRRP_GPU_speed_comparisons_block_size_*.txt` - Speed comparison results for block size sweep -- `BQRRP_GPU_speed_comparisons_mat_size_*.txt` - Speed comparison results for matrix size sweep -- `_BQRRP_GPU_runtime_breakdown_qrf_*.txt` - Detailed profiling with QRF (if profiling enabled) -- `_BQRRP_GPU_runtime_breakdown_cholqr_*.txt` - Detailed profiling with CholQR (if profiling enabled) - -**Last Updated:** 2025-11-26 -**Tested With:** -- GCC 13.3.0 -- CMake 3.31.9 -- CUDA 12.9.0 -- Intel MKL 2025.0.3 -- Ubuntu 22.04 / WSL2 +# Using RandLAPACK's Automated Install Script + +The installer scripts live in the `install/` directory (`install/install.sh` +for Linux/macOS, `install/install.ps1` for native Windows); a small wrapper is +kept at the repository root so `bash install.sh` keeps working. + +This guide explains how to use the `install.sh` script to automatically install +RandLAPACK and all of its dependencies (BLAS++, LAPACK++, Random123) with a +single command. + +**When to use this guide:** Use this automated installation method if you want +a quick, streamlined setup process. If you need fine-grained control over +dependency configurations, refer to RandLAPACK's `INSTALL.md` instead. + +> **Windows users:** this document describes the Linux/macOS installer +> (`install.sh`). The native Windows companion is `install\install.ps1`, +> documented in [INSTALL_WINDOWS.md](INSTALL_WINDOWS.md) -- prerequisites, +> every option, backend selection, and troubleshooting. + +## 0. Software Requirements + +Before running the install script, ensure you have the following software +available on your system: + +### Essential Requirements +* **C++ Compiler:** GNU GCC 13.3.0 or higher (required for C++20 features) +* **CMake:** Version 3.21 or higher (the project's CMake floor; recent + releases recommended) +* **BLAS/LAPACK Library:** Intel MKL 2022 or higher recommended +* **GoogleTest:** (Optional but recommended) For running RandLAPACK tests + +### GPU Support Requirements (Optional) +* **CUDA Toolkit:** Version 12.4.1 or higher + - **Recommended:** CUDA 12.9.0 + GCC 13.3.0 (verified working as of 2025-11-26) + - **IMPORTANT:** CUDA versions have strict GCC compatibility requirements: + - CUDA 12.9.0: Compatible with GCC 13.x ✓ + - CUDA 12.4.1: Compatible with GCC 13.x ✓ + - CUDA 12.2.1: Requires GCC ≤ 12.x (GCC 13.x will fail with "unsupported GNU version") + - See `INSTALL.md` Section 0 for full compatibility matrix + - Ensure compatible NVIDIA driver (v580+ recommended for CUDA 12.9) +* **CUDA Libraries:** cuBLAS and cuSOLVER (included with CUDA Toolkit) + +### Installing Requirements with Spack + +We strongly recommend using [Spack](https://github.com/spack/spack) to manage +these dependencies. A typical Spack installation would look like: + +```shell +# Step 1: Install the compiler FIRST +spack install gcc@13.3.0 + +# Step 2: Register the new compiler with Spack +spack compiler find + +# Step 3: Load the compiler +spack load gcc@13.3.0 + +# Step 4: Install all other dependencies using the new compiler +spack install cmake@3.31.9 +spack install intel-oneapi-mkl +spack install googletest + +# For GPU support +spack install cuda@12.9.0 +``` + +**IMPORTANT:** The compiler must be installed, registered with `spack compiler find`, +and loaded *before* installing other dependencies. This ensures all packages are +built with the correct compiler version. Spack will automatically use the loaded +compiler for subsequent package installations. + +After installation, load the environment: +```shell +spack load gcc@13.3.0 +spack load cmake +spack load intel-oneapi-mkl +spack load googletest +spack load cuda@12.9.0 # If GPU support needed +``` + +**Pro tip:** Add the spack load commands to your `~/.bashrc` to automatically +load the environment in every shell session. Make sure to load the compiler first +in your `.bashrc`. + +## 1. Preparing for Installation + +### Directory Structure + +The install script expects a specific directory structure: + +``` +~/RandNLA/ +├── RandLAPACK/ # Clone RandLAPACK here (script will move it) +└── RandNLA-project/ # Created automatically by script + ├── lib/ + │ ├── blaspp/ # Built by script + │ ├── lapackpp/ # Built by script + │ ├── random123/ # Built by script + │ └── RandLAPACK/ # Moved here by script + └── build/ # Build artifacts +``` + +### Initial Setup + +1. Create the base directory: + ```shell + mkdir -p ~/RandNLA + cd ~/RandNLA + ``` + +2. Clone RandLAPACK repository: + ```shell + git clone --recursive https://github.com/BallisticLA/RandLAPACK.git + cd RandLAPACK + ``` + +3. **(Important)** Switch to the correct development branch if needed: + ```shell + git checkout + ``` + + **Note:** Always verify with the development team which branch to use for + the latest GPU support and stability improvements. + +## 2. Running the Install Script + +### Basic Usage + +From inside the `RandLAPACK` directory: + +```shell +bash install.sh +``` + +The script will: +1. Detect if GPU hardware is available on your system and, on a terminal, + ask whether to build with CUDA support +2. Automatically clone and build all dependencies (or reuse preinstalled + ones, see the discovery variables below) +3. Build RandLAPACK with appropriate configuration +4. Build test and benchmark executables + +Run `bash install.sh --help` for the full option list. The main flags, each +with an environment-variable equivalent: + +``` +-y, --yes assume "yes" for every prompt + --gpu / --no-gpu decide GPU support without asking +-j, --jobs parallel build jobs (default: number of cores) + --fresh clear build directories first (default: reuse them, + so re-running is an incremental rebuild) + --modify-rc append RANDNLA_PROJECT_DIR/RANDNLA_PROJECT_GPU_AVAIL + exports to your shell config (default: never touch it; + the summary prints the lines to add yourself) + --project-dir place/locate RandNLA-project at D +``` + +### Automated Installation (Non-Interactive) + +Prompts appear only when stdin is a terminal. Piped and CI runs are already +non-interactive with safe defaults (NVIDIA detected: GPU build; AMD or no +GPU: CPU build), so no `yes |` piping is needed: + +```shell +bash install.sh < /dev/null # or simply: bash install.sh --yes +``` + +### Installation Logging + +All compiler output goes to `/install.log` automatically; the +console shows one line per step, and any failure prints the log path plus +the last lines of the log. There is no need to tee the output yourself. + +## 3. What the Script Does + +The `install.sh` script performs the following steps automatically: + +1. **Creates Project Structure** + - Creates `~/RandNLA/RandNLA-project/` directory tree + - Sets up subdirectories for libraries and build artifacts + +2. **Builds BLAS++** + - Clones BLAS++ from official repository + - Configures with appropriate BLAS backend (MKL if available) + - Builds with GPU support if requested + - Installs to `~/RandNLA/RandNLA-project/lib/blaspp/` + +3. **Builds LAPACK++** + - Clones LAPACK++ from official repository + - Configures to use previously built BLAS++ + - Builds with GPU support if requested + - Installs to `~/RandNLA/RandNLA-project/lib/lapackpp/` + +4. **Installs Random123** + - Clones Random123 header-only library + - Installs headers to `~/RandNLA/RandNLA-project/lib/random123/` + +5. **Moves and Builds RandLAPACK** + - Moves `RandLAPACK` directory to `~/RandNLA/RandNLA-project/lib/` + - Configures CMake with all dependency paths + - Builds RandLAPACK library + - Builds test suite and benchmarks + - Creates executables in `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/` + +## 4. Verifying the Installation + +### Running Tests + +After installation completes, verify everything works correctly: + +```shell +cd ~/RandNLA/RandNLA-project/build/RandLAPACK-build +ctest +``` + +This runs the complete test suite (456 tests). Expected output: +``` +99% tests passed, 1 tests failed out of 456 +Total Test time (real) = 124.62 sec +``` + +**Note:** Some test failures are known and acceptable in development branches. +Consult the development team if you see unexpected failures. + +### Running GPU Tests Only + +If you enabled GPU support, test GPU functionality specifically: + +```shell +./bin/RandLAPACK_tests_gpu +``` + +Expected output: 13-14 GPU tests should pass within 15-20 seconds. + +## 5. Working with the Installed Project + +### Key File Locations + +After installation: + +* **RandLAPACK library:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/libRandLAPACK.a` +* **Headers:** `~/RandNLA/RandNLA-project/lib/RandLAPACK/RandLAPACK/` +* **Tests:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/RandLAPACK_tests*` +* **Benchmarks:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/bin/RandLAPACK_bench*` +* **CMake config:** `~/RandNLA/RandNLA-project/build/RandLAPACK-build/RandLAPACKConfig.cmake` + +### Recompiling After Code Changes + +If you modify RandLAPACK source code: + +```shell +cd ~/RandNLA/RandNLA-project/build/RandLAPACK-build +source ~/.bashrc # Ensures environment is loaded +make -j +``` + +**Important:** Always source your `.bashrc` (or equivalent environment setup) +before running `make` to ensure CUDA libraries and other dependencies are in +your `LD_LIBRARY_PATH`. + +### Using RandLAPACK in Your Own Projects + +See Section 4 of `INSTALL.md` for details on linking RandLAPACK to external +CMake projects. You'll need to specify: + +```cmake +-Dblaspp_DIR=~/RandNLA/RandNLA-project/lib/blaspp/lib/cmake/blaspp +-Dlapackpp_DIR=~/RandNLA/RandNLA-project/lib/lapackpp/lib/cmake/lapackpp +-DRandBLAS_DIR=~/RandNLA/RandNLA-project/build/RandLAPACK-build/RandBLAS +-DRandLAPACK_DIR=~/RandNLA/RandNLA-project/build/RandLAPACK-build +``` + +--- + +## Building and Running GPU Benchmarks + +GPU benchmarks are in the `benchmark/` directory and must be built separately from the main RandLAPACK project. + +### Prerequisites + +- RandLAPACK must already be built and installed with CUDA support (`-DRequireCUDA=ON`) +- CUDA Toolkit must be available on your system +- GPU hardware must be available + +### Building GPU Benchmarks + +Navigate to the benchmark directory and build as a standalone project: + +```shell +cd ~/RandNLA/RandNLA-project/lib/RandLAPACK/benchmark +mkdir -p build +cd build +cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER=g++ \ + -DRandLAPACK_DIR=~/RandNLA/RandNLA-project/install/RandLAPACK-install/lib/cmake/RandLAPACK \ + .. +make -j +``` + +**Note:** Adjust the `RandLAPACK_DIR` path to match your installation location. + +### Running GPU Benchmarks + +#### BQRRP GPU Benchmark + +The BQRRP GPU benchmark supports two modes: + +**Block size sweep** (default): +```shell +./BQRRP_GPU_benchmark block_size [matrix_size] [profile_runtime] [run_qrf] +``` + +Examples: +```shell +# Run with default settings (16384x16384 matrix) +./BQRRP_GPU_benchmark block_size + +# Run with 32768x32768 matrix +./BQRRP_GPU_benchmark block_size 32768 + +# Run with profiling enabled and QRF comparison +./BQRRP_GPU_benchmark block_size 16384 1 1 +``` + +**Matrix size sweep**: +```shell +./BQRRP_GPU_benchmark mat_size [profile_runtime] [run_qrf] +``` + +Examples: +```shell +# Run with default settings +./BQRRP_GPU_benchmark mat_size + +# Run with profiling disabled but QRF comparison enabled +./BQRRP_GPU_benchmark mat_size 0 1 +``` + +### Output Files + +The benchmarks generate text files with timing results in the current directory: + +- `_BQRRP_GPU_speed_comparisons_block_size_*.txt` - Speed comparison results for block size sweep +- `BQRRP_GPU_speed_comparisons_mat_size_*.txt` - Speed comparison results for matrix size sweep +- `_BQRRP_GPU_runtime_breakdown_qrf_*.txt` - Detailed profiling with QRF (if profiling enabled) +- `_BQRRP_GPU_runtime_breakdown_cholqr_*.txt` - Detailed profiling with CholQR (if profiling enabled) + +**Last Updated:** 2025-11-26 +**Tested With:** +- GCC 13.3.0 +- CMake 3.31.9 +- CUDA 12.9.0 +- Intel MKL 2025.0.3 +- Ubuntu 22.04 / WSL2 diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index e2787f5c..5af31304 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -11,10 +11,21 @@ Install the two prerequisites (skip any you already have), from a regular PowerShell window: ```powershell -winget install Git.Git -winget install Microsoft.VisualStudio.2022.Community --override "--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended" +winget install --id Git.Git --exact + +winget install --id Microsoft.VisualStudio.2022.BuildTools --exact ` + --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" ``` +Build Tools is the compiler without the IDE. If you would rather have the full +IDE, swap the second line for +`--id Microsoft.VisualStudio.2022.Community` with +`--add Microsoft.VisualStudio.Workload.NativeDesktop`. Either way +`--includeRecommended` matters: it pulls in "C++ CMake tools for Windows", +which is what supplies CMake and Ninja, so you do not install those +separately. And `--wait` matters: without it winget returns while the Visual +Studio installer is still running, which looks like it finished. + Then open **"x64 Native Tools Command Prompt for VS 2022"** from the Start menu (the exact entry matters -- see section 3), and run: From 0224cebfb8315237cca0c1e9baf9ceb7a9e11579 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 12:00:15 -0700 Subject: [PATCH 12/20] Windows: drop a dead ctest exclusion copied from the Linux lanes Every lane excludes ^TestABRIK\.ABRIK_catch_instability. No such test exists: it is absent from the source, and the built tree registers six ABRIK tests, none by that name. The exclusion arrived in c028dc7 (Jan 2025) and the test it names was removed or renamed afterwards without dropping it. Two reasons to remove it from the Windows paths specifically. It is dead, so a --exclude-regex matching nothing stops protecting anything the moment a test with that name reappears, while reading as though a known-bad test is being skipped. And install.ps1 carried it while install.sh has no exclusion at all, so the Windows *user* installer was silently skipping something the Linux one runs -- a CI-only convention that should never have reached user-facing code. Verified: the full suite still passes, 749/749, with no exclusion. The same dead regex remains in core-linux.yaml, core-macos.yaml and install-script.yaml. Those predate this PR and are equally safe to clean up, but they belong in their own change rather than here. --- .github/scripts/windows/run-ci.ps1 | 2 -- install/install.ps1 | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/scripts/windows/run-ci.ps1 b/.github/scripts/windows/run-ci.ps1 index 0142a844..1e8dbab8 100644 --- a/.github/scripts/windows/run-ci.ps1 +++ b/.github/scripts/windows/run-ci.ps1 @@ -125,10 +125,8 @@ if ($SanitizeAddress) { $configureArgs += "-DSANITIZE_ADDRESS=ON" } Invoke-Checked "cmake" $configureArgs Invoke-Checked "cmake" @("--build", $buildDir, "--target", "install") -# Same exclusion as the Linux and macOS core jobs. Invoke-Checked "ctest" @( "--test-dir", $buildDir, - "--exclude-regex", "^TestABRIK\.ABRIK_catch_instability", "--output-on-failure") Write-Host "RandLAPACK Windows $Task validation succeeded." diff --git a/install/install.ps1 b/install/install.ps1 index b9137e91..43e4d1a1 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -190,7 +190,6 @@ Invoke-Checked "cmake" @("--build", $buildDir, "--target", "install") if (-not $SkipTests) { Invoke-Checked "ctest" @( "--test-dir", $buildDir, - "--exclude-regex", "^TestABRIK\.ABRIK_catch_instability", "--output-on-failure") } From 64b03e02ca309eaa82bfff0271f4b133fe31a115 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 12:09:34 -0700 Subject: [PATCH 13/20] Windows: reuse dependencies only when their provenance matches Changing a dependency's source had no effect on anyone who already had it built. Two layers both keyed on presence rather than identity: - Clone-Pinned returned early whenever the destination existed, without checking it was at the pinned remote and ref. A clone left by an earlier revision of this script -- for instance from the BallisticLA forks this branch just moved off -- was silently rebuilt as-is. - The blaspp/lapackpp reuse test only asked whether a config file existed, so a completed install from the old source was reused forever and the clone step never even ran. Together that meant the repoint to upstream would have been a no-op for every existing checkout, which is the same failure mode as reusing a dependency tree configured by the wrong compiler: the fix looks applied and nothing changes. Clone-Pinned now verifies remote and HEAD and re-clones on mismatch, and each dependency install carries a stamp naming the source it was built from, which the reuse test must match. The URL and ref are declared once and used for both the clone and the stamp so they cannot drift. Verified: an install predating the stamp rebuilds, a second run reuses it, and a clone pointed at the old fork is detected and re-cloned from upstream. --- .../setup-randlapack-deps-windows/setup.ps1 | 61 +++++++++++++++---- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 9804e7eb..92d06267 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -144,8 +144,18 @@ function Clone-Pinned { # "cache hit" restores. param([string]$Url, [string]$Destination, [string]$Ref) if (Test-Path $Destination) { - Write-Host "Reusing existing clone at $Destination" - return + # Reuse only if it is actually the pinned source. A clone left by an + # earlier revision of this script may sit at a different remote or + # commit, and silently rebuilding that is how a "fixed" dependency + # stays broken. + $head = (& git -C $Destination rev-parse HEAD 2>$null) + $remote = (& git -C $Destination remote get-url origin 2>$null) + if ($LASTEXITCODE -eq 0 -and $remote -eq $Url -and ($head -eq $Ref -or $head -like "$Ref*")) { + Write-Host "Reusing existing clone at $Destination" + return + } + Write-Host "Re-cloning $Destination : it is not at the pinned $Url@$Ref." + Remove-Item -Recurse -Force $Destination } # A tag can be cloned shallowly by name; a SHA cannot, so fetch it # directly (GitHub allows fetching a reachable commit by SHA). @@ -156,6 +166,22 @@ function Clone-Pinned { Invoke-Checked "git" @("-C", $Destination, "checkout", "--quiet", "FETCH_HEAD") } +function Test-Provenance { + # A dependency install is reusable only if it was built from the source we + # would build from now. Without this, changing a pin (or a repository) has + # no effect on anyone who already has an install: the reuse check only + # asks "does a config file exist?". That is the same failure mode as + # reusing a dependency tree configured by the wrong compiler. + param([string]$InstallRoot, [string]$Expected) + $stamp = Join-Path $InstallRoot ".randlapack-source" + return (Test-Path $stamp) -and ((Get-Content -Raw $stamp).Trim() -eq $Expected) +} + +function Write-Provenance { + param([string]$InstallRoot, [string]$Expected) + Set-Content -Path (Join-Path $InstallRoot ".randlapack-source") -Value $Expected -Encoding ascii +} + function Export-GitHubValue { # Publishes a value as a process env var, and to GITHUB_ENV/GITHUB_OUTPUT # when running under GitHub Actions (harmless locally). @@ -653,17 +679,21 @@ if (Test-Path (Join-Path $random123Install "include\Random123\philox.h")) { $blasppInstall = Join-Path $resolvedRoot "blaspp-$backendId-install" # Reuse only on an intact install: a partially restored cache directory must # trigger a rebuild, not silently skip it. +# Upstream, pinned to the commit that merged the MSVC fix (PR #132, +# 2026-08-06). Not in a release yet: the latest tag, v2025.05.28, predates it. +# Move to a tag once one includes it. Declared once and used for both the +# clone and the reuse stamp, so the two cannot drift. +$blasppUrl = "https://github.com/icl-utk-edu/blaspp.git" +$blasppRef = "30571853f980d3a2a1737124ea4789e025a5e045" +$blasppSource = "$blasppUrl@$blasppRef" $blasppReusable = (Test-Path $blasppInstall) -and (Get-ChildItem -Path $blasppInstall -Recurse ` - -Filter "blasppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) + -Filter "blasppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) ` + -and (Test-Provenance $blasppInstall $blasppSource) if ($blasppReusable) { Write-Host "Reusing BLAS++ at $blasppInstall" } else { $blasppSrc = Join-Path $resolvedRoot "blaspp-src" - # Upstream, pinned to the commit that merged the MSVC fix (PR #132, - # 2026-08-06). The fix is not in a release yet: the latest tag, - # v2025.05.28, predates it. Move to a tag once one includes it. - Clone-Pinned "https://github.com/icl-utk-edu/blaspp.git" $blasppSrc ` - "30571853f980d3a2a1737124ea4789e025a5e045" + Clone-Pinned $blasppUrl $blasppSrc $blasppRef # Never re-configure an existing blaspp build in place: that regenerates # blas/defines.h without the backend defines. Fresh build tree per run. $blasppBuild = Join-Path $resolvedRoot "blaspp-$backendId-build" @@ -683,22 +713,26 @@ if ($blasppReusable) { if ($backendBlasFortran -ne "") { $blasppArgs += "-Dblas_fortran=$backendBlasFortran" } Invoke-Checked "cmake" $blasppArgs Invoke-Checked "cmake" @("--build", $blasppBuild, "--target", "install") + Write-Provenance $blasppInstall $blasppSource } $blasppDir = Find-PackageConfigDirectory $blasppInstall "blaspp" # --------------------------------------------------------------- LAPACK++ ---- $lapackppInstall = Join-Path $resolvedRoot "lapackpp-$backendId-install" +# Upstream, pinned to the commit that merged the MSVC fix (PR #87, +# 2026-08-06); likewise not yet in a release. Declared once, as above. +$lapackppUrl = "https://github.com/icl-utk-edu/lapackpp.git" +$lapackppRef = "40b9d0daf29b6f1f3fa58bc3f22bd6cfb2c67fe4" +$lapackppSource = "$lapackppUrl@$lapackppRef" $lapackppReusable = (Test-Path $lapackppInstall) -and (Get-ChildItem -Path $lapackppInstall -Recurse ` - -Filter "lapackppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) + -Filter "lapackppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1) ` + -and (Test-Provenance $lapackppInstall $lapackppSource) if ($lapackppReusable) { Write-Host "Reusing LAPACK++ at $lapackppInstall" } else { $lapackppSrc = Join-Path $resolvedRoot "lapackpp-src" - # Upstream, pinned to the commit that merged the MSVC fix (PR #87, - # 2026-08-06); likewise not yet in a release. - Clone-Pinned "https://github.com/icl-utk-edu/lapackpp.git" $lapackppSrc ` - "40b9d0daf29b6f1f3fa58bc3f22bd6cfb2c67fe4" + Clone-Pinned $lapackppUrl $lapackppSrc $lapackppRef $lapackppBuild = Join-Path $resolvedRoot "lapackpp-$backendId-build" if (Test-Path $lapackppBuild) { Remove-Item -Recurse -Force $lapackppBuild } $lapackppArgs = @( @@ -714,6 +748,7 @@ if ($lapackppReusable) { } Invoke-Checked "cmake" $lapackppArgs Invoke-Checked "cmake" @("--build", $lapackppBuild, "--target", "install") + Write-Provenance $lapackppInstall $lapackppSource } $lapackppDir = Find-PackageConfigDirectory $lapackppInstall "lapackpp" From 9d36ba2e7a59a9d5467eb3cded1a5b378f69916d Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 12:57:13 -0700 Subject: [PATCH 14/20] INSTALL_WINDOWS.md: state exactly which dependency versions the installer fetches The installer never supplies a compiler, CMake or Ninja -- those are the user's to provide, same as on Linux and macOS. It does fetch the BLAS/LAPACK backend and the build-time dependencies, and until now the versions were only discoverable by reading setup.ps1. Adds a table naming each component, its exact pinned version, its upstream source and how it is verified, plus two points that were implicit: - oneMKL is downloaded ONLY when no existing oneAPI is discovered. When one is found the installer uses the user's version, so the documented version applies to the no-oneMKL case only. - BLAS++ and LAPACK++ are pinned to commits rather than to their latest release, v2025.05.28, because that release predates the two one-line MSVC fixes this build needs (blaspp #132, lapackpp #87, merged upstream 2026-08-06). They move to a release tag once one includes them. Everything else is a stable, released version. Sections renumbered to keep the new one in place; internal cross-references updated. --- INSTALL_WINDOWS.md | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 5af31304..d854d27c 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -96,7 +96,7 @@ Windows. The build tool is Ninja everywhere, which Visual Studio bundles. The practical consequence of the third row is worth internalizing: **on Windows you never need to edit PATH for RandLAPACK**. If an executable is staged, it runs; if you write your own program against RandLAPACK, either -call the provided staging helper (section 6) or copy the DLLs next to your +call the provided staging helper (section 7) or copy the DLLs next to your `.exe`. This "app-local deployment" is the idiomatic Windows layout -- it is what Visual Studio's own package manager does by default. @@ -181,7 +181,36 @@ then point `-BlasLibraries` at its `.lib` files. The custom path is expected to work with any well-formed backend but is not exercised by our CI; oneMKL and OpenBLAS are. -## 5. `install.ps1` reference +## 5. What the installer downloads, and at which versions + +The installer never installs a compiler, CMake or Ninja -- those are yours to provide, exactly +as on Linux and macOS (see §3). What it *can* fetch is the BLAS/LAPACK backend and RandLAPACK's +own build-time dependencies. Everything lands under ``, is pinned to an exact +version, and is verified by checksum where the source publishes archives: + +| Component | Version | Source | Verified by | +|---|---|---|---| +| Intel oneMKL | **2025.2.0.627** | `intelmkl.devel/redist.win-x64` on nuget.org | SHA256 | +| OpenBLAS | **0.3.34** | official GitHub release binaries | SHA256 | +| GoogleTest | **v1.17.0** | release tag | git tag | +| Random123 | **v1.14.0** | release tag | git tag | +| BLAS++ | commit `3057185` | icl-utk-edu/blaspp | git commit | +| LAPACK++ | commit `40b9d0d` | icl-utk-edu/lapackpp | git commit | + +All are fetched from the project's canonical upstream and pinned to an immutable reference, so +a given RandLAPACK revision always builds the same dependency versions. + +**oneMKL is only downloaded if you do not already have one.** If an existing oneAPI is +discovered (§4), the installer uses *your* version, whatever that is, and downloads nothing. +The version above therefore applies only to the no-oneMKL case. + +**Two deliberate exceptions to "pin a stable release".** BLAS++ and LAPACK++ are pinned to +commits rather than to their latest release, `v2025.05.28`, because that release predates the +two one-line MSVC fixes this build needs (blaspp #132 and lapackpp #87, both merged upstream on +2026-08-06). They move to a release tag as soon as one includes those fixes. Everything else is +a stable, released version. + +## 6. `install.ps1` reference ``` -ProjectDir Where dependencies/builds/installs go @@ -234,7 +263,7 @@ Worked examples: -BackendBinDir "C:\AOCL\bin" ``` -## 6. Runtime DLLs: what "staging" means +## 7. Runtime DLLs: what "staging" means A Windows executable that links a DLL-based library must be able to find those DLLs when it starts. Windows looks in the executable's own directory @@ -255,7 +284,7 @@ randlapack_stage_runtime_dlls(myprog) # no-op on non-Windows platforms If you prefer not to use the helper, copy the DLLs from the backend's `bin` directory (the installer prints it at the end) next to your `.exe`. -## 7. Troubleshooting +## 8. Troubleshooting - **"running scripts is disabled on this system"**: Windows' default PowerShell execution policy. Launch it as the quick start does @@ -287,7 +316,7 @@ directory (the installer prints it at the end) next to your `.exe`. its `Library\bin` on PATH when an environment is active. RandLAPACK's own staged executables are immune (the exe's folder outranks PATH), but a program of yours that relies on PATH can silently pick up conda's copies. - Stage your executable (section 6) and the problem disappears. To + Stage your executable (section 7) and the problem disappears. To deliberately build *against* a conda-provided MKL instead, pass `-MklRoot "\Library"` (requires conda's `mkl-devel` package). - **Path-length errors deep in dependency builds**: keep the project From c5aaf056e36e96e0811cf4d2e4f9b2b606d8c800 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 13:07:28 -0700 Subject: [PATCH 15/20] Windows deps: move oneMKL and GoogleTest to the newest releases, still pinned Everything the installer can fetch is now the newest stable release of that component, pinned to an exact version: oneMKL 2025.2.0.627 -> 2026.1.0.226 (was four releases behind) GoogleTest v1.17.0 -> v1.18.0 OpenBLAS 0.3.34 already newest Random123 v1.14.0 already newest BLAS++ and LAPACK++ stay pinned to commits rather than to v2025.05.28: that release predates the two MSVC fixes this build needs (blaspp #132, lapackpp #87). They move to a tag once one carries them; tracked in #158. The oneMKL bump renames the runtime DLLs from mkl_*.2.dll to mkl_*.3.dll. Nothing hardcodes those names -- staging globs *.dll -- so the change is transparent, but it would have silently staged nothing had anything hardcoded them. The package layout is otherwise unchanged (build/native/win-x64, build/native/include, runtimes/win-x64/native). Cache keys bumped for oneMKL and both GoogleTest variants, so no artifact built from the old versions can be restored. Verified on Windows, both provisioning paths: - discovery against an installed 2026.1.0 oneAPI: link check OK - forced NuGet download of 2026.1.0.226: both SHA256s verified, unpacked, "Found BLAS library", mkl_core.3.dll / mkl_sequential.3.dll staged - GoogleTest v1.18.0 is a major bump touching every test: 749/749 pass --- .../actions/setup-randlapack-deps-windows/action.yml | 6 +++--- .../actions/setup-randlapack-deps-windows/setup.ps1 | 10 +++++----- INSTALL_WINDOWS.md | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/action.yml b/.github/actions/setup-randlapack-deps-windows/action.yml index b0a7d64f..71f3fb8b 100644 --- a/.github/actions/setup-randlapack-deps-windows/action.yml +++ b/.github/actions/setup-randlapack-deps-windows/action.yml @@ -31,7 +31,7 @@ runs: uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\onemkl-2025.2.0.627 - key: windows-nuget-intel-mkl-2025.2.0.627-r1 + key: windows-nuget-intel-mkl-2026.1.0.226-r1 - name: cache OpenBLAS (release binaries) if: inputs.blas-backend == 'openblas' @@ -45,14 +45,14 @@ runs: uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\googletest-install - key: windows-msvc-googletest-1.17.0-ninja-r1 + key: windows-msvc-googletest-1.18.0-ninja-r1 - name: cache GoogleTest (ASan) if: inputs.sanitize-address == 'true' uses: actions/cache@v4 with: path: ${{ github.workspace }}\..\windows-deps\googletest-asan-install - key: windows-msvc-googletest-asan-1.17.0-ninja-r1 + key: windows-msvc-googletest-asan-1.18.0-ninja-r1 - name: cache Random123 uses: actions/cache@v4 diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 92d06267..df764030 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -3,7 +3,7 @@ # installed oneAPI or fetched from Intel's NuGet packages, ILP64 + # sequential), OpenBLAS (official release binaries, LP64), or # custom/bring-your-own libraries -# - GoogleTest v1.17.0 +# - GoogleTest v1.18.0 # - Random123 (headers only) # - BLAS++ from icl-utk-edu/blaspp (upstream), pinned by commit # - LAPACK++ from icl-utk-edu/lapackpp (upstream), pinned by commit @@ -397,12 +397,12 @@ if ($Backend -eq "mkl") { # instance), and nothing here needed vcpkg beyond this one download. # The OpenMP/TBB packages the devel nuspec references are skipped on # purpose -- RandLAPACK links the sequential MKL DLL set. - $mklVersion = "2025.2.0.627" + $mklVersion = "2026.1.0.226" $mklPackages = @( @{ Id = "intelmkl.devel.win-x64" - Sha256 = "988816fb3cdfc5dcfdd42036c28314dcfda22fe47a29056ae455e360a8833ee5" }, + Sha256 = "d4456ce3c767b235d9c212c093a40cdf073589102dbf70bc0fd2d59140be30d2" }, @{ Id = "intelmkl.redist.win-x64" - Sha256 = "42bf35a13581aa03ecbee62e83e2c6397a45f13ae8aa657c1727fd0335e52c9e" }) + Sha256 = "ac2d4a14a70b021557170f53460b57038af5ab1977e82a05aca8e4a5af7bcb61" }) $mklRoot = Join-Path $resolvedRoot "onemkl-$mklVersion" $mklLibDir = Join-Path $mklRoot "lib" $mklBin = Join-Path $mklRoot "bin" @@ -645,7 +645,7 @@ if (Test-Path (Join-Path $gtestInstall "include\gtest\gtest.h")) { Write-Host "Reusing GoogleTest at $gtestInstall" } else { $gtestSrc = Join-Path $resolvedRoot "$gtestVariant-src" - Clone-Pinned "https://github.com/google/googletest.git" $gtestSrc "v1.17.0" + Clone-Pinned "https://github.com/google/googletest.git" $gtestSrc "v1.18.0" $gtestBuild = Join-Path $resolvedRoot "$gtestVariant-build" $gtestArgs = @( "-S", $gtestSrc, "-B", $gtestBuild, "-G", "Ninja", diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index d854d27c..f23d7683 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -190,9 +190,9 @@ version, and is verified by checksum where the source publishes archives: | Component | Version | Source | Verified by | |---|---|---|---| -| Intel oneMKL | **2025.2.0.627** | `intelmkl.devel/redist.win-x64` on nuget.org | SHA256 | +| Intel oneMKL | **2026.1.0.226** | `intelmkl.devel/redist.win-x64` on nuget.org | SHA256 | | OpenBLAS | **0.3.34** | official GitHub release binaries | SHA256 | -| GoogleTest | **v1.17.0** | release tag | git tag | +| GoogleTest | **v1.18.0** | release tag | git tag | | Random123 | **v1.14.0** | release tag | git tag | | BLAS++ | commit `3057185` | icl-utk-edu/blaspp | git commit | | LAPACK++ | commit `40b9d0d` | icl-utk-edu/lapackpp | git commit | From 57c6bc84154b1c833692c10d923cb212a5364740 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 13:30:31 -0700 Subject: [PATCH 16/20] INSTALL_WINDOWS.md: state the toolchain contract instead of prescribing one shell The guide read as a fixed recipe -- install exactly these packages, open exactly this Start-menu entry -- which is not the contract the other platforms have. On Linux and macOS the user brings a compiler and CMake and works wherever they like; install.sh only suggests how to get them. Windows should be no different, and the previous framing was what led a user into the wrong shell in the first place: it named one entry without explaining that the requirement is an x64 toolchain, not that particular entry. Now the requirement comes first -- a C++ compiler, CMake, Ninja and Git, in whatever terminal you choose -- followed by ways to satisfy it, explicitly as options rather than mandates: - the x64 Native Tools prompt, - vswhere + vcvars64 from any Command Prompt (any edition or version), - whatever environment an editor or build tool already provides, with a single check that decides whether any of them worked: `where cl` must show Hostx64\x64. The x86 trap is still documented, because it remains easy to hit, and now also notes that shell bitness is not a usable signal (the "Developer Command Prompt" is a 64-bit process that still selects x86 tools). No behaviour change; the installer already supplied nothing but the BLAS backend and build dependencies. This aligns the documentation with that. --- INSTALL_WINDOWS.md | 73 +++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index f23d7683..94115c9f 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -7,8 +7,15 @@ development experience. If you are on Linux or macOS, use ## 1. Quick start -Install the two prerequisites (skip any you already have), from a regular -PowerShell window: +RandLAPACK expects the same things on Windows as on Linux and macOS: a **C++ +compiler, CMake, Ninja and Git**, available in whatever terminal you choose to +work in. The installer does not supply them and will not install them for you; +it checks for them and stops with a fix-it command if any are missing. + +If you already have those, skip to step 3. + +**1. Get a toolchain** (one way; use your own if you prefer). Visual Studio's +C++ workload provides MSVC, the Windows SDK, CMake and Ninja together: ```powershell winget install --id Git.Git --exact @@ -17,17 +24,36 @@ winget install --id Microsoft.VisualStudio.2022.BuildTools --exact ` --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" ``` -Build Tools is the compiler without the IDE. If you would rather have the full -IDE, swap the second line for -`--id Microsoft.VisualStudio.2022.Community` with -`--add Microsoft.VisualStudio.Workload.NativeDesktop`. Either way -`--includeRecommended` matters: it pulls in "C++ CMake tools for Windows", -which is what supplies CMake and Ninja, so you do not install those -separately. And `--wait` matters: without it winget returns while the Visual -Studio installer is still running, which looks like it finished. +Build Tools is the compiler without the IDE; `--id Microsoft.VisualStudio.2022.Community` +with `--add Microsoft.VisualStudio.Workload.NativeDesktop` works equally well. +`--includeRecommended` is what pulls in "C++ CMake tools for Windows", which +supplies CMake and Ninja. `--wait` matters too: without it winget returns while +the Visual Studio installer is still running, which looks like it finished. + +**2. Make the toolchain visible to your terminal.** This is the one real +difference from Unix: MSVC is never on `PATH` globally, only inside a +"developer environment" that Visual Studio sets up per session. Any of these +gets you one, and they are equivalent — pick whichever fits how you work: + +- Open **"x64 Native Tools Command Prompt for VS 2022"** from the Start menu. +- Or, in any Command Prompt, ask Visual Studio where it is and load it (works + for any edition or version): + + ```bat + for /f "usebackq delims=" %i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do call "%i\VC\Auxiliary\Build\vcvars64.bat" + ``` + +- Or use any shell your editor or build tool already sets up, as long as it + gives you an **x64** toolchain (see section 3 — this is worth checking, the + obvious Start-menu entries give you a 32-bit one). -Then open **"x64 Native Tools Command Prompt for VS 2022"** from the Start -menu (the exact entry matters -- see section 3), and run: +Whatever you pick, this must show a path containing `Hostx64\x64`: + +``` +where cl +``` + +**3. Build:** ```bat git clone --recursive https://github.com/BallisticLA/RandLAPACK.git @@ -102,12 +128,16 @@ what Visual Studio's own package manager does by default. ## 3. Prerequisites, in detail -- **Visual Studio 2022** (Community is free; Build Tools also works) with the - **"Desktop development with C++"** workload. That workload includes MSVC, - the Windows SDK, CMake, and Ninja -- you do not install those separately. +You provide these; the installer does not: + +- **A C++ compiler.** MSVC, from Visual Studio 2022 or its Build Tools, with + the **"Desktop development with C++"** workload. +- **CMake and Ninja.** Both ship with that workload's "C++ CMake tools for + Windows" component, so a separate install is usually unnecessary. Your own + copies are fine if they are on `PATH`. - **Git** (any recent version). -- A network connection for the first run (dependency downloads; roughly - 200 MB for the default backend). +- A network connection for the first run, to fetch the BLAS/LAPACK backend and + build dependencies (see section 5). Roughly 200 MB for the default backend. The installer checks all of this up front and prints a fix-it command for anything missing. @@ -115,14 +145,15 @@ anything missing. Two mistakes account for nearly every failed Windows install, and both are about *which shell you start from*: -1. **A regular PowerShell.** `cl.exe`, `cmake`, and `ninja` are on PATH only - inside a Visual Studio "developer" shell, which sets them up per session. -2. **A developer shell of the wrong architecture.** This one is easy to hit +1. **A regular PowerShell.** `cl.exe`, `cmake` and `ninja` are on `PATH` only + inside a Visual Studio developer environment, which is set up per session. +2. **A developer shell of the wrong architecture.** This is easy to hit because the obvious Start-menu entries are the wrong ones: **"Developer PowerShell for VS 2022"** and **"Developer Command Prompt for VS 2022"** both default to a **32-bit (x86)** toolchain. RandLAPACK and every BLAS backend here are 64-bit, and a 32-bit linker cannot use an x64 import - library. Use **"x64 Native Tools Command Prompt for VS 2022"** instead. + library. Note that shell bitness is not a usable signal: "Developer Command + Prompt" is itself a 64-bit process and still selects x86 tools. Confirm you are in the right place with: From e3c57b400c8db2f6bb0b4b2129bc172ba4f7d712 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 13:35:45 -0700 Subject: [PATCH 17/20] Trim comment and documentation bloat; fix two stale claims and a leaked path Audit of this PR's own prose. Net -65 lines, no behaviour change. Corrections, not just trimming: - A comment pointed at "randnla/reference/windows-software-distribution.md", a file in a private notes repository that does not exist here. Removed. - docs/CI.md still described BLAS++/LAPACK++ as coming from BallisticLA fork branches with upstream PRs open. Both merged on 2026-08-06 and this branch now pulls from icl-utk-edu pinned by commit, so the entry was actively misleading. Rewritten around what the pins actually are and why they are commits rather than tags. - docs/CI.md said "the two Windows guard jobs" after they were consolidated into one. Trimming: - The space-in-path comment was 17 lines re-deriving an analysis that now lives in icl-utk-edu/blaspp#137 and issue #158. Six lines and two references instead. - The no-backend-found comment was 14 lines, a third of which restated discovery rationale already in the parameter docs. Four lines. - install.ps1's header was 50 lines, a quarter of the file, and duplicated INSTALL_WINDOWS.md's guidance on shells and execution policy. 29 lines that point at the guide instead. - The no-oneMKL error printed 20 lines before its options. It now leads with what it searched and lists the four fixes as commands, not prose. Readability: - INSTALL_WINDOWS.md's "Quick start" was 625 words and contained the BLAS acquisition philosophy, a verbatim prompt transcript and flag references, all of which belong in sections 4 and 5. Now 480 words: prerequisites, make the toolchain visible, clone, run. - ILP64 and LP64 appeared nine times without ever being explained. One short gloss added where the backends are compared, in terms of what it means for the reader rather than what it means to a linker. --- .../setup-randlapack-deps-windows/setup.ps1 | 67 ++++++------------- INSTALL_WINDOWS.md | 38 +++-------- docs/CI.md | 15 +++-- install/install.ps1 | 61 ++++++----------- 4 files changed, 58 insertions(+), 123 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index df764030..0d719496 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -349,42 +349,26 @@ if ($Backend -eq "mkl") { } if (-not $mklLayout -and -not $provisionMkl) { # Reached two ways: -NoDownload, or the question above answered no. - # Both mean "no backend is available", so both get the same full list - # of ways to supply one; only the reason differs. - # - # Probing the literal oneAPI path (rather than requiring MKLROOT) is - # the polished Windows behaviour: that path IS canonical for oneMKL, - # even though Windows has no general system prefix for third-party - # libraries. See randnla/reference/windows-software-distribution.md. - # - # Details to the console, short throw -- the same shape install.ps1's - # preflight uses. A long multi-line throw message gets echoed twice by - # PowerShell (message, then FullyQualifiedErrorId) and buried in a - # stack trace, which makes actionable guidance harder to read, not - # easier. + # Both mean no backend is available, so both get the same options. + # Details go to the console and the throw stays short: PowerShell + # echoes a long throw message twice and buries it in a stack trace. $mklRootShown = if ($env:MKLROOT) { $env:MKLROOT } else { "(not set)" } $oneApiShown = if ($env:ONEAPI_ROOT) { Join-Path $env:ONEAPI_ROOT "mkl\latest" } else { "(not set)" } Write-Host "" $why = if ($NoDownload) { "-NoDownload was given" } else { "the download was declined" } - Write-Host "No oneMKL installation found, and $why." + Write-Host "No oneMKL found, and $why." Write-Host "" - Write-Host " Looked in (in order):" - Write-Host " -MklRoot (not given)" - Write-Host " `$env:MKLROOT $mklRootShown" - Write-Host " `$env:ONEAPI_ROOT\mkl\latest $oneApiShown" - Write-Host " C:\Program Files (x86)\Intel\oneAPI\mkl\latest (default oneAPI location)" + Write-Host " Searched: -MklRoot (not given)" + Write-Host " `$env:MKLROOT $mklRootShown" + Write-Host " `$env:ONEAPI_ROOT $oneApiShown" + Write-Host " C:\Program Files (x86)\Intel\oneAPI\mkl\latest" Write-Host "" - Write-Host " A directory only counts if it holds mkl_intel_ilp64_dll.lib under lib\ or" - Write-Host " lib\intel64\ alongside a bin\ (or redist\intel64\) DLL directory, so a" - Write-Host " partial install is rejected rather than half-used." - Write-Host "" - Write-Host " Pick one:" - Write-Host " 1. Install oneMKL: winget install --id Intel.oneMKL --exact" - Write-Host " 2. Use a copy you already have: -MklRoot `"`"" - Write-Host " 3. Use OpenBLAS instead: -Backend openblas" - Write-Host " 4. Let the installer fetch a pinned oneMKL (~155 MB into" - Write-Host " $resolvedRoot; nothing installed system-wide):" - Write-Host " re-run and answer yes, or pass -Yes to skip the question." + Write-Host " Any of these works:" + Write-Host " winget install --id Intel.oneMKL --exact" + Write-Host " -MklRoot `"`" use a copy you already have" + Write-Host " -Backend openblas use OpenBLAS instead" + Write-Host " re-run and answer yes (or pass -Yes) download a pinned oneMKL," + Write-Host " ~155 MB, into this project only" Write-Host "" throw "No BLAS/LAPACK backend available; see the options above." } @@ -596,23 +580,12 @@ if ($Backend -eq "custom") { } function Copy-LibrariesToSpaceFreePath { - # BLAS++'s BLASFinder feeds library paths into a try_compile *unquoted*, - # so under the Ninja generator a path containing a space is split at the - # space and the probe dies with, e.g.: - # ninja: error: 'C:/Program', needed by 'cmTC_x.exe', missing - # BLASFinder reports only "BLAS library not found", which points at the - # library rather than at the path that broke. - # - # This is not a corner case: Intel installs oneMKL to - # C:\Program Files (x86)\Intel\oneAPI\ by default, so EVERY discovered - # oneMKL hits it, as does any custom backend under Program Files. It is - # invisible to CI and to the download path because those land in a - # space-free directory under the dependency root. - # - # Import libraries are self-contained (they only name their DLL, which is - # still resolved at run time from $backendBin), so copying them next to - # the rest of the dependency tree is safe. Only done when needed, to keep - # the common case transparent. + # BLAS++ splits library paths on spaces before probing them, so any path + # containing one fails as "BLAS library not found". Intel's default oneMKL + # location has a space, making this the common case rather than a corner + # one. Fixed upstream in icl-utk-edu/blaspp#137; removing this workaround + # is tracked in #158. Import libraries only name their DLL, still loaded + # at run time from $backendBin, so relocating them is safe. param([string[]]$Libraries, [string]$Destination, [string]$Label) if (-not ($Libraries | Where-Object { $_ -match ' ' })) { return $Libraries } New-Item -ItemType Directory -Force -Path $Destination | Out-Null diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 94115c9f..5ff61552 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -73,34 +73,10 @@ dependencies, builds RandLAPACK, and runs its test suite. Everything lands in a sibling `RandNLA-project\` directory; re-running the script reuses what is already built. -Unlike Linux and macOS, you are not expected to install a BLAS library first. -Windows has no system location for third-party libraries, so projects acquire -their own -- that is what vcpkg, Conan, and NuGet exist for. Anything the -installer downloads goes inside `RandNLA-project\`, never into your system, -and deleting that directory removes it completely. - -If no oneMKL is found, the installer explains what it searched and asks before -downloading anything: - -``` -No existing oneMKL found (checked -MklRoot, $env:MKLROOT, $env:ONEAPI_ROOT, -and C:\Program Files (x86)\Intel\oneAPI\mkl\latest). - -A pinned, checksum-verified copy (~155 MB) can be downloaded into - ...\RandNLA-project\install -It is used only by this project: nothing is installed system-wide, no PATH -or registry changes, and deleting that directory removes it completely. - -Download oneMKL now? [Y/n] -``` - -Answering no prints the alternatives and stops. Questions are skipped when the -installer is not attached to a terminal (a script, a pipeline, CI), taking the -default shown in brackets; `-Yes` skips them in an interactive session too. - -If you would rather supply the library yourself, use `-MklRoot` to point at an -existing oneMKL, or `-NoDownload` to make a missing one an error instead of a -download. See §4. +You are not expected to install a BLAS library first, unlike on Linux and +macOS. If the installer cannot find one it says what it searched and asks +before downloading a pinned copy into the project directory -- nothing is +installed system-wide. Section 4 covers the choices. ## 2. How this differs from Linux and macOS @@ -206,6 +182,12 @@ on a BLAS/LAPACK library of your choice. On Windows the installer supports: | **OpenBLAS** | `-Backend openblas` | solid free backend; 32-bit integers (LP64); RandBLAS's portable sparse fallbacks replace the MKL-only accelerations | official OpenBLAS release binaries, pinned and checksum-verified; the archive is self-contained and includes full LAPACK | | **Custom / bring-your-own** | `-Backend custom -BlasLibraries ` | anything BLAS++/LAPACK++ can link -- e.g. AMD AOCL, a local ILP64 OpenBLAS build | you provide the import libraries (and their DLL directory via `-BackendBinDir`); the installer verifies them with a small link-and-run check before building anything | +**ILP64 and LP64** describe the integer width a BLAS library uses for matrix +dimensions -- 64-bit and 32-bit respectively. It matters only if you mix +libraries built for different widths; the installer keeps it consistent for +you, and the practical difference is that ILP64 lets RandBLAS use oneMKL's +accelerated sparse routines. + Notes for the custom path: AMD AOCL downloads sit behind a click-through license page, so the installer cannot fetch them -- download AOCL yourself, then point `-BlasLibraries` at its `.lib` files. The custom path is expected diff --git a/docs/CI.md b/docs/CI.md index e51b1490..be73fad1 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -65,7 +65,7 @@ candidates once they have a green track record. mkl-openmp), no second install-script backend leg (the installer's `-Backend` forwarding is thin; provisioning is covered by the openblas core leg), and no Windows ASan lane. -- **The two Windows guard jobs test the documented *user* path, which the +- **The Windows guard job tests the documented *user* path, which the build matrix structurally cannot.** Every build leg initializes MSVC with an explicit `arch: x64` under `pwsh`, so none of them exercises the shell the install docs actually tell users to open. That gap is precisely how a @@ -138,12 +138,13 @@ candidates once they have a green track record. prepend remains in run-ci.ps1/install.ps1 only for the RandBLAS submodule's own test executables, until RandBLAS gains the same staging (planned follow-up). -- **BLAS++ and LAPACK++ on Windows come from BallisticLA fork branches** - (`BallisticLA/blaspp@remove-symv-debug-print`, - `BallisticLA/lapackpp@msvc-direct-includes`) carrying two one-line MSVC - fixes. Upstream PRs are open (blaspp #132, lapackpp #87); once merged, the - clones in `.github/actions/setup-randlapack-deps-windows/setup.ps1` move - back to upstream master. +- **Every Windows dependency is pinned to an immutable ref**, and comes from + its canonical upstream. BLAS++ and LAPACK++ are pinned to commits rather + than tags only because the latest release of each, `v2025.05.28`, predates + the MSVC fixes this build needs (blaspp #132, lapackpp #87, merged upstream + 2026-08-06); they move to a tag once one carries them. A branch name would + be a moving target inside a cache keyed on the setup script, so a cache hit + could restore a different revision than a miss builds. ## Caches diff --git a/install/install.ps1 b/install/install.ps1 index 43e4d1a1..85aee87b 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -1,53 +1,32 @@ # RandLAPACK native Windows installer -- the companion to install.sh. -# -# Run from an "x64 Native Tools Command Prompt for VS 2022" in the repository -# root: +# Full guide, including how to get an x64 toolchain: INSTALL_WINDOWS.md # # powershell -ExecutionPolicy Bypass -File .\install\install.ps1 # -# (That prompt is cmd, hence the explicit launch; the policy flag is because -# Windows blocks PowerShell scripts by default on a fresh machine.) -# -# The architecture matters: the plain "Developer PowerShell/Command Prompt for -# VS 2022" entries default to a 32-bit (x86) toolchain, which cannot link the -# x64 BLAS/LAPACK libraries this installer provisions. Preflight rejects that -# case with an explanation rather than letting it fail deep in the build. -# -# What it does, mirroring install.sh's layout in a sibling RandNLA-project -# directory: -# 1. Builds/reuses the dependencies (a BLAS/LAPACK backend, GoogleTest, -# Random123, BLAS++, LAPACK++) under -# \install. -# 2. Configures, builds, installs, and tests RandLAPACK. +# Builds or reuses the dependencies (a BLAS/LAPACK backend, GoogleTest, +# Random123, BLAS++, LAPACK++) under \install, then configures, +# builds, installs and tests RandLAPACK. Mirrors install.sh's layout in a +# sibling RandNLA-project directory. GPU support is not available on native +# Windows yet. # # Options: # -ProjectDir Where dependencies/builds/installs go -# (default: ..\RandNLA-project relative to this script). -# -Backend BLAS/LAPACK backend: mkl (default; discovered from an -# installed oneAPI, otherwise offered as a pinned -# download), openblas (official release binaries), or -# custom (bring your own via -BlasLibraries). -# -MklRoot Use this oneMKL install (oneAPI layout) instead of -# auto-discovery. Backend mkl only. -# -NoDownload Fail instead of downloading a backend that was not -# found locally. The default is to fetch one into -# (project-local; nothing is installed -# system-wide), which is ordinary Windows practice. -# -Yes Skip interactive questions, taking each documented -# default. Questions are already skipped when stdin -# is not a terminal. +# (default: ..\RandNLA-project next to the clone). +# -Backend mkl (default) | openblas | custom. See setup.ps1. +# -MklRoot Use this oneMKL install instead of discovery. +# -NoDownload Fail rather than download a backend that was not +# found locally. The default fetches one into +# ; nothing is installed system-wide. +# -Yes Skip interactive questions, taking each default. +# Already skipped when stdin is not a terminal. +# -NoOpenMP Build serially. The default enables OpenMP through +# MSVC's /openmp:llvm runtime. +# -Fresh Reconfigure RandLAPACK from scratch. Dependencies are +# always reused; delete \install to rebuild +# them. +# -SkipTests Do not run the test suite after building. # -BlasLibraries / -LapackLibraries / -BackendBinDir / -BlasInt / -BlasFortran # Backend custom only; see setup.ps1's header. -# -Fresh Reconfigure RandLAPACK from scratch (dependencies are -# always reused when present; delete \install -# subdirectories to force dependency rebuilds). -# -NoOpenMP Build serially. The default enables OpenMP via MSVC's -# /openmp:llvm runtime (the only mode that accepts -# RandLAPACK's 64-bit loop indices and collapse -# clauses); a serial build is fully functional too. -# -SkipTests Do not run the test suite after building. -# -# GPU support is not available on native Windows yet. [CmdletBinding()] param( From f37ef421a53b2935d4363c53698026b094fdfbad Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 13:39:07 -0700 Subject: [PATCH 18/20] INSTALL_WINDOWS.md: document the oneMKL prompt again, in section 4 Trimming the quick start removed the prompt transcript with a pointer to section 4, but section 4 never carried it, so the interactive prompt ended up documented nowhere -- a regression introduced by that trim, not present before it. Section 4 now shows what the installer asks and what declining does, which is where it belongs: the quick start says a question may appear, the backend section explains it. --- INSTALL_WINDOWS.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 5ff61552..151bb1b9 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -188,6 +188,30 @@ libraries built for different widths; the installer keeps it consistent for you, and the practical difference is that ILP64 lets RandBLAS use oneMKL's accelerated sparse routines. +### When no oneMKL is found + +The installer says what it searched and asks before downloading anything: + +``` +No oneMKL found, and the download was declined. + + Searched: -MklRoot (not given) + $env:MKLROOT (not set) + $env:ONEAPI_ROOT (not set) + C:\Program Files (x86)\Intel\oneAPI\mkl\latest + + Any of these works: + winget install --id Intel.oneMKL --exact + -MklRoot "" use a copy you already have + -Backend openblas use OpenBLAS instead + re-run and answer yes (or pass -Yes) download a pinned oneMKL, + ~155 MB, into this project only +``` + +Answering no stops with those options. Questions are skipped entirely when the +installer is not attached to a terminal -- a script, a pipeline, CI -- taking +the default shown in brackets; `-Yes` skips them in an interactive session too. + Notes for the custom path: AMD AOCL downloads sit behind a click-through license page, so the installer cannot fetch them -- download AOCL yourself, then point `-BlasLibraries` at its `.lib` files. The custom path is expected From 0190e9a18055810e499f5c7a533cafa7feb61360 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 14:14:19 -0700 Subject: [PATCH 19/20] CI: stop running every job twice, and supersede in-flight pull-request runs Every workflow fired on `pull_request` AND on `push` for every branch, so each commit on a pull request ran every job twice: same SHA, same result. With 11 jobs across the four workflows that is 22 check runs per commit, half of them redundant -- visible on this very branch, where every check count all session read double. `push` is now restricted to `main`; a branch with an open pull request is already covered by the `pull_request` event, and `main` still gets a run of its own. Nothing cancelled superseded runs either, so pushing a fix while CI was in flight left the previous run going to completion, a full Windows matrix included. A `concurrency` group now supersedes in-flight runs, but only for pull requests: on `main` every commit should be validated, not just the newest, so `cancel-in-progress` is false there. The one behaviour this removes is automatic CI for a branch with no pull request open. `workflow_dispatch` covers that on demand and is already declared in all four workflows. Rationale recorded in docs/CI.md so neither is "tidied" back. --- .github/workflows/core-linux.yaml | 14 ++++++++++++-- .github/workflows/core-macos.yaml | 14 ++++++++++++-- .github/workflows/core-windows.yaml | 14 ++++++++++++-- .github/workflows/install-script.yaml | 14 ++++++++++++-- docs/CI.md | 12 ++++++++++++ 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/.github/workflows/core-linux.yaml b/.github/workflows/core-linux.yaml index c060fdff..881db980 100644 --- a/.github/workflows/core-linux.yaml +++ b/.github/workflows/core-linux.yaml @@ -4,9 +4,19 @@ on: branches-ignore: - cqrrp-gpu-benchmarking workflow_dispatch: + # Only main. A branch with an open pull request is already covered by the + # pull_request event above, and firing on both ran every job twice per + # commit -- same SHA, same result. push: - branches-ignore: - - cqrrp-gpu-benchmarking + branches: + - main + +# One run per ref at a time. Pushing a fix used to leave the superseded run +# going to completion. Superseding is only safe for pull requests: on main we +# want every commit validated, not just the newest. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} # BLAS++/LAPACK++/Random123 track their upstream default branches but change # rarely; their installs are cached below. Bump the -v suffix in the cache key diff --git a/.github/workflows/core-macos.yaml b/.github/workflows/core-macos.yaml index 6ec0f90f..2a86325e 100644 --- a/.github/workflows/core-macos.yaml +++ b/.github/workflows/core-macos.yaml @@ -4,9 +4,19 @@ on: branches-ignore: - cqrrp-gpu-benchmarking workflow_dispatch: + # Only main. A branch with an open pull request is already covered by the + # pull_request event above, and firing on both ran every job twice per + # commit -- same SHA, same result. push: - branches-ignore: - - cqrrp-gpu-benchmarking + branches: + - main + +# One run per ref at a time. Pushing a fix used to leave the superseded run +# going to completion. Superseding is only safe for pull requests: on main we +# want every commit validated, not just the newest. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} # Dependency installs are cached and the RandBLAS submodule's own test suite # is disabled; see core-linux.yaml for the reasoning. diff --git a/.github/workflows/core-windows.yaml b/.github/workflows/core-windows.yaml index 1e24928a..6349301a 100644 --- a/.github/workflows/core-windows.yaml +++ b/.github/workflows/core-windows.yaml @@ -4,9 +4,19 @@ on: branches-ignore: - cqrrp-gpu-benchmarking workflow_dispatch: + # Only main. A branch with an open pull request is already covered by the + # pull_request event above, and firing on both ran every job twice per + # commit -- same SHA, same result. push: - branches-ignore: - - cqrrp-gpu-benchmarking + branches: + - main + +# One run per ref at a time. Pushing a fix used to leave the superseded run +# going to completion. Superseding is only safe for pull requests: on main we +# want every commit validated, not just the newest. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: # Structural gate for the documented *user* path, which the build matrix diff --git a/.github/workflows/install-script.yaml b/.github/workflows/install-script.yaml index 50276d1d..d78fffd8 100644 --- a/.github/workflows/install-script.yaml +++ b/.github/workflows/install-script.yaml @@ -16,9 +16,19 @@ on: branches-ignore: - cqrrp-gpu-benchmarking workflow_dispatch: + # Only main. A branch with an open pull request is already covered by the + # pull_request event above, and firing on both ran every job twice per + # commit -- same SHA, same result. push: - branches-ignore: - - cqrrp-gpu-benchmarking + branches: + - main + +# One run per ref at a time. Pushing a fix used to leave the superseded run +# going to completion. Superseding is only safe for pull requests: on main we +# want every commit validated, not just the newest. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: install-linux: diff --git a/docs/CI.md b/docs/CI.md index be73fad1..6bf7789a 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -25,6 +25,18 @@ candidates once they have a green track record. ## Things that are deliberate (do not "fix" without reading this) +- **Workflows fire on `pull_request`, and on `push` only for `main`.** They + used to fire on `push` for every branch as well, so each commit on a pull + request ran every job twice -- same SHA, same result, double the minutes. + A branch with an open pull request is covered by the `pull_request` event; + `main` still gets a run of its own. The one thing this drops is CI for a + branch with no pull request open, which `workflow_dispatch` covers on + demand. +- **`concurrency` supersedes in-flight runs, for pull requests only.** Pushing + a fix used to leave the previous run going to completion. On `main`, + `cancel-in-progress` is deliberately false: every commit there should be + validated, not just the newest. + - **`TestQB.Polynomial_Decay_general1` is QUARANTINED on macOS -- THIS IS TEMPORARY AND MUST BE REVERTED.** The test fails on Apple Silicon because Apple's default (old) Accelerate LAPACK has a broken divide-and-conquer From e756c0c2155dcf29b504b57bd0f545db915d8317 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 14:16:50 -0700 Subject: [PATCH 20/20] Qualify upstream PR references so they do not link to this repository References like "blaspp #132" and "lapackpp #87" were written bare. GitHub resolves a bare #NNN against the repository it is rendered in, so these linked to RandLAPACK #132 and #87 -- unrelated pull requests (funNystrom++ and a benchmarking DNM) -- rather than to the upstream fixes they name. A reader following them lands somewhere plausible and wrong, which is worse than a dead link. Now written as icl-utk-edu/blaspp#132 and icl-utk-edu/lapackpp#87, which GitHub renders as cross-repository links. Same for the other upstream references in these files. --- .github/actions/setup-randlapack-deps-windows/setup.ps1 | 2 +- INSTALL_WINDOWS.md | 3 ++- docs/CI.md | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/actions/setup-randlapack-deps-windows/setup.ps1 b/.github/actions/setup-randlapack-deps-windows/setup.ps1 index 0d719496..d0cf2579 100644 --- a/.github/actions/setup-randlapack-deps-windows/setup.ps1 +++ b/.github/actions/setup-randlapack-deps-windows/setup.ps1 @@ -10,7 +10,7 @@ # # Everything is fetched from its canonical upstream and pinned to an immutable # ref. The two one-line MSVC fixes this build needs merged upstream on -# 2026-08-06 (blaspp PR #132, lapackpp PR #87), so the BallisticLA forks these +# 2026-08-06 (icl-utk-edu/blaspp#132, icl-utk-edu/lapackpp#87), so the forks these # once pointed at are no longer needed. The pins are commits rather than tags # only because the latest release of each, v2025.05.28, predates those merges. # diff --git a/INSTALL_WINDOWS.md b/INSTALL_WINDOWS.md index 151bb1b9..3286051b 100644 --- a/INSTALL_WINDOWS.md +++ b/INSTALL_WINDOWS.md @@ -243,7 +243,8 @@ The version above therefore applies only to the no-oneMKL case. **Two deliberate exceptions to "pin a stable release".** BLAS++ and LAPACK++ are pinned to commits rather than to their latest release, `v2025.05.28`, because that release predates the -two one-line MSVC fixes this build needs (blaspp #132 and lapackpp #87, both merged upstream on +two one-line MSVC fixes this build needs (icl-utk-edu/blaspp#132 and +icl-utk-edu/lapackpp#87, both merged upstream on 2026-08-06). They move to a release tag as soon as one includes those fixes. Everything else is a stable, released version. diff --git a/docs/CI.md b/docs/CI.md index 6bf7789a..8509830a 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -45,8 +45,8 @@ candidates once they have a green track record. is always red reports nothing. **Revert once Apple's new Accelerate interface is in use**, i.e. when both - of these land (order matters -- lapackpp#88 needs the `defines.h` from - blaspp#134): + of these land (order matters -- icl-utk-edu/lapackpp#88 needs the + `defines.h` from icl-utk-edu/blaspp#134): - -- New Apple Accelerate support, a rebased and completed continuation of the stalled @@ -153,7 +153,8 @@ candidates once they have a green track record. - **Every Windows dependency is pinned to an immutable ref**, and comes from its canonical upstream. BLAS++ and LAPACK++ are pinned to commits rather than tags only because the latest release of each, `v2025.05.28`, predates - the MSVC fixes this build needs (blaspp #132, lapackpp #87, merged upstream + the MSVC fixes this build needs (icl-utk-edu/blaspp#132, + icl-utk-edu/lapackpp#87, merged upstream 2026-08-06); they move to a tag once one carries them. A branch name would be a moving target inside a cache keyed on the setup script, so a cache hit could restore a different revision than a miss builds.