fix(ci): provide the Vulkan loader on the windows leg, the way everyone else does - #391
Closed
Sunrisepeak wants to merge 2 commits into
Closed
fix(ci): provide the Vulkan loader on the windows leg, the way everyone else does#391Sunrisepeak wants to merge 2 commits into
Sunrisepeak wants to merge 2 commits into
Conversation
… the image This package ships `vulkan-1.lib` — symbol stubs, no code. The code is `vulkan-1.dll`, and that is not an OS component: it arrives with a GPU driver, with LunarG's Runtime redistributable, or bundled beside an application. A machine without a driver therefore has no loader at all, and a consumer linking the import library dies at process start with 0xC0000135, before `main`. Measured on this index's CI: `vulkan`, `eui-neo-vulkan` and `vulkan-hpp-module` fail that way on `windows-2022` and pass on images that happen to carry a driver. #388 spent four rounds looking for an image where that and everything else worked; each round broke a different consumer of the VS installation, which is why it is closed. Declaring the loader takes the image out of the question instead of chasing it. `xim:vulkan-loader` grew a windows payload for this (openxlings/xim-pkgindex#818). It is built on a runner, and the build loads the DLL and resolves vkEnumerateInstanceVersion, vkCreateInstance and vkGetInstanceProcAddr before publishing — an artifact that exists is not an artifact that works. It brings no ICD, and does not need to. The loader finds drivers through the registry, so a driverless machine enumerates no devices — which is exactly what this package's test already scopes itself to: "answered by the LOADER itself, before any ICD is involved, so it is meaningful on a CI runner with no GPU and no driver."
Sunrisepeak
added a commit
to openxlings/xim-pkgindex
that referenced
this pull request
Sep 11, 2026
…819) The windows branch added in #818 shipped a payload and declared nothing about where it is. This adds `exports.runtime.libdirs = {"bin"}` to the windows xpm block and removes the `subos.env` PATH prepend from config(). WHY THE FIRST ATTEMPT WAS THE WRONG LAYER. My comment in #818 claimed `exports.runtime.libdirs` is read only by xlings's elfpatch and therefore has nothing to say about a PE. That is false: mcpp reads the same field to COPY a dependency's *.dll beside the executable it builds (`src/build/plan.cppm`, `runtimeDeployFiles`), and its own comment names this exact case -- "only a Windows prebuilt-DLL package (or a test that ships a .dll) populates it". The chain is resolver.cpp:167 -> installer.cpp:2486 -> the runtime binding mcpp reads. `bin` and not `lib`: bin/ holds vulkan-1.dll, lib/ holds the import library the linker reads. PATH was also too wide even where it worked -- process-wide, so it would put our loader in front of the system's for every child of that shell. MEASURED, BOTH HALVES. The PATH declaration installs cleanly under a full xlings (this repo's windows-test: `done`, `[PASS] new shim`, subos "default" present) and fails inside mcpp's project sandbox with `E_INTERNAL: config hook failed` (mcpplibs/mcpp-index#391, windows leg; every other leg green, loader downloaded and installed before the hook runs). Two environments, one descriptor, opposite results. The asymmetry behind that is filed as mcpp-community/mcpp#614 -- mcpp invokes xlings with `env -u XLINGS_PROJECT_DIR` on POSIX and sets it to the EMPTY STRING on Windows, and absent and present-but-empty are not the same answer to "which scope is this". This change does not depend on that being fixed. RECORDED HONESTLY: WHY THIS PACKAGE EXISTS AT ALL. The windows payload (#818) was not motivated by a user report. It was motivated by a runner image choice made in mcpplibs/mcpp-index#385, where the Windows leg was pinned to `windows-2022` to dodge an MSVC STL 14.51 bug (mcpp-community/mcpp#609, microsoft/STL#6294). That image is the one without the loader. Measured on three images (mcpp-index#387 probe): windows-2022 vulkan-1.dll absent MSVC 14.29/14.44 huxerui PASS vulkan FAIL 0xC0000135 windows-2025 vulkan-1.dll PRESENT MSVC +14.51 huxerui FAIL (STL) vulkan PASS windows-latest same as windows-2025 Neither image satisfies both, which is what four rounds of mcpp-index#388 failed to escape before it was closed. Shipping the loader takes the image out of the question instead -- and the underlying claim stands on its own: vulkan-1.dll is not an OS component, it arrives with a GPU driver or as LunarG's redistributable, so a driverless machine has no loader and anything linking vulkan-1.lib dies before main. The `subos` import goes with the call; nothing else used it. Linux is untouched. Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
…ne else does `vulkan-1.dll` is not part of Windows. It arrives with a GPU driver, with LunarG's Vulkan Runtime redistributable, or bundled beside an application, so a runner with no driver has no loader and anything linking `vulkan-1.lib` dies at process start with 0xC0000135, before main. MEASURED across the three images (#387 probe, run 34569282837): windows-2022 vulkan-1.dll absent MSVC 14.29/14.44 huxerui PASS vulkan FAIL 0xC0000135 windows-2025 vulkan-1.dll PRESENT MSVC +14.51 huxerui FAIL (STL) vulkan PASS windows-latest same as windows-2025 Neither image satisfies both. This leg is pinned to `windows-2022` to dodge the MSVC STL 14.51 bug (mcpp-community/mcpp#609, microsoft/STL#6294), and windows-2022 is the image without the loader. THIS REPLACES THE PACKAGE-DEPENDENCY APPROACH, which did not work. The first version of this PR had `compat.vulkan`'s windows branch declare `xim:vulkan-loader@>=1.4.313`. Three runs: 1. cold store -- loader installed, its config() hook failed (`subos.env` PATH declaration; fixed by openxlings/xim-pkgindex#819, asymmetry filed as mcpp-community/mcpp#614) 2. warm store -- `compat.vulkan@1.4.357.0` already installed, so its dependency closure was never re-evaluated and the loader was not installed at all 3. cold store again, after deleting the three registry caches -- still no loader, and no error saying why The declaration sits in `xpm.windows` beside working precedents (`compat.cuda-runtime`, `compat.cudart`), and range versions have precedent too (`compat.openssl`, `compat.vulkan-runtime`), so the shape is not obviously wrong -- but three runs produced no loader and no diagnostic, and chasing it further is not worth it for a problem whose actual shape is "the runner is missing a redistributable". WHAT EVERYONE ELSE DOES, since this is a common Windows problem: * dynamic loading in the consumer -- volk, Vulkan-Hpp's VULKAN_HPP_DISPATCH_LOADER_DYNAMIC, glfwVulkanSupported(), SDL_Vulkan_LoadLibrary. The process starts and degrades gracefully instead of dying before main. This is the robust answer for applications. * rely on the GPU driver -- what most games do, and sound: a machine that can run Vulkan has a driver, and the driver brought the loader. * ship the loader -- Apache-2.0, which is what the LunarG Runtime redistributable is for. * on CI specifically -- install the Runtime/SDK, or register a software ICD (SwiftShader, lavapipe) when device-level coverage is wanted. These members assert loader-level calls only (`vkEnumerateInstanceVersion`, instance extension enumeration), deliberately: the loader advertises WSI extensions only when an ICD supports them, so asserting on those would be testing the runner's hardware. A loader with no ICD is exactly the right amount here. The payload is the one built for openxlings/xim-pkgindex#818 -- built on a runner from Khronos' source, and the build loads the DLL and resolves vkEnumerateInstanceVersion, vkCreateInstance and vkGetInstanceProcAddr before publishing. Hash-pinned here, and the step is idempotent: if the image ever grows the DLL, it leaves it alone. The xim-pkgindex loader package stays. It is a real ecosystem capability for a driverless Windows machine; it is simply not the layer that fixes a missing redistributable on a CI runner. Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
Sunrisepeak
added a commit
that referenced
this pull request
Sep 11, 2026
Every reason to distrust the diff used to escalate to the whole workspace. On a PR that is the wrong trade twice over: it buries the change under ~22 shards of unrelated work, and it puts the PR's own legs behind an hour of queue, so the thing under review is the last thing to report. Touching this file was enough to trigger it, which is the case that keeps biting: a one-line CI edit cannot be reviewed against a fast signal. Measured three times today -- #390, #391 and #392 each ran a full matrix to validate a change whose blast radius was one job. `full` now answers only where "check everything" IS the request: the weekly cron and a manual `workflow_dispatch`. On a PR or a push, the same reasons call `widen` instead, which records them, prints them, and writes them to the run summary so a reviewer sees WITHOUT opening a job that this run deliberately tested less -- and that the sweep is one manual dispatch away. Degraded, with their reasons kept: .github/workflows/validate.yml, tests/*.sh mcpp.toml non-member change unclassified path push with no predecessor / predecessor not in history NOT weakened: descriptor and member changes select their members exactly as before. The only thing that changed is what happens when the diff cannot be classified. One consequence handled. `widen` does not exit, so the push-with-no- predecessor path has to leave a usable range behind. `HEAD` alone would diff root-to-HEAD, name every file and select every member -- a full run by another name, which is what this change exists to stop. It leaves the range EMPTY instead: nothing selected, the reason already said, the summary carrying it to a human. Rare either way, since merges here are squashes and `event.before` is present on every normal push. The weekly Sunday 06:00 sweep is deliberately kept. It is the only thing that would have caught the windows vulkan regression before a user did -- main's leg was pinned to `windows-2022` in #385 and the vulkan members were never re-tested there until a full run happened to fire today. Removing the automatic full run on PRs and keeping the periodic net is the same shape openxlings/xim-pkgindex#815 just added on the other side. Verified: the rendered fragment on `pull_request` with a validate.yml edit plus an unclassified path selects only the member its descriptor change names, and the same input under `workflow_dispatch` still goes full. Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
Member
Author
|
Superseded by #395, closing. This PR put #395 ships the loader inside Deployed beside the executable by |
Sunrisepeak
added a commit
that referenced
this pull request
Sep 11, 2026
* feat(compat.vulkan): 1.4.357.3 ships the Vulkan loader on Windows A Windows program that links `vulkan-1.lib` needs `vulkan-1.dll` at process start, and that DLL is not part of Windows. It arrives with a GPU driver, with LunarG's Vulkan Runtime redistributable, or beside an application. A machine without a driver has none, and the program dies before `main` with 0xC0000135 (STATUS_DLL_NOT_FOUND). Measured on GitHub's `windows-2022` image (#387 probe, run 34569282837): `vulkan`, `eui-neo-vulkan` and `vulkan-hpp-module` all fail that way, while images that happen to carry a driver pass. WHAT CHANGES compat.vulkan 1.4.357.3 windows artifact adds bin/vulkan-1.dll and LICENSE.txt; mcpp.windows.runtime gains library_dirs = { "bin" } khronos.vulkan-hpp 1.4.357.1 same headers, pin -> compat.vulkan 1.4.357.3 compat.eui-neo 0.5.9.1 upstream 0.5.9 unchanged, `vulkan` feature pin -> compat.vulkan 1.4.357.3 members vulkan 1.4.357.3, vulkan-hpp-module 1.4.357.1, eui-neo-vulkan 0.5.9.1 tests/examples/vulkan on Windows, asserts the mapped vulkan-1.dll lives in the executable's own directory THE MECHANISM ALREADY EXISTS AND IS NOT NEW HERE. mcpp copies every *.dll under a dependency's `runtime.library_dirs` beside the executable it builds (mcpp #185, v0.0.73); `compat.openblas` has shipped `bin/libopenblas.dll` this way on Windows CI since mcpp-index #55. Two properties this change leans on were measured locally with mcpp 2026.9.11.2 rather than assumed: * transitive -- app -> mid -> dep(bin/vulkan-1.dll): the DLL lands beside `app`. `vulkan-hpp-module` and `eui-neo-vulkan` reach compat.vulkan only transitively. * test binaries -- `mcpp test` places it beside the test executable too. `mcpp pack` needs nothing further: its PE closure always searches the executable's own directory ("whatever the build staged beside it ... is by definition part of what it runs with"), and `vulkan-1.dll` is not in its system-DLL allow-list, so the deployed copy is what a packed program carries. THE ARTIFACT (xlings-res/vulkan-import 1.4.357.3, sha256 8118f1bd...12f5) lib/vulkan-1.lib byte-identical to 1.4.357.1's vulkan-1.def upstream `loader/vulkan-1.def`, tag vulkan-sdk-1.4.357.0 bin/vulkan-1.dll built from vulkan-sdk-1.4.357.0 by xlings-res/vulkan-loader's windows workflow, which loads the DLL and resolves vkEnumerateInstanceVersion, vkCreateInstance and vkGetInstanceProcAddr before publishing (run 34608619850) LICENSE.txt Vulkan-Loader's Apache-2.0 -- a redistributed binary carries its license README.md how each file was produced Packed deterministically (sorted, fixed mtime, numeric owner, gzip -n); the sha was computed twice, read back from GitHub, and the gitcode mirror (mcpp-res/vulkan-import 1.4.357.3) is byte-identical. COMPATIBILITY, MEASURED. The DLL exports exactly the 265 names in the .def -- no additions, no omissions -- so every import `vulkan-1.lib` can produce resolves, and no consumer can hit "entry point not found". The loader version the earlier xim payload carried (1.4.313) exports the same 265; the build is at 1.4.357 anyway so the loader matches the headers it is consumed with. On a machine that ALREADY has a GPU driver nothing is lost: the copy beside the executable is found first (the application directory precedes System32), and the loader still reads HKLM\SOFTWARE\Khronos\Vulkan\Drivers, so the driver the machine has is the ICD it uses. The ICD is deliberately not supplied -- a software fallback would hide a missing driver behind a slow device. WHY NEW VERSIONS INSTEAD OF MOVING PINS. An installed copy records the pins it resolved with. Moving a pin inside a published version does not reach a warm store, which is not hypothetical here: with #391's first approach, a warm CI store kept `compat.vulkan@1.4.357.0`, never re-evaluated its closure, and the loader never arrived. That is the same rule as compat.vulkan 1.4.357.1. Versions before 1.4.357.3 have no bin/; mcpp skips a declared runtime directory that does not exist, so `library_dirs` is inert for them. WHY THE TEST ASSERTION IS A REAL CHECK. On Windows the test now requires the mapped vulkan-1.dll to sit in the executable's directory. With an older compat.vulkan that fails on a machine with a driver (the loader comes from System32) and never runs on one without (the process dies first). The `windows-2022` leg of this PR has no system loader at all, so it can only pass if the deployment works. This is also why #391 -- which put the DLL in System32 on the runner -- is superseded rather than merged: it would make that leg pass whether or not the package works. Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com> * fix(eui-neo): find upstream's directory under a re-released version; vulkan-hpp moves to a follow-up TWO THINGS LOCAL VERIFICATION FOUND in the first push of this branch. 1. compat.eui-neo 0.5.9.1 could not install. The install hook looks for the unpacked archive by name, `EUI-NEO-<version>`, and took the version verbatim -- so it looked for `EUI-NEO-0.5.9.1/` in an archive that unpacks to `EUI-NEO-0.5.9/`: eui-neo: no CMakeLists.txt under .../compat-x-eui-neo/0.5.9.1/eui-neo-0.5.9.1 after unpacking; the archive layout is neither wrapped nor flat A fourth version component is this index re-releasing the same upstream tag, so the lookup now drops it. Three-component versions are unchanged; checked directly: 0.5.9 -> 0.5.9, 0.5.9.1 -> 0.5.9, 0.5.10 -> 0.5.10, 1.2.3.45 -> 1.2.3, 0.5.9-rc1 -> 0.5.9-rc1. The `layer` directory keeps the package's own version; the sources are `*/` globs, so its name never mattered. After the fix, locally with mcpp 2026.9.11.2: compat.eui-neo[vulkan]: ok (backend=vulkan, loader api 1.4.357) i.e. eui-neo 0.5.9.1 resolved compat.vulkan 1.4.357.3 through the feature. This is the same class of bug as openxlings/xim-pkgindex#821 fixed in vulkan-loader: a hook deriving a path from the version works exactly until a second version shares an archive. 2. khronos.vulkan-hpp 1.4.357.1 cannot be verified in the same change that introduces compat.vulkan 1.4.357.3. `vulkan-hpp-module` redirects only the `khronos` namespace to this checkout, so `compat` comes from the PUBLISHED index, which does not have 1.4.357.3 until this merges: xlings install_packages failed (exit 1) for 'compat.vulkan@1.4.357.3' with 1 index repo configured [mcpplibs -> https://github.com/mcpplibs/mcpp-index.git] Redirecting `compat` as well was tried and is still refused -- mcpp reports "≥2 project-level index repos is a known xlings resolution gap (mcpp #238; root cause openxlings/xlings#374)" even though #238 is closed. So the khronos.vulkan-hpp bump and its member pin are reverted here and follow once this is merged and the index republished -- the same order openxlings/xim-pkgindex#818 and mcpp-index#391 needed. Consequence for this PR's CI, stated in advance: `vulkan-hpp-module` is still selected (its manifest names vulkan) and still resolves compat.vulkan 1.4.357.0, so on the windows leg it fails exactly as it does on main today. `vulkan` and `eui-neo-vulkan` are the members this change is judged by. Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com> --------- Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes what #388 could not: the three Windows Vulkan failures, fixed by shipping the loader rather than by finding a runner that already has one.
The gap
This package ships
vulkan-1.lib— symbol stubs, no code. The code isvulkan-1.dll, and that is not an OS component: it arrives with a GPU driver, with LunarG's Runtime redistributable, or bundled beside an application.So a machine without a driver has no loader, and a consumer linking the import library dies at process start with
0xC0000135(STATUS_DLL_NOT_FOUND), beforemain:vulkan-1.dllvulkan,eui-neo-vulkan,vulkan-hpp-modulewindows-latest/windows-2025windows-2022#388 spent four rounds looking for an image where that and everything else worked. Each round broke a different consumer of the VS installation — mcpp scans the toolset directory, clang reads the environment, and OpenSSL's nmake reads the VS registration. It is closed for that reason.
The fix
deps = { "xim:vulkan-loader@>=1.4.313" }on the windows branch.xim:vulkan-loadergrew a Windows payload for this (openxlings/xim-pkgindex#818, merged). It is built on a runner, and the build loads the DLL it just produced and resolvesvkEnumerateInstanceVersion,vkCreateInstance,vkGetInstanceProcAddrbefore publishing:Both mirrors verified byte-identical.
It brings no ICD, and does not need to
The loader finds drivers through
HKLM\SOFTWARE\Khronos\Vulkan\Drivers, so a driverless machine enumerates no devices. That is exactly the scope this package's own test already declares:What this run also proves
This is the first mcpp-index PR since #390 landed, which stopped the registry cache from carrying the resolved index. Before that, a change to a xim package could not be seen from this repo's CI at all — two successive
xim:wixfixes were each merged, each re-run, and each appeared to do nothing. If the windows legs go green here, they also confirm #390 did what it claims.