Source code for hlsfactory.define_frontend
# create a fornt end that uses the C++ preprocesser where a user passes in a list of stes fo define statenebts and that i used to paramaterse the diisgn using a C++ preprocessor
import hashlib
import shutil
import subprocess
import tempfile
import time
from pathlib import Path
from hlsfactory.framework import EXTENSIONS_CPP, Design, Frontend
from hlsfactory.utils import (
ExecutionDataStatus,
update_execution_data_with_flow_results,
)
[docs]
class CPPPreprocessorFrontend(Frontend):
name = "CPPPreprocessorFrontend"
def __init__(
self,
work_dir: Path,
define_statements: list[dict[str, str]],
log_execution_time: bool = True,
) -> None:
self.work_dir: Path = work_dir
self.define_statements: list[dict[str, str]] = define_statements
self.log_execution_time: bool = log_execution_time
[docs]
def execute(self, design: Design, timeout: float | None = None) -> list[Design]:
t_0 = time.perf_counter()
new_designs = []
for define_statement in self.define_statements:
# create a stable hash for this configuration
config_hash = hashlib.md5(str(define_statement).encode()).hexdigest()
new_design = design.copy_and_rename_to_new_parent_dir(
f"{design.name}__cpp_preprocessor_{config_hash}",
design.dir.parent,
)
new_designs.append(new_design)
# Process the design files with the C++ preprocessor
# build -D arguments from the define statement (dict)
defines_args: list[str] = []
for key, val in define_statement.items():
if val is None:
defines_args.append(f"-D{key}")
elif isinstance(val, bool):
if val:
defines_args.append(f"-D{key}")
else:
# convert non-bool values to strings
defines_args.append(f"-D{key}={val}")
# find C/C++ source files and run the preprocessor in-place
for src in sorted(new_design.dir.rglob("*")):
if not src.is_file():
continue
if src.suffix.lower() not in EXTENSIONS_CPP:
continue
# run g++ preprocessor and replace the original file with preprocessed output
with tempfile.NamedTemporaryFile(delete=False) as tmpf:
tmp_path = Path(tmpf.name)
cmd = [
"g++",
"-E", # preprocess only
"-P", # inhibit linemarkers (optional)
"-x",
"c++",
str(src),
"-o",
str(tmp_path),
]
# insert defines right after flags (safe)
cmd[1:1] = defines_args
try:
subprocess.run(
cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
# preserve file permissions
shutil.copymode(src, tmp_path)
shutil.move(str(tmp_path), str(src))
except subprocess.CalledProcessError as e:
# cleanup and raise a helpful error
if tmp_path.exists():
tmp_path.unlink()
raise RuntimeError(
f"Preprocessing failed for {src} with defines={define_statement}: "
f"{e.stderr.decode(errors='ignore')}"
) from e
t_1 = time.perf_counter()
update_execution_data_with_flow_results(
new_design.dir, self.name, ExecutionDataStatus.SUCCESS, t_0, t_1
)
update_execution_data_with_flow_results(
design.dir, self.name, ExecutionDataStatus.SUCCESS, t_0, t_1
)
return new_designs