blob: 62459e9dbc24401e3100aba40543c2ca13f00cfc [file] [log] [blame]
Darryl Greenda02eb32018-02-28 10:02:55 +00001#!/usr/bin/env python3
2"""
3This file is part of Mbed TLS (https://tls.mbed.org)
4
5Copyright (c) 2018, Arm Limited, All Rights Reserved
6
7Purpose
8
9This script checks the current state of the source code for minor issues,
10including incorrect file permissions, presence of tabs, non-Unix line endings,
11trailing whitespace, presence of UTF-8 BOM, and TODO comments.
12Note: requires python 3, must be run from Mbed TLS root.
13"""
14
15import os
16import argparse
17import logging
18import codecs
19import sys
20
21
22class IssueTracker(object):
23 """Base class for issue tracking. Issues should inherit from this and
24 overwrite either issue_with_line if they check the file line by line, or
25 overwrite check_file_for_issue if they check the file as a whole."""
26
27 def __init__(self):
28 self.heading = ""
29 self.files_exemptions = []
30 self.files_with_issues = {}
31
32 def should_check_file(self, filepath):
33 for files_exemption in self.files_exemptions:
34 if filepath.endswith(files_exemption):
35 return False
36 return True
37
38 def issue_with_line(self, line):
39 raise NotImplementedError
40
41 def check_file_for_issue(self, filepath):
42 with open(filepath, "rb") as f:
43 for i, line in enumerate(iter(f.readline, b"")):
44 self.check_file_line(filepath, line, i + 1)
45
Gilles Peskine232fae32018-11-23 21:11:30 +010046 def record_issue(self, filepath, line_number):
47 if filepath not in self.files_with_issues.keys():
48 self.files_with_issues[filepath] = []
49 self.files_with_issues[filepath].append(line_number)
50
Darryl Greenda02eb32018-02-28 10:02:55 +000051 def check_file_line(self, filepath, line, line_number):
52 if self.issue_with_line(line):
Gilles Peskine232fae32018-11-23 21:11:30 +010053 self.record_issue(filepath, line_number)
Darryl Greenda02eb32018-02-28 10:02:55 +000054
55 def output_file_issues(self, logger):
56 if self.files_with_issues.values():
57 logger.info(self.heading)
58 for filename, lines in sorted(self.files_with_issues.items()):
59 if lines:
60 logger.info("{}: {}".format(
61 filename, ", ".join(str(x) for x in lines)
62 ))
63 else:
64 logger.info(filename)
65 logger.info("")
66
67
68class PermissionIssueTracker(IssueTracker):
69
70 def __init__(self):
71 super().__init__()
72 self.heading = "Incorrect permissions:"
73
74 def check_file_for_issue(self, filepath):
75 if not (os.access(filepath, os.X_OK) ==
76 filepath.endswith((".sh", ".pl", ".py"))):
77 self.files_with_issues[filepath] = None
78
79
80class EndOfFileNewlineIssueTracker(IssueTracker):
81
82 def __init__(self):
83 super().__init__()
84 self.heading = "Missing newline at end of file:"
85
86 def check_file_for_issue(self, filepath):
87 with open(filepath, "rb") as f:
88 if not f.read().endswith(b"\n"):
89 self.files_with_issues[filepath] = None
90
91
92class Utf8BomIssueTracker(IssueTracker):
93
94 def __init__(self):
95 super().__init__()
96 self.heading = "UTF-8 BOM present:"
97
98 def check_file_for_issue(self, filepath):
99 with open(filepath, "rb") as f:
100 if f.read().startswith(codecs.BOM_UTF8):
101 self.files_with_issues[filepath] = None
102
103
104class LineEndingIssueTracker(IssueTracker):
105
106 def __init__(self):
107 super().__init__()
108 self.heading = "Non Unix line endings:"
109
110 def issue_with_line(self, line):
111 return b"\r" in line
112
113
114class TrailingWhitespaceIssueTracker(IssueTracker):
115
116 def __init__(self):
117 super().__init__()
118 self.heading = "Trailing whitespace:"
119 self.files_exemptions = [".md"]
120
121 def issue_with_line(self, line):
122 return line.rstrip(b"\r\n") != line.rstrip()
123
124
125class TabIssueTracker(IssueTracker):
126
127 def __init__(self):
128 super().__init__()
129 self.heading = "Tabs present:"
130 self.files_exemptions = [
131 "Makefile", "generate_visualc_files.pl"
132 ]
133
134 def issue_with_line(self, line):
135 return b"\t" in line
136
137
138class TodoIssueTracker(IssueTracker):
139
140 def __init__(self):
141 super().__init__()
142 self.heading = "TODO present:"
143 self.files_exemptions = [
144 __file__, "benchmark.c", "pull_request_template.md"
145 ]
146
147 def issue_with_line(self, line):
148 return b"todo" in line.lower()
149
150
151class IntegrityChecker(object):
152
153 def __init__(self, log_file):
154 self.check_repo_path()
155 self.logger = None
156 self.setup_logger(log_file)
157 self.files_to_check = (
158 ".c", ".h", ".sh", ".pl", ".py", ".md", ".function", ".data",
159 "Makefile", "CMakeLists.txt", "ChangeLog"
160 )
Gilles Peskine3400b4d2018-09-28 11:48:10 +0200161 self.excluded_directories = ['.git', 'mbed-os']
162 self.excluded_paths = list(map(os.path.normpath, [
163 'cov-int',
164 'examples',
165 'yotta/module'
166 ]))
Darryl Greenda02eb32018-02-28 10:02:55 +0000167 self.issues_to_check = [
168 PermissionIssueTracker(),
169 EndOfFileNewlineIssueTracker(),
170 Utf8BomIssueTracker(),
171 LineEndingIssueTracker(),
172 TrailingWhitespaceIssueTracker(),
173 TabIssueTracker(),
174 TodoIssueTracker(),
175 ]
176
177 def check_repo_path(self):
178 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
179 raise Exception("Must be run from Mbed TLS root")
180
181 def setup_logger(self, log_file, level=logging.INFO):
182 self.logger = logging.getLogger()
183 self.logger.setLevel(level)
184 if log_file:
185 handler = logging.FileHandler(log_file)
186 self.logger.addHandler(handler)
187 else:
188 console = logging.StreamHandler()
189 self.logger.addHandler(console)
190
Gilles Peskine3400b4d2018-09-28 11:48:10 +0200191 def prune_branch(self, root, d):
192 if d in self.excluded_directories:
193 return True
194 if os.path.normpath(os.path.join(root, d)) in self.excluded_paths:
195 return True
196 return False
197
Darryl Greenda02eb32018-02-28 10:02:55 +0000198 def check_files(self):
Gilles Peskine3400b4d2018-09-28 11:48:10 +0200199 for root, dirs, files in os.walk("."):
200 dirs[:] = sorted(d for d in dirs if not self.prune_branch(root, d))
Darryl Greenda02eb32018-02-28 10:02:55 +0000201 for filename in sorted(files):
202 filepath = os.path.join(root, filename)
Gilles Peskine3400b4d2018-09-28 11:48:10 +0200203 if not filepath.endswith(self.files_to_check):
Darryl Greenda02eb32018-02-28 10:02:55 +0000204 continue
205 for issue_to_check in self.issues_to_check:
206 if issue_to_check.should_check_file(filepath):
207 issue_to_check.check_file_for_issue(filepath)
208
209 def output_issues(self):
210 integrity_return_code = 0
211 for issue_to_check in self.issues_to_check:
212 if issue_to_check.files_with_issues:
213 integrity_return_code = 1
214 issue_to_check.output_file_issues(self.logger)
215 return integrity_return_code
216
217
218def run_main():
219 parser = argparse.ArgumentParser(
220 description=(
221 "This script checks the current state of the source code for "
222 "minor issues, including incorrect file permissions, "
223 "presence of tabs, non-Unix line endings, trailing whitespace, "
224 "presence of UTF-8 BOM, and TODO comments. "
225 "Note: requires python 3, must be run from Mbed TLS root."
226 )
227 )
228 parser.add_argument(
229 "-l", "--log_file", type=str, help="path to optional output log",
230 )
231 check_args = parser.parse_args()
232 integrity_check = IntegrityChecker(check_args.log_file)
233 integrity_check.check_files()
234 return_code = integrity_check.output_issues()
235 sys.exit(return_code)
236
237
238if __name__ == "__main__":
239 run_main()