blob: a23d3a5ce502d4eae0b9af5fbd1c736187410a5e [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]
David Horstmannc571c5b2023-01-04 18:33:25 +0000109 result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE, \
David Horstmannfa928f12022-11-01 15:46:16 +0000110 stderr=subprocess.PIPE, check=False)
David Horstmannc571c5b2023-01-04 18:33:25 +0000111 if result.returncode != 0:
112 print_err("Uncrustify returned " + str(result.returncode) + \
113 " correcting file " + src_file)
114 return False
David Horstmannfa928f12022-11-01 15:46:16 +0000115
116 # Uncrustify makes changes to the code and places the result in a new
117 # file with the extension ".uncrustify". To get the changes (if any)
118 # simply diff the 2 files.
David Horstmann0ebc12e2022-12-08 15:04:20 +0000119 diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
David Horstmannfa928f12022-11-01 15:46:16 +0000120 result = subprocess.run(diff_cmd, stdout=subprocess.PIPE, \
121 stderr=STDERR_UTF8, check=False)
122 if len(result.stdout) > 0:
David Horstmann0ebc12e2022-12-08 15:04:20 +0000123 print(src_file + " - Incorrect code style.", file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000124 print("File changed - diff:", file=STDOUT_UTF8)
125 print(str(result.stdout, "utf-8"), file=STDOUT_UTF8)
126 style_correct = False
127 else:
David Horstmann0ebc12e2022-12-08 15:04:20 +0000128 print(src_file + " - OK.", file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000129
130 # Tidy up artifact
David Horstmann0ebc12e2022-12-08 15:04:20 +0000131 os.remove(src_file + ".uncrustify")
David Horstmannfa928f12022-11-01 15:46:16 +0000132
133 return style_correct
134
135def fix_style_single_pass(src_file_list: List[str]) -> None:
136 """
137 Run Uncrustify once over the source files.
138 """
139 code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
140 for src_file in src_file_list:
141 uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
David Horstmannc571c5b2023-01-04 18:33:25 +0000142 result = subprocess.run(uncrustify_cmd, check=False, \
143 stdout=STDOUT_UTF8, stderr=STDERR_UTF8)
144 if result.returncode != 0:
145 print_err("Uncrustify with file returned: " + \
146 str(result.returncode) + " correcting file " + \
147 src_file)
148 return False
David Horstmannfa928f12022-11-01 15:46:16 +0000149
150def fix_style(src_file_list: List[str]) -> int:
151 """
152 Fix the code style. This takes 2 passes of Uncrustify.
153 """
David Horstmannc571c5b2023-01-04 18:33:25 +0000154 if fix_style_single_pass(src_file_list) != True:
155 return 1
156 if fix_style_single_pass(src_file_list) != True:
157 return 1
David Horstmannfa928f12022-11-01 15:46:16 +0000158
159 # Guard against future changes that cause the codebase to require
160 # more passes.
161 if not check_style_is_correct(src_file_list):
David Horstmannca13c4f2022-12-08 14:33:52 +0000162 print("Code style still incorrect after second run of Uncrustify.")
David Horstmannfa928f12022-11-01 15:46:16 +0000163 return 1
164 else:
165 return 0
166
167def main() -> int:
168 """
169 Main with command line arguments.
170 """
David Horstmann2cf779c2022-12-08 14:44:36 +0000171 uncrustify_version = get_uncrustify_version().strip()
172 if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
David Horstmannfa928f12022-11-01 15:46:16 +0000173 print("Warning: Using unsupported Uncrustify version '" \
David Horstmann2cf779c2022-12-08 14:44:36 +0000174 + uncrustify_version + "' (Note: The only supported version" \
175 "is " + UNCRUSTIFY_SUPPORTED_VERSION + ")", file=STDOUT_UTF8)
David Horstmannfa928f12022-11-01 15:46:16 +0000176
177 src_files = get_src_files()
178
179 parser = argparse.ArgumentParser()
180 parser.add_argument('-f', '--fix', action='store_true', \
181 help='modify source files to fix the code style')
182
183 args = parser.parse_args()
184
185 if args.fix:
186 # Fix mode
187 return fix_style(src_files)
188 else:
189 # Check mode
190 if check_style_is_correct(src_files):
191 return 0
192 else:
193 return 1
194
195if __name__ == '__main__':
196 sys.exit(main())