Repository navigation
clang-tidy as part of the actual build #3272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # Layout is clang-format's job (see .clang-format), so clang-tidy does no | ||
| # formatting of its own (FormatStyle: none below). Each enabled check is meant | ||
| # to be adoptable on its own, so findings can be fixed one check at a time. | ||
| # | ||
| # Checks are opt-in (-*) by group, minus the ones below. They cannot be | ||
| # commented inline because Checks is a single YAML string. | ||
| # | ||
| # Disabled because they conflict with the C-style CUDA driver API idioms: | ||
| # bugprone-easily-swappable-parameters: wrappers of driver calls take many | ||
| # adjacent parameters of the same type; the signatures mirror the API. | ||
| # bugprone-assignment-in-if-condition: `if (CUDA_SUCCESS != (err = CALL(...)))` | ||
| # is the standard error-handling pattern here. | ||
| # performance-no-int-to-ptr: opaque handles and file descriptors are | ||
| # converted to pointers with reinterpret_cast on purpose. | ||
| # modernize-avoid-c-arrays: arrays are passed to and filled by C APIs. | ||
| # modernize-return-braced-init-list: `return {};` vs. an explicit type is | ||
| # left to the author; it adds noise for the driver-API result structs. | ||
| # bugprone-throwing-static-initialization: the handle registries are | ||
| # process-lifetime globals whose constructors allocate on some standard | ||
| # libraries (MSVC); running out of memory while loading the module is | ||
| # fatal either way. | ||
| # | ||
| # Disabled because they do not match the codebase style: | ||
| # performance-enum-size: enum underlying types are left as they are. | ||
| # misc-const-correctness: const on every local is not the convention here. | ||
| # readability-function-cognitive-complexity: too disruptive. | ||
| # readability-identifier-length: short names such as the handle parameter `h` | ||
| # are used throughout. | ||
| # readability-implicit-bool-conversion: `if (ptr)` and `if (handle)` are the | ||
| # idiom for pointers and handles. | ||
| # misc-include-cleaner: too noisy with the CUDA and CPython umbrella headers. | ||
| # misc-no-recursion: recursion is used deliberately. | ||
| # misc-use-anonymous-namespace: file-local functions use `static`. | ||
| # modernize-use-trailing-return-type: the code uses `Type\nname()` layout. | ||
| Checks: > | ||
| -*, | ||
| bugprone-*, | ||
| -bugprone-easily-swappable-parameters, | ||
| -bugprone-assignment-in-if-condition, | ||
| -bugprone-throwing-static-initialization, | ||
| clang-analyzer-*, | ||
| misc-*, | ||
| -misc-include-cleaner, | ||
| -misc-const-correctness, | ||
| -misc-no-recursion, | ||
| -misc-use-anonymous-namespace, | ||
| modernize-*, | ||
| -modernize-use-trailing-return-type, | ||
| -modernize-avoid-c-arrays, | ||
| -modernize-return-braced-init-list, | ||
| performance-*, | ||
| -performance-enum-size, | ||
| -performance-no-int-to-ptr, | ||
| readability-*, | ||
| -readability-function-cognitive-complexity, | ||
| -readability-identifier-length, | ||
| -readability-implicit-bool-conversion | ||
| FormatStyle: none | ||
| CheckOptions: | ||
| # Records whose members are all public (the *Box and guard structs) are plain | ||
| # data holders; only a mix of public and private members is flagged. | ||
| misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: true | ||
| WarningsAsErrors: '*' | ||
| # Only report diagnostics for our own headers, not CUDA/CPython/libstdc++. | ||
| HeaderFilterRegex: 'cuda/core/_(cpp|include)/' | ||
| ExcludeHeaderFilterRegex: 'cuda/core/_include/(dlpack|aoti_shim)\.h' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| import importlib.util | ||
| import os | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import tempfile | ||
| import zipfile | ||
|
|
@@ -704,6 +705,59 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): | |
| return wheel_name | ||
|
|
||
|
|
||
| def _find_clang_tidy() -> str: | ||
| """The clang-tidy executable of the clang-tidy wheel in the build environment. | ||
|
|
||
| The wheel is a build-system requirement in pyproject.toml. | ||
|
|
||
| It is located through the wheel's ``clang_tidy`` package, never PATH, so a | ||
| system clang-tidy cannot be picked up. Raises RuntimeError when the wheel is | ||
| not installed. | ||
| """ | ||
| spec = importlib.util.find_spec("clang_tidy") | ||
| if spec is not None and spec.submodule_search_locations is not None: | ||
| exe = "clang-tidy" | ||
| if sys.platform == "win32": | ||
| exe = "clang-tidy.exe" | ||
| for base in spec.submodule_search_locations: | ||
| candidate = Path(base) / "data" / "bin" / exe | ||
| if candidate.is_file(): | ||
| return str(candidate) | ||
| raise RuntimeError( | ||
| "The clang-tidy build requirement of pyproject.toml is not installed. Isolated builds install it " | ||
| "automatically. For other builds, install the pinned version of it." | ||
| ) | ||
|
|
||
|
|
||
| def _is_handwritten_cpp(path) -> bool: | ||
| """Whether path is a handwritten C++ source, not a Cython-generated one.""" | ||
| path = Path(path) | ||
| return path.suffix == ".cpp" and path.resolve().is_relative_to(_PACKAGE_DIR / "_cpp") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. important: Resolve the package directory before comparing paths. If the checkout is accessed through a symlink, |
||
|
|
||
|
|
||
| def check_with_clang_tidy(compile_command) -> None: | ||
| """Run clang-tidy on the handwritten .cpp that a compiler command line compiles. | ||
|
|
||
| ``compile_command`` is the exact command that setuptools is about to spawn, | ||
| so clang-tidy sees the same compiler, include directories, macros and | ||
| flags as the build. Commands that do not compile a handwritten source are | ||
| ignored. The configuration is cuda_core/.clang-tidy, which turns every | ||
| finding into an error. Raises RuntimeError with the findings. | ||
| """ | ||
| sources = [arg for arg in compile_command if _is_handwritten_cpp(arg)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if not sources: | ||
| return | ||
| (source,) = sources | ||
| config = Path(__file__).resolve().parent / ".clang-tidy" | ||
| result = subprocess.run( # noqa: S603 | ||
| [_find_clang_tidy(), f"--config-file={config}", "--quiet", source, "--", *compile_command], | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if result.returncode != 0: | ||
| raise RuntimeError(f"clang-tidy reported errors in {source}:\n{result.stdout}{result.stderr}") | ||
|
|
||
|
|
||
| def _get_cuda_bindings_require(): | ||
| """The cuda-bindings build requirement for isolated builds. | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: NVIDIA/cuda-python
Length of output: 3745
Web query:
clang-tidy HeaderFilterRegex Windows backslash path separators matching normalize forward slashesResult:
Script executed:
Repository: NVIDIA/cuda-python
Length of output: 3667
Web query:
LLVM clang-tidy HeaderFilterRegex implementation Windows path normalization source ClangTidyDiagnosticConsumer.cpp HeaderFilterRegexResult:
Web query:
llvmorg-22.1.8 ClangTidyDiagnosticConsumer.cpp HeaderFilterRegex File->getName Windows path separator FileEntry getNameResult:
Web query:
llvmorg-22.1.8 clang FileEntry getName FileManager Windows native path separator sourceResult:
Script executed:
Repository: NVIDIA/cuda-python
Length of output: 1259
important: Match native Windows separators in
HeaderFilterRegex.clang-tidy==22.1.8matchesHeaderFilterRegexagainstFileEntryRef::getName()without path normalization. Native Windows paths can use backslashes, but this regex matches only forward slashes. Diagnostics from project headers can therefore be filtered out during Windows builds.Suggested fix
Committable suggestion