e4085f78e7
There are cases when we have header files in a folder structure, so we should not flatten the included paths. So this commit adds get_includes_paths_tree, that should be self explaining, and renames get_includes_paths to get_includes_paths_flat. Change-Id: I2cff35687adb2a6caee1f4d3c631e62b8c5b53e4
612 lines
21 KiB
Python
612 lines
21 KiB
Python
# Copyright 2024 Volvo Car Corporation
|
|
# Licensed under Apache 2.0.
|
|
|
|
# -*- coding: utf-8 -*-
|
|
"""Module used to read project and base configuration files and provides methods for abstraction."""
|
|
|
|
import glob
|
|
import json
|
|
import os
|
|
import shutil
|
|
import pathlib
|
|
from pprint import pformat
|
|
|
|
from pybuild.lib.helper_functions import deep_dict_update
|
|
from pybuild.versioncheck import Version
|
|
|
|
|
|
class BuildProjConfig:
|
|
"""A class holding build project configurations."""
|
|
|
|
def __init__(self, prj_config_file):
|
|
"""Read project configuration file to internal an representation.
|
|
|
|
Args:
|
|
prj_config_file (str): Project config filename
|
|
"""
|
|
super().__init__()
|
|
self._prj_cfg_file = prj_config_file
|
|
prj_root_dir, _ = os.path.split(prj_config_file)
|
|
self._prj_root_dir = os.path.abspath(prj_root_dir)
|
|
|
|
with open(prj_config_file, 'r', encoding="utf-8") as pcfg:
|
|
self._prj_cfg = json.load(pcfg)
|
|
if not Version.is_compatible(self._prj_cfg.get('ConfigFileVersion')):
|
|
raise ValueError('Incompatible project config file version.')
|
|
# Load a basic config that can be common for several projects
|
|
# the local config overrides the base config
|
|
if 'BaseConfig' in self._prj_cfg:
|
|
fil_tmp = os.path.join(prj_root_dir, self._prj_cfg['BaseConfig'])
|
|
fil_ = os.path.abspath(fil_tmp)
|
|
with open(fil_, 'r', encoding="utf-8") as bcfg:
|
|
base_cnfg = json.load(bcfg)
|
|
deep_dict_update(self._prj_cfg, base_cnfg)
|
|
if not Version.is_compatible(self._prj_cfg.get('BaseConfigFileVersion')):
|
|
raise ValueError('Incompatible base config file version.')
|
|
self.has_yaml_interface = self._prj_cfg['ProjectInfo'].get('yamlInterface', False)
|
|
self.device_domains = self._get_device_domains()
|
|
self.services_file = self._get_services_file()
|
|
self._load_unit_configs()
|
|
self._add_global_const_file()
|
|
self._all_units = []
|
|
self._calc_all_units()
|
|
self.name = self._prj_cfg['ProjectInfo']['projConfig']
|
|
self.allow_undefined_unused = self._prj_cfg['ProjectInfo'].get('allowUndefinedUnused', True)
|
|
self._scheduler_prefix = self._prj_cfg['ProjectInfo'].get('schedulerPrefix', '')
|
|
if self._scheduler_prefix:
|
|
self._scheduler_prefix = self._scheduler_prefix + '_'
|
|
|
|
def __repr__(self):
|
|
"""Get string representation of object."""
|
|
return pformat(self._prj_cfg['ProjectInfo'])
|
|
|
|
def _get_device_domains(self):
|
|
file_name = self._prj_cfg['ProjectInfo'].get('deviceDomains')
|
|
full_path = pathlib.Path(self._prj_root_dir, file_name)
|
|
if full_path.is_file():
|
|
with open(full_path, 'r', encoding="utf-8") as device_domains:
|
|
return json.loads(device_domains.read())
|
|
return {}
|
|
|
|
def _get_services_file(self):
|
|
file_name = self._prj_cfg['ProjectInfo'].get('serviceInterfaces', '')
|
|
full_path = pathlib.Path(self._prj_root_dir, file_name)
|
|
return full_path
|
|
|
|
@staticmethod
|
|
def get_services(services_file):
|
|
"""Get the services from the services file.
|
|
|
|
Args:
|
|
services_file (pathlib.Path): The services file.
|
|
|
|
Returns:
|
|
(dict): The services.
|
|
"""
|
|
if services_file.is_file():
|
|
with services_file.open() as services:
|
|
return json.loads(services.read())
|
|
return {}
|
|
|
|
def _load_unit_configs(self):
|
|
"""Load Unit config json file.
|
|
|
|
This file contains which units are included in which projects.
|
|
"""
|
|
if 'UnitCfgs' in self._prj_cfg:
|
|
fil_tmp = os.path.join(self._prj_root_dir, self._prj_cfg['UnitCfgs'])
|
|
with open(fil_tmp, 'r', encoding="utf-8") as fpr:
|
|
tmp_unit_cfg = json.load(fpr)
|
|
sample_times = tmp_unit_cfg.pop('SampleTimes')
|
|
self._unit_cfg = {
|
|
'Rasters': tmp_unit_cfg,
|
|
'SampleTimes': sample_times
|
|
}
|
|
else:
|
|
raise ValueError('UnitCfgs is not specified in project config')
|
|
|
|
def _add_global_const_file(self):
|
|
"""Add the global constants definition to the 'not_scheduled' time raster."""
|
|
ugc = self.get_use_global_const()
|
|
if ugc:
|
|
self._unit_cfg['Rasters'].setdefault('NoSched', []).append(ugc)
|
|
|
|
def create_build_dirs(self):
|
|
"""Create the necessary output build dirs if they are missing.
|
|
|
|
Clear the output build dirs if they exist.
|
|
"""
|
|
src_outp = self.get_src_code_dst_dir()
|
|
if os.path.exists(src_outp):
|
|
shutil.rmtree(src_outp)
|
|
os.makedirs(src_outp)
|
|
|
|
log_outp = self.get_log_dst_dir()
|
|
if os.path.exists(log_outp):
|
|
shutil.rmtree(log_outp)
|
|
os.makedirs(log_outp)
|
|
|
|
rep_outp = self.get_reports_dst_dir()
|
|
if os.path.exists(rep_outp):
|
|
shutil.rmtree(rep_outp)
|
|
os.makedirs(rep_outp)
|
|
|
|
unit_cfg_outp = self.get_unit_cfg_deliv_dir()
|
|
if os.path.exists(unit_cfg_outp):
|
|
shutil.rmtree(unit_cfg_outp)
|
|
if unit_cfg_outp is not None:
|
|
os.makedirs(unit_cfg_outp)
|
|
|
|
def get_a2l_cfg(self):
|
|
""" Get A2L configuration from A2lConfig.
|
|
|
|
Returns:
|
|
config (dict): A2L configuration
|
|
"""
|
|
a2l_config = self._prj_cfg.get('A2lConfig', {})
|
|
return {
|
|
'name': a2l_config.get('name', self._prj_cfg["ProjectInfo"]["projConfig"]),
|
|
'allow_kp_blob': a2l_config.get('allowKpBlob', True),
|
|
'ip_address': a2l_config.get('ipAddress', "169.254.4.10"),
|
|
'ip_port': '0x%X' % a2l_config.get('ipPort', 30000),
|
|
'asap2_version': a2l_config.get('asap2Version', "1 51")
|
|
}
|
|
|
|
def get_unit_cfg_deliv_dir(self):
|
|
"""Get the directory where to put the unit configuration files.
|
|
|
|
If this key is undefined, or set to None, the unit-configs will
|
|
not be copied to the output folder.
|
|
|
|
Returns:
|
|
A path to the unit deliver dir, or None
|
|
|
|
"""
|
|
if 'unitCfgDeliveryDir' in self._prj_cfg['ProjectInfo']:
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['unitCfgDeliveryDir']))
|
|
return None
|
|
|
|
def get_root_dir(self):
|
|
"""Get the root directory of the project.
|
|
|
|
Returns:
|
|
A path to the project root (with wildcards)
|
|
|
|
"""
|
|
return self._prj_root_dir
|
|
|
|
def get_src_code_dst_dir(self):
|
|
"""Return the absolute path to the source output folder."""
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['srcCodeDstDir']))
|
|
|
|
def get_composition_name(self):
|
|
"""Return the composition name."""
|
|
name, _ = os.path.splitext(self._prj_cfg['ProjectInfo']['compositionName'])
|
|
return name
|
|
|
|
def get_composition_ending(self):
|
|
"""Return the composition ending."""
|
|
_, ending = os.path.splitext(self._prj_cfg['ProjectInfo']['compositionName'])
|
|
if ending:
|
|
return ending
|
|
return 'yml'
|
|
|
|
def get_composition_arxml(self):
|
|
"""Return the relative composition arxml path."""
|
|
return self._prj_cfg['ProjectInfo']['compositionArxml']
|
|
|
|
def get_gen_ext_impl_type(self):
|
|
"""Return the generate external implementation type."""
|
|
return self._prj_cfg['ProjectInfo'].get('generateExternalImplementationType', True)
|
|
|
|
def get_swc_name(self):
|
|
"""Returns the software component name."""
|
|
a2lname = f"{self.get_a2l_cfg()['name']}_SC"
|
|
return self._prj_cfg['ProjectInfo'].get('softwareComponentName', a2lname)
|
|
|
|
def get_car_com_dst(self):
|
|
"""Return the absolute path to the source output folder."""
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['didCarCom']))
|
|
|
|
def get_reports_dst_dir(self):
|
|
"""Get the destination dir for build reports.
|
|
|
|
Returns:
|
|
A path to the report files destination directory (with wildcards)
|
|
|
|
"""
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['reportDstDir']))
|
|
|
|
def get_all_reports_dst_dir(self):
|
|
"""Get the destination dir for build reports.
|
|
|
|
Returns:
|
|
A path to the report files destination directory (for all projects)
|
|
|
|
"""
|
|
return os.path.join(self.get_root_dir(), "..", "..", "Reports")
|
|
|
|
def get_log_dst_dir(self):
|
|
"""Return the absolute path to the log output folder.
|
|
|
|
Returns:
|
|
A path to the log files destination directory (with wildcards)
|
|
|
|
"""
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['logDstDir']))
|
|
|
|
def get_core_dummy_name(self):
|
|
"""Return the file name of the core dummy file from the config file.
|
|
|
|
Returns:
|
|
A file name for the core dummy files
|
|
|
|
"""
|
|
path = os.path.join(self.get_src_code_dst_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['coreDummyFileName']))
|
|
return path
|
|
|
|
def get_feature_conf_header_name(self):
|
|
"""Return the feature configuration header file name.
|
|
|
|
Returns:
|
|
A file name for the feature config header file
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']['featureHeaderName']
|
|
|
|
def get_ts_header_name(self):
|
|
"""Return the name of the ts header file, defined in the config file.
|
|
|
|
Returns:
|
|
The file name of the file defining all unit raster times
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']['tsHeaderName']
|
|
|
|
def get_included_units(self):
|
|
"""Return a list of all the included units in the project.
|
|
|
|
TODO:Consider moving this to the Feature Configs class if we start
|
|
using our configuration tool for model inclusion and scheduling
|
|
TODO:Consider calculate this on init and storing the result in the
|
|
class. this method would the just return the stored list.
|
|
"""
|
|
units_dict = self._unit_cfg['Rasters']
|
|
units = []
|
|
for unit in units_dict.values():
|
|
units.extend(unit)
|
|
return units
|
|
|
|
def get_included_common_files(self):
|
|
"""Return a list of all the included common files in the project.
|
|
|
|
Returns:
|
|
included_common_files ([str]): The names of the common files which are included in the project.
|
|
|
|
"""
|
|
return self._prj_cfg.get('includedCommonFiles', [])
|
|
|
|
def _calc_all_units(self):
|
|
"""Return a list of all the units."""
|
|
units = set()
|
|
for runits in self._unit_cfg['Rasters'].values():
|
|
units = units.union(set(runits))
|
|
self._all_units = list(units)
|
|
|
|
def get_includes_paths_flat(self):
|
|
"""Return list of paths to files to be included flat in source directory."""
|
|
includes_paths = self._prj_cfg.get('includesPaths', [])
|
|
return [os.path.join(self.get_root_dir(), os.path.normpath(path)) for path in includes_paths]
|
|
|
|
def get_includes_paths_tree(self):
|
|
"""Return list of paths to files to included with directories in source directory."""
|
|
includes_paths_tree = self._prj_cfg.get('includesPathsTree', [])
|
|
return [os.path.join(self.get_root_dir(), os.path.normpath(path)) for path in includes_paths_tree]
|
|
|
|
def get_all_units(self):
|
|
"""Return a list of all the units."""
|
|
return self._all_units
|
|
|
|
def get_prj_cfg_dir(self):
|
|
"""Return the directory containing the project configuration files.
|
|
|
|
Returns:
|
|
An absolute path to the project configuration files
|
|
|
|
"""
|
|
return os.path.join(self._prj_root_dir,
|
|
self._prj_cfg['ProjectInfo']['configDir'])
|
|
|
|
def get_scheduler_prefix(self):
|
|
"""Returns a prefix used to distinguish function calls in one project from
|
|
similarly named functions in other projects, when linked/compiled together
|
|
|
|
Returns:
|
|
scheduler_prefix (string): prefix for scheduler functions.
|
|
"""
|
|
return self._scheduler_prefix
|
|
|
|
def get_local_defs_name(self):
|
|
"""Return a string which defines the file name of local defines.
|
|
|
|
Returns:
|
|
A string containing the wildcard file name local defines
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']['prjLocalDefs']
|
|
|
|
def get_codeswitches_name(self):
|
|
"""Return a string which defines the file name of code switches.
|
|
|
|
Returns:
|
|
A string containing the wildcard file name code switches
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']['prjCodeswitches']
|
|
|
|
def get_did_cfg_file_name(self):
|
|
"""Return the did definition file name.
|
|
|
|
Returns:
|
|
DID definition file name
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']['didDefFile']
|
|
|
|
def get_prj_config(self):
|
|
"""Get the project configuration name from the config file.
|
|
|
|
Returns:
|
|
Project config name
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo']["projConfig"]
|
|
|
|
def get_a2l_name(self):
|
|
"""Get the name of the a2l-file, which the build system shall generate."""
|
|
return self._prj_cfg['ProjectInfo']['a2LFileName']
|
|
|
|
def get_ecu_info(self):
|
|
"""Return ecuSupplier and ecuType.
|
|
|
|
Returns:
|
|
(ecuSupplier, ecuType)
|
|
|
|
"""
|
|
return (self._prj_cfg['ProjectInfo']['ecuSupplier'],
|
|
self._prj_cfg['ProjectInfo'].get('ecuType', ''))
|
|
|
|
def get_xcp_enabled(self):
|
|
"""Return True/False whether XCP is enabled in the project or not.
|
|
|
|
Returns:
|
|
(bool): True/False whether XCP is enabled in the project or not
|
|
|
|
"""
|
|
return self._prj_cfg['ProjectInfo'].get('enableXcp', True)
|
|
|
|
def get_nvm_defs(self):
|
|
"""Return NVM-ram block definitions.
|
|
|
|
The definitions contains the sizes of the six NVM areas
|
|
which are defined in the build-system.
|
|
|
|
Returns:
|
|
NvmConfig dict from config file.
|
|
|
|
"""
|
|
return self._prj_cfg['NvmConfig']
|
|
|
|
def _get_inc_dirs(self, path):
|
|
"""Get the dirs with the models defined in the units config file.
|
|
|
|
Model name somewhere in the path.
|
|
"""
|
|
all_dirs = glob.glob(path)
|
|
inc_units = self.get_included_units()
|
|
psep = os.path.sep
|
|
out = {}
|
|
for dir_ in all_dirs:
|
|
folders = dir_.split(psep)
|
|
for inc_unit in inc_units:
|
|
if inc_unit in folders:
|
|
out.update({inc_unit: dir_})
|
|
break
|
|
return out
|
|
|
|
def get_units_raster_cfg(self):
|
|
"""Get the units' scheduling raster config.
|
|
|
|
I.e. which units are included, and in which
|
|
rasters they are scheduled, and in which order.
|
|
|
|
Returns:
|
|
A dict in the following format.
|
|
|
|
::
|
|
|
|
{
|
|
"SampleTimes": {
|
|
"NameOfRaster": scheduling time},
|
|
"Rasters": {
|
|
"NameOfRaster": [
|
|
"NameOfFunction",
|
|
...],
|
|
...}
|
|
}
|
|
|
|
Example::
|
|
|
|
{
|
|
"SampleTimes": {
|
|
"Vc10ms": 0.01,
|
|
"Vc40ms": 0.04},
|
|
"Rasters": {
|
|
"Vc10ms": [
|
|
"VcPpmImob",
|
|
"VcPpmPsm",
|
|
"VcPpmRc",
|
|
"VcPpmSt",
|
|
"VcPemAlc"],
|
|
"Vc40ms": [
|
|
"VcRegCh"]
|
|
}
|
|
|
|
"""
|
|
return self._unit_cfg
|
|
|
|
def get_unit_cfg_dirs(self):
|
|
"""Get config dirs which matches the project config parameter prjUnitCfgDir.
|
|
|
|
Furthermore, they should be included in the unit definition for this project
|
|
|
|
Returns:
|
|
A list with absolute paths to all unit config dirs
|
|
included in the project
|
|
|
|
"""
|
|
path = os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['prjUnitCfgDir']))
|
|
return self._get_inc_dirs(path)
|
|
|
|
def get_translation_files_dirs(self):
|
|
"""Get translation files directories, specified as a path regex in project
|
|
config by key prjTranslationDir. If key is not present, will fall back to
|
|
prjUnitCfgDir.
|
|
|
|
Returns:
|
|
A dictionary with absolute paths to all translation file dirs included
|
|
in the project
|
|
"""
|
|
|
|
if "prjTranslationDir" not in self._prj_cfg['ProjectInfo']:
|
|
return self.get_unit_cfg_dirs()
|
|
|
|
normpath_dir = os.path.normpath(self._prj_cfg['ProjectInfo']['prjTranslationDir'])
|
|
path = os.path.join(self.get_root_dir(), normpath_dir)
|
|
|
|
all_dirs = glob.glob(path)
|
|
translation_dirs = {}
|
|
for directory in all_dirs:
|
|
file = pathlib.Path(directory).stem
|
|
translation_dirs[file] = directory
|
|
return translation_dirs
|
|
|
|
def get_common_src_dir(self):
|
|
"""Get source dir which matches the project config parameter commonSrcDir.
|
|
|
|
Returns:
|
|
Absolute path to common source dir
|
|
|
|
"""
|
|
return os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']['commonSrcDir']))
|
|
|
|
def get_unit_src_dirs(self):
|
|
"""Get source dirs which matches the project config parameter prjUnitCfgDir.
|
|
|
|
Furthermore, they should be included in the unit definition for this project
|
|
|
|
Returns:
|
|
A list with absolute paths to all source dirs included in the
|
|
project
|
|
|
|
"""
|
|
path = os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['prjUnitSrcDir']))
|
|
return self._get_inc_dirs(path)
|
|
|
|
def get_unit_mdl_dirs(self):
|
|
"""Get source dirs which matches the project config parameter prjUnitCfgDir.
|
|
|
|
Furthermore, they should be included in the unit definition for this project
|
|
|
|
Returns:
|
|
A list with absolute paths to all model dirs included in the
|
|
project
|
|
|
|
"""
|
|
path = os.path.join(self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']
|
|
['prjUnitMdlDir']))
|
|
return self._get_inc_dirs(path)
|
|
|
|
def get_use_global_const(self):
|
|
"""Get the name of the global constant module."""
|
|
return self._prj_cfg['ProjectInfo']['useGlobalConst']
|
|
|
|
def get_use_volatile_globals(self):
|
|
"""Get if global variables should be defined as volatile or not."""
|
|
if 'useVolatileGlobals' in self._prj_cfg['ProjectInfo']:
|
|
return self._prj_cfg['ProjectInfo']['useVolatileGlobals']
|
|
return False
|
|
|
|
def get_use_custom_dummy_spm(self):
|
|
"""Get path to file defining missing internal variables, if any.
|
|
|
|
This file will be used instead of generating VcDummy_spm.c,
|
|
to make it easier to maintain missing internal signals.
|
|
|
|
Returns:
|
|
customDummySpm (os.path): An absolute path to the custom dummy spm file, if existent.
|
|
"""
|
|
if 'customDummySpm' in self._prj_cfg['ProjectInfo']:
|
|
return os.path.join(
|
|
self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']['customDummySpm'])
|
|
)
|
|
return None
|
|
|
|
def get_use_custom_sources(self):
|
|
"""Get path to files with custom handwritten sourcecode, if any.
|
|
|
|
Returns:
|
|
customSources (os.path): A list of absolute paths to custom sources, if existent.
|
|
"""
|
|
if 'customSources' in self._prj_cfg['ProjectInfo']:
|
|
normalized_paths = (os.path.normpath(p) for p in self._prj_cfg['ProjectInfo']['customSources'])
|
|
return [os.path.join(self.get_root_dir(), p) for p in normalized_paths]
|
|
return None
|
|
|
|
def get_if_cfg_dir(self):
|
|
"""Return the directory containing the interface configuration files.
|
|
|
|
Returns:
|
|
An absolute path to the interface configuration files
|
|
|
|
"""
|
|
return os.path.join(self._prj_root_dir,
|
|
self._prj_cfg['ProjectInfo']['interfaceCfgDir'])
|
|
|
|
def get_enum_def_dir(self):
|
|
"""Get path to dir containing simulink enumeration definitions, if any.
|
|
|
|
Returns:
|
|
enumDefDir (os.path): An absolute path to the simulink enumerations, if existent.
|
|
"""
|
|
if 'enumDefDir' in self._prj_cfg['ProjectInfo']:
|
|
return os.path.join(
|
|
self.get_root_dir(),
|
|
os.path.normpath(self._prj_cfg['ProjectInfo']['enumDefDir'])
|
|
)
|
|
return None
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Function for testing the module
|
|
BPC = BuildProjConfig('../../ProjectCfg.json')
|