blob: 350381b39248c1682da6ad1e1955246247a68b56 [file] [log] [blame]
Fathi Boudra422bf772019-12-02 11:10:16 +02001#!/usr/bin/env python3
2#
3# Copyright (c) 2019, Arm Limited. All rights reserved.
4#
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:
11 * Used by .c, .h, .S, .dts and .dtsi files
12 # Used by Makefile (including .mk)
13"""
14
15import argparse
16import datetime
17import collections
18import fnmatch
19import shlex
20import os
21import re
22import sys
23import utils
24from itertools import islice
25
26# File extensions to check
27VALID_FILE_EXTENSIONS = ('.c', '.S', '.h', 'Makefile', '.mk', '.dts', '.dtsi', '.ld')
28
29# Paths inside the tree to ignore. Hidden folders and files are always ignored.
30# They mustn't end in '/'.
31IGNORED_FOLDERS = (
32 'include/lib/libfdt',
33 'lib/compiler-rt',
34 'lib/libfdt',
35 'lib/zlib'
36)
37
38# List of ignored files in folders that aren't ignored
39IGNORED_FILES = (
40 'include/tools_share/uuid.h'
41)
42
43# Supported comment styles (Python regex)
44COMMENT_PATTERN = '^(( \* ?)|(\# ?))'
45
46# License pattern to match
47LICENSE_PATTERN = '''(?P<copyright_prologue>
48{0}Copyright \(c\) (?P<years>[0-9]{{4}}(-[0-9]{{4}})?), (Arm Limited|ARM Limited and Contributors)\. All rights reserved\.$
49{0}$
50{0}SPDX-License-Identifier: BSD-3-Clause$
51)'''.format(
52 COMMENT_PATTERN
53)
54
55# Compiled license pattern
56RE_PATTERN = re.compile(LICENSE_PATTERN, re.MULTILINE)
57
58COPYRIGHT_OK = 0
59COPYRIGHT_ERROR = 1
60COPYRIGHT_WARNING = 2
61
62def check_copyright(path):
63 '''Checks a file for a correct copyright header.'''
64
65 with open(path) as file_:
66 file_content = file_.read()
67
68 if RE_PATTERN.search(file_content):
69 return COPYRIGHT_OK
70
71 for line in file_content.split('\n'):
72 if 'SPDX-License-Identifier' in line:
73 if ('BSD-3-Clause' in line or
74 'BSD-2-Clause-FreeBSD' in line):
75 return COPYRIGHT_WARNING
76 break
77
78 return COPYRIGHT_ERROR
79
80
81def main(args):
82 print("Checking the copyrights in the code...")
83
84 all_files_correct = True
85
86 if args.patch:
87 print("Checking files modified between patches " + args.from_ref
88 + " and " + args.to_ref + "...")
89
90 (rc, stdout, stderr) = utils.shell_command(['git', 'diff',
91 '--diff-filter=ACMRT', '--name-only', args.from_ref, args.to_ref ])
92 if rc:
93 return 1
94
95 files = stdout.splitlines()
96
97 else:
98 print("Checking all files tracked by git...")
99
100 (rc, stdout, stderr) = utils.shell_command([ 'git', 'ls-files' ])
101 if rc:
102 return 1
103
104 files = stdout.splitlines()
105
106 count_ok = 0
107 count_warning = 0
108 count_error = 0
109
110 for f in files:
111
112 if utils.file_is_ignored(f, VALID_FILE_EXTENSIONS, IGNORED_FILES, IGNORED_FOLDERS):
113 if args.verbose:
114 print("Ignoring file " + f)
115 continue
116
117 if args.verbose:
118 print("Checking file " + f)
119
120 rc = check_copyright(f)
121
122 if rc == COPYRIGHT_OK:
123 count_ok += 1
124 elif rc == COPYRIGHT_WARNING:
125 count_warning += 1
126 print("WARNING: " + f)
127 elif rc == COPYRIGHT_ERROR:
128 count_error += 1
129 print("ERROR: " + f)
130
131 print("\nSummary:")
132 print("\t{} files analyzed".format(count_ok + count_warning + count_error))
133
134 if count_warning == 0 and count_error == 0:
135 print("\tNo errors found")
136 return 0
137
138 if count_error > 0:
139 print("\t{} errors found".format(count_error))
140
141 if count_warning > 0:
142 print("\t{} warnings found".format(count_warning))
143
144
145def parse_cmd_line(argv, prog_name):
146 parser = argparse.ArgumentParser(
147 prog=prog_name,
148 formatter_class=argparse.RawTextHelpFormatter,
149 description="Check copyright of all files of codebase",
150 epilog="""
151For each source file in the tree, checks that the copyright header
152has the correct format.
153""")
154
155 parser.add_argument("--tree", "-t",
156 help="Path to the source tree to check (default: %(default)s)",
157 default=os.curdir)
158
159 parser.add_argument("--verbose", "-v",
160 help="Increase verbosity to the source tree to check (default: %(default)s)",
161 action='store_true', default=False)
162
163 parser.add_argument("--patch", "-p",
164 help="""
165Patch mode.
166Instead of checking all files in the source tree, the script will consider
167only files that are modified by the latest patch(es).""",
168 action="store_true")
169 parser.add_argument("--from-ref",
170 help="Base commit in patch mode (default: %(default)s)",
171 default="master")
172 parser.add_argument("--to-ref",
173 help="Final commit in patch mode (default: %(default)s)",
174 default="HEAD")
175
176 args = parser.parse_args(argv)
177 return args
178
179
180if __name__ == "__main__":
181 args = parse_cmd_line(sys.argv[1:], sys.argv[0])
182
183 os.chdir(args.tree)
184
185 rc = main(args)
186
187 sys.exit(rc)