vxSDK/vxsdk/core/build/meta.py

388 lines
14 KiB
Python

"""
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) [<empty>] (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):
"""<property> get project name"""
return self._toml['project']['name']
@property
def path(self):
"""<property> get project path"""
return self._path
@property
def description(self):
"""<property> get project description"""
if 'description' in self._toml['project']:
return self._toml['project']['description']
return ''
@property
def build_prefix(self):
"""<property> Return project build path """
return self._build_prefix
@property
def is_original(self):
"""<property> Return if the project is the target or a dependency"""
return self._build_prefix == f"{self._path}/.vxsdk"
@property
def extra_conf(self):
"""<property> Return extra pacakge information """
return self._extra_conf
@property
def type(self):
"""<property> 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):
"""<property> get current package version"""
return version_get(self.path)
@property
def target(self):
"""<property> 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' : <dep name>,
'version' : <dep target version>,
'target' : <dep build target>,
'extra_conf' : <dep extra configuration>
},
...
]
"""
# 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' : <configure rule string>,
'build' : <build rule string>,
'install' : <install rule string>,
'uninstall' : <uninstall rule string>,
}
"""
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