Skip to content

[CI] Windows: drop test-stage devcontainer + use static CRT - #127

Merged
leofang merged 17 commits into
NVIDIA:mainfrom
leofang:leofang/windows-host-test
Jun 12, 2026
Merged

leofang merged 17 commits into
NVIDIA:mainfrom
leofang:leofang/windows-host-test

Conversation

@leofang

@leofang leofang commented Jun 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Tests run on the bare Windows host runner — no more docker pull of the devcontainer per matrix entry.
  • Build LLVM, MLIR, and cext modules on Windows with /MT (static CRT) so the wheel has no msvcp140 runtime dependency. No delvewheel repair needed; wheel is self-contained.
  • Re-enable the 5 TCC matrix entries PR [CI] Introduce Windows CI #81 deferred as untested.

Other cleanups

  • ci/tools/env-vars Windows path handling now matches cuda-python (cygpath -w REPO_DIR + realpath wrap on derived exports).
  • Drop restore_llvm_artifacts + LD_LIBRARY_PATH from wheel-test jobs on both Linux and Windows; verified the wheel is self-contained (Linux pass/skip counts identical to main).
  • Delete ci/windows-run-tests-container.ps1.

Test plan

  • All 15 Windows test entries pass on the host runner.
  • Linux tests unchanged vs main.

PR NVIDIA#81 introduced devcontainer-based execution for both the build and
test stages of Windows CI. The build stage needs the devcontainer for
MSVC, but the test stage doesn't: the wheel is already built, GPU
access works through the host driver, and pip install + the wheel's
bundled DLL-search patch handle runtime DLL discovery. The
devcontainer adds ~6-8 min per matrix entry to pull the
rapidsai/devcontainers image, repeated across all entries.

This change removes the devcontainer from test-wheel-windows.yml in
favor of host execution, mirroring cuda-python's pattern and the
existing Linux job:

- Drop the `Resolve devcontainer image`, `Pull devcontainer image`,
  and `Run numba-cuda-mlir tests in devcontainer` steps.
- Add a single `Run numba-cuda-mlir tests` step that exports
  NUMBA_CUDA_MLIR*_TEST_WHEEL_ONLY when LOCAL_CTK=0 (matching the
  Linux job) and invokes the test script directly under the
  workflow's default bash shell.
- Delete ci/windows-run-tests-container.ps1 (no longer used).

Also re-enable the TCC driver-mode combinations in ci/test-matrix.yml
that PR NVIDIA#81 commented out as "untested." With host-side execution
they should work, and the entries match cuda-python's Windows TCC
coverage (py3.12 @ 13.0.2/13.2.1, py3.13 @ 12.9.1, py3.14 @ 12.9.1,
py3.14t @ 12.9.1).

ci/windows-common.ps1 and ci/windows-devcontainer.env are left in
place; the build stage still uses them.
@copy-pr-bot

copy-pr-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

The cuSIMT wheel bundles libMLIRPythonCAPI.so / libMLIRToLLVM70.so /
libMLIRPythonSupport-numba_cuda_mlir.so / libLLVM-7.so under
numba_cuda_mlir/_mlir/_mlir_libs and numba_cuda_mlir/lib (via setup.py's
manual staging plus auditwheel-exclude on Linux). The LLVM install dirs
that the test jobs were restoring contain the same libs that the wheel
already ships.

Python tests don't invoke any external LLVM CLI tools either: PR NVIDIA#81
moved FileCheck-style assertions to an in-process parser/matcher, and
there are no subprocess calls to mlir-opt / mlir-translate / llc /
FileCheck / llvm-dis anywhere under tests/ or src/.

If the wheel's RPATH (or DLL-search patch on Windows) is set up
correctly, the restore + LD_LIBRARY_PATH steps are dead weight. Drop
them from:

- test-wheel-linux.yml
- test-expensive-linux.yml
- test-wheel-windows.yml

test-numbast-mlir-linux.yml is left alone: it builds numbast-mlir C++
against the LLVM install dirs inside a conda env, so it may genuinely
need them. Treat separately if confirmed unnecessary.

If CI fails after this change with import/load errors, the fix is to
correct the wheel's RPATH / DLL-search setup in setup.py rather than
to restore these workflow steps.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 7769cbe

@github-actions

This comment has been minimized.

The first round of CI on PR NVIDIA#127 showed all Windows test entries failing
with:

    ImportError: DLL load failed while importing _mlir:
    A dynamic link library (DLL) initialization routine failed.

Investigation of the wheel showed:

- All bundled DLLs are present (MLIRPythonCAPI, MLIRPythonSupport,
  MLIRModernToNVVM, MLIRToLLVM70, nanobind, LLVM-C, and the
  _mlir.*.pyd).
- The DLL-search patch in _mlir_libs/__init__.py is correctly applied.
- _mlir.pyd's static imports are all resolvable (next-to-pyd DLLs,
  python311.dll, and Windows system DLLs).
- The "LLVM install dir on PATH" trick the devcontainer used wasn't
  actually providing missing user-mode DLLs — only MLIR DLLs that the
  wheel already bundles.

So the failure is a DllMain / static-initializer error in one of the
bundled DLLs, not a missing-file issue. Two changes to make progress:

1. Add CIBW_REPAIR_WHEEL_COMMAND_WINDOWS using delvewheel, matching
   what auditwheel does on Linux. cibuildwheel's Windows default is
   no-op (unlike Linux/macOS), so the wheel had only setup.py's manual
   staging — no transitive dep walk, no DLL search-path init shim from
   delvewheel, no MSVC redist consideration. --add-path points
   delvewheel at the LLVM install bins so it can find anything that
   may live outside the wheel during the dependency walk.

2. Add a continue-on-error "Diagnose DLL loading" step to the Windows
   test job that, before the test run, ctypes.WinDLL-loads each
   bundled DLL in dependency order and reports which one fails first
   with the Windows error code. The bare ImportError doesn't say which
   DLL initialization actually failed; this pins it down.

If delvewheel repair fixes the import on its own, great. If not, the
diagnostic step tells us which specific DLL's init routine is failing,
and we can target the fix there.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 3b889a5

leofang added 2 commits June 12, 2026 05:26
Setting CIBW_REPAIR_WHEEL_COMMAND_WINDOWS breaks the build because
cibuildwheel only auto-installs delvewheel when the *default* repair
runs; when you override the command, delvewheel isn't on PATH and the
build fails with "'delvewheel' is not recognized".

cibuildwheel v3.4.1's default Windows repair is already
`delvewheel repair -w {dest_dir} -v {wheel}` with delvewheel
auto-installed — that's what was running on PR NVIDIA#81's wheels. The
diagnostic step on the test side stays, since that's what'll tell us
which specific DLL init is failing.

This also matches cuda-python and numba-cuda, which both leave the
Windows repair command unset and rely on cibuildwheel's default.
cibuildwheel's Windows default repair-wheel-command is a no-op, so the
wheel that PR NVIDIA#81 produced was never run through delvewheel. setup.py's
manual DLL staging is real, but it doesn't walk the transitive
dependency tree the way auditwheel does on Linux.

cuda-python (cuda_bindings, cuda_core) and numba-cuda both configure
delvewheel through pyproject.toml's [tool.cibuildwheel.windows] block.
Mirror that pattern here (without --namespace-pkg, since
numba_cuda_mlir isn't a namespace package).
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 110aefa

First delvewheel attempt failed with
"FileNotFoundError: Unable to find library: nanobind-numba_cuda_mlir.dll".

Per delvewheel docs, it only inspects .pyd import tables and resolves
each dependency against PATH or --add-path; it does NOT look at DLLs
already bundled inside the wheel. setup.py manually stages the MLIR /
nanobind / LLVM7 DLLs into _mlir_libs/ and lib/, but delvewheel needs
the on-disk source path to find them during dependency walking.

Add --add-path entries for the LLVM modern install's MLIR Python libs
dir (which has MLIRPythonCAPI.dll, nanobind-numba_cuda_mlir.dll,
MLIRPythonSupport-numba_cuda_mlir.dll, MLIRModernToNVVM.dll), the LLVM
modern install's bin dir, and the LLVM 7 install's bin dir (LLVM-C.dll).

cibuildwheel invokes repair-wheel-command via Git Bash on Windows (the
earlier failure log shows `+ delvewheel ...` -- bash's set -x prefix),
so $LLVM_MODERN_INSTALL_DIR / $LLVM7_INSTALL_DIR expansion works.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 33eba60

leofang added 2 commits June 12, 2026 12:22
…and \$VAR)

The previous attempt used bash-style \$LLVM_MODERN_INSTALL_DIR in the
delvewheel command, but cibuildwheel runs repair-wheel-command via
cmd.exe on Windows, which doesn't expand \$VAR. The variable appeared
literally in the command and delvewheel still failed to find
nanobind-numba_cuda_mlir.dll.

Switch to repo-relative paths: cibuildwheel runs the command from the
package directory (repo root), and llvm-modern-install/ and
llvm7-install/ are restored there earlier in the workflow. No shell
variable expansion needed.
Per design feedback: Linux's auditwheel command already lives in the
workflow's CIBW_REPAIR_WHEEL_COMMAND_LINUX. Putting Windows's delvewheel
config in pyproject.toml created an inconsistent split. Move it back to
the workflow so both platforms' repair configurations sit together.

Benefits:
- One place to read/edit wheel-repair logic.
- ${{ env.LLVM_MODERN_INSTALL_DIR }} / ${{ env.LLVM7_INSTALL_DIR }} are
  GitHub Actions expressions substituted at workflow render time,
  before cibuildwheel/cmd.exe ever sees the string. No reliance on
  shell variable expansion (which differs between cmd.exe and bash).
- CIBW_BEFORE_BUILD_WINDOWS handles installing delvewheel — the piece
  the original commit 3b889a5 was missing.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 8918c6d

REPO_DIR=\$(pwd) returns MSYS-style paths under Git Bash on Windows
(e.g. /d/a/.../llvm-modern-install). Downstream tools that run as
Windows-native processes -- cibuildwheel, delvewheel, the wheel's
Python imports -- can't resolve these paths.

The most recent build failure shows delvewheel receiving the MSYS path
verbatim and raising FileNotFoundError on
nanobind-numba_cuda_mlir.dll, even though the .dll exists at the
equivalent Windows path.

Match cuda-python's pattern: on Windows, run REPO_DIR (and the derived
TOOLS_PATH) through cygpath -w so the value is D:\\a\\... rather than
/d/a/... . LLVM_MODERN_INSTALL_DIR and LLVM7_INSTALL_DIR (set later in
this script as REPO_DIR/...) inherit the Windows-native style
automatically, so the delvewheel command's --add-path entries finally
point at paths Windows can resolve.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test e6c987f

leofang added 2 commits June 12, 2026 13:13
Commit e6c987f converted REPO_DIR to Windows-native (D:\a\...) in
env-vars on Windows. That broke the build because LLVM_MODERN_INSTALL_DIR
(derived from REPO_DIR) ended up containing backslashes, and the
existing CIBW_ENVIRONMENT_WINDOWS uses cygpath -w on the value as an
unquoted argument. Bash strips backslashes from unquoted args (\a -> a,
\n -> n, etc.), corrupting the path to nonsense before cygpath sees
it. cygpath then produces a garbage Windows path, MLIR_DIR points
nowhere real, and CMake find_package(MLIR) fails.

cuda-python sidesteps this by double-escaping backslashes via a
normpath() helper, but only on CUDA_PATH (which IS used in
CIBW_ENVIRONMENT_WINDOWS); their REPO_DIR isn't piped back through
unquoted bash. cuSIMT's LLVM_*_INSTALL_DIR is derived from REPO_DIR
and *is* used in CIBW_ENVIRONMENT_WINDOWS, so the bash backslash-eating
applies.

Smallest fix: revert env-vars to the MSYS-style REPO_DIR that worked
before, and add an inline cygpath -w in CIBW_REPAIR_WHEEL_COMMAND_WINDOWS
specifically for delvewheel's --add-path entries (delvewheel is the
only consumer that needs Windows-native paths; everything else either
already does its own cygpath conversion or is happy with MSYS form).
cibuildwheel runs the repair command via Git Bash on Windows, so
\$(cygpath -w ...) works inline.
…wrap

Implements option B from the design discussion: restore cygpath -w on
REPO_DIR (matching cuda-python and numba-cuda), wrap the derived
LLVM_*_INSTALL_DIR exports in realpath so the backslashes from
cygpath -w are normalized out of the env value, and revert the inline
$(cygpath -w ...) from CIBW_REPAIR_WHEEL_COMMAND_WINDOWS.

The reason cuSIMT hit the backslash-eating bug while cuda-python /
numba-cuda don't: cuda-python's env-vars pipes all its REPO_DIR-derived
paths through realpath before exporting them to $GITHUB_ENV (e.g.
CUDA_CORE_ARTIFACTS_DIR=\$(realpath "\${REPO_DIR}/cuda_core/dist"));
cuSIMT was concatenating REPO_DIR with /llvm-modern-install directly,
which left raw backslashes in the env value. Downstream
cygpath -w \${{ env.VAR }} calls then fed those backslashes to bash as
an unquoted argument, where bash silently strips them (\\a → a, \\n →
n, etc.), corrupting the path before cygpath sees it.

Move the env-vars BUILD step to run AFTER Restore LLVM artifacts so
realpath doesn't need -m (the LLVM install dirs exist by then in both
cold-cache and warm-cache flows; the composite action either restores
from actions/cache or falls back to actions/download-artifact, and
either produces the directories). No other step between checkout and
Build numba-cuda-mlir wheel needs env-vars output, so the reorder is
safe.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test bad8b94

The previous delvewheel-repaired wheel ended up with two physical
copies of each project DLL:
  - numba_cuda_mlir/_mlir/_mlir_libs/MLIRPythonCAPI.dll
    (un-mangled, from setup.py staging)
  - numba_cuda_mlir.libs/MLIRPythonCAPI-<hash>.dll
    (mangled, from delvewheel)

delvewheel patched _mlir.pyd's import table to the mangled name, but
MLIRToLLVM70.dll (loaded later via ctypes.CDLL, not visited by
delvewheel) still imports the un-mangled name and resolves to the
copy in _mlir_libs/. The process winds up with two distinct
MLIRPythonCAPI images, each with its own static state / LLVM
registries. When MLIRToLLVM70 calls into MLIR state that _mlir.pyd
set up via the OTHER copy, the registries are missing -> access
violation in the test job.

setup.py already manages staging of MLIRPythonCAPI / MLIRPythonSupport
/ nanobind into the wheel for uniform Linux/Windows treatment. Tell
delvewheel via --exclude not to bundle a second mangled copy of these.
delvewheel still gets to bundle the real missing pieces (msvcp140
etc.) that setup.py doesn't stage.

Mirrors how cuda_core/pyproject.toml does --exclude for torch DLLs.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 1162b16

leofang added 2 commits June 12, 2026 15:59
Per design discussion: we want the project DLLs (MLIRPythonCAPI etc.)
un-mangled because setup.py already stages them into _mlir_libs/ and
because other in-wheel DLLs (MLIRToLLVM70 in particular, loaded later
via ctypes.CDLL) import them under their original names. But we want
msvcp140 (and other transitive system deps) mangled to preserve
isolation from other wheels in the same process.

delvewheel exposes exactly this:
  --ignore-existing  -> don't re-bundle DLLs already in the wheel
                       (so no duplicate MLIRPythonCAPI copies)
  --analyze-existing -> walk the in-wheel DLLs for their transitive
                       deps anyway (so msvcp140 still gets discovered)
  --with-mangle      -> mangle those deps for isolation (default is
                       no-mangle when combined with --ignore-existing)

delvewheel then patches the existing in-wheel DLLs' import tables to
reference msvcp140-<hash>.dll, so the un-mangled MLIRPythonCAPI in
_mlir_libs/ correctly resolves to our bundled mangled msvcp140 at
runtime. Other wheels in the same process keep using their own
mangled copies. No name collisions, no duplicate images.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 3c87a7d

leofang added 2 commits June 12, 2026 16:40
Stepping back from the delvewheel-repair rabbit hole. The whole reason
delvewheel was wired in was to bundle a matching msvcp140.dll for our
Windows DLLs, because they were built with the default MSVC CRT linkage
(/MD, dynamic). On the build devcontainer the right redist version was
on PATH; on the bare runner the system msvcp140 didn't match and
init routines failed.

Building with the static CRT (/MT) links the C++ runtime *into* each
DLL, removing the msvcp140 / vcruntime140 import dependency entirely.
The wheel is then genuinely self-contained -- nothing for delvewheel
to bundle. This also dissolves the "two competing bundling
mechanisms" problem (setup.py's manual staging + delvewheel's
repair-with-mangling) we kept tripping over.

Changes:
- ci/build-windows.sh: add `-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`
  and `-DLLVM_USE_CRT_RELEASE=MT` to the LLVM7, modern LLVM/MLIR, and
  MLIRModernToNVVM bridge cmake invocations.
- ci/build-windows.sh: change the llvm-c-stub.c manual `cl` compile
  from `-MD` to `-MT` so it matches the static LLVM static libs it
  links against (mixing /MT and /MD within the same DLL is a link
  error).
- setup.py: pass `-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded` to the
  cmake invocation for our _cext / _helperlib / _typeconv / _mviewbuf
  and MLIRToLLVM70 builds on Windows.
- .github/workflows/build-wheel.yml: drop CIBW_BEFORE_BUILD_WINDOWS
  (the `pip install delvewheel` line) and CIBW_REPAIR_WHEEL_COMMAND_WINDOWS.
  cibuildwheel's Windows default is no-op, which is what we want now.

The ci/llvm-cache-key.sh hash already includes ci/build-windows.sh, so
editing this file automatically invalidates the existing LLVM cache
keys and triggers a clean rebuild.

Trade-offs of /MT:
- Larger DLLs (each carries its own CRT). MLIRPythonCAPI is already
  ~100 MB; an extra 1-2 MB of CRT code is in the noise.
- C++ stdlib types are unsafe to pass across DLL boundaries (different
  heaps per CRT instance). Our boundary is MLIRPythonCAPI's C API
  plus nanobind/Python-object handoffs through the .pyd files -- no
  C++ stdlib types cross the wire.
- LLVM/MLIR officially supports /MT builds via LLVM_USE_CRT_RELEASE=MT.
Per design feedback: the previous comment in setup.py conflated
"/MT everywhere" with "same CRT everywhere". These are different
constraints. /MT only requires per-binary consistency (all .objs
and statically-linked .libs feeding a single DLL must agree on
/MT vs /MD). Across DLLs each /MT binary has its own embedded
CRT instance with its own heap; that's safe as long as no C++
stdlib types cross the DLL boundary, which holds here because
MLIRPythonCAPI exposes a C API and our cext modules call into
that API via the import lib only.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test c757877

The "Install numba-cuda-mlir for diagnostics" + "Diagnose DLL loading"
steps were added in 3b889a5 to pinpoint which bundled DLL was failing
to initialize. Now that /MT static CRT (commit 99d354c) eliminates the
underlying msvcp140 mismatch and Windows tests pass, the diagnostic
step has no purpose -- it's pure debug code that prepends 30+ seconds
to every test job and re-installs the wheel.
@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

/ok to test 972cf8f

@leofang leofang changed the title [CI] Run Windows tests on host (no devcontainer); re-enable TCC matrix entries [CI] Windows: drop test-stage devcontainer + use static CRT Jun 12, 2026
@leofang
leofang marked this pull request as ready for review June 12, 2026 18:56
@leofang leofang added the bug Something isn't working label Jun 12, 2026
@leofang
leofang requested a review from atmnp June 12, 2026 18:58

@atmnp atmnp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks great! Thank you for handling it/fixing the bugs inadvertently introduced by me. Surprised that it was just the /MT / multithreading option that I had overlooked to get it working.

@leofang

leofang commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Journey to the final solution

The simple change in this PR — drop the devcontainer at the Windows test stage — immediately exposed that the wheel wasn't actually self-contained on Windows: DLL init failures (WinError 1114) on the bare runner because the build's MSVC redist version didn't match the runner's system msvcp140.

The canonical pip-ecosystem fix for this is wheel repair — auditwheel on Linux, delvewheel on Windows. Repair tools walk the .pyd import tables, find the missing redistributable DLLs (msvcp140, libstdc++, etc.), bundle them into the wheel with content-hash-mangled names for cross-wheel isolation, and patch the .pyd's import table to use the mangled name. Most native-Python projects ship Windows wheels this way (cuda-bindings, cuda-core, numpy, scipy, etc.). It seemed like the obvious lever to reach for, so that's where we started.

Ten iterations of trying to make delvewheel work before pivoting to fix the root cause:

  1. Wire delvewheel into the workflow with --add-path — failed: delvewheel wasn't even installed in cibuildwheel's env.
  2. Move delvewheel config to pyproject.toml (matching cuda-python) — failed: FileNotFoundError: nanobind-numba_cuda_mlir.dll. delvewheel doesn't see in-wheel DLLs.
  3. Add --add-path pointing at the LLVM install dir — failed: shell variable expansion didn't reach delvewheel under cmd.exe.
  4. Switch to repo-relative --add-path — failed: same env-var path problem.
  5. Substitute ${{ env.VAR }} at workflow render time (in build-wheel.yml) — found paths were MSYS-style /d/a/..., delvewheel needs Windows-native.
  6. cygpath -w REPO_DIR in env-vars — failed: bash silently ate backslashes from the resulting D:\a\... paths when passed unquoted to downstream cygpath -w.
  7. Revert env-vars + apply cygpath -w inline in delvewheel cmd — build worked; tests crashed with access violations. delvewheel-mangled MLIRPythonCAPI + setup.py-staged un-mangled copy ⇒ two images in process ⇒ static-state desync.
  8. Mirror cuda-python: cygpath -w + realpath wrap (in env-vars) — same access violation, same duplicate-image cause.
  9. --exclude project DLLs from delvewheel — avoided duplicates, but msvcp140 got mangled to a name the un-mangled MLIRPythonCAPI couldn't find.
  10. --ignore-existing --analyze-existing --with-mangle — hit an actual delvewheel bug: KeyError: 'msvcp140-<hash>.dll' in its double-patching loop.

The pivot: stepped back, asked why delvewheel was needed at all. Answer: only to bundle msvcp140 because everything was built with /MD (dynamic CRT). Built LLVM 7, modern LLVM/MLIR, and the cext modules with /MT (static CRT) instead — each binary embeds its own C++ runtime, the wheel has zero msvcp140 dependency, delvewheel becomes a no-op, the "two competing bundling pipelines" problem (setup.py wholesale-stages the MLIR bindings tree and delvewheel re-bundles) dissolves. The first commit on the static-CRT track (99d354c) made every Windows test pass on the first CI run.

Lessons:

  • Reach for the build-system fix before the wheel-repair fix when you control the build. Static CRT was a 3-line cmake-flag change that obsoleted ~9 commits of delvewheel plumbing. delvewheel is the right answer when you can't change how upstream DLLs are linked — but we do control how LLVM is built here.
  • delvewheel's design assumes the wheel doesn't pre-stage DLLs. cuSIMT's setup.py wholesale-copies the MLIR Python bindings tree, which violates that assumption; the combination has corner cases delvewheel doesn't handle well.
  • When debugging keeps looping on the same component, suspect the component is the wrong tool for the job.

@leofang
leofang enabled auto-merge (squash) June 12, 2026 19:16
@leofang
leofang merged commit f1742cb into NVIDIA:main Jun 12, 2026
126 checks passed
@leofang
leofang deleted the leofang/windows-host-test branch June 12, 2026 20:11
@github-actions

Copy link
Copy Markdown
Doc Preview CI
Preview removed because the pull request was closed or merged.

leofang added a commit to leofang/numba-cuda-mlir that referenced this pull request Jun 16, 2026
cibuildwheel v4 made `delvewheel repair -w {dest_dir} -v {wheel}` the
default for `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS`; v3.x had no default. We
intentionally do not need wheel repair on Windows because the wheel is
built with `/MT` (static CRT) per NVIDIA#127 -- it is self-contained and has
no DLL dependencies for delvewheel to bundle.

Without this opt-out, the default delvewheel invocation discovers an
unresolvable reference to the project's own internal nanobind module
and fails wheel build for every win-64 matrix entry. This regression
re-surfaced when the cibuildwheel v3.4.1 -> v4.1.0 bump from NVIDIA#125 was
cherry-picked into this branch to validate the new aggregator catches
it. With the opt-out, the build succeeds again.

Refs: NVIDIA#127 (static-CRT Windows wheel design), NVIDIA#125 / NVIDIA#134 (original
cibuildwheel bump + revert).
atmnp added a commit that referenced this pull request Jun 23, 2026
* ci: adopt CCCL check_result aggregator and add [doc-only] PR mode

Mirrors the gating-check fix in NVIDIA/cuda-python#2210 (root-cause
analysis: NVIDIA/cuda-python#2208) and bundles in cuda-python's
`[doc-only]` PR-mode feature, which had not been propagated to this
repo.

Changes:

* should-skip: emit both `skip` (existing `[no-ci]` gate) and a new
  `doc_only` output, computed from PR title containing `[doc-only]`.
  Single `gh pr view` call now drives both, matching cuda-python's
  pattern.

* `[doc-only]` gating on non-doc-chain jobs: build-llvm-linux-aarch64,
  build-llvm-windows, build-linux-aarch64, build-windows, and all 11
  test-* jobs now have
  `&& \!fromJSON(needs.should-skip.outputs.doc-only)` in their `if:`.
  The linux-64 chain (build-llvm-linux-64, build-linux-64) and
  build-docs are intentionally NOT `[doc-only]`-gated because
  build-docs needs the linux-64 wheel.

* checks: aggregator rewritten to CCCL's `check_result` pattern:
  explicit `expected="success"` per dependency, anything else
  (including `'skipped'` from a failed upstream) fails the gate.
  `[no-ci]` short-circuits at the top; `[doc-only]` flips `expected`
  to `"skipped"` for every test-* dep. `should-skip` itself is now in
  `needs:` and checked, so a crash in the gating job can't masquerade
  as a green CI run.

Verified the aggregator script with a local simulation across 8
scenarios: normal-green, build-fail-cascade, `[no-ci]`,
`[doc-only]`-OK, `[doc-only]`-doc-fails, build-windows-fail-cascade,
single-test-fail, should-skip-itself-fails. All behave correctly.

Reference: CCCL gate,
https://github.com/NVIDIA/cccl/blob/8c0e6cb1b6412d7bd0070f9ac3f55fa80231961a/.github/workflows/ci-workflow-pull-request.yml#L463-L526

Refs: NVIDIA/cuda-python#2208, NVIDIA/cuda-python#2210

* Bump the actions-monthly group across 1 directory with 5 updates (#125)

Bumps the actions-monthly group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `8.1.0` | `8.2.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.35.3` | `4.36.2` |
| [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) | `3.4.1` | `4.1.0` |
| [prefix-dev/setup-pixi](https://github.com/prefix-dev/setup-pixi) | `0.9.5` | `0.9.6` |
| [test-summary/action](https://github.com/test-summary/action) | `2.4` | `2.6` |



Updates `astral-sh/setup-uv` from 8.1.0 to 8.2.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@0880764...fac544c)

Updates `github/codeql-action` from 4.35.3 to 4.36.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@e46ed2c...8aad20d)

Updates `pypa/cibuildwheel` from 3.4.1 to 4.1.0
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](pypa/cibuildwheel@8d2b08b...2947353)

Updates `prefix-dev/setup-pixi` from 0.9.5 to 0.9.6
- [Release notes](https://github.com/prefix-dev/setup-pixi/releases)
- [Commits](prefix-dev/setup-pixi@1b2de7f...5185adf)

Updates `test-summary/action` from 2.4 to 2.6
- [Release notes](https://github.com/test-summary/action/releases)
- [Commits](test-summary/action@v2.4...v2.6)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-monthly
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-monthly
- dependency-name: prefix-dev/setup-pixi
  dependency-version: 0.9.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-monthly
- dependency-name: pypa/cibuildwheel
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions-monthly
- dependency-name: test-summary/action
  dependency-version: '2.6'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-monthly
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci: opt out of delvewheel repair on Windows (cibuildwheel v4 default)

cibuildwheel v4 made `delvewheel repair -w {dest_dir} -v {wheel}` the
default for `CIBW_REPAIR_WHEEL_COMMAND_WINDOWS`; v3.x had no default. We
intentionally do not need wheel repair on Windows because the wheel is
built with `/MT` (static CRT) per #127 -- it is self-contained and has
no DLL dependencies for delvewheel to bundle.

Without this opt-out, the default delvewheel invocation discovers an
unresolvable reference to the project's own internal nanobind module
and fails wheel build for every win-64 matrix entry. This regression
re-surfaced when the cibuildwheel v3.4.1 -> v4.1.0 bump from #125 was
cherry-picked into this branch to validate the new aggregator catches
it. With the opt-out, the build succeeds again.

Refs: #127 (static-CRT Windows wheel design), #125 / #134 (original
cibuildwheel bump + revert).

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Atmn Patel <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants