blob: 8fce8be5a45434247393b416fd07d7a60c9b0a29 [file] [log] [blame]
Darryl Green10d9ce32018-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 Peskine04398052018-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 Green10d9ce32018-02-28 10:02:55 +000051 def check_file_line(self, filepath, line, line_number):
52 if self.issue_with_line(line):
Gilles Peskine04398052018-11-23 21:11:30 +010053 self.record_issue(filepath, line_number)
Darryl Green10d9ce32018-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 Peskine95c55752018-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',
Gilles Peskine95c55752018-09-28 11:48:10 +0200165 ]))
Darryl Green10d9ce32018-02-28 10:02:55 +0000166 self.issues_to_check = [
167 PermissionIssueTracker(),
168 EndOfFileNewlineIssueTracker(),
169 Utf8BomIssueTracker(),
170 LineEndingIssueTracker(),
171 TrailingWhitespaceIssueTracker(),
172 TabIssueTracker(),
173 TodoIssueTracker(),
174 ]
175
176 def check_repo_path(self):
177 if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
178 raise Exception("Must be run from Mbed TLS root")
179
180 def setup_logger(self, log_file, level=logging.INFO):
181 self.logger = logging.getLogger()
182 self.logger.setLevel(level)
183 if log_file:
184 handler = logging.FileHandler(log_file)
185 self.logger.addHandler(handler)
186 else:
187 console = logging.StreamHandler()
188 self.logger.addHandler(console)
189
Gilles Peskine95c55752018-09-28 11:48:10 +0200190 def prune_branch(self, root, d):
191 if d in self.excluded_directories:
192 return True
193 if os.path.normpath(os.path.join(root, d)) in self.excluded_paths:
194 return True
195 return False
196
Darryl Green10d9ce32018-02-28 10:02:55 +0000197 def check_files(self):
Gilles Peskine95c55752018-09-28 11:48:10 +0200198 for root, dirs, files in os.walk("."):
199 dirs[:] = sorted(d for d in dirs if not self.prune_branch(root, d))
Darryl Green10d9ce32018-02-28 10:02:55 +0000200 for filename in sorted(files):
201 filepath = os.path.join(root, filename)
Gilles Peskine95c55752018-09-28 11:48:10 +0200202 if not filepath.endswith(self.files_to_check):
Darryl Green10d9ce32018-02-28 10:02:55 +0000203 continue
204 for issue_to_check in self.issues_to_check:
205 if issue_to_check.should_check_file(filepath):
206 issue_to_check.check_file_for_issue(filepath)
207
208 def output_issues(self):
209 integrity_return_code = 0
210 for issue_to_check in self.issues_to_check:
211 if issue_to_check.files_with_issues:
212 integrity_return_code = 1
213 issue_to_check.output_file_issues(self.logger)
214 return integrity_return_code
215
216
217def run_main():
218 parser = argparse.ArgumentParser(
219 description=(
220 "This script checks the current state of the source code for "
221 "minor issues, including incorrect file permissions, "
222 "presence of tabs, non-Unix line endings, trailing whitespace, "
223 "presence of UTF-8 BOM, and TODO comments. "
224 "Note: requires python 3, must be run from Mbed TLS root."
225 )
226 )
227 parser.add_argument(
228 "-l", "--log_file", type=str, help="path to optional output log",
229 )
230 check_args = parser.parse_args()
231 integrity_check = IntegrityChecker(check_args.log_file)
232 integrity_check.check_files()
233 return_code = integrity_check.output_issues()
234 sys.exit(return_code)
235
236
237if __name__ == "__main__":
238 run_main()