blob: 23bb21b3a1300d62fa39311a3e80a98121c83521 [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 Horstmann190041d2022-12-08 14:56:18 +00003
4Note: The code style enforced by this script is not yet introduced to
5Mbed TLS. At present this script will only be used to prepare for a future
6change of code style.
David Horstmannfa928f12022-11-01 15:46:16 +00007"""
8# Copyright The Mbed TLS Contributors
9# SPDX-License-Identifier: Apache-2.0
10#
11# Licensed under the Apache License, Version 2.0 (the "License"); you may
12# not use this file except in compliance with the License.
13# You may obtain a copy of the License at
14#
15# http://www.apache.org/licenses/LICENSE-2.0
16#
17# Unless required by applicable law or agreed to in writing, software
18# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20# See the License for the specific language governing permissions and
21# limitations under the License.
22import argparse
23import io
24import os
Gilles Peskine9a3771e2022-12-19 00:48:58 +010025import re
David Horstmannfa928f12022-11-01 15:46:16 +000026import subprocess
27import sys
Gilles Peskine9a3771e2022-12-19 00:48:58 +010028from typing import FrozenSet, List
David Horstmannfa928f12022-11-01 15:46:16 +000029
David Horstmann2cf779c2022-12-08 14:44:36 +000030UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
David Horstmannae93a3f2022-12-08 17:03:01 +000031CONFIG_FILE = ".uncrustify.cfg"
David Horstmannfa928f12022-11-01 15:46:16 +000032UNCRUSTIFY_EXE = "uncrustify"
33UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
34STDOUT_UTF8 = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
35STDERR_UTF8 = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
Gilles Peskine9a3771e2022-12-19 00:48:58 +010036CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
David Horstmannfa928f12022-11-01 15:46:16 +000037
David Horstmannca13c4f2022-12-08 14:33:52 +000038def print_err(*args):
39 print("Error: ", *args, file=STDERR_UTF8)
40
Gilles Peskine9a3771e2022-12-19 00:48:58 +010041# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
42CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
43 re.ASCII)
44def list_generated_files() -> FrozenSet[str]:
45 """Return the names of generated files.
46
47 We don't reformat generated files, since the result might be different
48 from the output of the generator. Ideally the result of the generator
49 would conform to the code style, but this would be difficult, especially
50 with respect to the placement of line breaks in long logical lines.
51 """
52 # Parse check-generated-files.sh to get an up-to-date list of
53 # generated files. Read the file rather than calling it so that
54 # this script only depends on Git, Python and uncrustify, and not other
55 # tools such as sh or grep which might not be available on Windows.
56 # This introduces a limitation: check-generated-files.sh must have
57 # the expected format and must list the files explicitly, not through
58 # wildcards or command substitution.
59 content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
60 checks = re.findall(CHECK_CALL_RE, content)
61 return frozenset(word for s in checks for word in s.split())
62
David Horstmannfa928f12022-11-01 15:46:16 +000063def get_src_files() -> List[str]:
64 """
65 Use git ls-files to get a list of the source files
66 """
David Horstmannb7dab412022-12-08 13:12:21 +000067 git_ls_files_cmd = ["git", "ls-files",
David Horstmannc6b604e2022-12-08 17:38:27 +000068 "*.[hc]",
69 "tests/suites/*.function",
70 "scripts/data_files/*.fmt"]
David Horstmannfa928f12022-11-01 15:46:16 +000071
72 result = subprocess.run(git_ls_files_cmd, stdout=subprocess.PIPE, \
73 stderr=STDERR_UTF8, check=False)
74
75 if result.returncode != 0:
David Horstmann0ebc12e2022-12-08 15:04:20 +000076 print_err("git ls-files returned: " + str(result.returncode))
David Horstmannfa928f12022-11-01 15:46:16 +000077 return []
78 else:
Gilles Peskine9a3771e2022-12-19 00:48:58 +010079 generated_files = list_generated_files()
David Horstmannfa928f12022-11-01 15:46:16 +000080 src_files = str(result.stdout, "utf-8").split()
Gilles Peskine9a3771e2022-12-19 00:48:58 +010081 # Don't correct style for third-party files (and, for simplicity,
82 # companion files in the same subtree), or for automatically
83 # generated files (we're correcting the templates instead).
84 src_files = [filename for filename in src_files
85 if not (filename.startswith("3rdparty/") or
86 filename in generated_files)]
David Horstmannfa928f12022-11-01 15:46:16 +000087 return src_files
88
89def get_uncrustify_version() -> str:
90 """
91 Get the version string from Uncrustify
92 """
David Horstmannb7dab412022-12-08 13:12:21 +000093 result = subprocess.run([UNCRUSTIFY_EXE, "--version"], \
David Horstmannfa928f12022-11-01 15:46:16 +000094 stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
95 if result.returncode != 0:
David Horstmannca13c4f2022-12-08 14:33:52 +000096 print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
David Horstmannfa928f12022-11-01 15:46:16 +000097 return ""
98 else:
99 return str(result.stdout, "utf-8")
100
101def check_style_is_correct(src_file_list: List[str]) -> bool:
102 """
David Horstmann9711f4e2022-12-08 14:36:10 +0000103 Check the code style and output a diff for each file whose style is
David Horstmannfa928f12022-11-01 15:46:16 +0000104 incorrect.
105 """
106 style_correct = True
107 for src_file in src_file_list:
108 uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
109 subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE, \
110 stderr=subprocess.PIPE, check=False)
111
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 Horstmann0ebc12e2022-12-08 15:04:20 +0000115 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmannfa928f12022-11-01 15:46:16 +0000116 result = subprocess.run(diff_cmd, stdout=subprocess.PIPE, \
117 stderr=STDERR_UTF8, check=False)
118 if len(result.stdout) > 0:
David Horstmann0ebc12e2022-12-08 15:04:20 +0000119 print(src_file + " - Incorrect code style.", file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000120 print("File changed - diff:", file=STDOUT_UTF8)
121 print(str(result.stdout, "utf-8"), file=STDOUT_UTF8)
122 style_correct = False
123 else:
David Horstmann0ebc12e2022-12-08 15:04:20 +0000124 print(src_file + " - OK.", file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000125
126 # Tidy up artifact
David Horstmann0ebc12e2022-12-08 15:04:20 +0000127 os.remove(src_file + ".uncrustify")
David Horstmannfa928f12022-11-01 15:46:16 +0000128
129 return style_correct
130
131def fix_style_single_pass(src_file_list: List[str]) -> None:
132 """
133 Run Uncrustify once over the source files.
134 """
135 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
136 for src_file in src_file_list:
137 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
138 subprocess.run(uncrustify_cmd, check=False, stdout=STDOUT_UTF8, \
139 stderr=STDERR_UTF8)
140
141def fix_style(src_file_list: List[str]) -> int:
142 """
143 Fix the code style. This takes 2 passes of Uncrustify.
144 """
145 fix_style_single_pass(src_file_list)
146 fix_style_single_pass(src_file_list)
147
148 # Guard against future changes that cause the codebase to require
149 # more passes.
150 if not check_style_is_correct(src_file_list):
David Horstmannca13c4f2022-12-08 14:33:52 +0000151 print("Code style still incorrect after second run of Uncrustify.")
David Horstmannfa928f12022-11-01 15:46:16 +0000152 return 1
153 else:
154 return 0
155
156def main() -> int:
157 """
158 Main with command line arguments.
159 """
David Horstmann2cf779c2022-12-08 14:44:36 +0000160 uncrustify_version = get_uncrustify_version().strip()
161 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100162 print("Warning: Using unsupported Uncrustify version '" +
163 uncrustify_version + "'", file=STDOUT_UTF8)
164 print("Note: The only supported version is " +
165 UNCRUSTIFY_SUPPORTED_VERSION, file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000166
David Horstmannfa928f12022-11-01 15:46:16 +0000167 parser = argparse.ArgumentParser()
Gilles Peskine59803db2022-12-22 16:34:01 +0100168 parser.add_argument('-f', '--fix', action='store_true',
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100169 help=('modify source files to fix the code style '
170 '(default: print diff, do not modify files)'))
Gilles Peskine59803db2022-12-22 16:34:01 +0100171 # --files is almost useless: it only matters if there are no files
172 # ('code_style.py' without arguments checks all files known to Git,
Gilles Peskine9d34cf32022-12-23 18:15:19 +0100173 # 'code_style.py --files' does nothing). In particular,
174 # 'code_style.py --files ...' is intended as a stable ("porcelain") way
175 # to restyle a possibly empty set of files.
Gilles Peskine59803db2022-12-22 16:34:01 +0100176 parser.add_argument('--files', action='store_true',
177 help='only check the specified files (default with non-option arguments)')
178 parser.add_argument('operands', nargs='*', metavar='FILE',
179 help='files to check (if none: check files that are known to git)')
David Horstmannfa928f12022-11-01 15:46:16 +0000180
181 args = parser.parse_args()
182
Gilles Peskine59803db2022-12-22 16:34:01 +0100183 if args.files or args.operands:
184 src_files = args.operands
185 else:
186 src_files = get_src_files()
187
David Horstmannfa928f12022-11-01 15:46:16 +0000188 if args.fix:
189 # Fix mode
190 return fix_style(src_files)
191 else:
192 # Check mode
193 if check_style_is_correct(src_files):
194 return 0
195 else:
196 return 1
197
198if __name__ == '__main__':
199 sys.exit(main())