New utility Python module: generate_files_helper A new utility to give a common interface to committed generated files. For the time being, this module is only intended for committed generated files, and `make_generated_files_common.py` is only intended for non-committed generated files. The two may be unified at some point in the future. Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/scripts/mbedtls_framework/config_macros.py b/scripts/mbedtls_framework/config_macros.py index a30fb8a..0fe1ac7 100644 --- a/scripts/mbedtls_framework/config_macros.py +++ b/scripts/mbedtls_framework/config_macros.py
@@ -6,9 +6,10 @@ import glob import os import re -from typing import FrozenSet, Iterable, Iterator +from typing import FrozenSet, Iterable, Iterator, List from . import build_tree +from . import generate_files_helper class ConfigMacros: @@ -34,7 +35,7 @@ for line in input_) -class Current(ConfigMacros): +class Current(ConfigMacros, generate_files_helper.Generator): """Information about config-like macros parsed from the source code.""" _SHADOW_FILE = 'scripts/data_files/config-options-current.txt' @@ -136,6 +137,25 @@ for name in sorted(self.live_config_options()): out.write(name + '\n') + # Implement the generate_files_helper.Generator interface + def generator_name(self) -> str: + """Name as a generate_files_helper.Generator.""" + return 'options' + + def target_files(self) -> List[str]: + """List the (single) generated file name.""" + return [os.path.join(self._submodule, self._SHADOW_FILE)] + + def outdated_files(self) -> List[str]: + """List the (single) generated file name if it is out of date.""" + if self.is_shadow_file_up_to_date(): + return [] + else: + return self.target_files() + + def update(self, always: bool) -> None: + """Update the shadow file from the live config file.""" + self.update_shadow_file(always) class History(ConfigMacros):
diff --git a/scripts/mbedtls_framework/generate_files_helper.py b/scripts/mbedtls_framework/generate_files_helper.py new file mode 100644 index 0000000..a4ec4a5 --- /dev/null +++ b/scripts/mbedtls_framework/generate_files_helper.py
@@ -0,0 +1,147 @@ +"""Utilities for intermediate files that are generated, but platform-independent +and configuration-independent. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import sys +from typing import Dict, Iterable, List, Sequence, Set + + +class Generator: + """An abstract base class for generators of intermediate files.""" + + def generator_name(self) -> str: + """A name for this generator. + + Generator names must be unique and should not be identical to + the name of any target. + """ + raise NotImplementedError + + def target_files(self) -> List[str]: + """The list of files targeted by this generator. + + File names are relative to the project root. + """ + raise NotImplementedError + + def outdated_files(self) -> Iterable[str]: + """Return the list of targets that are out of date. + + This is empty after running update(). + Missing targets are considered out of date. + """ + raise NotImplementedError + + def update(self, always: bool) -> None: + """Update the target(s) of this generator. + + If always is false, avoid changing the output file if it already has + the desired content. If always is true, make sure to update the + time stamp on the output file even if it already has the desired content. + """ + raise NotImplementedError + + +def assemble(available: Iterable[Generator]) -> Dict[str, Generator]: + """Assemble the generators into a dictionary with both names and targets as keys.""" + by_ident = {} #type: Dict[str, Generator] + for generator in available: + ident = generator.generator_name() + if ident in by_ident: + raise Exception(f'Generator conflict: name "{ident}" of {generator} ' + f'already recorded for {by_ident[ident]}') + by_ident[ident] = generator + for ident in generator.target_files(): + if ident in by_ident: + raise Exception(f'Generator conflict: target "{ident}" of {generator} ' + f'already recorded for {by_ident[ident]}') + by_ident[ident] = generator + return by_ident + +def list_names(available: Iterable[Generator]) -> List[str]: + """Return the list of generator names.""" + return sorted(generator.generator_name() for generator in available) + +def list_targets(available: Iterable[Generator]) -> List[str]: + """Return the list of generator targets.""" + return sorted(target + for generator in available + for target in generator.target_files()) + +def select(available: Dict[str, Generator], + wanted: Iterable[str]) -> List[Generator]: + """Select generators by name or target.""" + wanted_names = set() #type: Set[str] + for ident in wanted: + if ident not in available: + raise Exception(f'No generator found for {ident}') + wanted_names.add(ident) + return [available[name] for name in sorted(wanted_names)] + +def main(generators: Sequence[Generator], + description: str) -> None: + #pylint: disable=too-many-branches + """Command line entry point. + """ + parser = argparse.ArgumentParser(description=description) + parser.add_argument('--always-update', '-U', + action='store_true', + help=('Update target files unconditionally ' + '(overrides --update)')) + parser.add_argument('--list', + action='store_true', + help='List generator names and targets and exit') + parser.add_argument('--list-names', + action='store_true', + help='List generator names and exit') + parser.add_argument('--list-targets', + action='store_true', + help='List generator targets and exit') + parser.add_argument('--update', '-u', + action='store_true', + help='Update target files if needed') + parser.add_argument('--verbose', '-v', + action='store_true', + help='Be more verbose') + parser.add_argument('idents', nargs='*', metavar='NAME|TARGET', + help='List of generator names or targets (all targets if empty)') + args = parser.parse_args() + + if args.list: + args.list_names = True + args.list_targets = True + if args.list_names: + for name in list_names(generators): + print(name) + if args.list_targets: + for target in list_targets(generators): + print(target) + if args.list_names or args.list_targets: + return + + if args.idents: + available = assemble(generators) + wanted = select(available, args.idents) #type: Sequence[Generator] + else: + wanted = generators + if args.update or args.always_update: + for generator in wanted: + if args.verbose: + sys.stderr.write(f'Running generator {generator.generator_name()}...\n') + generator.update(args.always_update) + else: + outdated = [] #type: List[str] + for generator in wanted: + if args.verbose: + sys.stderr.write(f'Checking targets of generator {generator.generator_name()}...\n') + outdated += generator.outdated_files() + if outdated: + sys.stderr.write(f'Some targets are missing or out of date.\n') + for target in outdated: + print(target) + sys.stderr.write(f'Run {sys.argv[0]} -u and commit the result.') + sys.exit(1)