"""Generic code to generate, check and list the generated files """ # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later import argparse import filecmp import shutil import subprocess import sys from pathlib import Path from typing import List, Optional class GenerationScript: """ Representation of a script generating a configuration independent file. """ # pylint: disable=too-few-public-methods,too-many-arguments def __init__(self, script: Path, files: List[Path], output_dir_option: Optional[str] = None, output_file_option: Optional[str] = None, optional: bool = False) -> None: # Path from the root of Mbed TLS or TF-PSA-Crypto of the generation script self.script = script # Executable to run the script, needed for Windows if script.suffix == ".py": self.exe = sys.executable elif script.suffix == ".pl": self.exe = "perl" # List of the default paths from the Mbed TLS or TF-PSA-Crypto root of the # files the script generates. self.files = files # Output directory script argument. Can be an empty string in case it is a # positional argument. self.output_dir_option = output_dir_option # Output file script argument. Can be an empty string in case it is a # positional argument. self.output_file_option = output_file_option # Optional files are skipped in --check mode if they don't exist. # This normally shouldn't happen, but it can happen during transition # periods where we're adding a new script or a new file, and a # consuming repository hasn't been updated yet. self.optional = optional def get_generation_script_files(generation_script: str) -> List[Path]: """ Get the list of the default paths of the files that a given script generates. It is assumed that the script supports the "--list" option. """ files = [] if generation_script.endswith(".py"): cmd = [sys.executable] elif generation_script.endswith(".pl"): cmd = ["perl"] cmd += [generation_script, "--list"] output = subprocess.check_output(cmd, universal_newlines=True) for line in output.splitlines(): files.append(Path(line)) return files def get_generated_files(generation_scripts: List[GenerationScript]) -> List[Path]: """ List the generated files in Mbed TLS or TF-PSA-Crypto. The path from root is returned for each generated files. """ files = [] for generation_script in generation_scripts: files += generation_script.files return files def make_generated_files(generation_scripts: List[GenerationScript]) -> None: """ Generate the configuration independent files in their default location in the Mbed TLS or TF-PSA-Crypto tree. """ for generation_script in generation_scripts: subprocess.run([generation_script.exe, str(generation_script.script)], check=True) def check_generated_files(generation_scripts: List[GenerationScript], root: Path) -> bool: """ Check that the given root directory contains the generated files as expected/ generated by this script. """ ok = True for generation_script in generation_scripts: for file in generation_script.files: file = root / file if not file.exists(): # If the script is just being added, allow its files not # to exist. This can happen, at least, when adding a new # generation script in crypto: until mbedtls is updated, # the files from that script won't be present when # the updated crypto is built from mbedtls development. if generation_script.optional: continue raise Exception(f"Expected generated file does not exist: {file}") bak_file = file.with_name(file.name + ".bak") if bak_file.exists(): bak_file.unlink() file.rename(bak_file) command = [generation_script.exe, str(generation_script.script)] if generation_script.output_dir_option is not None: command += [generation_script.output_dir_option, str(root / Path(generation_script.files[0].parent))] elif generation_script.output_file_option is not None: command += generation_script.output_file_option.split() command += [str(root / Path(generation_script.files[0]))] subprocess.run([item for item in command if item.strip()], check=True) for file in generation_script.files: file = root / file bak_file = file.with_name(file.name + ".bak") if generation_script.optional and not bak_file.exists(): # This file is optional and didn't exist before, so # there's nothing to compare to, or clean up. continue if not filecmp.cmp(file, bak_file): ok = False ref_file = file.with_name(file.name + ".ref") ref_file = root / ref_file if ref_file.exists(): ref_file.unlink() shutil.copy(file, ref_file) print(f"Generated file {file} not identical to the reference one {ref_file}.") file.unlink() bak_file.rename(file) return ok def main(generation_scripts: List[GenerationScript]) -> int: """ Main function of this program """ parser = argparse.ArgumentParser() parser.add_argument('--list', action='store_true', default=False, help='List generated files.') parser.add_argument('--root', metavar='DIR', help='Root of the tree containing the generated files \ to check (default: Mbed TLS or TF-PSA-Cryto root.)') parser.add_argument('--check', action='store_true', default=False, help='Check the generated files in root') args = parser.parse_args() if args.list: files = get_generated_files(generation_scripts) for file in files: print(str(file)) return 0 elif args.check: ok = check_generated_files(generation_scripts, Path(args.root or ".")) return 0 if ok else 1 else: make_generated_files(generation_scripts) return 0 # Any error causes an exception