mirror of
https://github.com/Mbed-TLS/mbedtls-framework.git
synced 2026-06-05 21:15:09 +00:00
d85217d8f5
Support check and always-update mode. Update-if-needed mode falls back to always-update mode because test_data_generation.py doesn't support update-if-needed. Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
183 lines
6.8 KiB
Python
183 lines
6.8 KiB
Python
"""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 os
|
|
import subprocess
|
|
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
|
|
|
|
|
|
class TestDataGenerator(Generator):
|
|
"""A test data generator script.
|
|
|
|
Even though the test data generator scripts are written in Python, we
|
|
run them as a separate process, because their output depends on the
|
|
program name (they write sys.argv[0] in a comment in the .data file).
|
|
"""
|
|
|
|
def __init__(self, script: str) -> None:
|
|
"""Run the specified test generator to generate files.
|
|
|
|
Assume that the script is written in Python and has the command line
|
|
interface of test_data_generation.py.
|
|
"""
|
|
self.script = script
|
|
|
|
def generator_name(self) -> str:
|
|
return os.path.basename(self.script)
|
|
|
|
def target_files(self) -> List[str]:
|
|
output = subprocess.check_output([sys.executable, self.script, '--list'],
|
|
encoding='utf-8')
|
|
return output.splitlines()
|
|
|
|
def outdated_files(self) -> List[str]:
|
|
output = subprocess.check_output([sys.executable, self.script, '--list-outdated'],
|
|
encoding='utf-8')
|
|
return output.splitlines()
|
|
|
|
def update(self, _always) -> None:
|
|
subprocess.check_call([sys.executable, self.script])
|
|
|
|
|
|
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)
|