mirror of
				https://github.com/Mbed-TLS/mbedtls.git
				synced 2025-10-31 21:50:31 +03:00 
			
		
		
		
	This commit improves support for submodules within code_style.py. A new function get_submodule_hash retrieves the submodule hash at the time of a commit allowing an appropriate diff to be seen. Signed-off-by: Harry Ramsey <harry.ramsey@arm.com>
		
			
				
	
	
		
			280 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| """Check or fix the code style by running Uncrustify.
 | |
| 
 | |
| This script must be run from the root of a Git work tree containing Mbed TLS.
 | |
| """
 | |
| # Copyright The Mbed TLS Contributors
 | |
| # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 | |
| import argparse
 | |
| import os
 | |
| import re
 | |
| import subprocess
 | |
| import sys
 | |
| from typing import FrozenSet, List, Optional
 | |
| 
 | |
| UNCRUSTIFY_SUPPORTED_VERSION = "0.75.1"
 | |
| CONFIG_FILE = ".uncrustify.cfg"
 | |
| UNCRUSTIFY_EXE = "uncrustify"
 | |
| UNCRUSTIFY_ARGS = ["-c", CONFIG_FILE]
 | |
| CHECK_GENERATED_FILES = "tests/scripts/check-generated-files.sh"
 | |
| 
 | |
| def print_err(*args):
 | |
|     print("Error: ", *args, file=sys.stderr)
 | |
| 
 | |
| # Print the file names that will be skipped and the help message
 | |
| def print_skip(files_to_skip):
 | |
|     print()
 | |
|     print(*files_to_skip, sep=", SKIP\n", end=", SKIP\n")
 | |
|     print("Warning: The listed files will be skipped because\n"
 | |
|           "they are not known to git.")
 | |
|     print()
 | |
| 
 | |
| # Match FILENAME(s) in "check SCRIPT (FILENAME...)"
 | |
| CHECK_CALL_RE = re.compile(r"\n\s*check\s+[^\s#$&*?;|]+([^\n#$&*?;|]+)",
 | |
|                            re.ASCII)
 | |
| def list_generated_files() -> FrozenSet[str]:
 | |
|     """Return the names of generated files.
 | |
| 
 | |
|     We don't reformat generated files, since the result might be different
 | |
|     from the output of the generator. Ideally the result of the generator
 | |
|     would conform to the code style, but this would be difficult, especially
 | |
|     with respect to the placement of line breaks in long logical lines.
 | |
|     """
 | |
|     # Parse check-generated-files.sh to get an up-to-date list of
 | |
|     # generated files. Read the file rather than calling it so that
 | |
|     # this script only depends on Git, Python and uncrustify, and not other
 | |
|     # tools such as sh or grep which might not be available on Windows.
 | |
|     # This introduces a limitation: check-generated-files.sh must have
 | |
|     # the expected format and must list the files explicitly, not through
 | |
|     # wildcards or command substitution.
 | |
|     content = open(CHECK_GENERATED_FILES, encoding="utf-8").read()
 | |
|     checks = re.findall(CHECK_CALL_RE, content)
 | |
|     return frozenset(word for s in checks for word in s.split())
 | |
| 
 | |
| # Check for comment string indicating an auto-generated file
 | |
| AUTOGEN_RE = re.compile(r"Warning[ :-]+This file is (now )?auto[ -]?generated",
 | |
|                         re.ASCII | re.IGNORECASE)
 | |
| def is_file_autogenerated(filename):
 | |
|     content = open(filename, encoding="utf-8").read()
 | |
|     return AUTOGEN_RE.search(content) is not None
 | |
| 
 | |
| def get_src_files(since: Optional[str]) -> List[str]:
 | |
|     """
 | |
|     Use git to get a list of the source files.
 | |
| 
 | |
|     The optional argument since is a commit, indicating to only list files
 | |
|     that have changed since that commit. Without this argument, list all
 | |
|     files known to git.
 | |
| 
 | |
|     Only C files are included, and certain files (generated, or third party)
 | |
|     are excluded.
 | |
|     """
 | |
|     file_patterns = ["*.[hc]",
 | |
|                      "tests/suites/*.function",
 | |
|                      "tf-psa-crypto/tests/suites/*.function",
 | |
|                      "scripts/data_files/*.fmt"]
 | |
|     output = subprocess.check_output(["git", "ls-files"] + file_patterns,
 | |
|                                      universal_newlines=True)
 | |
|     src_files = output.split()
 | |
| 
 | |
|     # When this script is called from a git hook, some environment variables
 | |
|     # are set by default which force all git commands to use the main repository
 | |
|     # (i.e. prevent us from performing commands on the framework repo).
 | |
|     # Create an environment without these variables for running commands on the
 | |
|     # framework repo.
 | |
|     framework_env = os.environ.copy()
 | |
|     # Get a list of environment vars that git sets
 | |
|     git_env_vars = subprocess.check_output(["git", "rev-parse", "--local-env-vars"],
 | |
|                                            universal_newlines=True)
 | |
|     # Remove the vars from the environment
 | |
|     for var in git_env_vars.split():
 | |
|         framework_env.pop(var, None)
 | |
| 
 | |
|     output = subprocess.check_output(["git", "-C", "framework", "ls-files"]
 | |
|                                      + file_patterns,
 | |
|                                      universal_newlines=True,
 | |
|                                      env=framework_env)
 | |
|     framework_src_files = output.split()
 | |
| 
 | |
|     if since:
 | |
|         # get all files changed in commits since the starting point in ...
 | |
|         # ... the main repository
 | |
|         cmd = ["git", "log", since + "..HEAD", "--ignore-submodules",
 | |
|                "--name-only", "--pretty=", "--"] + src_files
 | |
|         output = subprocess.check_output(cmd, universal_newlines=True)
 | |
|         committed_changed_files = output.split()
 | |
| 
 | |
|         # ... the framework submodule
 | |
|         framework_since = get_submodule_hash(since, "framework")
 | |
|         cmd = ["git", "-C", "framework", "log", framework_since + "..HEAD",
 | |
|                "--name-only", "--pretty=", "--"] + framework_src_files
 | |
|         output = subprocess.check_output(cmd, universal_newlines=True,
 | |
|                                          env=framework_env)
 | |
|         committed_changed_files += ["framework/" + s for s in output.split()]
 | |
| 
 | |
|         # and also get all files with uncommitted changes in ...
 | |
|         # ... the main repository
 | |
|         cmd = ["git", "diff", "--name-only", "--"] + src_files
 | |
|         output = subprocess.check_output(cmd, universal_newlines=True)
 | |
|         uncommitted_changed_files = output.split()
 | |
|         # ... the framework submodule
 | |
|         cmd = ["git", "-C", "framework", "diff", "--name-only", "--"] + \
 | |
|               framework_src_files
 | |
|         output = subprocess.check_output(cmd, universal_newlines=True,
 | |
|                                          env=framework_env)
 | |
|         uncommitted_changed_files += ["framework/" + s for s in output.split()]
 | |
| 
 | |
|         src_files = committed_changed_files + uncommitted_changed_files
 | |
|     else:
 | |
|         src_files += ["framework/" + s for s in framework_src_files]
 | |
| 
 | |
|     generated_files = list_generated_files()
 | |
|     # Don't correct style for third-party files (and, for simplicity,
 | |
|     # companion files in the same subtree), or for automatically
 | |
|     # generated files (we're correcting the templates instead).
 | |
|     src_files = [filename for filename in src_files
 | |
|                  if not (filename.startswith("tf-psa-crypto/drivers/everest/") or
 | |
|                          filename.startswith("tf-psa-crypto/drivers/p256-m/") or
 | |
|                          filename in generated_files or
 | |
|                          is_file_autogenerated(filename))]
 | |
|     return src_files
 | |
| 
 | |
| def get_submodule_hash(commit: str, submodule: str) -> str:
 | |
|     """Get the commit hash of a submodule at a given commit in the Git repository."""
 | |
|     cmd = ["git", "ls-tree", commit, submodule]
 | |
|     output = subprocess.check_output(cmd, universal_newlines=True)
 | |
|     return output.split()[2]
 | |
| 
 | |
| def get_uncrustify_version() -> str:
 | |
|     """
 | |
|     Get the version string from Uncrustify
 | |
|     """
 | |
|     result = subprocess.run([UNCRUSTIFY_EXE, "--version"],
 | |
|                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
 | |
|                             check=False)
 | |
|     if result.returncode != 0:
 | |
|         print_err("Could not get Uncrustify version:", str(result.stderr, "utf-8"))
 | |
|         return ""
 | |
|     else:
 | |
|         return str(result.stdout, "utf-8")
 | |
| 
 | |
| def check_style_is_correct(src_file_list: List[str]) -> bool:
 | |
|     """
 | |
|     Check the code style and output a diff for each file whose style is
 | |
|     incorrect.
 | |
|     """
 | |
|     style_correct = True
 | |
|     for src_file in src_file_list:
 | |
|         uncrustify_cmd = [UNCRUSTIFY_EXE] + UNCRUSTIFY_ARGS + [src_file]
 | |
|         result = subprocess.run(uncrustify_cmd, stdout=subprocess.PIPE,
 | |
|                                 stderr=subprocess.PIPE, check=False)
 | |
|         if result.returncode != 0:
 | |
|             print_err("Uncrustify returned " + str(result.returncode) +
 | |
|                       " correcting file " + src_file)
 | |
|             return False
 | |
| 
 | |
|         # Uncrustify makes changes to the code and places the result in a new
 | |
|         # file with the extension ".uncrustify". To get the changes (if any)
 | |
|         # simply diff the 2 files.
 | |
|         diff_cmd = ["diff", "-u", src_file, src_file + ".uncrustify"]
 | |
|         cp = subprocess.run(diff_cmd, check=False)
 | |
| 
 | |
|         if cp.returncode == 1:
 | |
|             print(src_file + " changed - code style is incorrect.")
 | |
|             style_correct = False
 | |
|         elif cp.returncode != 0:
 | |
|             raise subprocess.CalledProcessError(cp.returncode, cp.args,
 | |
|                                                 cp.stdout, cp.stderr)
 | |
| 
 | |
|         # Tidy up artifact
 | |
|         os.remove(src_file + ".uncrustify")
 | |
| 
 | |
|     return style_correct
 | |
| 
 | |
| def fix_style_single_pass(src_file_list: List[str]) -> bool:
 | |
|     """
 | |
|     Run Uncrustify once over the source files.
 | |
|     """
 | |
|     code_change_args = UNCRUSTIFY_ARGS + ["--no-backup"]
 | |
|     for src_file in src_file_list:
 | |
|         uncrustify_cmd = [UNCRUSTIFY_EXE] + code_change_args + [src_file]
 | |
|         result = subprocess.run(uncrustify_cmd, check=False)
 | |
|         if result.returncode != 0:
 | |
|             print_err("Uncrustify with file returned: " +
 | |
|                       str(result.returncode) + " correcting file " +
 | |
|                       src_file)
 | |
|             return False
 | |
|     return True
 | |
| 
 | |
| def fix_style(src_file_list: List[str]) -> int:
 | |
|     """
 | |
|     Fix the code style. This takes 2 passes of Uncrustify.
 | |
|     """
 | |
|     if not fix_style_single_pass(src_file_list):
 | |
|         return 1
 | |
|     if not fix_style_single_pass(src_file_list):
 | |
|         return 1
 | |
| 
 | |
|     # Guard against future changes that cause the codebase to require
 | |
|     # more passes.
 | |
|     if not check_style_is_correct(src_file_list):
 | |
|         print_err("Code style still incorrect after second run of Uncrustify.")
 | |
|         return 1
 | |
|     else:
 | |
|         return 0
 | |
| 
 | |
| def main() -> int:
 | |
|     """
 | |
|     Main with command line arguments.
 | |
|     """
 | |
|     uncrustify_version = get_uncrustify_version().strip()
 | |
|     if UNCRUSTIFY_SUPPORTED_VERSION not in uncrustify_version:
 | |
|         print("Warning: Using unsupported Uncrustify version '" +
 | |
|               uncrustify_version + "'")
 | |
|         print("Note: The only supported version is " +
 | |
|               UNCRUSTIFY_SUPPORTED_VERSION)
 | |
| 
 | |
|     parser = argparse.ArgumentParser()
 | |
|     parser.add_argument('-f', '--fix', action='store_true',
 | |
|                         help=('modify source files to fix the code style '
 | |
|                               '(default: print diff, do not modify files)'))
 | |
|     parser.add_argument('-s', '--since', metavar='COMMIT', const='development', nargs='?',
 | |
|                         help=('only check files modified since the specified commit'
 | |
|                               ' (e.g. --since=HEAD~3 or --since=development). If no'
 | |
|                               ' commit is specified, default to development.'))
 | |
|     # --subset is almost useless: it only matters if there are no files
 | |
|     # ('code_style.py' without arguments checks all files known to Git,
 | |
|     # 'code_style.py --subset' does nothing). In particular,
 | |
|     # 'code_style.py --fix --subset ...' is intended as a stable ("porcelain")
 | |
|     # way to restyle a possibly empty set of files.
 | |
|     parser.add_argument('--subset', action='store_true',
 | |
|                         help='only check the specified files (default with non-option arguments)')
 | |
|     parser.add_argument('operands', nargs='*', metavar='FILE',
 | |
|                         help='files to check (files MUST be known to git, if none: check all)')
 | |
| 
 | |
|     args = parser.parse_args()
 | |
| 
 | |
|     covered = frozenset(get_src_files(args.since))
 | |
|     # We only check files that are known to git
 | |
|     if args.subset or args.operands:
 | |
|         src_files = [f for f in args.operands if f in covered]
 | |
|         skip_src_files = [f for f in args.operands if f not in covered]
 | |
|         if skip_src_files:
 | |
|             print_skip(skip_src_files)
 | |
|     else:
 | |
|         src_files = list(covered)
 | |
| 
 | |
|     if args.fix:
 | |
|         # Fix mode
 | |
|         return fix_style(src_files)
 | |
|     else:
 | |
|         # Check mode
 | |
|         if check_style_is_correct(src_files):
 | |
|             print("Checked {} files, style ok.".format(len(src_files)))
 | |
|             return 0
 | |
|         else:
 | |
|             return 1
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     sys.exit(main())
 |