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="""
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.
""",
""").strip(),
)
add_model_args(model)
model.set_defaults(func=model_check)
project = subparsers.add_parser(
"projects",
description="""
description=dedent("""
Check projects as a whole.
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="""
description=dedent("""
Check models specifically for projects.
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