Added powertrain-build entrypoint

The entrypoint will make it easier to install powertrain-build to an
isolated venv without having to call python -m powertrain_build.

Change-Id: I3850c97d17707f9bc03640bd1d997508637d97ba
This commit is contained in:
Axel Andersson 2024-10-17 10:52:44 +02:00
parent ec77977757
commit 816553f1bc
24 changed files with 613 additions and 198 deletions

@ -34,6 +34,35 @@ In git bash:
py -3.6 -m powertrain_build.wrapper --codegen --models Models/ICEAES/VcAesTx/VcAesTx.mdl
```
#### CLI Entrypoint
If the Python `bin`/`Scripts` folder has been added to the `PATH`, you can also use
`powertrain-build`'s CLI entrypoint in a similar way:
```bash
powertrain-build wrapper --codegen --models Models/ICEAES/VcAesTx/VcAesTx.mdl
```
In general, the python call
```bash
python -m powertrain_build.submodule.subsubmodule <args>
```
corresponds to the CLI entrypoint call
```bash
powertrain-build submodule subsubmodule <args>
```
Run
```bash
powertrain-build --help
```
for more information on how to use the CLI entrypoint.
#### Set Matlab 2017 as Environmental Variable
Add New User Variables

@ -0,0 +1,6 @@
import sys
from powertrain_build.cli import main
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

@ -12,6 +12,8 @@ import re
import sys
from itertools import product
from pathlib import Path
from textwrap import dedent
from typing import List, Optional
import git
@ -340,14 +342,7 @@ def check_signals(left_signals, right_signals, errors, left_path=None, right_pat
return serious_mismatch
def parse_args():
"""Parse arguments
Returns:
Namespace: the parsed arguments
"""
parser = argparse.ArgumentParser(
description="""
PARSER_HELP = dedent(r"""
Checks attributes and existence of signals
Produced but not consumed signals are giving warnings
@ -368,50 +363,58 @@ def parse_args():
py -3.6 -m powertrain_build.check_interface projects <Projects> \
--projects ProjectOne ProjectTwo ProjectThree
Checks the interfaces of ProjectOne, ProjectTwo and ProjectThree in the folder Projects
""",
formatter_class=argparse.RawTextHelpFormatter,
""").strip()
def configure_parser(parser: argparse.ArgumentParser):
"""Configure arguments in parser."""
subparsers = parser.add_subparsers(
help="help for subcommand",
dest="mode",
required=True,
)
subparsers = parser.add_subparsers(help="help for subcommand", dest="mode")
# create the parser for the different commands
model = subparsers.add_parser(
"models",
description="""
Check models independently of projects.
description=dedent("""
Check models independently of projects.
All signals are assumed to be active.
Any signal that gives and error is used in a model but is not produced in any model or project
interface.
""",
All signals are assumed to be active.
Any signal that gives and error is used in a model but is not produced in any model or project
interface.
""").strip(),
)
add_model_args(model)
model.set_defaults(func=model_check)
project = subparsers.add_parser(
"projects",
description="""
Check projects as a whole.
description=dedent("""
Check projects as a whole.
It checks all models intenally and the SPM vs the interface.
""",
It checks all models intenally and the SPM vs the interface.
""").strip(),
)
add_project_args(project)
project.set_defaults(func=projects_check)
models_in_projects = subparsers.add_parser(
"models_in_projects",
description="""
Check models specifically for projects.
description=dedent("""
Check models specifically for projects.
Codeswitches are used to determine if the signals are produced and consumed in each model.
""",
Codeswitches are used to determine if the signals are produced and consumed in each model.
""").strip(),
)
add_project_args(models_in_projects)
add_model_args(models_in_projects)
models_in_projects.add_argument("--properties", help="Check properties such as type", action="store_true")
models_in_projects.add_argument("--existence", help="Check signal existence consistency", action="store_true")
return parser.parse_args()
models_in_projects.set_defaults(func=models_in_projects_check)
def add_project_args(parser):
def add_project_args(parser: argparse.ArgumentParser):
"""Add project arguments to subparser"""
parser.add_argument("project_root", help="Path to start looking for projects", type=Path)
parser.add_argument(
@ -419,7 +422,7 @@ def add_project_args(parser):
)
def add_model_args(parser):
def add_model_args(parser: argparse.ArgumentParser):
"""Add model arguments to subparser"""
parser.add_argument("model_root", help="Path to start looking for models", type=Path)
parser.add_argument("--models", help="Name of models to check", nargs="+")
@ -446,7 +449,7 @@ def model_path_to_name(model_paths):
return model_names
def model_check(args):
def model_check(args: argparse.Namespace):
"""Entry point for models command."""
serious_mismatch = False
all_models = get_all_models(args.model_root)
@ -460,10 +463,14 @@ def model_check(args):
model_names = [model.name for model in all_models]
serious_mismatch |= check_models_generic(all_models, model_names, [])
if serious_mismatch:
LOGGER.error("Serious interface errors found.")
return serious_mismatch
def projects_check(args):
def projects_check(args: argparse.Namespace):
"""Entry point for projects command."""
serious_mismatch = False
projects = get_projects(args.project_root, args.projects)
@ -472,10 +479,14 @@ def projects_check(args):
serious_mismatch |= check_internal_signals(app, None)
if ems is not None:
serious_mismatch |= check_external_signals(ems, app, None)
if serious_mismatch:
LOGGER.error("Serious interface errors found.")
return serious_mismatch
def models_in_projects_check(args):
def models_in_projects_check(args: argparse.Namespace):
"""Entry point for models_in_projects command."""
serious_mismatch = False
projects = get_projects(args.project_root, args.projects)
@ -489,6 +500,10 @@ def models_in_projects_check(args):
all_models = get_all_models(args.model_root)
model_names = [model.name for model in all_models] if args.models is None else args.models
serious_mismatch |= signal_existence(projects, model_names)
if serious_mismatch:
LOGGER.error("Serious interface errors found.")
return serious_mismatch
@ -545,19 +560,16 @@ def signal_match(signals_to_check, signals_to_check_against, matches):
matches[a_signal.name] = True
def main():
def main(argv: Optional[List[str]] = None):
"""Main function for stand alone execution."""
args = parse_args()
if args.mode == "models":
serious_errors = model_check(args)
if args.mode == "projects":
serious_errors = projects_check(args)
if args.mode == "models_in_projects":
serious_errors = models_in_projects_check(args)
if serious_errors:
LOGGER.error("Serious interface errors found.")
sys.exit(1)
parser = argparse.ArgumentParser(
description=PARSER_HELP,
formatter_class=argparse.RawTextHelpFormatter,
)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main()
main(sys.argv[1:])

141
powertrain_build/cli.py Normal file

@ -0,0 +1,141 @@
# Copyright 2024 Volvo Car Corporation
# Licensed under Apache 2.0.
from argparse import ArgumentParser, Namespace
from typing import List, Optional
import powertrain_build.check_interface
import powertrain_build.create_conversion_table
import powertrain_build.interface.export_global_vars
import powertrain_build.interface.generate_adapters
import powertrain_build.interface.generate_hi_interface
import powertrain_build.interface.generate_service
import powertrain_build.interface.generate_wrappers
import powertrain_build.interface.model_yaml_verification
import powertrain_build.interface.update_model_yaml
import powertrain_build.interface.update_call_sources
import powertrain_build.replace_compu_tab_ref
import powertrain_build.signal_inconsistency_check
from powertrain_build import __version__
from powertrain_build.config import ProcessHandler
from powertrain_build.lib import logger
from powertrain_build.wrapper import PyBuildWrapper
LOGGER = logger.create_logger(__file__)
def parse_args(argv: Optional[List[str]] = None) -> Namespace:
"""Parse command line arguments."""
parser = ArgumentParser(
prog="powertrain-build",
description="Powertrain-build",
)
parser.add_argument(
"-V", "--version",
action="version",
version=f"%(prog)s {__version__}",
)
command_subparsers = parser.add_subparsers(title="Commands", dest="command", required=True)
wrapper_parser = command_subparsers.add_parser(
"wrapper",
help=PyBuildWrapper.PARSER_HELP,
)
PyBuildWrapper.add_args(wrapper_parser)
config_parser = command_subparsers.add_parser(
"config",
help=ProcessHandler.PARSER_HELP,
)
ProcessHandler.configure_parser(config_parser)
check_interface_parser = command_subparsers.add_parser(
"check-interface",
help=powertrain_build.check_interface.PARSER_HELP,
)
powertrain_build.check_interface.configure_parser(check_interface_parser)
create_conversion_table_parser = command_subparsers.add_parser(
"create-conversion-table",
help=powertrain_build.create_conversion_table.PARSER_HELP,
)
powertrain_build.create_conversion_table.configure_parser(create_conversion_table_parser)
replace_compu_tab_ref_parser = command_subparsers.add_parser(
"replace-compu-tab-ref",
help=powertrain_build.replace_compu_tab_ref.PARSER_HELP,
)
powertrain_build.replace_compu_tab_ref.configure_parser(replace_compu_tab_ref_parser)
signal_inconsistency_check_parser = command_subparsers.add_parser(
"signal-inconsistency-check",
help=powertrain_build.signal_inconsistency_check.PARSER_HELP,
)
powertrain_build.signal_inconsistency_check.configure_parser(signal_inconsistency_check_parser)
interface_parser = command_subparsers.add_parser(
"interface",
help="Interface commands",
)
interface_subparsers = interface_parser.add_subparsers(
title="Interface commands",
dest="interface_command",
required=True,
)
export_global_vars_parser = interface_subparsers.add_parser(
"export-global-vars",
help=powertrain_build.interface.export_global_vars.PARSER_HELP,
)
powertrain_build.interface.export_global_vars.configure_parser(export_global_vars_parser)
generate_adapters_parser = interface_subparsers.add_parser(
"generate-adapters",
help=powertrain_build.interface.generate_adapters.PARSER_HELP,
)
powertrain_build.interface.generate_adapters.configure_parser(generate_adapters_parser)
generate_hi_interface_parser = interface_subparsers.add_parser(
"generate-hi-interface",
help=powertrain_build.interface.generate_hi_interface.PARSER_HELP,
)
powertrain_build.interface.generate_hi_interface.configure_parser(generate_hi_interface_parser)
generate_service_parser = interface_subparsers.add_parser(
"generate-service",
help=powertrain_build.interface.generate_service.PARSER_HELP,
)
powertrain_build.interface.generate_service.configure_parser(generate_service_parser)
generate_wrappers_parser = interface_subparsers.add_parser(
"generate-wrappers",
help=powertrain_build.interface.generate_wrappers.PARSER_HELP,
)
powertrain_build.interface.generate_wrappers.configure_parser(generate_wrappers_parser)
model_yaml_verification_parser = interface_subparsers.add_parser(
"model-yaml-verification",
help=powertrain_build.interface.model_yaml_verification.PARSER_HELP,
)
powertrain_build.interface.model_yaml_verification.configure_parser(model_yaml_verification_parser)
update_model_yaml_parser = interface_subparsers.add_parser(
"update-model-yaml",
help=powertrain_build.interface.update_model_yaml.PARSER_HELP,
)
powertrain_build.interface.update_model_yaml.configure_parser(update_model_yaml_parser)
update_call_sources_parser = interface_subparsers.add_parser(
"update-call-sources",
help=powertrain_build.interface.update_call_sources.PARSER_HELP,
)
powertrain_build.interface.update_call_sources.configure_parser(update_call_sources_parser)
return parser.parse_args(argv)
def main(argv: Optional[List[str]] = None) -> Namespace:
"""Run main function."""
args = parse_args(argv)
return args.func(args)

@ -10,7 +10,9 @@ import json
import operator
import os
import re
import sys
from pprint import pformat
from typing import List, Optional
from powertrain_build.lib import logger
@ -412,17 +414,25 @@ class HeaderConfigParser(ConfigParserCommon):
class ProcessHandler:
"""Class to collect functions for the process."""
PARSER_HELP = "Parse configs.json and c-files, to update code switch configs"
@staticmethod
def parse_args():
def configure_parser(parser: argparse.ArgumentParser):
"""Parse arguments."""
parser = argparse.ArgumentParser("Parse configs.json and c-files, to update code switch configes")
subparser = parser.add_subparsers(title='Operation mode', dest='mode',
help="Run chosen files on in a number of directories")
parser.description = "Parse configs.json and c-files, to update code switch configs"
subparser = parser.add_subparsers(
title='Operation mode',
dest='mode',
help="Run chosen files on in a number of directories",
required=True,
)
dir_parser = subparser.add_parser(
'models',
help="Run for one or multiple models. Script finds files generated from the model(s).")
dir_parser.add_argument('models', nargs='+',
help="Space separated list of model directories")
file_parser = subparser.add_parser('files',
help="Choose specific files. Mainly for manually written configs.")
file_parser.add_argument('c_file',
@ -433,8 +443,8 @@ class ProcessHandler:
help="Full path to tl_aux file. (Optional) ")
file_parser.add_argument('--local_file',
help="Full path to OPort file. (Optional) ")
args = parser.parse_args()
return args
parser.set_defaults(func=ProcessHandler.main)
@staticmethod
def get_files(model_path):
@ -505,9 +515,8 @@ class ProcessHandler:
return parser.get_config()
@classmethod
def main(cls):
def main(cls, args: argparse.Namespace):
"""Run the main function of the script."""
args = cls.parse_args()
if args.mode == 'files':
LOGGER.info('Using manually supplied files %s', args)
local_defs = cls.get_header_config(args.local_file, {})
@ -522,5 +531,13 @@ class ProcessHandler:
cls.update_config_file(c_file, config_file, aux_defs)
def main(argv: Optional[List[str]] = None):
"""Run main function."""
parser = argparse.ArgumentParser(ProcessHandler.PARSER_HELP)
ProcessHandler.configure_parser(parser)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
ProcessHandler.main()
main(sys.argv[1:])

@ -5,7 +5,12 @@
import argparse
import json
import sys
from pathlib import Path
from typing import List, Optional
PARSER_HELP = "Create a2l file from conversion_table.json file."
def get_vtab_text(vtab):
@ -40,22 +45,29 @@ def create_conversion_table(input_json: Path, output_a2l: Path):
f_h.write(get_vtab_text(vtab))
def parse_args():
"""Parse args."""
parser = argparse.ArgumentParser('Create a2l file from conversion_table.json file')
def create_conversion_table_cli(args: argparse.Namespace):
"""CLI wrapper function for passing in Namespace object.
This allows maintaining a standardized CLI function signature while not breaking backwards
compatibility with create_converstion_table.
"""
create_conversion_table(args.input_file, args.output_file)
def configure_parser(parser: argparse.ArgumentParser):
"""Set up parser for CLI."""
parser.add_argument('input_file', type=Path)
parser.add_argument('output_file', type=Path)
args = parser.parse_args()
return args
parser.set_defaults(func=create_conversion_table_cli)
def main():
"""Main."""
args = parse_args()
conversion_table_json = args.input_file
conversion_table_a2l = args.output_file
create_conversion_table(conversion_table_json, conversion_table_a2l)
def main(argv: Optional[List[str]] = None):
"""Main function for CLI."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
if __name__ == '__main__':
main()
main(sys.argv[1:])

@ -6,7 +6,7 @@
import argparse
import os
import sys
from typing import Dict, Tuple
from typing import Dict, List, Optional, Tuple
from ruamel.yaml import YAML
@ -15,6 +15,9 @@ from powertrain_build.feature_configs import FeatureConfigs
from powertrain_build.unit_configs import UnitConfigs
PARSER_HELP = "Export global variables."
def get_global_variables(project_config_path: str) -> Dict:
"""Get global variables connected to PyBuild project.
@ -81,18 +84,38 @@ def _export_yaml(data: Dict, file_path: str) -> None:
yaml.dump(data, yaml_file)
def _main():
args = _parse_args()
def export_global_vars(args: argparse.Namespace):
"""Exports global variables as yaml file."""
global_variables = get_global_variables(args.project_config)
_export_yaml(global_variables, args.output_file)
def _parse_args():
parser = argparse.ArgumentParser(description="Export global variables.")
parser.add_argument("--project-config", help="Project root configuration file.", required=True)
parser.add_argument("--output-file", help="Output file to export global variables.", required=True)
return parser.parse_args()
def _main(argv: Optional[List[str]] = None):
"""Main function for CLI."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
def configure_parser(parser: argparse.ArgumentParser):
"""Configures the argument parser for the script.
Args:
parser (argparse.ArgumentParser): Argument parser.
"""
parser.add_argument(
"--project-config",
help="Project root configuration file.",
required=True,
)
parser.add_argument(
"--output-file",
help="Output file to export global variables.",
required=True,
)
parser.set_defaults(func=export_global_vars)
if __name__ == "__main__":
sys.exit(_main())
sys.exit(_main(sys.argv[1:]))

@ -3,8 +3,12 @@
# -*- coding: utf-8 -*-
"""Python module used for calculating interfaces for CSP"""
import argparse
import sys
from pathlib import Path
from os import path
from typing import List, Optional
from powertrain_build.interface.hal import HALA
from powertrain_build.interface.device_proxy import DPAL
from powertrain_build.interface.service import ServiceFramework
@ -15,14 +19,11 @@ from powertrain_build.lib.helper_functions import deep_json_update
LOGGER = logger.create_logger("CSP adapters")
PARSER_HELP = "Generate adapters"
def parse_args():
"""Parse arguments
Returns:
Namespace: the parsed arguments
"""
parser = generation_utils.base_parser()
def configure_parser(parser: argparse.ArgumentParser):
generation_utils.add_base_args(parser)
parser.add_argument(
"--dp-interface",
help="Add dp interface to adapter specification",
@ -48,14 +49,20 @@ def parse_args():
help="Update project config file with path to adapter specifications",
action="store_true"
)
return parser.parse_args()
parser.set_defaults(func=generate_adapters)
def main():
def main(argv: Optional[List[str]] = None):
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications
"""
args = parse_args()
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
def generate_adapters(args: argparse.Namespace):
app = generation_utils.process_app(args.config)
adapters(args, app)
@ -122,4 +129,4 @@ def adapters(args, app):
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -3,7 +3,11 @@
# -*- coding: utf-8 -*-
"""Python module used for calculating interfaces for CSP HI"""
import argparse
import sys
from pathlib import Path
from typing import List, Optional
from powertrain_build.interface import generation_utils
from powertrain_build.interface.device_proxy import DPAL
from powertrain_build.lib.helper_functions import recursive_default_dict, to_normal_dict
@ -11,6 +15,8 @@ from powertrain_build.lib.helper_functions import recursive_default_dict, to_nor
OP_READ = 'read'
OP_WRITE = 'write'
PARSER_HELP = "Generate HI YAML interface file."
def generate_hi_interface(args, hi_interface):
"""Generate HI YAML interface file.
@ -44,31 +50,38 @@ def generate_hi_interface(args, hi_interface):
generation_utils.write_to_file(to_normal_dict(result), args.output, is_yaml=True)
def parse_args():
"""Parse arguments.
def generate_hi_interface_cli(args: argparse.Namespace):
"""CLI entrypoint for generating HI YAML interface file.
Returns:
Namespace: the parsed arguments.
Args:
args (Namespace): Arguments from command line.
"""
parser = generation_utils.base_parser()
parser.add_argument(
"output",
help="Output file with interface specifications.",
type=Path
)
return parser.parse_args()
def main():
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications.
"""
args = parse_args()
app = generation_utils.process_app(args.config)
hi_app = DPAL(app)
interface = generation_utils.get_interface(app, hi_app)
generate_hi_interface(args, interface)
def configure_parser(parser: argparse.ArgumentParser):
"""Configure parser for generating HI YAML interface file."""
generation_utils.add_base_args(parser)
parser.add_argument(
"output",
help="Output file with interface specifications.",
type=Path
)
parser.set_defaults(func=generate_hi_interface_cli)
def main(argv: Optional[List[str]] = None):
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications.
"""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -3,21 +3,23 @@
# -*- coding: utf-8 -*-
"""Python module used for calculating interfaces for CSP"""
import argparse
import sys
from pathlib import Path
from typing import List, Optional
from powertrain_build.interface.service import get_service
from powertrain_build.lib import logger
from powertrain_build.interface import generation_utils
LOGGER = logger.create_logger("CSP service")
PARSER_HELP = "Generate CSP service models"
def parse_args():
"""Parse command line arguments
Returns:
Namespace: Arguments from command line
"""
parser = generation_utils.base_parser()
def configure_parser(parser: argparse.ArgumentParser):
"""Configure parser for CSP service generation"""
generation_utils.add_base_args(parser)
parser.add_argument(
"--client-name",
help="Name of the context object in CSP. Defaults to project name."
@ -27,19 +29,26 @@ def parse_args():
help="Output directory for service models",
type=Path
)
return parser.parse_args()
parser.set_defaults(func=generate_service_cli)
def main():
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications
"""
args = parse_args()
def generate_service_cli(args: argparse.Namespace):
"""CLI function for CSP service generation"""
app = generation_utils.process_app(args.config)
client_name = generation_utils.get_client_name(args, app)
service(args, app, client_name)
def main(argv: Optional[List[str]] = None):
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications
"""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
def service(args, app, client_name):
""" Generate specifications for pt-scheduler wrappers.
@ -57,4 +66,4 @@ def service(args, app, client_name):
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -3,7 +3,11 @@
# -*- coding: utf-8 -*-
"""Python module used for calculating interfaces for CSP"""
import argparse
import sys
from pathlib import Path
from typing import List, Optional
from powertrain_build.interface.hal import HALA, get_hal_list
from powertrain_build.interface.device_proxy import DPAL
from powertrain_build.interface.service import ServiceFramework, get_service_list
@ -12,6 +16,8 @@ from powertrain_build.interface import generation_utils
LOGGER = logger.create_logger("CSP wrappers")
PARSER_HELP = "Generate specifications for pt-scheduler wrappers"
def get_manifest(app, domain, client_name):
"""Get signal manifest for application
@ -40,13 +46,9 @@ def get_manifest(app, domain, client_name):
return dpal.to_manifest(client_name)
def parse_args():
"""Parse command line arguments
Returns:
Namespace: Arguments from command line
"""
parser = generation_utils.base_parser()
def configure_parser(parser: argparse.ArgumentParser):
"""Configure parser for generating pt-scheduler wrappers."""
generation_utils.add_base_args(parser)
parser.add_argument(
"--client-name",
help="Name of the context object in CSP. Defaults to project name."
@ -71,19 +73,30 @@ def parse_args():
help="Output file with service interface specifications",
type=Path
)
return parser.parse_args()
parser.set_defaults(func=generate_wrappers_cli)
def main():
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications
def generate_wrappers_cli(args: argparse.Namespace):
"""Generate specifications for pt-scheduler wrappers.
Args:
args (Namespace): Arguments from command line
"""
args = parse_args()
app = generation_utils.process_app(args.config)
client_name = generation_utils.get_client_name(args, app)
wrappers(args, app, client_name)
def main(argv: Optional[List[str]] = None):
""" Main function for stand alone execution.
Mostly useful for testing and generation of dummy hal specifications
"""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
def wrappers(args, app, client_name):
""" Generate specifications for pt-scheduler wrappers.
@ -131,4 +144,4 @@ def wrappers(args, app, client_name):
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -13,15 +13,13 @@ from powertrain_build.lib import logger
LOGGER = logger.create_logger("CSP interface generation utils")
def base_parser():
def add_base_args(parser: argparse.ArgumentParser):
""" Base parser that adds config argument.
Returns:
parser (ArgumentParser): Base parser
"""
parser = argparse.ArgumentParser()
parser.add_argument("config", help="The project configuration file", type=Path)
return parser
def get_client_name(args, app):

@ -5,12 +5,16 @@
import argparse
import logging
import sys
import typing
from pathlib import Path
from voluptuous import All, MultipleInvalid, Optional, Required, Schema
from ruamel.yaml import YAML
from powertrain_build.interface.application import Application
from powertrain_build.interface.base import BaseApplication
PARSER_HELP = "Verify the model yaml files."
class ModelYmlVerification(BaseApplication):
"""Class for verifying the model yaml files."""
@ -317,20 +321,14 @@ def get_app(project_config):
return app
def parse_args():
"""Parse command line arguments.
Returns:
(Namespace): parsed command line arguments.
"""
parser = argparse.ArgumentParser()
def configure_parser(parser: argparse.ArgumentParser):
"""Configure the argument parser."""
parser.add_argument("config", help="The SPA2 project config file", type=Path)
return parser.parse_args()
parser.set_defaults(func=model_yaml_verification_cli)
def main():
"""Main function for model yaml verification."""
args = parse_args()
def model_yaml_verification_cli(args: argparse.Namespace):
"""CLI function for model yaml verification."""
app = get_app(args.config)
model_yamls = app.get_translation_files()
model_yaml_ver = ModelYmlVerification(app)
@ -338,5 +336,13 @@ def main():
model_yaml_ver.print_success_msg()
def main(argv: typing.Optional[typing.List[str]] = None):
"""Main function for model yaml verification."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -6,17 +6,17 @@
import argparse
import re
import sys
from typing import List, Optional
from ruamel.yaml import YAML
from pathlib import Path
def parse_args():
""" Parse arguments
PARSER_HELP = "Update call sources for method calls in source files."
Returns:
Namespace: the parsed arguments
"""
parser = argparse.ArgumentParser()
def configure_parser(parser: argparse.ArgumentParser):
"""Configure the parser for the update call sources command."""
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(
@ -26,12 +26,11 @@ def parse_args():
default=None,
help="Path to project config json file",
)
return parser.parse_args()
parser.set_defaults(func=update_call_sources_cli)
def main():
""" Main function for stand alone execution."""
args = parse_args()
def update_call_sources_cli(args: argparse.Namespace):
"""CLI function for updating call sources."""
method_config = read_project_config(args.project_config)
with open(args.interface, encoding="utf-8") as interface_file:
yaml = YAML(typ='safe', pure=True)
@ -39,6 +38,14 @@ def main():
update_call_sources(args.src_dir, adapter_spec, method_config)
def main(argv: Optional[List[str]] = None):
""" Main function for stand alone execution."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
def read_project_config(project_config_path):
""" Reads project config file and extract method specific settings if they are present.
@ -170,4 +177,4 @@ def generate_src_code(adapter, method, old_src, method_config):
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -5,11 +5,16 @@
import argparse
import sys
from pathlib import Path
from typing import List, Optional
from ruamel.yaml import YAML
from powertrain_build.interface.application import Application
from powertrain_build.interface.base import BaseApplication
PARSER_HELP = "Update model yaml files."
class BadYamlFormat(Exception):
"""Exception to raise when signal is not in/out signal."""
@ -155,25 +160,27 @@ def get_app(config):
return app
def parse_args():
"""Parse command line arguments
Returns:
(Namespace): Parsed command line arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument("config", help="The SPA2 project config file", type=Path)
return parser.parse_args()
def main():
"""Main function for update model yaml."""
args = parse_args()
def update_model_yaml_cli(args: argparse.Namespace):
"""CLI for update model yaml."""
app = get_app(args.config)
translation_files = app.get_translation_files()
uymlf = UpdateYmlFormat(app)
uymlf.parse_definition(translation_files)
def configure_parser(parser: argparse.ArgumentParser):
"""Configure parser for update model yaml."""
parser.add_argument("config", help="The SPA2 project config file", type=Path)
parser.set_defaults(func=update_model_yaml_cli)
def main(argv: Optional[List[str]] = None):
"""Main function for update model yaml."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -3,18 +3,29 @@
"""Module for replacing $CVC_* style references in a2l file."""
import re
import argparse
import re
import sys
from pathlib import Path
from typing import List, Optional
def parse_args():
"""Parse args."""
parser = argparse.ArgumentParser("Replace $CVC_* style references in a2l file")
PARSER_HELP = "Replace $CVC_* style references in a2l file"
def configure_parser(parser: argparse.ArgumentParser):
"""Configure parser for CLI."""
parser.add_argument("a2l_target_file")
args = parser.parse_args()
return args
parser.set_defaults(func=replace_tab_verb_cli)
def replace_tab_verb_cli(args: argparse.Namespace):
"""CLI wrapper function for passing in Namespace object.
This allows maintaining a standardized CLI function signature while not breaking backwards
compatibility with replace_tab_verb.
"""
replace_tab_verb(args.a2l_target_file)
def replace_tab_verb(file_path: Path):
@ -82,11 +93,13 @@ def replace_tab_verb(file_path: Path):
f_h.write(a2l_patched)
def main():
"""Main."""
args = parse_args()
replace_tab_verb(args.a2l_target_file)
def main(argv: Optional[List[str]] = None):
"""Main function for CLI."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
args.func(args)
if __name__ == "__main__":
main()
main(sys.argv[1:])

@ -11,6 +11,7 @@ import sys
from collections import defaultdict
from os.path import join
from pathlib import Path
from typing import List, Optional
import git
@ -60,6 +61,8 @@ TEMPLATE = """<!DOCTYPE html>
</body>
</html>"""
PARSER_HELP = "Run signal inconsistency check."
def gen_sig_incons_index_file(project_list):
"""Generate Index_SigCheck_All.html."""
@ -74,9 +77,8 @@ def gen_sig_incons_index_file(project_list):
f_h.write(TEMPLATE.format(project_rows=rows))
def parse_args():
def configure_parser(parser: argparse.ArgumentParser):
"""Parse the arguments sent to the script."""
parser = argparse.ArgumentParser("")
parser.add_argument(
"-m",
"--models",
@ -89,7 +91,7 @@ def parse_args():
parser.add_argument(
"-r", "--report", help="Create report for all projects", action="store_true"
)
return parser.parse_args()
parser.set_defaults(func=run_signal_inconsistency_check)
def get_project_configs():
@ -612,12 +614,19 @@ class SignalInconsistency:
return exit_code
def main():
def run_signal_inconsistency_check(args: argparse.Namespace) -> int:
"""Create Signal Inconsistency instance and run checks."""
args = parse_args()
sig_in = SignalInconsistency(args)
return sig_in.run(args)
def main(argv: Optional[List[str]] = None) -> int:
"""Create Signal Inconsistency instance and run checks."""
parser = argparse.ArgumentParser(description=PARSER_HELP)
configure_parser(parser)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
sys.exit(main(sys.argv[1:]))

@ -10,6 +10,7 @@ import os
import sys
from pathlib import Path
from re import search
from typing import List, Optional
try:
from importlib.resources import files
@ -28,6 +29,7 @@ class PyBuildWrapper(pt_matlab.Matlab):
"""Performs upgrade of Matlab models to PyBuild system."""
HASH_FILE_NAME = "pybuild_file_hashes.json"
PARSER_HELP = "Run PyBuild, update and/or generate code for selected models and/or build."
def __init__(self, args):
"""Constructor, initializes paths for PyBuild upgrader.
@ -469,8 +471,10 @@ class PyBuildWrapper(pt_matlab.Matlab):
default="matlab-scripts",
help="Path to folder containing Matlab scripts and simulink libraries to include.",
)
powertrain_build_parser = parser.add_subparsers(help="PyBuild specific build.")
build_specific_parser = powertrain_build_parser.add_parser(
parser.set_defaults(func=run_wrapper)
subparsers = parser.add_subparsers(help="PyBuild specific build.")
build_specific_parser = subparsers.add_parser(
"build-specific", help="Run PyBuild for project with specific settings."
)
build.add_args(build_specific_parser)
@ -479,12 +483,8 @@ class PyBuildWrapper(pt_matlab.Matlab):
pt_matlab.Matlab.add_args(parser)
def main():
"""Run main function."""
parser = argparse.ArgumentParser("PyBuild Wrapper")
PyBuildWrapper.add_args(parser)
args = parser.parse_args()
def run_wrapper(args: argparse.Namespace) -> int:
"""Run PyBuildWrapper."""
if args.build is not None and getattr(args, "project_config", None) is not None:
LOGGER.error("Cannot run both PyBuild quick build (--build <PROJECT>) " "and specific build (build-specific).")
return 1
@ -498,5 +498,13 @@ def main():
return wrapper.run()
def main(argv: Optional[List[str]] = None) -> int:
"""Run PyBuildWrapper"""
parser = argparse.ArgumentParser("PyBuild Wrapper")
PyBuildWrapper.add_args(parser)
args = parser.parse_args(argv)
return run_wrapper(args)
if __name__ == "__main__":
sys.exit(main())
sys.exit(main(sys.argv[1:]))

@ -12,3 +12,5 @@ scipy==1.5.4; python_version < "3.8"
scipy==1.9.1; python_version == "3.8" or python_version == "3.9" or python_version == "3.10"
scipy==1.14.1; python_version >= "3.11"
importlib-resources==5.4.0; python_version < "3.9"
pywin32==305; python_version == "3.6" and sys_platform == "win32"
pywin32==308; python_version > "3.6" and sys_platform == "win32"

@ -34,3 +34,7 @@ packages =
[pbr]
skip_git_sdist = 1
[options.entry_points]
console_scripts =
powertrain-build = powertrain_build.cli:main

@ -0,0 +1,24 @@
import sys
from subprocess import run
from unittest import TestCase
class TestCli(TestCase):
"""Test the cli module."""
def test_entry_points(self):
"""Tests that entrypoints are correctly defined and work both as __main__ and sub-commands."""
modules = [
"powertrain_build.wrapper",
"powertrain_build.interface.generate_adapters",
"powertrain_build.interface.generate_hi_interface",
"powertrain_build.interface.generate_service",
"powertrain_build.interface.generate_wrappers",
"powertrain_build.interface.model_yaml_verification",
"powertrain_build.interface.update_model_yaml",
"powertrain_build.interface.update_call_sources",
]
for module in modules:
entrypoint = module.replace("_", "-").split(".")
run(entrypoint + ["--help"], check=True)
run([sys.executable, "-m", module, "--help"], check=True)

@ -0,0 +1,46 @@
"""Tests for the cli module."""
from pathlib import Path
from unittest import TestCase
from unittest.mock import patch
from powertrain_build.cli import main
class TestCli(TestCase):
"""Test the cli module."""
def test_main(self):
"""Test that the main function parses arguments and calls correct function."""
# Simple command without arguments
with patch("powertrain_build.wrapper.run_wrapper") as run_wrapper_mock:
main(["wrapper"])
run_wrapper_mock.assert_called_once()
args = run_wrapper_mock.call_args[0][0]
self.assertEqual(args.func, run_wrapper_mock)
self.assertEqual(args.command, "wrapper")
# Sub-command with arguments
with patch("powertrain_build.interface.generate_adapters.generate_adapters") as generate_adapters_mock:
main([
"interface", "generate-adapters",
"--dp-interface",
"--hal-interface",
"--service-interface",
"--update-config",
"path/to/config",
"path/to/output",
])
generate_adapters_mock.assert_called_once()
args = generate_adapters_mock.call_args[0][0]
self.assertEqual(args.func, generate_adapters_mock)
self.assertEqual(args.command, "interface")
self.assertEqual(args.interface_command, "generate-adapters")
self.assertTrue(args.dp_interface)
self.assertTrue(args.hal_interface)
self.assertTrue(args.service_interface)
self.assertEqual(args.config, Path("path/to/config"))
self.assertEqual(args.output, Path("path/to/output"))
# Command without enough arguments
with self.assertRaises(SystemExit) as system_exit:
main(["interface", "generate-adapters"])
self.assertEqual(system_exit.exception.code, 2)

11
tox.ini

@ -1,7 +1,10 @@
[tox]
skipsdist = True
requires =
tox >= 2.0
envlist =
flake8
pytest
functest
[flake8]
exclude =
@ -42,3 +45,9 @@ exclude_lines =
[coverage:html]
directory = cover
[testenv:functest]
skipsdist = False
package = editable
commands =
pytest tests/powertrain_build/func_cli.py

@ -2,5 +2,5 @@
name: powertrain-build-tox
parent: tox-cover
vars:
tox_envlist: flake8,pytest
tox_envlist: flake8,pytest,functest
nodeset: ubuntu-jammy