blob: dd8305faf6a7a8f658ff10742ec3bf933883b8a6 [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
Gilles Peskine4ca54d42022-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 Horstmann20d6bfa2022-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 Horstmann27b37042022-12-08 13:12:21 +000062 git_ls_files_cmd = ["git", "ls-files",
David Horstmanneead72e2022-12-08 17:38:27 +000063 "*.[hc]",
64 "tests/suites/*.function",
65 "scripts/data_files/*.fmt"]
David Horstmann20d6bfa2022-11-01 15:46:16 +000066
David Horstmann6956cb52023-01-24 18:36:41 +000067 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE,
68 check=False)
David Horstmann20d6bfa2022-11-01 15:46:16 +000069
70 if result.returncode != 0:
David Horstmann1f8b4d92022-12-08 15:04:20 +000071 print_err("git ls-files returned: " + str(result.returncode))
David Horstmann20d6bfa2022-11-01 15:46:16 +000072 return []
73 else:
Gilles Peskine4ca54d42022-12-19 00:48:58 +010074 generated_files = list_generated_files()
David Horstmann20d6bfa2022-11-01 15:46:16 +000075 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine4ca54d42022-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 Horstmann20d6bfa2022-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 Horstmann04aaa452023-01-25 11:39:04 +000088 result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
89 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
90 check=False)
David Horstmann20d6bfa2022-11-01 15:46:16 +000091 if result.returncode != 0:
David Horstmann448cfec2022-12-08 14:33:52 +000092 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmann20d6bfa2022-11-01 15:46:16 +000093 return ""
94 else:
95 return str(result.stdout, "utf-8")
96
97def check_style_is_correct(src_file_list: List[str]) -> bool:
98 """
David Horstmann99a669a2022-12-08 14:36:10 +000099 Check the code style and output a diff for each file whose style is
David Horstmann20d6bfa2022-11-01 15:46:16 +0000100 incorrect.
101 """
102 style_correct = True
103 for src_file in src_file_list:
104 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
David Horstmann04aaa452023-01-25 11:39:04 +0000105 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
106 stderr=subprocess.PIPE, check=False)
David Horstmannb92d30f2023-01-04 18:33:25 +0000107 if result.returncode != 0:
David Horstmann04aaa452023-01-25 11:39:04 +0000108 print_err("Uncrustify returned " + str(result.returncode) +
109 " correcting file " + src_file)
David Horstmannb92d30f2023-01-04 18:33:25 +0000110 return False
David Horstmann20d6bfa2022-11-01 15:46:16 +0000111
112 # Uncrustify makes changes to the code and places the result in a new
113 # file with the extension ".uncrustify". To get the changes (if any)
114 # simply diff the 2 files.
David Horstmann1f8b4d92022-12-08 15:04:20 +0000115 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmann5682e802023-01-24 18:08:49 +0000116 cp = subprocess.run(diff_cmd, check=False)
117
118 if cp.returncode == 1:
David Horstmann6956cb52023-01-24 18:36:41 +0000119 print(src_file + " changed - code style is incorrect.")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000120 style_correct = False
David Horstmann5682e802023-01-24 18:08:49 +0000121 elif cp.returncode != 0:
122 raise subprocess.CalledProcessError(cp.returncode, cp.args,
123 cp.stdout, cp.stderr)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000124
125 # Tidy up artifact
David Horstmann1f8b4d92022-12-08 15:04:20 +0000126 os.remove(src_file + ".uncrustify")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000127
128 return style_correct
129
David Horstmannfa69def2023-01-05 09:59:35 +0000130def fix_style_single_pass(src_file_list: List[str]) -> bool:
David Horstmann20d6bfa2022-11-01 15:46:16 +0000131 """
132 Run Uncrustify once over the source files.
133 """
134 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
135 for src_file in src_file_list:
136 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmann6956cb52023-01-24 18:36:41 +0000137 result = subprocess.run(uncrustify_cmd, check=False)
David Horstmannb92d30f2023-01-04 18:33:25 +0000138 if result.returncode != 0:
David Horstmann04aaa452023-01-25 11:39:04 +0000139 print_err("Uncrustify with file returned: " +
140 str(result.returncode) + " correcting file " +
141 src_file)
David Horstmannb92d30f2023-01-04 18:33:25 +0000142 return False
David Horstmannfa69def2023-01-05 09:59:35 +0000143 return True
David Horstmann20d6bfa2022-11-01 15:46:16 +0000144
145def fix_style(src_file_list: List[str]) -> int:
146 """
147 Fix the code style. This takes 2 passes of Uncrustify.
148 """
David Horstmann242df482023-01-05 10:02:09 +0000149 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000150 return 1
David Horstmann242df482023-01-05 10:02:09 +0000151 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000152 return 1
David Horstmann20d6bfa2022-11-01 15:46:16 +0000153
154 # Guard against future changes that cause the codebase to require
155 # more passes.
156 if not check_style_is_correct(src_file_list):
David Horstmann64827e42023-01-16 18:32:56 +0000157 print_err("Code style still incorrect after second run of Uncrustify.")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000158 return 1
159 else:
160 return 0
161
162def main() -> int:
163 """
164 Main with command line arguments.
165 """
David Horstmann3a6f9f92022-12-08 14:44:36 +0000166 uncrustify_version = get_uncrustify_version().strip()
167 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine75289862022-12-23 18:15:19 +0100168 print("Warning: Using unsupported Uncrustify version '" +
David Horstmann6956cb52023-01-24 18:36:41 +0000169 uncrustify_version + "'")
Gilles Peskine75289862022-12-23 18:15:19 +0100170 print("Note: The only supported version is " +
David Horstmann6956cb52023-01-24 18:36:41 +0000171 UNCRUSTIFY_SUPPORTED_VERSION)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000172
173 parser = argparse.ArgumentParser()
Gilles Peskine38f514d2022-12-22 16:34:01 +0100174 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine75289862022-12-23 18:15:19 +0100175 help=('modify source files to fix the code style '
176 '(default: print diff, do not modify files)'))
Gilles Peskine38f514d2022-12-22 16:34:01 +0100177 # --files is almost useless: it only matters if there are no files
178 # ('code_style.py' without arguments checks all files known to Git,
Gilles Peskine75289862022-12-23 18:15:19 +0100179 # 'code_style.py --files' does nothing). In particular,
Gilles Peskineb71d28b2023-01-12 15:45:32 +0100180 # 'code_style.py --fix --files ...' is intended as a stable ("porcelain")
181 # way to restyle a possibly empty set of files.
Gilles Peskine38f514d2022-12-22 16:34:01 +0100182 parser.add_argument('--files', action='store_true',
183 help='only check the specified files (default with non-option arguments)')
184 parser.add_argument('operands', nargs='*', metavar='FILE',
185 help='files to check (if none: check files that are known to git)')
David Horstmann20d6bfa2022-11-01 15:46:16 +0000186
187 args = parser.parse_args()
188
Gilles Peskine38f514d2022-12-22 16:34:01 +0100189 if args.files or args.operands:
190 src_files = args.operands
191 else:
192 src_files = get_src_files()
193
David Horstmann20d6bfa2022-11-01 15:46:16 +0000194 if args.fix:
195 # Fix mode
196 return fix_style(src_files)
197 else:
198 # Check mode
199 if check_style_is_correct(src_files):
David Horstmann6956cb52023-01-24 18:36:41 +0000200 print("Checked {} files, style ok.".format(len(src_files)))
David Horstmann20d6bfa2022-11-01 15:46:16 +0000201 return 0
202 else:
203 return 1
204
205if __name__ == '__main__':
206 sys.exit(main())