olindgre 2ece01e1d7 Make powertrain-build not overlap with pybuild in site-packages
Change-Id: I7b59f3f04f0f787d35db0b9389f295bf1ad24f56
2024-09-17 10:25:04 +02:00

174 lines
5.8 KiB
Python

# Copyright 2024 Volvo Car Corporation
# Licensed under Apache 2.0.
"""Module that handles the update of call sources."""
import argparse
import re
from ruamel.yaml import YAML
from pathlib import Path
def parse_args():
""" Parse arguments
Returns:
Namespace: the parsed arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument("interface", help="Interface specification dict", type=Path)
parser.add_argument("src_dir", help="Path to source file directory", type=Path)
parser.add_argument(
"-p",
"--project-config",
type=Path,
default=None,
help="Path to project config json file",
)
return parser.parse_args()
def main():
""" Main function for stand alone execution."""
args = parse_args()
method_config = read_project_config(args.project_config)
with open(args.interface, encoding="utf-8") as interface_file:
yaml = YAML(typ='safe', pure=True)
adapter_spec = yaml.load(interface_file)
update_call_sources(args.src_dir, adapter_spec, method_config)
def read_project_config(project_config_path):
""" Reads project config file and extract method specific settings if they are present.
Args:
project_config_path (Path): path to the ProjectCfg.json file
Returns:
method_config (dict): dictionary of method related configs.
"""
project_info = {}
if project_config_path is not None:
with project_config_path.open() as config_file:
yaml = YAML(typ='safe', pure=True)
config = yaml.load(config_file)
project_info = config["ProjectInfo"]
method_config = {
"adapter_declarations_change": project_info.get("adapterDeclarations", None)
}
method_config["method_call_wrapper"] = {
"pre": project_info.get("methodPreCallWrapper", ""),
"post": project_info.get("methodPostCallWrapper", "")
}
for key, value in method_config["method_call_wrapper"].items():
if value != "":
method_config["method_call_wrapper"][key] = value + "\n"
return method_config
def is_method_adapter(adapter):
""" Check if adapter has methods in it
Args:
adapter (dict): adapter specification
Returns:
methods_in_adapter (bool): true if adapter contains methods,
false otherwise
"""
methods_in_adapter = "methods" in adapter and len(adapter["methods"]) > 0
return methods_in_adapter
def update_call_sources(src_dir, adapter_spec, method_config):
""" Update the source files for specified method calls with
adapter function calls that trigger the methods.
Args:
src_dir (Path): path to folder for method call sources
adapter_spec (list): adapter specifications with methods
method_config (dict): project specific method configs
"""
method_adapters = [a for a in adapter_spec if is_method_adapter(a)]
for adapter in method_adapters:
for method in adapter["methods"]:
method_src = src_dir / (method["name"] + ".c")
with method_src.open("r+") as src_file:
old_src = src_file.read()
new_src = generate_src_code(adapter, method, old_src, method_config)
method_src.unlink()
with open(method_src.with_suffix(".cpp"), "w", encoding="utf-8") as dst_file:
dst_file.write(new_src)
method_header = src_dir / (method["name"] + ".h")
with method_header.open("r+") as header_file:
old_header = header_file.read()
new_header = generate_header_code(method_config, old_header)
header_file.seek(0)
header_file.write(new_header)
header_file.truncate()
def generate_header_code(method_config, old_header):
""" Change header code to include project specific adapter wrapper.
Args:
method_config (dict): project specific method settings
old_header (string): header source code
Returns:
new_header (string): modified header source code
"""
adapter_pattern = r'(?<=#include ")adapter_wrapper.hh'
if method_config["adapter_declarations_change"]:
new_header = re.sub(
adapter_pattern, method_config["adapter_declarations_change"], old_header
)
return new_header
return old_header
def generate_src_code(adapter, method, old_src, method_config):
""" Generate the method call source code to trigger the method call
Args:
adapter (dict): adapter specification
method (dict): method specification
old_src (string): method call source code with mock call
method_config (dict): project specific method settings
Returns:
new_src (string): method call source code with adapter function call
that triggers method
"""
call_pattern = (
r" \/\* CSP method call\n"
r"(.*)ADAPTER->METHOD(.*)"
r" CSP method call end\*\/\n"
)
dummy_pattern = (
r" \/\* C dummy call\*\/" r".*" r" \/\* C dummy call end\*\/\n"
)
comment = (
r"\/\* Used for running spm without csp, such as silver\n"
r" This code should be replaced when using csp. \*\/\n"
)
name = method["name"]
adapter_name = [adapter["name"].title()]
namespace = []
if method["namespace"] != "":
namespace = [method["namespace"]]
adapter = "::".join(namespace + adapter_name) + "->" + name
pre_call = method_config["method_call_wrapper"]["pre"]
post_call = method_config["method_call_wrapper"]["post"]
sans_comment = re.sub(comment, "", old_src)
sans_dummy_call = re.sub(dummy_pattern, "", sans_comment, flags=re.DOTALL)
new_src = re.sub(
call_pattern, pre_call + r"\1" + adapter + r"\2" + post_call, sans_dummy_call, flags=re.DOTALL
)
return new_src
if __name__ == "__main__":
main()