blob: 85008bec1c5e8c67fa4782c41e3a96c37137fe16 [file] [log] [blame]
David Horstmann20d6bfa2022-11-01 15:46:16 +00001#!/usr/bin/env python3
2"""Check or fix the code style by running Uncrustify.
David Horstmanna27d8722023-01-16 18:28:21 +00003
4This script must be run from the root of a Git work tree containing Mbed TLS.
David Horstmann20d6bfa2022-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 Horstmann20d6bfa2022-11-01 15:46:16 +000021import os
Gilles Peskine4ca54d42022-12-19 00:48:58 +010022import re
David Horstmann20d6bfa2022-11-01 15:46:16 +000023import subprocess
24import sys
Gilles Peskine4ca54d42022-12-19 00:48:58 +010025from typing import FrozenSet, List
David Horstmann20d6bfa2022-11-01 15:46:16 +000026
David Horstmann3a6f9f92022-12-08 14:44:36 +000027UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
David Horstmannc747fdf2022-12-08 17:03:01 +000028CONFIG_FILE = ".uncrustify.cfg"
David Horstmann20d6bfa2022-11-01 15:46:16 +000029UNCRUSTIFY_EXE = "uncrustify"
30UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
Gilles Peskine4ca54d42022-12-19 00:48:58 +010031CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
David Horstmann20d6bfa2022-11-01 15:46:16 +000032
David Horstmann448cfec2022-12-08 14:33:52 +000033def print_err(*args):
David Horstmann6956cb52023-01-24 18:36:41 +000034 print("Error: ", *args, file=sys.stderr)
David Horstmann448cfec2022-12-08 14:33:52 +000035
Pengyu Lva4b9b772023-02-06 14:27:30 +080036# Print the file names that will be skipped and the help message
37def print_skip(files_to_skip):
38 print()
39 print(*files_to_skip, sep=", SKIP\n", end=", SKIP\n")
Pengyu Lv75e11d32023-02-10 10:55:29 +080040 print("Warn: The listed files will be skipped because\n"
41 "they are not included in the default list.")
Pengyu Lva4b9b772023-02-06 14:27:30 +080042 print()
43
Gilles Peskine4ca54d42022-12-19 00:48:58 +010044# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
45CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
46 re.ASCII)
47def list_generated_files() -> FrozenSet[str]:
48 """Return the names of generated files.
49
50 We don't reformat generated files, since the result might be different
51 from the output of the generator. Ideally the result of the generator
52 would conform to the code style, but this would be difficult, especially
53 with respect to the placement of line breaks in long logical lines.
54 """
55 # Parse check-generated-files.sh to get an up-to-date list of
56 # generated files. Read the file rather than calling it so that
57 # this script only depends on Git, Python and uncrustify, and not other
58 # tools such as sh or grep which might not be available on Windows.
59 # This introduces a limitation: check-generated-files.sh must have
60 # the expected format and must list the files explicitly, not through
61 # wildcards or command substitution.
62 content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
63 checks = re.findall(CHECK_CALL_RE, content)
64 return frozenset(word for s in checks for word in s.split())
65
David Horstmann20d6bfa2022-11-01 15:46:16 +000066def get_src_files() -> List[str]:
67 """
68 Use git ls-files to get a list of the source files
69 """
David Horstmann27b37042022-12-08 13:12:21 +000070 git_ls_files_cmd = ["git", "ls-files",
David Horstmanneead72e2022-12-08 17:38:27 +000071 "*.[hc]",
72 "tests/suites/*.function",
73 "scripts/data_files/*.fmt"]
David Horstmann20d6bfa2022-11-01 15:46:16 +000074
David Horstmann6956cb52023-01-24 18:36:41 +000075 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE,
76 check=False)
David Horstmann20d6bfa2022-11-01 15:46:16 +000077
78 if result.returncode != 0:
David Horstmann1f8b4d92022-12-08 15:04:20 +000079 print_err("git ls-files returned: " + str(result.returncode))
David Horstmann20d6bfa2022-11-01 15:46:16 +000080 return []
81 else:
Gilles Peskine4ca54d42022-12-19 00:48:58 +010082 generated_files = list_generated_files()
David Horstmann20d6bfa2022-11-01 15:46:16 +000083 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine4ca54d42022-12-19 00:48:58 +010084 # Don't correct style for third-party files (and, for simplicity,
85 # companion files in the same subtree), or for automatically
86 # generated files (we're correcting the templates instead).
87 src_files = [filename for filename in src_files
88 if not (filename.startswith("3rdparty/") or
89 filename in generated_files)]
David Horstmann20d6bfa2022-11-01 15:46:16 +000090 return src_files
91
92def get_uncrustify_version() -> str:
93 """
94 Get the version string from Uncrustify
95 """
David Horstmann04aaa452023-01-25 11:39:04 +000096 result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
97 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
98 check=False)
David Horstmann20d6bfa2022-11-01 15:46:16 +000099 if result.returncode != 0:
David Horstmann448cfec2022-12-08 14:33:52 +0000100 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmann20d6bfa2022-11-01 15:46:16 +0000101 return ""
102 else:
103 return str(result.stdout, "utf-8")
104
105def check_style_is_correct(src_file_list: List[str]) -> bool:
106 """
David Horstmann99a669a2022-12-08 14:36:10 +0000107 Check the code style and output a diff for each file whose style is
David Horstmann20d6bfa2022-11-01 15:46:16 +0000108 incorrect.
109 """
110 style_correct = True
111 for src_file in src_file_list:
112 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
David Horstmann04aaa452023-01-25 11:39:04 +0000113 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
114 stderr=subprocess.PIPE, check=False)
David Horstmannb92d30f2023-01-04 18:33:25 +0000115 if result.returncode != 0:
David Horstmann04aaa452023-01-25 11:39:04 +0000116 print_err("Uncrustify returned " + str(result.returncode) +
117 " correcting file " + src_file)
David Horstmannb92d30f2023-01-04 18:33:25 +0000118 return False
David Horstmann20d6bfa2022-11-01 15:46:16 +0000119
120 # Uncrustify makes changes to the code and places the result in a new
121 # file with the extension ".uncrustify". To get the changes (if any)
122 # simply diff the 2 files.
David Horstmann1f8b4d92022-12-08 15:04:20 +0000123 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmann5682e802023-01-24 18:08:49 +0000124 cp = subprocess.run(diff_cmd, check=False)
125
126 if cp.returncode == 1:
David Horstmann6956cb52023-01-24 18:36:41 +0000127 print(src_file + " changed - code style is incorrect.")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000128 style_correct = False
David Horstmann5682e802023-01-24 18:08:49 +0000129 elif cp.returncode != 0:
130 raise subprocess.CalledProcessError(cp.returncode, cp.args,
131 cp.stdout, cp.stderr)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000132
133 # Tidy up artifact
David Horstmann1f8b4d92022-12-08 15:04:20 +0000134 os.remove(src_file + ".uncrustify")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000135
136 return style_correct
137
David Horstmannfa69def2023-01-05 09:59:35 +0000138def fix_style_single_pass(src_file_list: List[str]) -> bool:
David Horstmann20d6bfa2022-11-01 15:46:16 +0000139 """
140 Run Uncrustify once over the source files.
141 """
142 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
143 for src_file in src_file_list:
144 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmann6956cb52023-01-24 18:36:41 +0000145 result = subprocess.run(uncrustify_cmd, check=False)
David Horstmannb92d30f2023-01-04 18:33:25 +0000146 if result.returncode != 0:
David Horstmann04aaa452023-01-25 11:39:04 +0000147 print_err("Uncrustify with file returned: " +
148 str(result.returncode) + " correcting file " +
149 src_file)
David Horstmannb92d30f2023-01-04 18:33:25 +0000150 return False
David Horstmannfa69def2023-01-05 09:59:35 +0000151 return True
David Horstmann20d6bfa2022-11-01 15:46:16 +0000152
153def fix_style(src_file_list: List[str]) -> int:
154 """
155 Fix the code style. This takes 2 passes of Uncrustify.
156 """
David Horstmann242df482023-01-05 10:02:09 +0000157 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000158 return 1
David Horstmann242df482023-01-05 10:02:09 +0000159 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000160 return 1
David Horstmann20d6bfa2022-11-01 15:46:16 +0000161
162 # Guard against future changes that cause the codebase to require
163 # more passes.
164 if not check_style_is_correct(src_file_list):
David Horstmann64827e42023-01-16 18:32:56 +0000165 print_err("Code style still incorrect after second run of Uncrustify.")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000166 return 1
167 else:
168 return 0
169
170def main() -> int:
171 """
172 Main with command line arguments.
173 """
David Horstmann3a6f9f92022-12-08 14:44:36 +0000174 uncrustify_version = get_uncrustify_version().strip()
175 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine75289862022-12-23 18:15:19 +0100176 print("Warning: Using unsupported Uncrustify version '" +
David Horstmann6956cb52023-01-24 18:36:41 +0000177 uncrustify_version + "'")
Gilles Peskine75289862022-12-23 18:15:19 +0100178 print("Note: The only supported version is " +
David Horstmann6956cb52023-01-24 18:36:41 +0000179 UNCRUSTIFY_SUPPORTED_VERSION)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000180
181 parser = argparse.ArgumentParser()
Gilles Peskine38f514d2022-12-22 16:34:01 +0100182 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine75289862022-12-23 18:15:19 +0100183 help=('modify source files to fix the code style '
184 '(default: print diff, do not modify files)'))
Pengyu Lvb1c9cc32023-02-06 14:29:02 +0800185 parser.add_argument('--subset', action='store_true',
186 help=('check a subset of the files known to git '
187 '(default: empty FILE means full set)'))
Gilles Peskine38f514d2022-12-22 16:34:01 +0100188 parser.add_argument('operands', nargs='*', metavar='FILE',
Pengyu Lvb1c9cc32023-02-06 14:29:02 +0800189 help='files to check')
David Horstmann20d6bfa2022-11-01 15:46:16 +0000190
191 args = parser.parse_args()
192
Pengyu Lvb1c9cc32023-02-06 14:29:02 +0800193 all_src_files = get_src_files()
194 src_files = args.operands if args.operands else all_src_files
195 if args.subset:
196 # We are to check a subset of the default list
197 src_files = [f for f in args.operands if f in all_src_files]
Pengyu Lva4b9b772023-02-06 14:27:30 +0800198 skip_src_files = [f for f in args.operands if f not in src_files]
199 if skip_src_files:
200 print_skip(skip_src_files)
Gilles Peskine38f514d2022-12-22 16:34:01 +0100201
David Horstmann20d6bfa2022-11-01 15:46:16 +0000202 if args.fix:
203 # Fix mode
204 return fix_style(src_files)
205 else:
206 # Check mode
207 if check_style_is_correct(src_files):
David Horstmann6956cb52023-01-24 18:36:41 +0000208 print("Checked {} files, style ok.".format(len(src_files)))
David Horstmann20d6bfa2022-11-01 15:46:16 +0000209 return 0
210 else:
211 return 1
212
213if __name__ == '__main__':
214 sys.exit(main())