diff --git a/.gitattributes b/.gitattributes index 81f2361d4c..f713b27424 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,7 +6,9 @@ cuda/_version.py export-subst # we do not own any headers checked in, don't touch them *.h binary *.hpp binary -# Exception: headers we own (cuda_core C++ implementation) +# Exception: headers we own +cuda_bindings/cuda/bindings/_bindings/*.h -binary text diff +cuda_bindings/cuda/bindings/_lib/*.h -binary text diff cuda_core/cuda/core/_cpp/*.h -binary text diff cuda_core/cuda/core/_cpp/*.hpp -binary text diff # git should not convert line endings in PNG files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9727cb2d34..1c4339b948 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -30,6 +30,7 @@ repos: additional_dependencies: - https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl exclude: '.*pixi\.lock' + args: ["--fix"] - id: no-markdown-in-docs-source name: Prevent markdown files in docs/source directories @@ -89,5 +90,6 @@ repos: args: [--no-pycodestyle] exclude: ^cuda_bindings/ + default_language_version: python: python3 diff --git a/toolshed/check_spdx.py b/toolshed/check_spdx.py index 06072385b7..2359a89708 100644 --- a/toolshed/check_spdx.py +++ b/toolshed/check_spdx.py @@ -1,7 +1,10 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import datetime import os +import re +import subprocess import sys import pathspec @@ -26,29 +29,88 @@ def load_spdx_ignore(): return pathspec.PathSpec.from_lines("gitwildmatch", lines) -def has_spdx_or_is_empty(filepath): +COPYRIGHT_REGEX = ( + rb"Copyright \(c\) (?P[0-9]{4}(-[0-9]{4})?) " + rb"(?PNVIDIA CORPORATION( & AFFILIATES\. All rights reserved\.)?)" +) +COPYRIGHT_SUB = r"Copyright (c) {} \g" +CURRENT_YEAR = str(datetime.date.today().year) + + +def is_staged(filepath): + # If the file is staged, we need to update it to the current year + process = subprocess.run( # noqa: S603 + ["git", "diff", "--staged", "--", filepath], # noqa: S607 + capture_output=True, + text=True, + ) + return process.stdout.strip() != "" + + +def find_or_fix_spdx(filepath, fix): with open(filepath, "rb") as f: blob = f.read() if len(blob.strip()) == 0: return True + good = True for expected_bytes in EXPECTED_SPDX_BYTES: if expected_bytes not in blob: print(f"MISSING {expected_bytes.decode()}{filepath!r}") good = False + continue + + match = re.search(COPYRIGHT_REGEX, blob) + if match is None: + print(f"MISSING valid copyright line in {filepath!r}") + good = False + continue + + years = match.group("years").decode() + if "-" in years: + start_year, end_year = years.split("-", 1) + if int(start_year) > int(end_year): + print(f"INVALID copyright years {years!r} in {filepath!r}") + good = False + continue + else: + start_year = end_year = years + + staged = is_staged(filepath) + + if staged and int(end_year) < int(CURRENT_YEAR): + print(f"OUTDATED copyright {years!r} (expected {CURRENT_YEAR!r}) in {filepath!r}") + good = False + + if fix: + new_years = f"{start_year}-{CURRENT_YEAR}" + blob = re.sub( + COPYRIGHT_REGEX, + COPYRIGHT_SUB.format(new_years).encode("ascii"), + blob, + ) + with open(filepath, "wb") as f: + f.write(blob) + return good def main(args): assert args, "filepaths expected to be passed from pre-commit" + if "--fix" in args: + fix = True + del args[args.index("--fix")] + else: + fix = False + ignore_spec = load_spdx_ignore() returncode = 0 for filepath in args: if ignore_spec.match_file(filepath): continue - if not has_spdx_or_is_empty(filepath): + if not find_or_fix_spdx(filepath, fix): returncode = 1 return returncode