blob: d9c61a5afd98b2670717611d08500da702818527 [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
21import io
22import os
Gilles Peskine4ca54d42022-12-19 00:48:58 +010023import re
David Horstmann20d6bfa2022-11-01 15:46:16 +000024import subprocess
25import sys
Gilles Peskine4ca54d42022-12-19 00:48:58 +010026from typing import FrozenSet, List
David Horstmann20d6bfa2022-11-01 15:46:16 +000027
David Horstmann3a6f9f92022-12-08 14:44:36 +000028UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
David Horstmannc747fdf2022-12-08 17:03:01 +000029CONFIG_FILE = ".uncrustify.cfg"
David Horstmann20d6bfa2022-11-01 15:46:16 +000030UNCRUSTIFY_EXE = "uncrustify"
31UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
32STDOUT_UTF8 = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
33STDERR_UTF8 = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
Gilles Peskine4ca54d42022-12-19 00:48:58 +010034CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
David Horstmann20d6bfa2022-11-01 15:46:16 +000035
David Horstmann448cfec2022-12-08 14:33:52 +000036def print_err(*args):
37 print("Error: ", *args, file=STDERR_UTF8)
38
Gilles Peskine4ca54d42022-12-19 00:48:58 +010039# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
40CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
41 re.ASCII)
42def list_generated_files() -> FrozenSet[str]:
43 """Return the names of generated files.
44
45 We don't reformat generated files, since the result might be different
46 from the output of the generator. Ideally the result of the generator
47 would conform to the code style, but this would be difficult, especially
48 with respect to the placement of line breaks in long logical lines.
49 """
50 # Parse check-generated-files.sh to get an up-to-date list of
51 # generated files. Read the file rather than calling it so that
52 # this script only depends on Git, Python and uncrustify, and not other
53 # tools such as sh or grep which might not be available on Windows.
54 # This introduces a limitation: check-generated-files.sh must have
55 # the expected format and must list the files explicitly, not through
56 # wildcards or command substitution.
57 content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
58 checks = re.findall(CHECK_CALL_RE, content)
59 return frozenset(word for s in checks for word in s.split())
60
David Horstmann20d6bfa2022-11-01 15:46:16 +000061def get_src_files() -> List[str]:
62 """
63 Use git ls-files to get a list of the source files
64 """
David Horstmann27b37042022-12-08 13:12:21 +000065 git_ls_files_cmd = ["git", "ls-files",
David Horstmanneead72e2022-12-08 17:38:27 +000066 "*.[hc]",
67 "tests/suites/*.function",
68 "scripts/data_files/*.fmt"]
David Horstmann20d6bfa2022-11-01 15:46:16 +000069
70 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE, \
71 stderr=STDERR_UTF8, check=False)
72
73 if result.returncode != 0:
David Horstmann1f8b4d92022-12-08 15:04:20 +000074 print_err("git ls-files returned: " + str(result.returncode))
David Horstmann20d6bfa2022-11-01 15:46:16 +000075 return []
76 else:
Gilles Peskine4ca54d42022-12-19 00:48:58 +010077 generated_files = list_generated_files()
David Horstmann20d6bfa2022-11-01 15:46:16 +000078 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine4ca54d42022-12-19 00:48:58 +010079 # Don't correct style for third-party files (and, for simplicity,
80 # companion files in the same subtree), or for automatically
81 # generated files (we're correcting the templates instead).
82 src_files = [filename for filename in src_files
83 if not (filename.startswith("3rdparty/") or
84 filename in generated_files)]
David Horstmann20d6bfa2022-11-01 15:46:16 +000085 return src_files
86
87def get_uncrustify_version() -> str:
88 """
89 Get the version string from Uncrustify
90 """
David Horstmann27b37042022-12-08 13:12:21 +000091 result = subprocess.run([UNCRUSTIFY_EXE, "--version"], \
David Horstmann20d6bfa2022-11-01 15:46:16 +000092 stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
93 if result.returncode != 0:
David Horstmann448cfec2022-12-08 14:33:52 +000094 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmann20d6bfa2022-11-01 15:46:16 +000095 return ""
96 else:
97 return str(result.stdout, "utf-8")
98
99def check_style_is_correct(src_file_list: List[str]) -> bool:
100 """
David Horstmann99a669a2022-12-08 14:36:10 +0000101 Check the code style and output a diff for each file whose style is
David Horstmann20d6bfa2022-11-01 15:46:16 +0000102 incorrect.
103 """
104 style_correct = True
105 for src_file in src_file_list:
106 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
David Horstmannb92d30f2023-01-04 18:33:25 +0000107 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE, \
David Horstmann20d6bfa2022-11-01 15:46:16 +0000108 stderr=subprocess.PIPE, check=False)
David Horstmannb92d30f2023-01-04 18:33:25 +0000109 if result.returncode != 0:
110 print_err("Uncrustify returned " + str(result.returncode) + \
111 " correcting file " + src_file)
112 return False
David Horstmann20d6bfa2022-11-01 15:46:16 +0000113
114 # Uncrustify makes changes to the code and places the result in a new
115 # file with the extension ".uncrustify". To get the changes (if any)
116 # simply diff the 2 files.
David Horstmann1f8b4d92022-12-08 15:04:20 +0000117 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmann5682e802023-01-24 18:08:49 +0000118 cp = subprocess.run(diff_cmd, check=False)
119
120 if cp.returncode == 1:
121 print(src_file + " changed - code style is incorrect.", file=STDOUT_UTF8)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000122 style_correct = False
David Horstmann5682e802023-01-24 18:08:49 +0000123 elif cp.returncode != 0:
124 raise subprocess.CalledProcessError(cp.returncode, cp.args,
125 cp.stdout, cp.stderr)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000126
127 # Tidy up artifact
David Horstmann1f8b4d92022-12-08 15:04:20 +0000128 os.remove(src_file + ".uncrustify")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000129
130 return style_correct
131
David Horstmannfa69def2023-01-05 09:59:35 +0000132def fix_style_single_pass(src_file_list: List[str]) -> bool:
David Horstmann20d6bfa2022-11-01 15:46:16 +0000133 """
134 Run Uncrustify once over the source files.
135 """
136 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
137 for src_file in src_file_list:
138 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmannb92d30f2023-01-04 18:33:25 +0000139 result = subprocess.run(uncrustify_cmd, check=False, \
140 stdout=STDOUT_UTF8, stderr=STDERR_UTF8)
141 if result.returncode != 0:
142 print_err("Uncrustify with file returned: " + \
143 str(result.returncode) + " correcting file " + \
144 src_file)
145 return False
David Horstmannfa69def2023-01-05 09:59:35 +0000146 return True
David Horstmann20d6bfa2022-11-01 15:46:16 +0000147
148def fix_style(src_file_list: List[str]) -> int:
149 """
150 Fix the code style. This takes 2 passes of Uncrustify.
151 """
David Horstmann242df482023-01-05 10:02:09 +0000152 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000153 return 1
David Horstmann242df482023-01-05 10:02:09 +0000154 if not fix_style_single_pass(src_file_list):
David Horstmannb92d30f2023-01-04 18:33:25 +0000155 return 1
David Horstmann20d6bfa2022-11-01 15:46:16 +0000156
157 # Guard against future changes that cause the codebase to require
158 # more passes.
159 if not check_style_is_correct(src_file_list):
David Horstmann64827e42023-01-16 18:32:56 +0000160 print_err("Code style still incorrect after second run of Uncrustify.")
David Horstmann20d6bfa2022-11-01 15:46:16 +0000161 return 1
162 else:
163 return 0
164
165def main() -> int:
166 """
167 Main with command line arguments.
168 """
David Horstmann3a6f9f92022-12-08 14:44:36 +0000169 uncrustify_version = get_uncrustify_version().strip()
170 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine75289862022-12-23 18:15:19 +0100171 print("Warning: Using unsupported Uncrustify version '" +
172 uncrustify_version + "'", file=STDOUT_UTF8)
173 print("Note: The only supported version is " +
174 UNCRUSTIFY_SUPPORTED_VERSION, file=STDOUT_UTF8)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000175
176 parser = argparse.ArgumentParser()
Gilles Peskine38f514d2022-12-22 16:34:01 +0100177 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine75289862022-12-23 18:15:19 +0100178 help=('modify source files to fix the code style '
179 '(default: print diff, do not modify files)'))
Gilles Peskine38f514d2022-12-22 16:34:01 +0100180 # --files is almost useless: it only matters if there are no files
181 # ('code_style.py' without arguments checks all files known to Git,
Gilles Peskine75289862022-12-23 18:15:19 +0100182 # 'code_style.py --files' does nothing). In particular,
Gilles Peskineb71d28b2023-01-12 15:45:32 +0100183 # 'code_style.py --fix --files ...' is intended as a stable ("porcelain")
184 # way to restyle a possibly empty set of files.
Gilles Peskine38f514d2022-12-22 16:34:01 +0100185 parser.add_argument('--files', action='store_true',
186 help='only check the specified files (default with non-option arguments)')
187 parser.add_argument('operands', nargs='*', metavar='FILE',
188 help='files to check (if none: check files that are known to git)')
David Horstmann20d6bfa2022-11-01 15:46:16 +0000189
190 args = parser.parse_args()
191
Gilles Peskine38f514d2022-12-22 16:34:01 +0100192 if args.files or args.operands:
193 src_files = args.operands
194 else:
195 src_files = get_src_files()
196
David Horstmann20d6bfa2022-11-01 15:46:16 +0000197 if args.fix:
198 # Fix mode
199 return fix_style(src_files)
200 else:
201 # Check mode
202 if check_style_is_correct(src_files):
David Horstmann6d02f0c2023-01-24 16:56:18 +0000203 print("Checked {} files, style ok.".format(len(src_files)),
204 file=STDOUT_UTF8)
David Horstmann20d6bfa2022-11-01 15:46:16 +0000205 return 0
206 else:
207 return 1
208
209if __name__ == '__main__':
210 sys.exit(main())