Skip to content

Windows: fetch oneMKL through vcpkg manifest mode (fixes setup on VS-bundled vcpkg) - #185

Merged
rileyjmurray merged 1 commit into
mainfrom
windows-vcpkg-manifest
Aug 10, 2026
Merged

Windows: fetch oneMKL through vcpkg manifest mode (fixes setup on VS-bundled vcpkg)#185
rileyjmurray merged 1 commit into
mainfrom
windows-vcpkg-manifest

Conversation

@mmelnich

Copy link
Copy Markdown
Contributor

Problem

On a plain Visual Studio 2022/18 (or Build Tools) machine, the Windows dependency setup fails immediately at the oneMKL step:

error: Could not locate a manifest (vcpkg.json) above the current working directory.
This vcpkg distribution does not have a classic mode instance.

setup.ps1 located the vcpkg copy that Visual Studio bundles with the C++ workload and ran a classic-mode vcpkg install intel-mkl:x64-windows. The bundled copy is manifest-only: it lives read-only under Program Files and has no classic-mode instance, so every classic invocation fails. CI never caught this because GitHub's windows-2022 runners ship a standalone, classic-capable vcpkg at C:\vcpkg (exported as VCPKG_INSTALLATION_ROOT), which the discovery logic finds first. The manual recipe in INSTALL.md Appendix A taught the same classic-mode command. First reported against RandLAPACK's installer, which reuses this pattern.

Change

  • setup.ps1 now provisions oneMKL in manifest mode, the one mode every vcpkg distribution supports. It generates a minimal vcpkg.json under the dependency root and redirects vcpkg's downloads/buildtrees/packages scratch trees there as well, since the bundled copy's default scratch locations are not writable. The manifest pins builtin-baseline to vcpkg release 2026.07.29 (intel-mkl 2025.2.0, matching the current CI cache key); the bundled vcpkg requires that field, and the pin makes the installed oneMKL version independent of how old the user's vcpkg copy is. The installed-tree layout (vcpkg-installed\x64-windows) is unchanged, so caches and downstream paths are untouched.
  • vcpkg discovery gained a fallback to the Visual Studio bundled copy via VSINSTALLDIR (a developer prompt does not always put it on PATH).
  • New CI lane windows-vs-bundled-vcpkg (core workflow): hides the runner's standalone vcpkg and runs dependency setup plus the serial core build through the VS-bundled copy, reproducing the user environment that was broken. The vcpkg-installed tree is deliberately not cached so the manifest-mode fetch is exercised on every run; only the oneMKL installer download is cached.
  • INSTALL.md Appendix A now gives manifest-mode commands that work with both the bundled and standalone distributions.

Verification

setup.ps1 parses clean under Windows PowerShell and the workflow is valid YAML. The end-to-end proof is the new CI lane in this PR, which fails on main's classic-mode invocation by construction and must pass here.

A matching RandLAPACK PR (BallisticLA/RandLAPACK#156) applies the same fix to its installer; its RandBLAS submodule pin can pick this change up once merged.

🤖 Generated with Claude Code

@rileyjmurray rileyjmurray left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike how much this complicates the (already complicated) Windows installs, but I don't know enough to push back on anything in particular. Approving.

@rileyjmurray
rileyjmurray merged commit 0aec321 into main Aug 10, 2026
22 checks passed
@rileyjmurray
rileyjmurray deleted the windows-vcpkg-manifest branch August 10, 2026 16:32
mmelnich added a commit to BallisticLA/RandLAPACK that referenced this pull request Aug 10, 2026
…ages

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.
mmelnich added a commit to BallisticLA/RandLAPACK that referenced this pull request Aug 12, 2026
…ages

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.
rileyjmurray added a commit that referenced this pull request Aug 13, 2026
This PR adds  ...
 1. new scripts `install/install.sh` and `install/install.ps1`,
 2. an `install-script` GitHub Actions CI workflow that exercises the scripts, 
 3. documentation for the new scripts, and
 4. console progress rendering.

Both scripts build into a `RandNLA-project` tree laid out exactly as
RandLAPACK's installer lays one out, and both honor
`RANDNLA_PROJECT_DIR`. Running both projects' installers on one machine
therefore **shares one dependency tree** rather than building BLAS++
twice.

### The integer width is read back, not assumed

The part most worth reviewing. BLAS++ probes `int32` first and `int64`
second, and `blas_int` only filters which *library names* to consider
(`cmake/BLASFinder.cmake`). That makes ILP64 genuinely detectable for
MKL, where `mkl_intel_ilp64` is a different library from
`mkl_intel_lp64` — but not for OpenBLAS, where BLAS++ only ever tries
`-lopenblas`. An LP64 OpenBLAS passes the `int32` probe and is accepted,
so **a successful `blas_int=int64` configure proves nothing there**.

The resolved width therefore comes from BLAS++'s generated
`blas/defines.h` after the build. My first version recorded which
configure attempt succeeded, which would have stamped an LP64 install as
ILP64 and let every later run reuse it believing otherwise.

Resulting policy: **ILP64 wherever it is real, LP64 with a named warning
elsewhere.** Accelerate is refused outright for ILP64 — Apple has
shipped one since macOS 13.3, but BLAS++ implements only the legacy
interface
([icl-utk-edu/lapackpp#43](icl-utk-edu/lapackpp#43),
tracked our side as #189). An ILP64 OpenBLAS is reachable through
`--blas=custom`.

### Everything pinned, reuse gated on provenance

BLAS++ (`3057185`), Random123 (`v1.14.0`), GoogleTest (`v1.18.0`) and
LAPACK++ (`40b9d0d`) at immutable refs — the same refs RandLAPACK
validated. Each install *and each source tree* carries a provenance
stamp and is reused only when it matches what we would build now,
including backend and integer width. Without that, changing a pin is a
silent no-op for anyone who already ran the script. The source stamp
exists because a shallow checkout of a tag does not keep the tag ref
locally.

On the Windows side this replaces `Clone-Head`, which took a branch name
and returned early whenever the destination merely existed — so a branch
tip could move between runs, and Random123 was fetched at its default
branch entirely unpinned. It also moves BLAS++/LAPACK++ off the
`RaphaelArkadyMeyerNYU/*` forks; both MSVC fixes merged upstream on
2026-08-06.

### Verification runs, it does not just link

The final step compiles, links and *runs* a program against the finished
install: it sketches a matrix, multiplies through BLAS++, and checks the
result is symmetric positive semidefinite. Through BLAS++ rather than
raw `dgemm_`, because that is how RandBLAS actually reaches the BLAS.

### The x64 toolchain guard (Windows)

"Developer PowerShell for VS 2022" and "Developer Command Prompt for VS
2022" **both default to an x86 toolchain**, and an x86 linker cannot use
the x64 import libraries every BLAS backend ships. Unchecked, the
failure surfaces three layers down as BLAS++ reporting `BLAS library not
found`. Shell bitness is not a usable signal either — the Developer
Command Prompt is a 64-bit process that still selects x86 tools.
`toolchain-arch.ps1` reads `VSCMD_ARG_TGT_ARCH`, then the
`bin\Host<host>\<target>\` convention, then `cl.exe`'s banner, and
refuses x86 and arm64 with different messages because they need
different answers.

### Smaller decisions

- **GoogleTest is provisioned**, because `BUILD_TESTS` defaults to ON
while `find_package(GTest)` is not `REQUIRED` — so a machine without it
silently produces a build with zero tests.
- **Examples are opt-in.** They need two dependencies RandBLAS does not
(LAPACK++, `fast_matrix_market`) and `examples/CMakeLists.txt` requires
OpenMP, which stock Apple Clang cannot supply. Declining prints the
exact follow-up command; it re-invokes the same script, so there is one
code path.
- **`randblas_stage_runtime_dlls()` is now exported from the installed
package.** Windows searches an executable's own directory first and
`PATH` last, so a downstream project linking installed RandBLAS had no
way to find the BLAS DLLs — the function lived only in the build tree.
Found because the installer's own verification step is such a consumer.
- **The clone is never moved.** RandLAPACK's installer relocates its own
repository into `lib/`, which breaks git worktrees. A symlink gives the
same layout.
- **The log is appended, not truncated** — the previous run's output is
what you want when this run fails the same way.
- `.gitignore` matched `**install/`, intended for build-output trees but
also swallowing a source directory named `install/`. Narrowed to
`**/*-install/`, the pattern RandLAPACK already uses; the
`!**install/.gitkeep` exception it carried was vestigial.
- **Progress rendering** has three tiers: Unicode bar on a capable
terminal, ASCII bar without colour or UTF-8, and one line per step when
not a terminal. Tier 0 is a requirement rather than a fallback —
redirected output becomes `install.log` and CI transcripts. The bar is
determinate, parsed from Ninja's `[12/34]` and Make's `[ 42%]` on the
stream already being captured.

## CI

The `install-script` workflow asserts what the core workflows
structurally cannot: idempotent re-runs, dependency discovery, the LP64
fallback warning firing for stock OpenBLAS, ILP64 for MKL, Accelerate
refusing ILP64, and **piped output containing no terminal escape
sequences** — which is what stops the progress bar from quietly filling
every CI log.

A separate **packager lane deliberately does not run the installer**. It
configures with plain CMake against hand-installed dependencies, inside
a network namespace with no interfaces, then builds, installs and
consumes the result via `find_package`. If that lane stays green a
conda-forge recipe and a Spack `package.py` are possible.

## Verification

**Linux** (gcc 15.2, oneAPI MKL):

| Scenario | Result |
|---|---|
| Fresh MKL build | 9/9 steps, ILP64 confirmed from `blas/defines.h` |
| Re-run in place | all six dependency steps reused, seconds |
| Dependency discovery (`*_INSTALL_DIR` pre-seeded) | all three reused,
step count drops to 3/3 |
| `--examples` | 13/13 steps, all 7 binaries, `fast_matrix_market` at
`v1.7.6` |
| `--blas=openblas` with no OpenBLAS present | clean failure naming both
apt packages |
| `--blas=accelerate --blas-int=ilp64` | refused at argument parsing |
| Output redirected | no ANSI escapes, no carriage returns |
| Progress under a pty | advances 5% → 20% → 49% during the RandBLAS
build |

**Windows 11, Windows PowerShell 5.1** (not pwsh 7), VS 2022 Build
Tools:

| Scenario | Result |
|---|---|
| Plain shell, no `cl.exe`/`cmake` | refused at preflight with the
`vcvars64` one-liner |
| Real x86 toolchain (`vcvars32.bat`, `VSCMD_ARG_TGT_ARCH=x86`) |
refused with the x86-specific explanation |
| Full x64 install from scratch | oneMKL via vcpkg, BLAS++, GoogleTest,
RandBLAS |
| Verification program | compiled, linked, staged its DLLs, ran,
reported ILP64 |

All `install-script` lanes green, including the offline packager lane.

## Notes for posterity

- **Two bugs here were only reachable on real hardware**.
`$LASTEXITCODE` is unset until some native command runs and `Set-StrictMode`
makes reading an unset variable an error, so the installer failed on exactly the runs
where every dependency was cached. And the missing `randblas_stage_runtime_dlls`
export only appears when something consumes the *installed* package.

- **vcpkg is kept, deliberately.** My plan called for replacing it with
pinned NuGet as RandLAPACK did, but #185 had just made vcpkg work in
manifest mode with a pinned baseline, and ripping that out immediately
would be churn. Worth revisiting: the vcpkg oneMKL path expanded to
**6.2 GB** on disk during testing, against roughly 155 MB for
RandLAPACK's NuGet fetch.

- Windows CI for the installer itself is not added here; the existing
`core-windows` lanes cover the provisioner changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants