1- # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22# SPDX-License-Identifier: Apache-2.0
33
4+ import datetime
45import os
6+ import re
7+ import subprocess
58import sys
69
710import pathspec
@@ -26,29 +29,88 @@ def load_spdx_ignore():
2629 return pathspec .PathSpec .from_lines ("gitwildmatch" , lines )
2730
2831
29- def has_spdx_or_is_empty (filepath ):
32+ COPYRIGHT_REGEX = (
33+ rb"Copyright \(c\) (?P<years>[0-9]{4}(-[0-9]{4})?) "
34+ rb"(?P<affiliation>NVIDIA CORPORATION( & AFFILIATES\. All rights reserved\.)?)"
35+ )
36+ COPYRIGHT_SUB = r"Copyright (c) {} \g<affiliation>"
37+ CURRENT_YEAR = str (datetime .date .today ().year )
38+
39+
40+ def is_staged (filepath ):
41+ # If the file is staged, we need to update it to the current year
42+ process = subprocess .run ( # noqa: S603
43+ ["git" , "diff" , "--staged" , "--" , filepath ], # noqa: S607
44+ capture_output = True ,
45+ text = True ,
46+ )
47+ return process .stdout .strip () != ""
48+
49+
50+ def find_or_fix_spdx (filepath , fix ):
3051 with open (filepath , "rb" ) as f :
3152 blob = f .read ()
3253 if len (blob .strip ()) == 0 :
3354 return True
55+
3456 good = True
3557 for expected_bytes in EXPECTED_SPDX_BYTES :
3658 if expected_bytes not in blob :
3759 print (f"MISSING { expected_bytes .decode ()} { filepath !r} " )
3860 good = False
61+ continue
62+
63+ match = re .search (COPYRIGHT_REGEX , blob )
64+ if match is None :
65+ print (f"MISSING valid copyright line in { filepath !r} " )
66+ good = False
67+ continue
68+
69+ years = match .group ("years" ).decode ()
70+ if "-" in years :
71+ start_year , end_year = years .split ("-" , 1 )
72+ if int (start_year ) > int (end_year ):
73+ print (f"INVALID copyright years { years !r} in { filepath !r} " )
74+ good = False
75+ continue
76+ else :
77+ start_year = end_year = years
78+
79+ staged = is_staged (filepath )
80+
81+ if staged and int (end_year ) < int (CURRENT_YEAR ):
82+ print (f"OUTDATED copyright { years !r} (expected { CURRENT_YEAR !r} ) in { filepath !r} " )
83+ good = False
84+
85+ if fix :
86+ new_years = f"{ start_year } -{ CURRENT_YEAR } "
87+ blob = re .sub (
88+ COPYRIGHT_REGEX ,
89+ COPYRIGHT_SUB .format (new_years ).encode ("ascii" ),
90+ blob ,
91+ )
92+ with open (filepath , "wb" ) as f :
93+ f .write (blob )
94+
3995 return good
4096
4197
4298def main (args ):
4399 assert args , "filepaths expected to be passed from pre-commit"
44100
101+ if "--fix" in args :
102+ fix = True
103+ del args [args .index ("--fix" )]
104+ else :
105+ fix = False
106+
45107 ignore_spec = load_spdx_ignore ()
46108
47109 returncode = 0
48110 for filepath in args :
49111 if ignore_spec .match_file (filepath ):
50112 continue
51- if not has_spdx_or_is_empty (filepath ):
113+ if not find_or_fix_spdx (filepath , fix ):
52114 returncode = 1
53115 return returncode
54116
0 commit comments