From cffe1368790e4480249ce7c2ba162b1137249dd9 Mon Sep 17 00:00:00 2001 From: mmelnich Date: Wed, 12 Aug 2026 17:53:19 -0700 Subject: [PATCH] Add install/install.ps1, and pin the Windows dependency provisioner Completes the installer pair. install.ps1 produces the same RandNLA-project layout as install.sh, honours RANDNLA_PROJECT_DIR with the same precedence, and delegates dependency provisioning to the setup script CI already uses so there is one implementation rather than two that drift. The x64 toolchain guard is the reason this exists in the form it does. "Developer PowerShell for VS" and "Developer Command Prompt for VS" both default to an *x86* toolchain, and an x86 linker cannot use the x64 import libraries every BLAS backend ships. Left unchecked the failure surfaces three layers down as BLAS++ reporting "BLAS library not found", blaming the libraries when the compiler is at fault -- which is exactly how this was diagnosed in RandLAPACK. toolchain-arch.ps1 reads VSCMD_ARG_TGT_ARCH, then the bin\Host\\ convention, then cl.exe's banner, and refuses x86 and arm64 with different messages because they need different answers. Provisioner changes: * Off personal forks. BLAS++ and LAPACK++ came from RaphaelArkadyMeyerNYU/*; both MSVC fixes merged upstream on 2026-08-06 (icl-utk-edu/blaspp#132, icl-utk-edu/lapackpp#87), so both now come from icl-utk-edu pinned to the merge commits. * Pinned and provenance-stamped. Clone-Head took a branch name and returned early whenever the destination merely existed, so a branch tip could move between runs and changing a ref was a silent no-op for anyone who already had the directory. Random123 in particular was fetched at the default branch, unpinned. Clone-Pinned fetches one ref and records it. Also exports randblas_stage_runtime_dlls() from the installed package. Windows searches an executable's own directory first and PATH last, so a downstream project linking installed RandBLAS could not find the BLAS DLLs at run time -- the function existed only in the build tree. Found because the installer's own verification step is such a consumer and could not configure without it. Verified on Windows 11 with Windows PowerShell 5.1 and VS 2022 Build Tools: missing-prerequisite path, x86 toolchain refused at preflight under a real vcvars32 environment, and a full x64 install from scratch -- oneMKL through vcpkg, BLAS++, GoogleTest, RandBLAS -- ending with the verification program compiling, linking, staging its DLLs and running, reporting ILP64. --- .../setup-randblas-deps-windows/setup.ps1 | 66 +++- .github/scripts/windows/toolchain-arch.ps1 | 75 ++++ CMake/RandBLASConfig.cmake.in | 7 + CMake/rb_config.cmake | 9 +- install/install.ps1 | 364 ++++++++++++++++++ 5 files changed, 500 insertions(+), 21 deletions(-) create mode 100644 .github/scripts/windows/toolchain-arch.ps1 create mode 100644 install/install.ps1 diff --git a/.github/actions/setup-randblas-deps-windows/setup.ps1 b/.github/actions/setup-randblas-deps-windows/setup.ps1 index 63454e33..44c7cfbe 100644 --- a/.github/actions/setup-randblas-deps-windows/setup.ps1 +++ b/.github/actions/setup-randblas-deps-windows/setup.ps1 @@ -44,25 +44,59 @@ function Find-PackageConfigDirectory { return $config.Directory.FullName } -function Clone-Head { +# Fetch exactly one commit or tag, and record where it came from. +# +# This replaces a clone that took a branch name and returned early whenever the +# destination merely existed. Two problems with that: a branch tip moves, so +# two runs of the same script could build different source; and reuse keyed on +# presence means changing a ref is a silent no-op for anyone who already has +# the directory, so the new pin never takes effect. The stamp is needed +# because a shallow fetch of a tag does not keep the tag ref locally, so git +# cannot be asked afterwards whether a tree is at the pin. +function Clone-Pinned { param( [Parameter(Mandatory = $true)][string] $Url, [Parameter(Mandatory = $true)][string] $Destination, - [string] $Branch = "" + [Parameter(Mandatory = $true)][string] $Ref ) - if (Test-Path -LiteralPath $Destination) { + $stampPath = Join-Path $Destination ".randblas-provenance" + $stamp = "$Url@$Ref" + if ((Test-Path -LiteralPath $stampPath) -and + ((Get-Content -LiteralPath $stampPath -Raw).Trim() -eq $stamp)) { + Write-Host "Reusing $Destination (already at $Ref)" return } - $arguments = @("clone", "--depth", "1") - if ($Branch) { - $arguments += @("--branch", $Branch) + if (Test-Path -LiteralPath $Destination) { + Remove-Item -Recurse -Force -LiteralPath $Destination } - $arguments += @($Url, $Destination) - Invoke-Checked -Program "git" -Arguments $arguments + New-Item -ItemType Directory -Force -Path $Destination | Out-Null + Invoke-Checked -Program "git" -Arguments @("-C", $Destination, "init", "--quiet") + Invoke-Checked -Program "git" -Arguments @("-C", $Destination, "remote", "add", "origin", $Url) + Invoke-Checked -Program "git" -Arguments @("-C", $Destination, "fetch", "--quiet", "--depth", "1", "origin", $Ref) + Invoke-Checked -Program "git" -Arguments @("-C", $Destination, "checkout", "--quiet", "FETCH_HEAD") + Set-Content -LiteralPath $stampPath -Value $stamp -Encoding ascii } +#------------------------------------------------------------------ pins ------ +# Immutable refs only: a tag or a full commit hash, never a branch. These match +# install/install.sh and the refs RandLAPACK validated, so the two installers +# and CI cannot disagree about what they built. +# +# BLAS++ and LAPACK++ previously came from personal forks carrying one-line +# MSVC fixes. Both merged upstream on 2026-08-06 (icl-utk-edu/blaspp#132, +# icl-utk-edu/lapackpp#87), so both now come from icl-utk-edu, pinned to the +# merge commits: the latest release of each, v2025.05.28, predates the fixes. +$BlasppUrl = "https://github.com/icl-utk-edu/blaspp.git" +$BlasppRef = "30571853f980d3a2a1737124ea4789e025a5e045" +$LapackppUrl = "https://github.com/icl-utk-edu/lapackpp.git" +$LapackppRef = "40b9d0daf29b6f1f3fa58bc3f22bd6cfb2c67fe4" +$Random123Url = "https://github.com/DEShawResearch/Random123.git" +$Random123Ref = "v1.14.0" +$GTestUrl = "https://github.com/google/googletest.git" +$GTestRef = "v1.18.0" + function Export-GitHubValue { param( [Parameter(Mandatory = $true)][string] $Name, @@ -188,8 +222,7 @@ $gtestVariant = if ($SanitizeAddress) { "googletest-asan" } else { "googletest" $gtestBuild = Join-Path $DependencyRoot "$gtestVariant-build" $gtestInstall = Join-Path $DependencyRoot "$gtestVariant-install" if (-not (Test-Path -LiteralPath (Join-Path $gtestInstall "lib\cmake\GTest\GTestConfig.cmake"))) { - Clone-Head -Url "https://github.com/google/googletest.git" ` - -Destination $gtestSource -Branch "v1.17.0" + Clone-Pinned -Url $GTestUrl -Destination $gtestSource -Ref $GTestRef $gtestArguments = @( "-S", $gtestSource, "-B", $gtestBuild, @@ -215,8 +248,7 @@ $random123Source = Join-Path $DependencyRoot "Random123" $random123Install = Join-Path $DependencyRoot "Random123-install" $random123Include = Join-Path $random123Install "include" if (-not (Test-Path -LiteralPath (Join-Path $random123Include "Random123\philox.h"))) { - Clone-Head -Url "https://github.com/DEShawResearch/Random123.git" ` - -Destination $random123Source + Clone-Pinned -Url $Random123Url -Destination $random123Source -Ref $Random123Ref New-Item -ItemType Directory -Force -Path $random123Include | Out-Null Copy-Item -LiteralPath (Join-Path $random123Source "include\Random123") ` -Destination $random123Include -Recurse @@ -229,10 +261,7 @@ $blasppConfig = Get-ChildItem -LiteralPath $blasppInstall -Recurse -File ` -Filter "blasppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $blasppConfig) { - Clone-Head ` - -Url "https://github.com/RaphaelArkadyMeyerNYU/blaspp.git" ` - -Destination $blasppSource ` - -Branch "windows-portability" + Clone-Pinned -Url $BlasppUrl -Destination $blasppSource -Ref $BlasppRef $blasLibraryArgument = ($mklLibraries | ForEach-Object { Convert-ToCMakePath $_ }) -join ";" @@ -267,10 +296,7 @@ if ($InstallLapackpp) { -Filter "lapackppConfig.cmake" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $lapackppConfig) { - Clone-Head ` - -Url "https://github.com/RaphaelArkadyMeyerNYU/lapackpp.git" ` - -Destination $lapackppSource ` - -Branch "msvc-compatibility" + Clone-Pinned -Url $LapackppUrl -Destination $lapackppSource -Ref $LapackppRef Invoke-Checked -Program "cmake" -Arguments @( "-S", $lapackppSource, diff --git a/.github/scripts/windows/toolchain-arch.ps1 b/.github/scripts/windows/toolchain-arch.ps1 new file mode 100644 index 00000000..906be200 --- /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: RandBLAS 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 RandBLAS and its BLAS 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/CMake/RandBLASConfig.cmake.in b/CMake/RandBLASConfig.cmake.in index 3ac3f89a..4b5ee52c 100644 --- a/CMake/RandBLASConfig.cmake.in +++ b/CMake/RandBLASConfig.cmake.in @@ -50,4 +50,11 @@ endif() # MKL sparse BLAS set(RandBLAS_HAS_MKL @RandBLAS_HAS_MKL@) +# Provides randblas_stage_runtime_dlls(), which copies a target's +# imported DLL dependencies next to the executable. Windows searches the +# executable's own directory first and PATH last, so a consumer that links +# installed RandBLAS otherwise cannot find the BLAS DLLs at run time. Exported +# rather than kept build-tree-only so consumers do not each reinvent it. +include("${CMAKE_CURRENT_LIST_DIR}/RuntimeDLLs.cmake") + include(RandBLAS) diff --git a/CMake/rb_config.cmake b/CMake/rb_config.cmake index 83e2c903..ec8ae331 100644 --- a/CMake/rb_config.cmake +++ b/CMake/rb_config.cmake @@ -11,7 +11,14 @@ configure_file(CMake/RandBLASConfigVersion.cmake.in ${CMAKE_INSTALL_LIBDIR}/cmake/RandBLAS/RandBLASConfigVersion.cmake @ONLY) if (PROJECT_NAME STREQUAL "RandBLAS") - install(FILES CMake/FindRandom123.cmake + # RuntimeDLLs.cmake ships with the package because downstream Windows + # consumers need randblas_stage_runtime_dlls() as much as this project + # does: on Windows the loader searches the executable's own directory + # first and PATH last, so an executable linking installed RandBLAS has no + # way to find the BLAS DLLs unless they are staged beside it. Without + # this, the function exists only in the build tree and every consumer has + # to reinvent it. + install(FILES CMake/FindRandom123.cmake CMake/RuntimeDLLs.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/RandBLAS") endif() diff --git a/install/install.ps1 b/install/install.ps1 new file mode 100644 index 00000000..fd2a43e3 --- /dev/null +++ b/install/install.ps1 @@ -0,0 +1,364 @@ +# RandBLAS autoinstaller for native Windows (MSVC). +# +# Builds RandBLAS and the dependencies it needs into a self-contained +# "RandNLA-project" directory, the same layout install.sh produces on Linux and +# macOS: +# lib: dependency sources +# install: RandBLAS-install and the dependency installs +# build: one build directory per project above +# +# Nothing is installed system-wide, no PATH entry is created, and your +# environment is not modified unless you pass -ModifyEnvironment. +# +# You bring Visual Studio (or the Build Tools), CMake and Git, in an x64 +# developer shell. This script does not install a toolchain; when one is +# missing or wrong it says so and tells you how to fix it. +# +# Prerequisites and supported configurations are in INSTALL.md. + +[CmdletBinding()] +param( + # Where dependencies, builds and installs go. Defaults to + # $env:RANDNLA_PROJECT_DIR when set -- which is what lets this installer + # and RandLAPACK's share one dependency tree -- and otherwise to a + # RandNLA-project directory beside this clone. + [string] $ProjectDir = "", + + # Where the dependency stack lives. Defaults to \install. CI + # points this at a cache shared with the core workflow. + [string] $DependencyRoot = "", + + # Install RandBLAS itself here instead of \install\RandBLAS-install. + # Dependencies still go in the project directory. + [string] $Prefix = "", + + [int] $Jobs = 0, + [switch] $Fresh, + [switch] $SkipTests, + [switch] $Examples, + [switch] $ModifyEnvironment, + [switch] $Yes +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoDir = Split-Path -Parent $scriptDir + +#============================================================================== +# Toolchain preflight. +# +# The architecture guard is the important one. "Developer PowerShell for VS" +# and "Developer Command Prompt for VS" both default to an *x86* toolchain, and +# an x86 linker cannot use the x64 import libraries every BLAS backend here +# ships. Without this check the failure surfaces three layers down as BLAS++ +# reporting "BLAS library not found", which blames the libraries when the +# compiler is at fault. +#============================================================================== +. (Join-Path $repoDir ".github\scripts\windows\toolchain-arch.ps1") + +$missing = @() +foreach ($tool in @("cl.exe", "cmake.exe", "git.exe")) { + if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { $missing += $tool } +} +if ($missing.Count -gt 0) { + Write-Host "" + Write-Host "PREREQUISITE MISSING: $($missing -join ', ') not found on PATH." -ForegroundColor Red + Write-Host "" + Write-Host " RandBLAS needs Visual Studio (or the Build Tools) with the C++ workload," + Write-Host " plus CMake and Git, in an x64 developer shell." + Write-Host "" + Write-Host " Open 'x64 Native Tools Command Prompt for VS 2022' from the Start menu," + Write-Host " or run this in any Command Prompt to configure one:" + Write-Host "" + Write-Host ' 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"' + Write-Host "" + Write-Host " See INSTALL.md for the full prerequisite list." + exit 1 +} + +$arch = Get-ClTargetArchitecture +$archProblem = Get-ToolchainArchitectureProblem $arch +if ($archProblem) { + Write-Host "" + Write-Host "WRONG TOOLCHAIN ARCHITECTURE" -ForegroundColor Red + Write-Host "" + Write-Host " $archProblem" + Write-Host "" + exit 1 +} + +#============================================================================== +# Interactivity. +# +# Prompts happen only when someone is there to answer: not with -Yes, and not +# when stdin is redirected. Every question has a defensible unattended default +# so an automated run cannot hang. +#============================================================================== +$script:Interactive = -not $Yes -and -not [Console]::IsInputRedirected ` + -and [Environment]::UserInteractive + +function Read-YesNo { + 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() + if ($reply -eq "") { return $Default } + if ($reply -match '^(y|yes)$') { return $true } + if ($reply -match '^(n|no)$') { return $false } + } +} + +if ($Jobs -le 0) { + $Jobs = [Environment]::ProcessorCount +} + +#============================================================================== +# Project layout. +# +# Precedence matches install.sh exactly: the flag, then RANDNLA_PROJECT_DIR, +# then a sibling of this clone. Honouring the environment variable is what +# lets a machine that has already run RandLAPACK's installer reuse its BLAS++ +# rather than building a second copy. +#============================================================================== +if (-not $ProjectDir) { + if ($env:RANDNLA_PROJECT_DIR) { + $ProjectDir = $env:RANDNLA_PROJECT_DIR + } else { + $ProjectDir = Join-Path (Split-Path $repoDir -Parent) "RandNLA-project" + } +} +$ProjectDir = [IO.Path]::GetFullPath($ProjectDir) + +# Deep dependency build trees plus MSVC's own path limits make long project +# paths fail in ways that are hard to attribute, so warn before the build +# rather than after. +if ($ProjectDir.Length -gt 150) { + Write-Warning ("ProjectDir is $($ProjectDir.Length) characters long. Deep dependency " + + "build trees may exceed Windows path limits; consider something shorter, such as C:\RandNLA.") +} + +if (-not $DependencyRoot) { $DependencyRoot = Join-Path $ProjectDir "install" } +$DependencyRoot = [IO.Path]::GetFullPath($DependencyRoot) + +$installDir = if ($Prefix) { + [IO.Path]::GetFullPath($Prefix) +} else { + Join-Path $ProjectDir "install\RandBLAS-install" +} +$buildDir = Join-Path $ProjectDir "build\RandBLAS-build" + +foreach ($d in @($ProjectDir, $DependencyRoot, (Join-Path $ProjectDir "lib"), (Join-Path $ProjectDir "build"))) { + New-Item -ItemType Directory -Force -Path $d | Out-Null +} +if ($Fresh -and (Test-Path -LiteralPath $buildDir)) { + Remove-Item -Recurse -Force -LiteralPath $buildDir +} +New-Item -ItemType Directory -Force -Path $buildDir | Out-Null + +Write-Host "" +Write-Host "RandBLAS installer" -ForegroundColor Cyan +Write-Host " toolchain x64 ($arch)" +Write-Host " project dir $ProjectDir" +Write-Host " dependencies $DependencyRoot" +Write-Host " install to $installDir" +Write-Host "" + +#============================================================================== +# Dependencies. +# +# Delegated to the same provisioner CI uses, so there is one implementation of +# "fetch oneMKL, build BLAS++, build GoogleTest" rather than two that drift. +# It pins every source to an immutable ref and records provenance, so a +# dependency is reused only when it came from what we would fetch now. +#============================================================================== +$setup = Join-Path $repoDir ".github\actions\setup-randblas-deps-windows\setup.ps1" +$setupArgs = @{ DependencyRoot = $DependencyRoot } +if ($Examples) { $setupArgs["InstallLapackpp"] = $true } + +Write-Host "[1/4] Provisioning dependencies (oneMKL, BLAS++, Random123, GoogleTest) ..." +# No $LASTEXITCODE check: setup.ps1 is a PowerShell script that sets +# $ErrorActionPreference = "Stop" and throws, so a failure propagates on its +# own. Reading $LASTEXITCODE here would be worse than redundant -- it is unset +# until some native command runs, and Set-StrictMode turns reading an unset +# variable into an error. That made the whole installer fail on exactly the +# runs where every dependency was already cached and no native command had run. +& $setup @setupArgs + +#============================================================================== +# RandBLAS. +#============================================================================== +$cmakeArgs = @( + "-S", $repoDir, + "-B", $buildDir, + "-G", "Ninja", + "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX=$($installDir.Replace('\','/'))", + "-Dblaspp_DIR=$($env:blaspp_DIR)", + "-DRandom123_DIR=$($env:Random123_DIR)" +) +if ($SkipTests) { + $cmakeArgs += "-DBUILD_TESTS=OFF" +} else { + $cmakeArgs += @("-DBUILD_TESTS=ON", "-DGTest_ROOT=$($env:googletest_PREFIX)") +} + +# Ninja is not guaranteed present outside a full Visual Studio install; fall +# back to the NMake generator the CI provisioner already uses. +if (-not (Get-Command "ninja.exe" -ErrorAction SilentlyContinue)) { + $cmakeArgs[5] = "NMake Makefiles" +} + +Write-Host "[2/4] Configuring RandBLAS ..." +& cmake @cmakeArgs +if ($LASTEXITCODE -ne 0) { throw "CMake configure failed." } + +Write-Host "[3/4] Building and installing RandBLAS ..." +& cmake --build $buildDir -j $Jobs --target install +if ($LASTEXITCODE -ne 0) { throw "Build failed." } + +#============================================================================== +# Verification. +# +# Compile, link and run a program against the finished install. Configuring is +# not the same as producing something that works: this catches a BLAS that +# resolves at configure time but fails to link, and a runtime DLL that was +# never staged beside the executable. +#============================================================================== +Write-Host "[4/4] Verifying the install links and runs ..." +$conftest = Join-Path $ProjectDir "build\conftest" +if (Test-Path -LiteralPath $conftest) { Remove-Item -Recurse -Force -LiteralPath $conftest } +New-Item -ItemType Directory -Force -Path (Join-Path $conftest "src") | Out-Null + +Set-Content -Path (Join-Path $conftest "src\CMakeLists.txt") -Encoding ascii -Value @( + 'cmake_minimum_required(VERSION 3.21)', + 'project(randblas_conftest CXX)', + 'find_package(RandBLAS REQUIRED)', + 'add_executable(conftest conftest.cc)', + 'target_link_libraries(conftest RandBLAS)', + 'randblas_stage_runtime_dlls(conftest)') + +Set-Content -Path (Join-Path $conftest "src\conftest.cc") -Encoding ascii -Value @( + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + 'int main() {', + '#if defined(BLAS_ILP64)', + ' std::printf("blas_ilp64=1\n");', + '#else', + ' std::printf("blas_ilp64=0\n");', + '#endif', + ' const int64_t m = 8, n = 4;', + ' std::vector S(m * n);', + ' RandBLAS::DenseDist D(m, n);', + ' RandBLAS::RNGState state(0);', + ' RandBLAS::fill_dense(D, S.data(), state);', + ' std::vector C(n * n, 0.0);', + ' blas::gemm(blas::Layout::ColMajor, blas::Op::Trans, blas::Op::NoTrans,', + ' n, n, m, 1.0, S.data(), m, S.data(), m, 0.0, C.data(), n);', + ' for (int64_t i = 0; i < n; ++i) {', + ' if (!(C[i + i * n] > 0.0) || !std::isfinite(C[i + i * n])) return 1;', + ' }', + ' std::printf("OK\n");', + ' return 0;', + '}') + +$conftestGenerator = if (Get-Command "ninja.exe" -ErrorAction SilentlyContinue) { "Ninja" } else { "NMake Makefiles" } +& cmake -S (Join-Path $conftest "src") -B (Join-Path $conftest "build") -G $conftestGenerator ` + "-DCMAKE_BUILD_TYPE=Release" ` + "-DCMAKE_PREFIX_PATH=$($installDir.Replace('\','/'))" ` + "-Dblaspp_DIR=$($env:blaspp_DIR)" ` + "-DRandom123_DIR=$($env:Random123_DIR)" | Out-Host +if ($LASTEXITCODE -ne 0) { throw "The verification program failed to configure." } +& cmake --build (Join-Path $conftest "build") | Out-Host +if ($LASTEXITCODE -ne 0) { throw "The verification program failed to build." } + +$conftestExe = Get-ChildItem -LiteralPath (Join-Path $conftest "build") -Recurse -Filter "conftest.exe" | + Select-Object -First 1 +if (-not $conftestExe) { throw "The verification program built but produced no executable." } +$conftestOutput = & $conftestExe.FullName +if ($LASTEXITCODE -ne 0 -or ($conftestOutput -notcontains "OK")) { + throw "The verification program ran but did not produce a correct result:`n$($conftestOutput -join "`n")" +} +$observedWidth = if ($conftestOutput -contains "blas_ilp64=1") { + "ILP64 (64-bit BLAS integers)" +} else { + "LP64 (32-bit BLAS integers)" +} + +#============================================================================== +# Optional: persist RANDNLA_PROJECT_DIR. +# +# Opt-in, mirroring install.sh's --modify-rc. SetEnvironmentVariable at User +# scope is the Windows equivalent of appending to a shell profile, and the only +# mechanism that survives a new shell. +#============================================================================== +if ($ModifyEnvironment) { + [Environment]::SetEnvironmentVariable("RANDNLA_PROJECT_DIR", $ProjectDir, "User") + Write-Host "" + Write-Host "Set RANDNLA_PROJECT_DIR=$ProjectDir for your user account (open a new shell to pick it up)." +} + +#============================================================================== +# Summary. +#============================================================================== +Write-Host "" +Write-Host "RandBLAS installed successfully." -ForegroundColor Green +Write-Host "" +Write-Host " Backend oneMKL, $observedWidth" +Write-Host " Project layout $ProjectDir" +Write-Host " Installed library $installDir" +Write-Host "" +if (-not $SkipTests) { + Write-Host " Run the test suite:" + Write-Host " ctest --test-dir $buildDir" + Write-Host "" +} +Write-Host " Consume from CMake with:" +Write-Host " -DRandBLAS_DIR=$($installDir.Replace('\','/'))/lib/cmake/RandBLAS" +if (-not $ModifyEnvironment) { + Write-Host "" + Write-Host " To have other RandNLA installers reuse these dependencies, set:" + Write-Host " setx RANDNLA_PROJECT_DIR `"$ProjectDir`"" + Write-Host " (or re-run with -ModifyEnvironment)" +} + +if (-not $Examples) { + Write-Host "" + Write-Host " The examples are not built by default: they additionally need" + Write-Host " LAPACK++ and fast_matrix_market, and they require OpenMP." + $buildNow = Read-YesNo " Build them now?" $false + if (-not $buildNow) { + Write-Host " To build them later, re-run with -Examples:" + Write-Host " powershell -ExecutionPolicy Bypass -File $scriptDir\install.ps1 -Examples -ProjectDir `"$ProjectDir`"" + Write-Host "" + exit 0 + } + $Examples = $true + & $setup -DependencyRoot $DependencyRoot -InstallLapackpp +} + +if ($Examples) { + $examplesBuild = Join-Path $ProjectDir "build\examples-build" + Write-Host "" + Write-Host "Configuring and building examples ..." + & cmake -S (Join-Path $repoDir "examples") -B $examplesBuild -G $conftestGenerator ` + "-DCMAKE_BUILD_TYPE=Release" ` + "-DCMAKE_PREFIX_PATH=$($installDir.Replace('\','/'))" ` + "-Dblaspp_DIR=$($env:blaspp_DIR)" ` + "-Dlapackpp_DIR=$($env:lapackpp_DIR)" ` + "-DRandom123_DIR=$($env:Random123_DIR)" ` + "-DFETCHCONTENT_BASE_DIR=$($ProjectDir.Replace('\','/'))/build/fetchcontent-cache" | Out-Host + if ($LASTEXITCODE -ne 0) { throw "Examples failed to configure." } + & cmake --build $examplesBuild -j $Jobs | Out-Host + if ($LASTEXITCODE -ne 0) { throw "Examples failed to build." } + Write-Host "" + Write-Host "Examples built: $examplesBuild" -ForegroundColor Green +} + +Write-Host ""