Skip to content

Commit 6b56002

Browse files
authored
WEB3-558: Update the license check script with --fix functionality (#717)
Copied from the version in `risc0/risc0`
1 parent 0dd148c commit 6b56002

File tree

1 file changed

+96
-12
lines changed

1 file changed

+96
-12
lines changed

license-check.py

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python
22

33
import sys
4-
import os
54
from pathlib import Path
65
import subprocess
6+
import argparse
7+
import re
78

89
PUBLIC_HEADER = '''
910
// Copyright {YEAR} RISC Zero, Inc.
@@ -19,7 +20,14 @@
1920
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2021
// See the License for the specific language governing permissions and
2122
// limitations under the License.
22-
'''.strip().splitlines()
23+
'''.strip()
24+
25+
PUBLIC_HEADER_RE = re.compile(
26+
"^"
27+
+ PUBLIC_HEADER.replace("(", "\\(")
28+
.replace(")", "\\)")
29+
.replace("{YEAR}", "(?P<year>[0-9]+)"),
30+
)
2331

2432
EXTENSIONS = [
2533
'.cpp',
@@ -36,24 +44,89 @@
3644
]
3745

3846
def check_header(expected_year, lines_actual):
39-
for (expected, actual) in zip(PUBLIC_HEADER, lines_actual):
47+
for (expected, actual) in zip(PUBLIC_HEADER.splitlines(), lines_actual):
4048
expected = expected.replace('{YEAR}', expected_year)
4149
if expected != actual:
4250
return (expected, actual)
4351
return None
4452

4553

46-
def check_file(root, file):
54+
def fix_file(file_obj, file_contents, start, end, insert):
55+
file_contents = file_contents[:start] + insert + file_contents[end:]
56+
file_obj.seek(0)
57+
file_obj.truncate()
58+
file_obj.write(file_contents)
59+
60+
61+
def is_comment_line(line: str) -> bool:
62+
return line.strip().startswith("//")
63+
64+
65+
def is_probably_license_block(lines: list[str]) -> bool:
66+
license_keywords = ["copyright", "license", "spdx", "apache", "mit"]
67+
text = "\n".join(lines).lower()
68+
return any(kw in text for kw in license_keywords)
69+
70+
71+
def find_license_block(file_contents: str) -> tuple[int, int] | None:
72+
"""Return (char_start, char_end) span of a license block, or None if not found."""
73+
lines = file_contents.splitlines(keepends=True)
74+
75+
license_lines = []
76+
for line in lines:
77+
stripped = line.strip()
78+
if stripped == "" or is_comment_line(stripped):
79+
license_lines.append(line)
80+
else:
81+
break
82+
83+
if license_lines and is_probably_license_block(license_lines):
84+
char_start = 0
85+
char_end = sum(len(line) for line in license_lines)
86+
return char_start, char_end
87+
88+
return None
89+
90+
91+
def check_file(root, file, fix):
4792
cmd = ['git', 'log', '-1', '--format=%ad', '--date=format:%Y', '--', file]
4893
expected_year = subprocess.check_output(cmd, encoding='UTF-8').strip()
4994
rel_path = file.relative_to(root)
50-
lines = file.read_text().splitlines()
51-
result = check_header(expected_year, lines)
52-
if result:
53-
print(f'{rel_path}: invalid header!')
54-
print(f' expected: {result[0]}')
55-
print(f' actual: {result[1]}')
56-
return 1
95+
96+
with open(file, "r+") as file_obj:
97+
file_contents = file_obj.read()
98+
match = PUBLIC_HEADER_RE.match(file_contents)
99+
100+
if match:
101+
actual_year = match.group("year")
102+
if actual_year != expected_year:
103+
print(f'{rel_path}: invalid header!')
104+
print(f'license has wrong year {actual_year}, expected {expected_year}')
105+
if fix:
106+
print(f'fixing {rel_path}')
107+
start, end = match.span(1)
108+
fix_file(file_obj, file_contents, start, end, expected_year)
109+
else:
110+
return 1
111+
else:
112+
lines = file_contents.splitlines()
113+
result = check_header(expected_year, lines)
114+
if result:
115+
print(f'{rel_path}: invalid header!')
116+
print(f' expected: {result[0]}')
117+
print(f' actual: {result[1]}')
118+
if fix:
119+
print(f'fixing {rel_path}')
120+
new_header = PUBLIC_HEADER.replace("{YEAR}", expected_year) + "\n\n"
121+
span = find_license_block(file_contents)
122+
if span:
123+
start, end = span
124+
fix_file(file_obj, file_contents, start, end, new_header)
125+
else:
126+
fix_file(file_obj, file_contents, 0, 0, new_header)
127+
else:
128+
return 1
129+
57130
return 0
58131

59132

@@ -72,8 +145,19 @@ def tracked_files():
72145

73146

74147
def main():
148+
parser = argparse.ArgumentParser(
149+
description="to update years, use the --fix option"
150+
)
151+
parser.add_argument("--file", type=Path)
152+
parser.add_argument(
153+
"--fix", action="store_true", help="modify files with correct year"
154+
)
155+
args = parser.parse_args()
156+
75157
root = repo_root()
76158
ret = 0
159+
if args.file:
160+
sys.exit(check_file(root, args.file.resolve(), args.fix))
77161
for path in tracked_files():
78162
if path.suffix in EXTENSIONS:
79163
skip = False
@@ -84,7 +168,7 @@ def main():
84168
if skip:
85169
continue
86170

87-
ret |= check_file(root, path)
171+
ret |= check_file(root, path, args.fix)
88172
sys.exit(ret)
89173

90174

0 commit comments

Comments
 (0)