test_driver.py: Add support for the list-vars-for-cmake option

Signed-off-by: Ronald Cron <[email protected]>
This commit is contained in:
Ronald Cron
2025-12-09 01:04:25 +01:00
parent 9d7330c411
commit 5fecb0e0fa
+97 -21
View File
@@ -12,7 +12,7 @@ import subprocess
from fnmatch import fnmatch from fnmatch import fnmatch
from pathlib import Path from pathlib import Path
from typing import Iterable, Match, Optional, Set from typing import Iterable, List, Match, Optional, Set
def get_parsearg_base() -> argparse.ArgumentParser: def get_parsearg_base() -> argparse.ArgumentParser:
""" Get base arguments for scripts building a TF-PSA-Crypto test driver """ """ Get base arguments for scripts building a TF-PSA-Crypto test driver """
@@ -26,6 +26,11 @@ def get_parsearg_base() -> argparse.ArgumentParser:
" - If relative, interpreted relative to the repository root.\n") " - If relative, interpreted relative to the repository root.\n")
parser.add_argument("--driver", default="libtestdriver1", metavar="DRIVER", parser.add_argument("--driver", default="libtestdriver1", metavar="DRIVER",
help="Test driver name (default: %(default)s).") help="Test driver name (default: %(default)s).")
parser.add_argument('--list-vars-for-cmake', nargs="?", const="__AUTO__", metavar="FILE",
help="Generate a file to be included from a CMakeLists.txt.\n"
"The file defines CMake list variables with the script's\n"
"inputs/outputs files. If FILE is omitted, the output \n"
"name defaults to '<DRIVER>-list-vars.cmake'.")
return parser return parser
def iter_code_files(root: Path) -> Iterable[Path]: def iter_code_files(root: Path) -> Iterable[Path]:
@@ -38,6 +43,40 @@ def iter_code_files(root: Path) -> Iterable[Path]:
for ext in (".c", ".h"): for ext in (".c", ".h"):
yield from directory_path.rglob(f"*{ext}") yield from directory_path.rglob(f"*{ext}")
def get_src_relpaths(builtin: Path, exclude_files: Set[str]) -> List[Path]:
"""
Return the relative paths of all *.c and *.h files under `builtin`,
excluding those whose names match any of the patterns in `exclude_files`.
The returned paths are relative to `builtin`.
"""
out = []
for file in iter_code_files(builtin):
if not any(fnmatch(file.name, pattern) for pattern in exclude_files):
out.append(file.relative_to(builtin))
out.sort()
return out
def get_dst_relpaths(src_relpaths: List[Path], driver: str) -> List[Path]:
"""
Return the relative paths of the *.c and *.h files generated by the script.
These paths are the same as in `src_relpaths`, except that occurrences of
`mbedtls` in `include/mbedtls/...` paths are replaced with `driver`.
The returned paths are relative to `dst_dir`.
"""
out = []
for path in src_relpaths:
parts = list(path.parts)
if parts[0] == "include" and parts[1] == "mbedtls":
parts[1] = driver
out.append(Path(*parts))
return out
def run_ctags(file: Path) -> Set[str]: def run_ctags(file: Path) -> Set[str]:
""" """
Extract the C identifiers in `file` using ctags. Extract the C identifiers in `file` using ctags.
@@ -71,20 +110,65 @@ def run_ctags(file: Path) -> Set[str]:
class TestDriverGenerator: class TestDriverGenerator:
"""A TF-PSA-Crypto test driver generator""" """A TF-PSA-Crypto test driver generator"""
def __init__(self, dst_dir: Path, driver: str): def __init__(self, src_dir: Path, dst_dir: Path, driver: str, \
exclude_files: Optional[Set[str]] = None) -> None:
"""
Initialize a test driver generator.
Args:
src_dir (Path):
Path to the source directory that contains the built-in driver.
If this path is relative, it should be relative to the repository
root so that the source paths returned by `write_list_vars_for_cmake`
are correct.
The source directory is expected to contain:
- an `include` directory
- an `src` directory
- the `include/` directory contains exactly one subdirectory
dst_dir (Path):
Path to the destination directory where the rewritten tree will
be created.
driver (str):
Name of the driver. This is used as a prefix when rewritting
the tree.
exclude_files (Optional[Set[str]]):
Glob patterns for the basename of the files to be excluded from
the source directory.
"""
self.src_dir = src_dir
self.dst_dir = dst_dir self.dst_dir = dst_dir
self.driver = driver self.driver = driver
self.exclude_files = set()
if exclude_files is not None:
self.exclude_files = exclude_files
# Path of 'dst_dir'/include/'driver' # Path of 'dst_dir'/include/'driver'
self.test_driver_include_dir = None #type: Path | None self.test_driver_include_dir = None #type: Path | None
def build_tree(self, src_dir: Path, exclude_files: Optional[Set[str]] = None) -> None: if not (src_dir / "include").is_dir():
""" raise RuntimeError(f'"include" directory in {src_dir} not found')
Build a test driver tree from `src_dir`.
The source directory `src_dir` is expected to have the following structure: if not (src_dir / "src").is_dir():
- an `include` directory raise RuntimeError(f'"src" directory in {src_dir} not found')
- an `src` directory
- the `include` directory contains exactly one subdirectory def write_list_vars_for_cmake(self, fname: str) -> None:
src_relpaths = get_src_relpaths(self.src_dir, self.exclude_files)
with open(self.dst_dir / fname, "w") as f:
f.write(f"set({self.driver}_input_files " + \
" ".join(str(path) for path in src_relpaths) + ")\n\n")
f.write(f"set({self.driver}_files " + \
" ".join(str(path) \
for path in get_dst_relpaths(src_relpaths, self.driver)) + ")\n\n")
f.write(f"set({self.driver}_src_files " + \
" ".join(str(path) \
for path in src_relpaths if path.suffix == ".c") + ")")
def build_tree(self) -> None:
"""
Build a test driver tree from `self.src_dir`.
Only the `include` and `src` directories from `src_dir` are used to build Only the `include` and `src` directories from `src_dir` are used to build
the test driver tree, and their directory structure is preserved. the test driver tree, and their directory structure is preserved.
@@ -95,14 +179,9 @@ class TestDriverGenerator:
The subdirectory inside `include` is renamed to `driver` in the test driver The subdirectory inside `include` is renamed to `driver` in the test driver
tree, and header inclusions are adjusted accordingly. tree, and header inclusions are adjusted accordingly.
""" """
include = src_dir / "include"
if not include.is_dir():
raise RuntimeError(f'Do not find "include" directory in {src_dir}')
src = src_dir / "src"
if not src.is_dir():
raise RuntimeError(f'Do not find "src" directory in {src_dir}')
include = self.src_dir / "include"
entries = list(include.iterdir()) entries = list(include.iterdir())
if len(entries) != 1 or not entries[0].is_dir(): if len(entries) != 1 or not entries[0].is_dir():
raise RuntimeError(f"Found more than one directory in {include}") raise RuntimeError(f"Found more than one directory in {include}")
@@ -115,13 +194,10 @@ class TestDriverGenerator:
if (self.dst_dir / "src").exists(): if (self.dst_dir / "src").exists():
shutil.rmtree(self.dst_dir / "src") shutil.rmtree(self.dst_dir / "src")
if exclude_files is None: for file in iter_code_files(self.src_dir):
exclude_files = set() if any(fnmatch(file.name, pattern) for pattern in self.exclude_files):
for file in iter_code_files(src_dir):
if any(fnmatch(file.name, pattern) for pattern in exclude_files):
continue continue
dst = self.dst_dir / file.relative_to(src_dir) dst = self.dst_dir / file.relative_to(self.src_dir)
dst.parent.mkdir(parents=True, exist_ok=True) dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(file, dst) shutil.copy2(file, dst)