blob: 61b1ab0e6ba3e730861db5b6a8bdcd37f539cd28 [file] [log] [blame]
David Horstmannfa928f12022-11-01 15:46:16 +00001#!/usr/bin/env python3
2"""Check or fix the code style by running Uncrustify.
David Horstmann8b5a4492023-01-16 18:28:21 +00003
4This script must be run from the root of a Git work tree containing Mbed TLS.
David Horstmannfa928f12022-11-01 15:46:16 +00005"""
6# Copyright The Mbed TLS Contributors
7# SPDX-License-Identifier: Apache-2.0
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License.
11# You may obtain a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18# See the License for the specific language governing permissions and
19# limitations under the License.
20import argparse
David Horstmannfa928f12022-11-01 15:46:16 +000021import os
Gilles Peskine9a3771e2022-12-19 00:48:58 +010022import re
David Horstmannfa928f12022-11-01 15:46:16 +000023import subprocess
24import sys
Gilles Peskine9a3771e2022-12-19 00:48:58 +010025from typing import FrozenSet, List
David Horstmannfa928f12022-11-01 15:46:16 +000026
David Horstmann2cf779c2022-12-08 14:44:36 +000027UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
David Horstmannae93a3f2022-12-08 17:03:01 +000028CONFIG_FILE = ".uncrustify.cfg"
David Horstmannfa928f12022-11-01 15:46:16 +000029UNCRUSTIFY_EXE = "uncrustify"
30UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
Gilles Peskine9a3771e2022-12-19 00:48:58 +010031CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
David Horstmannfa928f12022-11-01 15:46:16 +000032
David Horstmannca13c4f2022-12-08 14:33:52 +000033def print_err(*args):
David Horstmann6b3ce302023-01-24 18:36:41 +000034 print("Error: ", *args, file=sys.stderr)
David Horstmannca13c4f2022-12-08 14:33:52 +000035
Pengyu Lvacbeb7f2023-02-06 14:27:30 +080036def print_warn(*args):
37 print("Warn:", *args, file=sys.stderr)
38
39# Print the file names that will be skipped and the help message
40def print_skip(files_to_skip):
41 print()
42 print(*files_to_skip, sep=", SKIP\n", end=", SKIP\n")
43 print_warn("The listed files will be skipped because\n"
44 "they are not included in the default list.")
45 print()
46
Gilles Peskine9a3771e2022-12-19 00:48:58 +010047# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
48CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
49 re.ASCII)
50def list_generated_files() -> FrozenSet[str]:
51 """Return the names of generated files.
52
53 We don't reformat generated files, since the result might be different
54 from the output of the generator. Ideally the result of the generator
55 would conform to the code style, but this would be difficult, especially
56 with respect to the placement of line breaks in long logical lines.
57 """
58 # Parse check-generated-files.sh to get an up-to-date list of
59 # generated files. Read the file rather than calling it so that
60 # this script only depends on Git, Python and uncrustify, and not other
61 # tools such as sh or grep which might not be available on Windows.
62 # This introduces a limitation: check-generated-files.sh must have
63 # the expected format and must list the files explicitly, not through
64 # wildcards or command substitution.
65 content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
66 checks = re.findall(CHECK_CALL_RE, content)
67 return frozenset(word for s in checks for word in s.split())
68
David Horstmannfa928f12022-11-01 15:46:16 +000069def get_src_files() -> List[str]:
70 """
71 Use git ls-files to get a list of the source files
72 """
David Horstmannb7dab412022-12-08 13:12:21 +000073 git_ls_files_cmd = ["git", "ls-files",
David Horstmannc6b604e2022-12-08 17:38:27 +000074 "*.[hc]",
75 "tests/suites/*.function",
76 "scripts/data_files/*.fmt"]
David Horstmannfa928f12022-11-01 15:46:16 +000077
David Horstmann6b3ce302023-01-24 18:36:41 +000078 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE,
79 check=False)
David Horstmannfa928f12022-11-01 15:46:16 +000080
81 if result.returncode != 0:
David Horstmann0ebc12e2022-12-08 15:04:20 +000082 print_err("git ls-files returned: " + str(result.returncode))
David Horstmannfa928f12022-11-01 15:46:16 +000083 return []
84 else:
Gilles Peskine9a3771e2022-12-19 00:48:58 +010085 generated_files = list_generated_files()
David Horstmannfa928f12022-11-01 15:46:16 +000086 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine9a3771e2022-12-19 00:48:58 +010087 # Don't correct style for third-party files (and, for simplicity,
88 # companion files in the same subtree), or for automatically
89 # generated files (we're correcting the templates instead).
90 src_files = [filename for filename in src_files
91 if not (filename.startswith("3rdparty/") or
92 filename in generated_files)]
David Horstmannfa928f12022-11-01 15:46:16 +000093 return src_files
94
95def get_uncrustify_version() -> str:
96 """
97 Get the version string from Uncrustify
98 """
David Horstmann04bdbe32023-01-25 11:39:04 +000099 result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
100 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
101 check=False)
David Horstmannfa928f12022-11-01 15:46:16 +0000102 if result.returncode != 0:
David Horstmannca13c4f2022-12-08 14:33:52 +0000103 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmannfa928f12022-11-01 15:46:16 +0000104 return ""
105 else:
106 return str(result.stdout, "utf-8")
107
108def check_style_is_correct(src_file_list: List[str]) -> bool:
109 """
David Horstmann9711f4e2022-12-08 14:36:10 +0000110 Check the code style and output a diff for each file whose style is
David Horstmannfa928f12022-11-01 15:46:16 +0000111 incorrect.
112 """
113 style_correct = True
114 for src_file in src_file_list:
115 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
David Horstmann04bdbe32023-01-25 11:39:04 +0000116 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
117 stderr=subprocess.PIPE, check=False)
David Horstmannc571c5b2023-01-04 18:33:25 +0000118 if result.returncode != 0:
David Horstmann04bdbe32023-01-25 11:39:04 +0000119 print_err("Uncrustify returned " + str(result.returncode) +
120 " correcting file " + src_file)
David Horstmannc571c5b2023-01-04 18:33:25 +0000121 return False
David Horstmannfa928f12022-11-01 15:46:16 +0000122
123 # Uncrustify makes changes to the code and places the result in a new
124 # file with the extension ".uncrustify". To get the changes (if any)
125 # simply diff the 2 files.
David Horstmann0ebc12e2022-12-08 15:04:20 +0000126 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmannce42cc22023-01-24 18:08:49 +0000127 cp = subprocess.run(diff_cmd, check=False)
128
129 if cp.returncode == 1:
David Horstmann6b3ce302023-01-24 18:36:41 +0000130 print(src_file + " changed - code style is incorrect.")
David Horstmannfa928f12022-11-01 15:46:16 +0000131 style_correct = False
David Horstmannce42cc22023-01-24 18:08:49 +0000132 elif cp.returncode != 0:
133 raise subprocess.CalledProcessError(cp.returncode, cp.args,
134 cp.stdout, cp.stderr)
David Horstmannfa928f12022-11-01 15:46:16 +0000135
136 # Tidy up artifact
David Horstmann0ebc12e2022-12-08 15:04:20 +0000137 os.remove(src_file + ".uncrustify")
David Horstmannfa928f12022-11-01 15:46:16 +0000138
139 return style_correct
140
David Horstmann8d1d6ed2023-01-05 09:59:35 +0000141def fix_style_single_pass(src_file_list: List[str]) -> bool:
David Horstmannfa928f12022-11-01 15:46:16 +0000142 """
143 Run Uncrustify once over the source files.
144 """
145 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
146 for src_file in src_file_list:
147 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmann6b3ce302023-01-24 18:36:41 +0000148 result = subprocess.run(uncrustify_cmd, check=False)
David Horstmannc571c5b2023-01-04 18:33:25 +0000149 if result.returncode != 0:
David Horstmann04bdbe32023-01-25 11:39:04 +0000150 print_err("Uncrustify with file returned: " +
151 str(result.returncode) + " correcting file " +
152 src_file)
David Horstmannc571c5b2023-01-04 18:33:25 +0000153 return False
David Horstmann8d1d6ed2023-01-05 09:59:35 +0000154 return True
David Horstmannfa928f12022-11-01 15:46:16 +0000155
156def fix_style(src_file_list: List[str]) -> int:
157 """
158 Fix the code style. This takes 2 passes of Uncrustify.
159 """
David Horstmann78d566b2023-01-05 10:02:09 +0000160 if not fix_style_single_pass(src_file_list):
David Horstmannc571c5b2023-01-04 18:33:25 +0000161 return 1
David Horstmann78d566b2023-01-05 10:02:09 +0000162 if not fix_style_single_pass(src_file_list):
David Horstmannc571c5b2023-01-04 18:33:25 +0000163 return 1
David Horstmannfa928f12022-11-01 15:46:16 +0000164
165 # Guard against future changes that cause the codebase to require
166 # more passes.
167 if not check_style_is_correct(src_file_list):
David Horstmann28d21572023-01-16 18:32:56 +0000168 print_err("Code style still incorrect after second run of Uncrustify.")
David Horstmannfa928f12022-11-01 15:46:16 +0000169 return 1
170 else:
171 return 0
172
173def main() -> int:
174 """
175 Main with command line arguments.
176 """
David Horstmann2cf779c2022-12-08 14:44:36 +0000177 uncrustify_version = get_uncrustify_version().strip()
178 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100179 print("Warning: Using unsupported Uncrustify version '" +
David Horstmann6b3ce302023-01-24 18:36:41 +0000180 uncrustify_version + "'")
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100181 print("Note: The only supported version is " +
David Horstmann6b3ce302023-01-24 18:36:41 +0000182 UNCRUSTIFY_SUPPORTED_VERSION)
David Horstmannfa928f12022-11-01 15:46:16 +0000183
184 parser = argparse.ArgumentParser()
Gilles Peskine59803db2022-12-22 16:34:01 +0100185 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100186 help=('modify source files to fix the code style '
187 '(default: print diff, do not modify files)'))
Pengyu Lv8c6325c2023-02-06 14:29:02 +0800188 parser.add_argument('--subset', action='store_true',
189 help=('check a subset of the files known to git '
190 '(default: empty FILE means full set)'))
Gilles Peskine59803db2022-12-22 16:34:01 +0100191 parser.add_argument('operands', nargs='*', metavar='FILE',
Pengyu Lv8c6325c2023-02-06 14:29:02 +0800192 help='files to check')
David Horstmannfa928f12022-11-01 15:46:16 +0000193
194 args = parser.parse_args()
195
Pengyu Lv8c6325c2023-02-06 14:29:02 +0800196 all_src_files = get_src_files()
197 src_files = args.operands if args.operands else all_src_files
198 if args.subset:
199 # We are to check a subset of the default list
200 src_files = [f for f in args.operands if f in all_src_files]
Pengyu Lvacbeb7f2023-02-06 14:27:30 +0800201 skip_src_files = [f for f in args.operands if f not in src_files]
202 if skip_src_files:
203 print_skip(skip_src_files)
Gilles Peskine59803db2022-12-22 16:34:01 +0100204
David Horstmannfa928f12022-11-01 15:46:16 +0000205 if args.fix:
206 # Fix mode
207 return fix_style(src_files)
208 else:
209 # Check mode
210 if check_style_is_correct(src_files):
David Horstmann6b3ce302023-01-24 18:36:41 +0000211 print("Checked {} files, style ok.".format(len(src_files)))
David Horstmannfa928f12022-11-01 15:46:16 +0000212 return 0
213 else:
214 return 1
215
216if __name__ == '__main__':
217 sys.exit(main())