blob: d3ae624ec160b7109303d853642803444448cf80 [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
Gilles Peskine9a3771e2022-12-19 00:48:58 +010036# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
37CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
38 re.ASCII)
39def list_generated_files() -> FrozenSet[str]:
40 """Return the names of generated files.
41
42 We don't reformat generated files, since the result might be different
43 from the output of the generator. Ideally the result of the generator
44 would conform to the code style, but this would be difficult, especially
45 with respect to the placement of line breaks in long logical lines.
46 """
47 # Parse check-generated-files.sh to get an up-to-date list of
48 # generated files. Read the file rather than calling it so that
49 # this script only depends on Git, Python and uncrustify, and not other
50 # tools such as sh or grep which might not be available on Windows.
51 # This introduces a limitation: check-generated-files.sh must have
52 # the expected format and must list the files explicitly, not through
53 # wildcards or command substitution.
54 content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
55 checks = re.findall(CHECK_CALL_RE, content)
56 return frozenset(word for s in checks for word in s.split())
57
David Horstmannfa928f12022-11-01 15:46:16 +000058def get_src_files() -> List[str]:
59 """
60 Use git ls-files to get a list of the source files
61 """
David Horstmannb7dab412022-12-08 13:12:21 +000062 git_ls_files_cmd = ["git", "ls-files",
David Horstmannc6b604e2022-12-08 17:38:27 +000063 "*.[hc]",
64 "tests/suites/*.function",
65 "scripts/data_files/*.fmt"]
David Horstmannfa928f12022-11-01 15:46:16 +000066
David Horstmann6b3ce302023-01-24 18:36:41 +000067 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE,
68 check=False)
David Horstmannfa928f12022-11-01 15:46:16 +000069
70 if result.returncode != 0:
David Horstmann0ebc12e2022-12-08 15:04:20 +000071 print_err("git ls-files returned: " + str(result.returncode))
David Horstmannfa928f12022-11-01 15:46:16 +000072 return []
73 else:
Gilles Peskine9a3771e2022-12-19 00:48:58 +010074 generated_files = list_generated_files()
David Horstmannfa928f12022-11-01 15:46:16 +000075 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine9a3771e2022-12-19 00:48:58 +010076 # Don't correct style for third-party files (and, for simplicity,
77 # companion files in the same subtree), or for automatically
78 # generated files (we're correcting the templates instead).
79 src_files = [filename for filename in src_files
80 if not (filename.startswith("3rdparty/") or
81 filename in generated_files)]
David Horstmannfa928f12022-11-01 15:46:16 +000082 return src_files
83
84def get_uncrustify_version() -> str:
85 """
86 Get the version string from Uncrustify
87 """
David Horstmannb7dab412022-12-08 13:12:21 +000088 result = subprocess.run([UNCRUSTIFY_EXE, "--version"], \
David Horstmannfa928f12022-11-01 15:46:16 +000089 stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
90 if result.returncode != 0:
David Horstmannca13c4f2022-12-08 14:33:52 +000091 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmannfa928f12022-11-01 15:46:16 +000092 return ""
93 else:
94 return str(result.stdout, "utf-8")
95
96def check_style_is_correct(src_file_list: List[str]) -> bool:
97 """
David Horstmann9711f4e2022-12-08 14:36:10 +000098 Check the code style and output a diff for each file whose style is
David Horstmannfa928f12022-11-01 15:46:16 +000099 incorrect.
100 """
101 style_correct = True
102 for src_file in src_file_list:
103 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
David Horstmannc571c5b2023-01-04 18:33:25 +0000104 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE, \
David Horstmannfa928f12022-11-01 15:46:16 +0000105 stderr=subprocess.PIPE, check=False)
David Horstmannc571c5b2023-01-04 18:33:25 +0000106 if result.returncode != 0:
107 print_err("Uncrustify returned " + str(result.returncode) + \
108 " correcting file " + src_file)
109 return False
David Horstmannfa928f12022-11-01 15:46:16 +0000110
111 # Uncrustify makes changes to the code and places the result in a new
112 # file with the extension ".uncrustify". To get the changes (if any)
113 # simply diff the 2 files.
David Horstmann0ebc12e2022-12-08 15:04:20 +0000114 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmannce42cc22023-01-24 18:08:49 +0000115 cp = subprocess.run(diff_cmd, check=False)
116
117 if cp.returncode == 1:
David Horstmann6b3ce302023-01-24 18:36:41 +0000118 print(src_file + " changed - code style is incorrect.")
David Horstmannfa928f12022-11-01 15:46:16 +0000119 style_correct = False
David Horstmannce42cc22023-01-24 18:08:49 +0000120 elif cp.returncode != 0:
121 raise subprocess.CalledProcessError(cp.returncode, cp.args,
122 cp.stdout, cp.stderr)
David Horstmannfa928f12022-11-01 15:46:16 +0000123
124 # Tidy up artifact
David Horstmann0ebc12e2022-12-08 15:04:20 +0000125 os.remove(src_file + ".uncrustify")
David Horstmannfa928f12022-11-01 15:46:16 +0000126
127 return style_correct
128
David Horstmann8d1d6ed2023-01-05 09:59:35 +0000129def fix_style_single_pass(src_file_list: List[str]) -> bool:
David Horstmannfa928f12022-11-01 15:46:16 +0000130 """
131 Run Uncrustify once over the source files.
132 """
133 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
134 for src_file in src_file_list:
135 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmann6b3ce302023-01-24 18:36:41 +0000136 result = subprocess.run(uncrustify_cmd, check=False)
David Horstmannc571c5b2023-01-04 18:33:25 +0000137 if result.returncode != 0:
138 print_err("Uncrustify with file returned: " + \
139 str(result.returncode) + " correcting file " + \
140 src_file)
141 return False
David Horstmann8d1d6ed2023-01-05 09:59:35 +0000142 return True
David Horstmannfa928f12022-11-01 15:46:16 +0000143
144def fix_style(src_file_list: List[str]) -> int:
145 """
146 Fix the code style. This takes 2 passes of Uncrustify.
147 """
David Horstmann78d566b2023-01-05 10:02:09 +0000148 if not fix_style_single_pass(src_file_list):
David Horstmannc571c5b2023-01-04 18:33:25 +0000149 return 1
David Horstmann78d566b2023-01-05 10:02:09 +0000150 if not fix_style_single_pass(src_file_list):
David Horstmannc571c5b2023-01-04 18:33:25 +0000151 return 1
David Horstmannfa928f12022-11-01 15:46:16 +0000152
153 # Guard against future changes that cause the codebase to require
154 # more passes.
155 if not check_style_is_correct(src_file_list):
David Horstmann28d21572023-01-16 18:32:56 +0000156 print_err("Code style still incorrect after second run of Uncrustify.")
David Horstmannfa928f12022-11-01 15:46:16 +0000157 return 1
158 else:
159 return 0
160
161def main() -> int:
162 """
163 Main with command line arguments.
164 """
David Horstmann2cf779c2022-12-08 14:44:36 +0000165 uncrustify_version = get_uncrustify_version().strip()
166 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100167 print("Warning: Using unsupported Uncrustify version '" +
David Horstmann6b3ce302023-01-24 18:36:41 +0000168 uncrustify_version + "'")
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100169 print("Note: The only supported version is " +
David Horstmann6b3ce302023-01-24 18:36:41 +0000170 UNCRUSTIFY_SUPPORTED_VERSION)
David Horstmannfa928f12022-11-01 15:46:16 +0000171
172 parser = argparse.ArgumentParser()
Gilles Peskine59803db2022-12-22 16:34:01 +0100173 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100174 help=('modify source files to fix the code style '
175 '(default: print diff, do not modify files)'))
Gilles Peskine59803db2022-12-22 16:34:01 +0100176 # --files is almost useless: it only matters if there are no files
177 # ('code_style.py' without arguments checks all files known to Git,
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100178 # 'code_style.py --files' does nothing). In particular,
Gilles Peskined449ced2023-01-12 15:45:32 +0100179 # 'code_style.py --fix --files ...' is intended as a stable ("porcelain")
180 # way to restyle a possibly empty set of files.
Gilles Peskine59803db2022-12-22 16:34:01 +0100181 parser.add_argument('--files', action='store_true',
182 help='only check the specified files (default with non-option arguments)')
183 parser.add_argument('operands', nargs='*', metavar='FILE',
184 help='files to check (if none: check files that are known to git)')
David Horstmannfa928f12022-11-01 15:46:16 +0000185
186 args = parser.parse_args()
187
Gilles Peskine59803db2022-12-22 16:34:01 +0100188 if args.files or args.operands:
189 src_files = args.operands
190 else:
191 src_files = get_src_files()
192
David Horstmannfa928f12022-11-01 15:46:16 +0000193 if args.fix:
194 # Fix mode
195 return fix_style(src_files)
196 else:
197 # Check mode
198 if check_style_is_correct(src_files):
David Horstmann6b3ce302023-01-24 18:36:41 +0000199 print("Checked {} files, style ok.".format(len(src_files)))
David Horstmannfa928f12022-11-01 15:46:16 +0000200 return 0
201 else:
202 return 1
203
204if __name__ == '__main__':
205 sys.exit(main())