""" core.build.meta - vxsdk.toml document parser """ import os import toml from core.logger import log from core.pkg.version import version_get from core.build.env import env_config_evaluate, env_pkg_header __all__ = [ 'VxProjectMetaException', 'VxProjectMeta' ] #--- # Public #--- class VxProjectMetaException(Exception): """ Simple exception handling """ class VxProjectMeta(): r""" Represente the project meta-information All meta-information is stored in a file named `vxsdk.toml` at the root of the project directory. This class exposes: ================================== ======================================= Property Description ================================== ======================================= name (str) Project name version (str) Project versions path (str) Project path description (str) Project description type (str) Project type target_support (list,str) List of all supported target ================================== ======================================= Methods Description ================================== ======================================= get_dependencies (list,dict) List of all dependencies information get_build_rules (dict) Building rules information get_env_config (dict) List all env configuration var ================================== ======================================= For the file to be valid, it should expose *at least* the 'project' section with the name of the project. Other information like the project type have default value: ================================== ======================================= Information Default value ================================== ======================================= name (str) Required. version (str) auto. (see core.pkg.version.version_get) type (str) 'bin' description (str) Project description target_support (list,str) [] (support all) ================================== ======================================= Also, this class will perform any vxSDK configuration needed described in the "special" section 'config' which can expose "key/value" which will be added (if not exist) in the vxSDK's configuration file (~/.config/vxsdk/config.toml). Example of a valid 'vxsdk.toml' file: ``` [project] name = 'project' type = 'addin' [dependencies] vxkernel = '^1.0.0' [vxkernel.extra] build = '--static --verbose' [config] 'superh.path.sysroot' = '{path.sysroot}/superh' 'superh.path.sysroot_test' = '{superh.path.sysroot}/test/yes' ``` """ def __init__(self, path, build_prefix='', target='', extra_conf=None): """ Open and parse the vxsdk.toml file @args > path (str) : project path which we can find `vxsdk.toml` file > build_prefix (str) : project build prefix > extra_conf (dict) : extra flags information about particular steps @todo > proper handle exception """ # try to open the file self._path = os.path.abspath(path) with open(self._path + '/vxsdk.toml', encoding='utf-8') as file: self._toml = toml.loads(file.read()) # check mandatory information if 'project' not in self._toml or 'name' not in self._toml['project']: raise VxProjectMetaException( f"'{self._path}': missing project name information" ) # check if the requested target is valid target_support = [] if 'target' in self._toml['project']: target_support = self._toml['project']['target'] if target_support: error = 0 if not target: log.error( f"[{self.name}] no target specified, " " you should specify a target between:" ) error = -1 if target not in target_support: log.error( f"[{self.name}] target specified not supported, " " you should specify a target between:" ) error = -2 if error: for target_name in target_support: log.error(f" - {target_name}") raise VxProjectMetaException( f"[{self.name}] target {target} not supported" ) # setup information and cache self._type = '' self._target = target self._extra_conf = extra_conf self._asset_prefix_list = [] # handle build prefix self._build_prefix = build_prefix if not self._build_prefix: self._build_prefix = f"{self._path}/.vxsdk/{self._target}" def __str__(self): """ Display project information """ content = f"project '{self.name}' - {self.version}\n" content += '\n' content += 'project meta\n' content += ' - path:'.ljust(16) + f'{self.path}\n' content += ' - build:'.ljust(16) + f'{self.build_prefix}\n' content += ' - type:'.ljust(16) + f'{self.type}\n' content += '\n' content += 'Build information\n' #if not self.build: # content += ' default building rules used\n' # content += '\n' #else: # rule_list = self.get_build_rules() # for rule in rule_list: # content += ' - ' # content += f'{rule}:'.ljust(16) # content += f'{rule_list[rule]}\n' # content += '\n' #content += 'Dependencies list\n' #if not self.dependencies: # content += ' No dependencies for this project\n' #else: # for dep in self.dependencies: # content += f" - {dep.name:<16}({dep.version})\n" return content def __repr__(self): return f'{self.name}<{self.version}>({self.type})' #--- # Properties #--- @property def name(self): """ get project name""" return self._toml['project']['name'] @property def path(self): """ get project path""" return self._path @property def description(self): """ get project description""" if 'description' in self._toml['project']: return self._toml['project']['description'] return '' @property def build_prefix(self): """ Return project build path """ return self._build_prefix @property def is_original(self): """ Return if the project is the target or a dependency""" return self._build_prefix == f"{self._path}/.vxsdk" @property def extra_conf(self): """ Return extra pacakge information """ return self._extra_conf @property def type(self): """ get project build rules""" if self._type: return self._type if 'type' not in self._toml['project']: return 'bin' self._type = self._toml['project']['type'] if self._type not in ['app', 'bin', 'lib', 'addin']: log.warn( f"{self.path}: project type '{self._type}' is unknown !" f" Handled like a 'bin' type project" ) return self._type @property def version(self): """ get current package version""" return version_get(self.path) @property def target(self): """ Return build target """ return self._target #--- # Methods #--- def get_dependencies(self): """ Get project dependency list for a particular board target @return > Return a list of dependencies information : [ { 'name' : , 'version' : , 'target' : , 'extra_conf' : }, ... ] """ # help particular section dump def section_dep_fetch(section, target): if 'dependencies' not in section: return [] dep_list = [] for dep_name in section['dependencies']: extra = None if 'extra' in section and dep_name in section['extra']: extra = section['extra'][dep_name] pkg_target = target pkg_version = section['dependencies'][dep_name].split('@') if len(pkg_version) >= 2: pkg_target = pkg_version[1] dep_list.append({ 'name' : dep_name, 'version' : pkg_version[0], 'target' : pkg_target, 'extra_conf' : extra }) return dep_list # fetch dependencies information in common and target-specific section dep_list = section_dep_fetch(self._toml, self._target) if self._target and self._target in self._toml: dep_list += section_dep_fetch( self._toml[self._target], self._target ) return dep_list def get_build_rules(self): """ Get project building rules @args > target (str) - target name @return > a dictionary with all available building step : { 'configure' : , 'build' : , 'install' : , 'uninstall' : , } """ def section_rules_fetch(section): rules = {} for rule in section: if rule in ['configure', 'build', 'install', 'uninstall']: rules[rule] = section[rule] continue log.warn( f"[{self.name}] building rule '{rule}' doesn't exists" ) return rules # check if we need to fetch common rules or target-specific one if self._target \ and self._target in self._toml \ and 'build' in self._toml[self._target]: return section_rules_fetch(self._toml[self._target]['build']) if 'build' in self._toml: return section_rules_fetch(self._toml['build']) return {} def get_env_config(self): """ Get environment configuration @args > target (str) - project target @return > a dictionary with public and private environment variables """ def _key_update(env, key, value): if key in env: env[key] += value return env[key] = value def _dump_section_env_conf(env_config, section): for item in section.items(): if item[0].find('VXSDK_COMMON') == 0: key_name = f"VXSDK_{item[0][13:]}" _key_update(env_config['public'], key_name, item[1]) _key_update(env_config['private'], key_name, item[1]) elif item[0].find('VXSDK_PUBLIC') == 0: key_name = f"VXSDK_{item[0][13:]}" _key_update(env_config['public'], key_name, item[1]) elif item[0].find('VXSDK_PRIVATE') == 0: key_name = f"VXSDK_{item[0][14:]}" _key_update(env_config['private'], key_name, item[1]) else: log.warn(f"Unable to convert {item[0]}, skipped") env_config = { 'public' : {}, 'private' : {} } if self._target \ and self._target in self._toml \ and 'env' in self._toml[self._target]: _dump_section_env_conf(env_config, self._toml[self._target]['env']) if 'env' in self._toml: _dump_section_env_conf(env_config, self._toml['env']) log.debug(f"[{self.name}] get_env_config() pre-eval = {env_config}") # evaluate env content # (todo/8DC1) : isolate the PKG ENV information (shouldn't be edited) pkg_env_head = env_pkg_header(self) env_config_evaluate(env_config['public'], pkg_env_head) env_config_evaluate(env_config['private'], pkg_env_head) # return env information log.debug(f"[{self.name}] get_env_config() post-eval = {env_config}") return env_config def get_assets_prefix_list(self): """ Fetch all assets prefix @return > a list with all assets path """ if self._asset_prefix_list: return self._asset_prefix_list if 'converter' not in self._toml: return [f"{self._path}/assets"] if 'assets_prefix' not in self._toml['converter']: return [f"{self._path}/assets"] self._asset_prefix_list = [] for asset in self._toml['converter']['assets_prefix']: asset_prefix = f"{self._path}/{asset}" if not os.path.exists(asset_prefix): log.warn(f"{self.name}: assets path '{asset}' doesn't exists") continue self._asset_prefix_list.append(asset_prefix) return self._asset_prefix_list