blob: 5ed2efd07c64ad62eae4a2c9cd2c5562512dea35 [file] [log] [blame]
Fathi Boudra422bf772019-12-02 11:10:16 +02001#!/usr/bin/env python3
2#
Zachary Leafb6d86302024-10-29 10:29:15 +00003# Copyright (c) 2019-2024, Arm Limited. All rights reserved.
Fathi Boudra422bf772019-12-02 11:10:16 +02004#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8"""
9Check if a given file includes the copyright boiler plate.
10This checker supports the following comment styles:
Zelalem219df412020-05-17 19:21:20 -050011 /*
12 *
13 //
14 #
Fathi Boudra422bf772019-12-02 11:10:16 +020015"""
16
17import argparse
18import datetime
19import collections
20import fnmatch
21import shlex
22import os
23import re
24import sys
25import utils
26from itertools import islice
27
Sandrine Afsae37c35b2025-07-09 10:38:50 +020028class TfaConfig:
29 def __init__(self):
30 # File extensions to check
31 self.valid_file_ext = (
32 '.c', '.conf', '.dts', '.dtsi', '.editorconfig',
33 '.h', '.i', '.ld', 'Makefile', '.mk', '.msvc',
Sandrine Afsa1758dfa2025-07-09 10:56:06 +020034 '.py', '.S', '.scat', '.sh'
Sandrine Afsae37c35b2025-07-09 10:38:50 +020035 )
Fathi Boudra422bf772019-12-02 11:10:16 +020036
Sandrine Afsae37c35b2025-07-09 10:38:50 +020037 # Paths inside the tree to ignore. Hidden folders and files are always
38 # ignored. They mustn't end in '/'.
39 self.ignored_folders = (
40 'include/lib/hob',
41 'include/lib/libfdt',
42 'lib/compiler-rt',
43 'lib/hob',
44 'lib/libfdt',
45 'lib/zlib'
46 )
Fathi Boudra422bf772019-12-02 11:10:16 +020047
Sandrine Afsae37c35b2025-07-09 10:38:50 +020048 # List of ignored files in folders that aren't ignored
49 self.ignored_files = (
50 'include/tools_share/uuid.h',
51 )
52
53 self.copyright_line = LINE_START + 'Copyright' + '.*' + TIME_PERIOD + '.*' + EOL
54 self.copyright_pattern = re.compile(self.copyright_line, re.MULTILINE)
55 self.copyright_check_year = True
56
57class RfaConfig:
58 def __init__(self):
59 # File extensions to check
60 self.valid_file_ext = (
Sandrine Afsa1758dfa2025-07-09 10:56:06 +020061 '.h', '.ld', 'Makefile', '.mk',
62 '.py', '.S', '.sh', '.rs'
Sandrine Afsae37c35b2025-07-09 10:38:50 +020063 )
64
65 # Paths inside the tree to ignore. Hidden folders and files are always
66 # ignored. They mustn't end in '/'.
67 self.ignored_folders = (
Sandrine Afsae37c35b2025-07-09 10:38:50 +020068 )
69
70 # List of ignored files in folders that aren't ignored
71 self.ignored_files = (
Sandrine Afsae37c35b2025-07-09 10:38:50 +020072 )
73
74 self.copyright_line = LINE_START + 'Copyright The Rusted Firmware-A Contributors.' + EOL
75 self.copyright_pattern = re.compile(self.copyright_line, re.MULTILINE)
76 self.copyright_check_year = False
Fathi Boudra422bf772019-12-02 11:10:16 +020077
78# Supported comment styles (Python regex)
Zelalem219df412020-05-17 19:21:20 -050079COMMENT_PATTERN = '(\*|/\*|\#|//)'
Fathi Boudra422bf772019-12-02 11:10:16 +020080
Zelalem219df412020-05-17 19:21:20 -050081# Any combination of spaces and/or tabs
82SPACING = '[ \t]*'
Fathi Boudra422bf772019-12-02 11:10:16 +020083
Zelalem219df412020-05-17 19:21:20 -050084# Line must start with a comment and optional spacing
85LINE_START = '^' + SPACING + COMMENT_PATTERN + SPACING
86
87# Line end with optional spacing
88EOL = SPACING + '$'
89
90# Year or period as YYYY or YYYY-YYYY
91TIME_PERIOD = '[0-9]{4}(-[0-9]{4})?'
92
Sandrine Afsae37c35b2025-07-09 10:38:50 +020093CURRENT_YEAR = str(datetime.datetime.now().year)
94
Zelalem219df412020-05-17 19:21:20 -050095# Any string with valid license ID, don't allow adding postfix
Chris Kay345873c2021-04-27 13:24:51 +010096LICENSE_ID = '.*(BSD-3-Clause|BSD-2-Clause-FreeBSD|MIT)([ ,.\);].*)?'
Zelalem219df412020-05-17 19:21:20 -050097LICENSE_ID_LINE = LINE_START + 'SPDX-License-Identifier:' + LICENSE_ID + EOL
Zelalem219df412020-05-17 19:21:20 -050098LICENSE_ID_PATTERN = re.compile(LICENSE_ID_LINE, re.MULTILINE)
99
Fathi Boudra422bf772019-12-02 11:10:16 +0200100COPYRIGHT_OK = 0
101COPYRIGHT_ERROR = 1
Fathi Boudra422bf772019-12-02 11:10:16 +0200102
Sandrine Afsae37c35b2025-07-09 10:38:50 +0200103def check_copyright(path, copyright_pattern, check_year, encoding='utf-8'):
Fathi Boudra422bf772019-12-02 11:10:16 +0200104 '''Checks a file for a correct copyright header.'''
105
Zelalem219df412020-05-17 19:21:20 -0500106 result = COPYRIGHT_OK
107
108 with open(path, encoding=encoding) as file_:
Fathi Boudra422bf772019-12-02 11:10:16 +0200109 file_content = file_.read()
110
Tomás González3923b552025-06-04 14:04:00 +0100111 copyright_line = copyright_pattern.search(file_content)
Tomás González1150fb82025-06-04 10:05:21 +0100112
Zelalem219df412020-05-17 19:21:20 -0500113 if not copyright_line:
114 print("ERROR: Missing copyright in " + file_.name)
115 result = COPYRIGHT_ERROR
Tomás González3923b552025-06-04 14:04:00 +0100116 elif check_year and CURRENT_YEAR not in copyright_line.group():
Zelalem219df412020-05-17 19:21:20 -0500117 print("WARNING: Copyright is out of date in " + file_.name + ": '" +
118 copyright_line.group() + "'")
Fathi Boudra422bf772019-12-02 11:10:16 +0200119
Zelalem219df412020-05-17 19:21:20 -0500120 if not LICENSE_ID_PATTERN.search(file_content):
121 print("ERROR: License ID error in " + file_.name)
122 result = COPYRIGHT_ERROR
Fathi Boudra422bf772019-12-02 11:10:16 +0200123
Zelalem219df412020-05-17 19:21:20 -0500124 return result
Fathi Boudra422bf772019-12-02 11:10:16 +0200125
126def main(args):
Sandrine Afsae37c35b2025-07-09 10:38:50 +0200127 # Load the project's configuration (either TF-A's or RF-A's).
128 if not args.rusted:
129 config = TfaConfig()
130 else:
131 config = RfaConfig()
132
Fathi Boudra422bf772019-12-02 11:10:16 +0200133 print("Checking the copyrights in the code...")
134
Zelalem219df412020-05-17 19:21:20 -0500135 if args.verbose:
Sandrine Afsae37c35b2025-07-09 10:38:50 +0200136 print ("Copyright regexp: " + config.copyright_line)
Zelalem219df412020-05-17 19:21:20 -0500137 print ("License regexp: " + LICENSE_ID_LINE)
Fathi Boudra422bf772019-12-02 11:10:16 +0200138
139 if args.patch:
Harrison Mutai7a93cd22022-09-29 12:31:31 +0100140 print("Checking files added between patches " + args.from_ref
Fathi Boudra422bf772019-12-02 11:10:16 +0200141 + " and " + args.to_ref + "...")
142
143 (rc, stdout, stderr) = utils.shell_command(['git', 'diff',
Harrison Mutai7a93cd22022-09-29 12:31:31 +0100144 '--diff-filter=ACRT', '--name-only', args.from_ref, args.to_ref ])
Fathi Boudra422bf772019-12-02 11:10:16 +0200145 if rc:
Zelalem219df412020-05-17 19:21:20 -0500146 return COPYRIGHT_ERROR
Fathi Boudra422bf772019-12-02 11:10:16 +0200147
148 files = stdout.splitlines()
149
150 else:
151 print("Checking all files tracked by git...")
152
153 (rc, stdout, stderr) = utils.shell_command([ 'git', 'ls-files' ])
154 if rc:
Zelalem219df412020-05-17 19:21:20 -0500155 return COPYRIGHT_ERROR
Fathi Boudra422bf772019-12-02 11:10:16 +0200156
157 files = stdout.splitlines()
158
159 count_ok = 0
160 count_warning = 0
161 count_error = 0
162
163 for f in files:
164
Sandrine Afsae37c35b2025-07-09 10:38:50 +0200165 if utils.file_is_ignored(f, config.valid_file_ext, config.ignored_files, config.ignored_folders):
Fathi Boudra422bf772019-12-02 11:10:16 +0200166 if args.verbose:
167 print("Ignoring file " + f)
168 continue
169
170 if args.verbose:
171 print("Checking file " + f)
172
Sandrine Afsae37c35b2025-07-09 10:38:50 +0200173 rc = check_copyright(f, config.copyright_pattern, config.copyright_check_year)
Fathi Boudra422bf772019-12-02 11:10:16 +0200174
175 if rc == COPYRIGHT_OK:
176 count_ok += 1
Fathi Boudra422bf772019-12-02 11:10:16 +0200177 elif rc == COPYRIGHT_ERROR:
178 count_error += 1
Fathi Boudra422bf772019-12-02 11:10:16 +0200179
180 print("\nSummary:")
Zelalem219df412020-05-17 19:21:20 -0500181 print("\t{} files analyzed".format(count_ok + count_error))
Fathi Boudra422bf772019-12-02 11:10:16 +0200182
Zelalem219df412020-05-17 19:21:20 -0500183 if count_error == 0:
Fathi Boudra422bf772019-12-02 11:10:16 +0200184 print("\tNo errors found")
Zelalem219df412020-05-17 19:21:20 -0500185 return COPYRIGHT_OK
186 else:
Fathi Boudra422bf772019-12-02 11:10:16 +0200187 print("\t{} errors found".format(count_error))
Zelalem219df412020-05-17 19:21:20 -0500188 return COPYRIGHT_ERROR
Fathi Boudra422bf772019-12-02 11:10:16 +0200189
190def parse_cmd_line(argv, prog_name):
191 parser = argparse.ArgumentParser(
192 prog=prog_name,
193 formatter_class=argparse.RawTextHelpFormatter,
194 description="Check copyright of all files of codebase",
195 epilog="""
196For each source file in the tree, checks that the copyright header
197has the correct format.
198""")
199
200 parser.add_argument("--tree", "-t",
201 help="Path to the source tree to check (default: %(default)s)",
202 default=os.curdir)
203
Tomás González1150fb82025-06-04 10:05:21 +0100204 parser.add_argument("--rusted", "-r",
205 help="Check for Rusted Firmware CopyRight style (default: %(default)s)",
206 action='store_true', default=False)
207
Fathi Boudra422bf772019-12-02 11:10:16 +0200208 parser.add_argument("--verbose", "-v",
209 help="Increase verbosity to the source tree to check (default: %(default)s)",
210 action='store_true', default=False)
211
212 parser.add_argument("--patch", "-p",
213 help="""
214Patch mode.
215Instead of checking all files in the source tree, the script will consider
216only files that are modified by the latest patch(es).""",
217 action="store_true")
218 parser.add_argument("--from-ref",
219 help="Base commit in patch mode (default: %(default)s)",
Harrison Mutai1f1e6cf2025-06-24 09:28:18 +0000220 default="remotes/origin/integration")
Fathi Boudra422bf772019-12-02 11:10:16 +0200221 parser.add_argument("--to-ref",
222 help="Final commit in patch mode (default: %(default)s)",
223 default="HEAD")
224
225 args = parser.parse_args(argv)
226 return args
227
228
229if __name__ == "__main__":
230 args = parse_cmd_line(sys.argv[1:], sys.argv[0])
231
232 os.chdir(args.tree)
233
234 rc = main(args)
235
236 sys.exit(rc)