xref: /aosp_15_r20/external/mbedtls/scripts/code_style.py (revision 62c56f9862f102b96d72393aff6076c951fb8148)
1*62c56f98SSadaf Ebrahimi#!/usr/bin/env python3
2*62c56f98SSadaf Ebrahimi"""Check or fix the code style by running Uncrustify.
3*62c56f98SSadaf Ebrahimi
4*62c56f98SSadaf EbrahimiThis script must be run from the root of a Git work tree containing Mbed TLS.
5*62c56f98SSadaf Ebrahimi"""
6*62c56f98SSadaf Ebrahimi# Copyright The Mbed TLS Contributors
7*62c56f98SSadaf Ebrahimi# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
8*62c56f98SSadaf Ebrahimiimport argparse
9*62c56f98SSadaf Ebrahimiimport os
10*62c56f98SSadaf Ebrahimiimport re
11*62c56f98SSadaf Ebrahimiimport subprocess
12*62c56f98SSadaf Ebrahimiimport sys
13*62c56f98SSadaf Ebrahimifrom typing import FrozenSet, List, Optional
14*62c56f98SSadaf Ebrahimi
15*62c56f98SSadaf EbrahimiUNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
16*62c56f98SSadaf EbrahimiCONFIG_FILE = ".uncrustify.cfg"
17*62c56f98SSadaf EbrahimiUNCRUSTIFY_EXE = "uncrustify"
18*62c56f98SSadaf EbrahimiUNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
19*62c56f98SSadaf EbrahimiCHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
20*62c56f98SSadaf Ebrahimi
21*62c56f98SSadaf Ebrahimidef print_err(*args):
22*62c56f98SSadaf Ebrahimi    print("Error: ", *args, file=sys.stderr)
23*62c56f98SSadaf Ebrahimi
24*62c56f98SSadaf Ebrahimi# Print the file names that will be skipped and the help message
25*62c56f98SSadaf Ebrahimidef print_skip(files_to_skip):
26*62c56f98SSadaf Ebrahimi    print()
27*62c56f98SSadaf Ebrahimi    print(*files_to_skip, sep=", SKIP\n", end=", SKIP\n")
28*62c56f98SSadaf Ebrahimi    print("Warning: The listed files will be skipped because\n"
29*62c56f98SSadaf Ebrahimi          "they are not known to git.")
30*62c56f98SSadaf Ebrahimi    print()
31*62c56f98SSadaf Ebrahimi
32*62c56f98SSadaf Ebrahimi# Match FILENAME(s) in "check SCRIPT (FILENAME...)"
33*62c56f98SSadaf EbrahimiCHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
34*62c56f98SSadaf Ebrahimi                           re.ASCII)
35*62c56f98SSadaf Ebrahimidef list_generated_files() -> FrozenSet[str]:
36*62c56f98SSadaf Ebrahimi    """Return the names of generated files.
37*62c56f98SSadaf Ebrahimi
38*62c56f98SSadaf Ebrahimi    We don't reformat generated files, since the result might be different
39*62c56f98SSadaf Ebrahimi    from the output of the generator. Ideally the result of the generator
40*62c56f98SSadaf Ebrahimi    would conform to the code style, but this would be difficult, especially
41*62c56f98SSadaf Ebrahimi    with respect to the placement of line breaks in long logical lines.
42*62c56f98SSadaf Ebrahimi    """
43*62c56f98SSadaf Ebrahimi    # Parse check-generated-files.sh to get an up-to-date list of
44*62c56f98SSadaf Ebrahimi    # generated files. Read the file rather than calling it so that
45*62c56f98SSadaf Ebrahimi    # this script only depends on Git, Python and uncrustify, and not other
46*62c56f98SSadaf Ebrahimi    # tools such as sh or grep which might not be available on Windows.
47*62c56f98SSadaf Ebrahimi    # This introduces a limitation: check-generated-files.sh must have
48*62c56f98SSadaf Ebrahimi    # the expected format and must list the files explicitly, not through
49*62c56f98SSadaf Ebrahimi    # wildcards or command substitution.
50*62c56f98SSadaf Ebrahimi    content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
51*62c56f98SSadaf Ebrahimi    checks = re.findall(CHECK_CALL_RE, content)
52*62c56f98SSadaf Ebrahimi    return frozenset(word for s in checks for word in s.split())
53*62c56f98SSadaf Ebrahimi
54*62c56f98SSadaf Ebrahimidef get_src_files(since: Optional[str]) -> List[str]:
55*62c56f98SSadaf Ebrahimi    """
56*62c56f98SSadaf Ebrahimi    Use git to get a list of the source files.
57*62c56f98SSadaf Ebrahimi
58*62c56f98SSadaf Ebrahimi    The optional argument since is a commit, indicating to only list files
59*62c56f98SSadaf Ebrahimi    that have changed since that commit. Without this argument, list all
60*62c56f98SSadaf Ebrahimi    files known to git.
61*62c56f98SSadaf Ebrahimi
62*62c56f98SSadaf Ebrahimi    Only C files are included, and certain files (generated, or 3rdparty)
63*62c56f98SSadaf Ebrahimi    are excluded.
64*62c56f98SSadaf Ebrahimi    """
65*62c56f98SSadaf Ebrahimi    file_patterns = ["*.[hc]",
66*62c56f98SSadaf Ebrahimi                     "tests/suites/*.function",
67*62c56f98SSadaf Ebrahimi                     "scripts/data_files/*.fmt"]
68*62c56f98SSadaf Ebrahimi    output = subprocess.check_output(["git", "ls-files"] + file_patterns,
69*62c56f98SSadaf Ebrahimi                                     universal_newlines=True)
70*62c56f98SSadaf Ebrahimi    src_files = output.split()
71*62c56f98SSadaf Ebrahimi    if since:
72*62c56f98SSadaf Ebrahimi        # get all files changed in commits since the starting point
73*62c56f98SSadaf Ebrahimi        cmd = ["git", "log", since + "..HEAD", "--name-only", "--pretty=", "--"] + src_files
74*62c56f98SSadaf Ebrahimi        output = subprocess.check_output(cmd, universal_newlines=True)
75*62c56f98SSadaf Ebrahimi        committed_changed_files = output.split()
76*62c56f98SSadaf Ebrahimi        # and also get all files with uncommitted changes
77*62c56f98SSadaf Ebrahimi        cmd = ["git", "diff", "--name-only", "--"] + src_files
78*62c56f98SSadaf Ebrahimi        output = subprocess.check_output(cmd, universal_newlines=True)
79*62c56f98SSadaf Ebrahimi        uncommitted_changed_files = output.split()
80*62c56f98SSadaf Ebrahimi        src_files = list(set(committed_changed_files + uncommitted_changed_files))
81*62c56f98SSadaf Ebrahimi
82*62c56f98SSadaf Ebrahimi    generated_files = list_generated_files()
83*62c56f98SSadaf Ebrahimi    # Don't correct style for third-party files (and, for simplicity,
84*62c56f98SSadaf Ebrahimi    # companion files in the same subtree), or for automatically
85*62c56f98SSadaf Ebrahimi    # generated files (we're correcting the templates instead).
86*62c56f98SSadaf Ebrahimi    src_files = [filename for filename in src_files
87*62c56f98SSadaf Ebrahimi                 if not (filename.startswith("3rdparty/") or
88*62c56f98SSadaf Ebrahimi                         filename in generated_files)]
89*62c56f98SSadaf Ebrahimi    return src_files
90*62c56f98SSadaf Ebrahimi
91*62c56f98SSadaf Ebrahimidef get_uncrustify_version() -> str:
92*62c56f98SSadaf Ebrahimi    """
93*62c56f98SSadaf Ebrahimi    Get the version string from Uncrustify
94*62c56f98SSadaf Ebrahimi    """
95*62c56f98SSadaf Ebrahimi    result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
96*62c56f98SSadaf Ebrahimi                            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
97*62c56f98SSadaf Ebrahimi                            check=False)
98*62c56f98SSadaf Ebrahimi    if result.returncode != 0:
99*62c56f98SSadaf Ebrahimi        print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
100*62c56f98SSadaf Ebrahimi        return ""
101*62c56f98SSadaf Ebrahimi    else:
102*62c56f98SSadaf Ebrahimi        return str(result.stdout, "utf-8")
103*62c56f98SSadaf Ebrahimi
104*62c56f98SSadaf Ebrahimidef check_style_is_correct(src_file_list: List[str]) -> bool:
105*62c56f98SSadaf Ebrahimi    """
106*62c56f98SSadaf Ebrahimi    Check the code style and output a diff for each file whose style is
107*62c56f98SSadaf Ebrahimi    incorrect.
108*62c56f98SSadaf Ebrahimi    """
109*62c56f98SSadaf Ebrahimi    style_correct = True
110*62c56f98SSadaf Ebrahimi    for src_file in src_file_list:
111*62c56f98SSadaf Ebrahimi        uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
112*62c56f98SSadaf Ebrahimi        result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
113*62c56f98SSadaf Ebrahimi                                stderr=subprocess.PIPE, check=False)
114*62c56f98SSadaf Ebrahimi        if result.returncode != 0:
115*62c56f98SSadaf Ebrahimi            print_err("Uncrustify returned " + str(result.returncode) +
116*62c56f98SSadaf Ebrahimi                      " correcting file " + src_file)
117*62c56f98SSadaf Ebrahimi            return False
118*62c56f98SSadaf Ebrahimi
119*62c56f98SSadaf Ebrahimi        # Uncrustify makes changes to the code and places the result in a new
120*62c56f98SSadaf Ebrahimi        # file with the extension ".uncrustify". To get the changes (if any)
121*62c56f98SSadaf Ebrahimi        # simply diff the 2 files.
122*62c56f98SSadaf Ebrahimi        diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
123*62c56f98SSadaf Ebrahimi        cp = subprocess.run(diff_cmd, check=False)
124*62c56f98SSadaf Ebrahimi
125*62c56f98SSadaf Ebrahimi        if cp.returncode == 1:
126*62c56f98SSadaf Ebrahimi            print(src_file + " changed - code style is incorrect.")
127*62c56f98SSadaf Ebrahimi            style_correct = False
128*62c56f98SSadaf Ebrahimi        elif cp.returncode != 0:
129*62c56f98SSadaf Ebrahimi            raise subprocess.CalledProcessError(cp.returncode, cp.args,
130*62c56f98SSadaf Ebrahimi                                                cp.stdout, cp.stderr)
131*62c56f98SSadaf Ebrahimi
132*62c56f98SSadaf Ebrahimi        # Tidy up artifact
133*62c56f98SSadaf Ebrahimi        os.remove(src_file + ".uncrustify")
134*62c56f98SSadaf Ebrahimi
135*62c56f98SSadaf Ebrahimi    return style_correct
136*62c56f98SSadaf Ebrahimi
137*62c56f98SSadaf Ebrahimidef fix_style_single_pass(src_file_list: List[str]) -> bool:
138*62c56f98SSadaf Ebrahimi    """
139*62c56f98SSadaf Ebrahimi    Run Uncrustify once over the source files.
140*62c56f98SSadaf Ebrahimi    """
141*62c56f98SSadaf Ebrahimi    code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
142*62c56f98SSadaf Ebrahimi    for src_file in src_file_list:
143*62c56f98SSadaf Ebrahimi        uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
144*62c56f98SSadaf Ebrahimi        result = subprocess.run(uncrustify_cmd, check=False)
145*62c56f98SSadaf Ebrahimi        if result.returncode != 0:
146*62c56f98SSadaf Ebrahimi            print_err("Uncrustify with file returned: " +
147*62c56f98SSadaf Ebrahimi                      str(result.returncode) + " correcting file " +
148*62c56f98SSadaf Ebrahimi                      src_file)
149*62c56f98SSadaf Ebrahimi            return False
150*62c56f98SSadaf Ebrahimi    return True
151*62c56f98SSadaf Ebrahimi
152*62c56f98SSadaf Ebrahimidef fix_style(src_file_list: List[str]) -> int:
153*62c56f98SSadaf Ebrahimi    """
154*62c56f98SSadaf Ebrahimi    Fix the code style. This takes 2 passes of Uncrustify.
155*62c56f98SSadaf Ebrahimi    """
156*62c56f98SSadaf Ebrahimi    if not fix_style_single_pass(src_file_list):
157*62c56f98SSadaf Ebrahimi        return 1
158*62c56f98SSadaf Ebrahimi    if not fix_style_single_pass(src_file_list):
159*62c56f98SSadaf Ebrahimi        return 1
160*62c56f98SSadaf Ebrahimi
161*62c56f98SSadaf Ebrahimi    # Guard against future changes that cause the codebase to require
162*62c56f98SSadaf Ebrahimi    # more passes.
163*62c56f98SSadaf Ebrahimi    if not check_style_is_correct(src_file_list):
164*62c56f98SSadaf Ebrahimi        print_err("Code style still incorrect after second run of Uncrustify.")
165*62c56f98SSadaf Ebrahimi        return 1
166*62c56f98SSadaf Ebrahimi    else:
167*62c56f98SSadaf Ebrahimi        return 0
168*62c56f98SSadaf Ebrahimi
169*62c56f98SSadaf Ebrahimidef main() -> int:
170*62c56f98SSadaf Ebrahimi    """
171*62c56f98SSadaf Ebrahimi    Main with command line arguments.
172*62c56f98SSadaf Ebrahimi    """
173*62c56f98SSadaf Ebrahimi    uncrustify_version = get_uncrustify_version().strip()
174*62c56f98SSadaf Ebrahimi    if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
175*62c56f98SSadaf Ebrahimi        print("Warning: Using unsupported Uncrustify version '" +
176*62c56f98SSadaf Ebrahimi              uncrustify_version + "'")
177*62c56f98SSadaf Ebrahimi        print("Note: The only supported version is " +
178*62c56f98SSadaf Ebrahimi              UNCRUSTIFY_SUPPORTED_VERSION)
179*62c56f98SSadaf Ebrahimi
180*62c56f98SSadaf Ebrahimi    parser = argparse.ArgumentParser()
181*62c56f98SSadaf Ebrahimi    parser.add_argument('-f', '--fix', action='store_true',
182*62c56f98SSadaf Ebrahimi                        help=('modify source files to fix the code style '
183*62c56f98SSadaf Ebrahimi                              '(default: print diff, do not modify files)'))
184*62c56f98SSadaf Ebrahimi    parser.add_argument('-s', '--since', metavar='COMMIT', const='development', nargs='?',
185*62c56f98SSadaf Ebrahimi                        help=('only check files modified since the specified commit'
186*62c56f98SSadaf Ebrahimi                              ' (e.g. --since=HEAD~3 or --since=development). If no'
187*62c56f98SSadaf Ebrahimi                              ' commit is specified, default to development.'))
188*62c56f98SSadaf Ebrahimi    # --subset is almost useless: it only matters if there are no files
189*62c56f98SSadaf Ebrahimi    # ('code_style.py' without arguments checks all files known to Git,
190*62c56f98SSadaf Ebrahimi    # 'code_style.py --subset' does nothing). In particular,
191*62c56f98SSadaf Ebrahimi    # 'code_style.py --fix --subset ...' is intended as a stable ("porcelain")
192*62c56f98SSadaf Ebrahimi    # way to restyle a possibly empty set of files.
193*62c56f98SSadaf Ebrahimi    parser.add_argument('--subset', action='store_true',
194*62c56f98SSadaf Ebrahimi                        help='only check the specified files (default with non-option arguments)')
195*62c56f98SSadaf Ebrahimi    parser.add_argument('operands', nargs='*', metavar='FILE',
196*62c56f98SSadaf Ebrahimi                        help='files to check (files MUST be known to git, if none: check all)')
197*62c56f98SSadaf Ebrahimi
198*62c56f98SSadaf Ebrahimi    args = parser.parse_args()
199*62c56f98SSadaf Ebrahimi
200*62c56f98SSadaf Ebrahimi    covered = frozenset(get_src_files(args.since))
201*62c56f98SSadaf Ebrahimi    # We only check files that are known to git
202*62c56f98SSadaf Ebrahimi    if args.subset or args.operands:
203*62c56f98SSadaf Ebrahimi        src_files = [f for f in args.operands if f in covered]
204*62c56f98SSadaf Ebrahimi        skip_src_files = [f for f in args.operands if f not in covered]
205*62c56f98SSadaf Ebrahimi        if skip_src_files:
206*62c56f98SSadaf Ebrahimi            print_skip(skip_src_files)
207*62c56f98SSadaf Ebrahimi    else:
208*62c56f98SSadaf Ebrahimi        src_files = list(covered)
209*62c56f98SSadaf Ebrahimi
210*62c56f98SSadaf Ebrahimi    if args.fix:
211*62c56f98SSadaf Ebrahimi        # Fix mode
212*62c56f98SSadaf Ebrahimi        return fix_style(src_files)
213*62c56f98SSadaf Ebrahimi    else:
214*62c56f98SSadaf Ebrahimi        # Check mode
215*62c56f98SSadaf Ebrahimi        if check_style_is_correct(src_files):
216*62c56f98SSadaf Ebrahimi            print("Checked {} files, style ok.".format(len(src_files)))
217*62c56f98SSadaf Ebrahimi            return 0
218*62c56f98SSadaf Ebrahimi        else:
219*62c56f98SSadaf Ebrahimi            return 1
220*62c56f98SSadaf Ebrahimi
221*62c56f98SSadaf Ebrahimiif __name__ == '__main__':
222*62c56f98SSadaf Ebrahimi    sys.exit(main())
223