Glorify config.py (passes pylint)

pull/2458/head
Jimmy Brisson 2016-08-12 13:34:44 -05:00
parent b2156ceef5
commit acf5c40af4
1 changed files with 426 additions and 227 deletions

View File

@ -16,40 +16,52 @@ limitations under the License.
""" """
# Implementation of mbed configuration mechanism # Implementation of mbed configuration mechanism
from copy import deepcopy from tools.utils import json_file_to_dict
from collections import OrderedDict
from tools.utils import json_file_to_dict, ToolException
from tools.targets import Target from tools.targets import Target
import os import os
# Base class for all configuration exceptions # Base class for all configuration exceptions
class ConfigException(Exception): class ConfigException(Exception):
"""Config system only exception. Makes it easier to distinguish config
errors"""
pass pass
# This class keeps information about a single configuration parameter class ConfigParameter(object):
class ConfigParameter: """This class keeps information about a single configuration parameter"""
# name: the name of the configuration parameter
# data: the data associated with the configuration parameter
# unit_name: the unit (target/library/application) that defines this parameter
# unit_ kind: the kind of the unit ("target", "library" or "application")
def __init__(self, name, data, unit_name, unit_kind): def __init__(self, name, data, unit_name, unit_kind):
self.name = self.get_full_name(name, unit_name, unit_kind, allow_prefix = False) """
name: the name of the configuration parameter
data: the data associated with the configuration parameter
unit_name: the unit (target/library/application) that defines this
parameter
unit_ kind: the kind of the unit ("target", "library" or "application")
"""
self.name = self.get_full_name(name, unit_name, unit_kind,
allow_prefix=False)
self.defined_by = self.get_display_name(unit_name, unit_kind) self.defined_by = self.get_display_name(unit_name, unit_kind)
self.set_value(data.get("value", None), unit_name, unit_kind) self.set_value(data.get("value", None), unit_name, unit_kind)
self.help_text = data.get("help", None) self.help_text = data.get("help", None)
self.required = data.get("required", False) self.required = data.get("required", False)
self.macro_name = data.get("macro_name", "MBED_CONF_%s" % self.sanitize(self.name.upper())) self.macro_name = data.get("macro_name", "MBED_CONF_%s" %
self.sanitize(self.name.upper()))
self.config_errors = [] self.config_errors = []
# Return the full (prefixed) name of a parameter.
# If the parameter already has a prefix, check if it is valid
# name: the simple (unqualified) name of the parameter
# unit_name: the unit (target/library/application) that defines this parameter
# unit_kind: the kind of the unit ("target", "library" or "application")
# label: the name of the label in the 'target_config_overrides' section (optional)
# allow_prefix: True to allo the original name to have a prefix, False otherwise
@staticmethod @staticmethod
def get_full_name(name, unit_name, unit_kind, label = None, allow_prefix = True): def get_full_name(name, unit_name, unit_kind, label=None,
allow_prefix=True):
"""Return the full (prefixed) name of a parameter. If the parameter
already has a prefix, check if it is valid
name: the simple (unqualified) name of the parameter
unit_name: the unit (target/library/application) that defines this
parameter
unit_kind: the kind of the unit ("target", "library" or "application")
label: the name of the label in the 'target_config_overrides' section
(optional)
allow_prefix: True to allow the original name to have a prefix, False
otherwise
"""
if name.find('.') == -1: # the name is not prefixed if name.find('.') == -1: # the name is not prefixed
if unit_kind == "target": if unit_kind == "target":
prefix = "target." prefix = "target."
@ -60,24 +72,37 @@ class ConfigParameter:
return prefix + name return prefix + name
# The name has a prefix, so check if it is valid # The name has a prefix, so check if it is valid
if not allow_prefix: if not allow_prefix:
raise ConfigException("Invalid parameter name '%s' in '%s'" % (name, ConfigParameter.get_display_name(unit_name, unit_kind, label))) raise ConfigException("Invalid parameter name '%s' in '%s'" %
(name, ConfigParameter.get_display_name(
unit_name, unit_kind, label)))
temp = name.split(".") temp = name.split(".")
# Check if the parameter syntax is correct (must be unit_name.parameter_name) # Check if the parameter syntax is correct (must be
# unit_name.parameter_name)
if len(temp) != 2: if len(temp) != 2:
raise ConfigException("Invalid parameter name '%s' in '%s'" % (name, ConfigParameter.get_display_name(unit_name, unit_kind, label))) raise ConfigException("Invalid parameter name '%s' in '%s'" %
(name, ConfigParameter.get_display_name(
unit_name, unit_kind, label)))
prefix = temp[0] prefix = temp[0]
# Check if the given parameter prefix matches the expected prefix # Check if the given parameter prefix matches the expected prefix
if (unit_kind == "library" and prefix != unit_name) or (unit_kind == "target" and prefix != "target"): if (unit_kind == "library" and prefix != unit_name) or \
raise ConfigException("Invalid prefix '%s' for parameter name '%s' in '%s'" % (prefix, name, ConfigParameter.get_display_name(unit_name, unit_kind, label))) (unit_kind == "target" and prefix != "target"):
raise ConfigException(
"Invalid prefix '%s' for parameter name '%s' in '%s'" %
(prefix, name, ConfigParameter.get_display_name(
unit_name, unit_kind, label)))
return name return name
# Return the name displayed for a unit when interogating the origin
# and the last set place of a parameter
# unit_name: the unit (target/library/application) that defines this parameter
# unit_kind: the kind of the unit ("target", "library" or "application")
# label: the name of the label in the 'target_config_overrides' section (optional)
@staticmethod @staticmethod
def get_display_name(unit_name, unit_kind, label = None): def get_display_name(unit_name, unit_kind, label=None):
"""Return the name displayed for a unit when interrogating the origin
and the last set place of a parameter
unit_name: the unit (target/library/application) that defines this
parameter
unit_kind: the kind of the unit ("target", "library" or "application")
label: the name of the label in the 'target_config_overrides' section
(optional)
"""
if unit_kind == "target": if unit_kind == "target":
return "target:" + unit_name return "target:" + unit_name
elif unit_kind == "application": elif unit_kind == "application":
@ -85,33 +110,42 @@ class ConfigParameter:
else: # library else: # library
return "library:%s%s" % (unit_name, "[%s]" % label if label else "") return "library:%s%s" % (unit_name, "[%s]" % label if label else "")
# "Sanitize" a name so that it is a valid C macro name
# Currently it simply replaces '.' and '-' with '_'
# name: the un-sanitized name.
@staticmethod @staticmethod
def sanitize(name): def sanitize(name):
""" "Sanitize" a name so that it is a valid C macro name Currently it
simply replaces '.' and '-' with '_' name: the un-sanitized name.
"""
return name.replace('.', '_').replace('-', '_') return name.replace('.', '_').replace('-', '_')
# Sets a value for this parameter, remember the place where it was set. def set_value(self, value, unit_name, unit_kind, label=None):
# If the value is a boolean, it is converted to 1 (for True) or to 0 (for False). """ Sets a value for this parameter, remember the place where it was
# value: the value of the parameter set. If the value is a boolean, it is converted to 1 (for True) or
# unit_name: the unit (target/library/application) that defines this parameter to 0 (for False).
# unit_ kind: the kind of the unit ("target", "library" or "application")
# label: the name of the label in the 'target_config_overrides' section (optional) value: the value of the parameter
def set_value(self, value, unit_name, unit_kind, label = None): unit_name: the unit (target/library/application) that defines this
parameter
unit_ kind: the kind of the unit ("target", "library" or "application")
label: the name of the label in the 'target_config_overrides' section
(optional)
"""
self.value = int(value) if isinstance(value, bool) else value self.value = int(value) if isinstance(value, bool) else value
self.set_by = self.get_display_name(unit_name, unit_kind, label) self.set_by = self.get_display_name(unit_name, unit_kind, label)
# Return the string representation of this configuration parameter
def __str__(self): def __str__(self):
"""Return the string representation of this configuration parameter"""
if self.value is not None: if self.value is not None:
return '%s = %s (macro name: "%s")' % (self.name, self.value, self.macro_name) return '%s = %s (macro name: "%s")' % \
(self.name, self.value, self.macro_name)
else: else:
return '%s has no value' % self.name return '%s has no value' % self.name
# Return a verbose description of this configuration paramater as a string
def get_verbose_description(self): def get_verbose_description(self):
desc = "Name: %s%s\n" % (self.name, " (required parameter)" if self.required else "") """Return a verbose description of this configuration paramater as a
string
"""
desc = "Name: %s%s\n" % \
(self.name, " (required parameter)" if self.required else "")
if self.help_text: if self.help_text:
desc += " Description: %s\n" % self.help_text desc += " Description: %s\n" % self.help_text
desc += " Defined by: %s\n" % self.defined_by desc += " Defined by: %s\n" % self.defined_by
@ -121,69 +155,139 @@ class ConfigParameter:
desc += " Value: %s (set by %s)" % (self.value, self.set_by) desc += " Value: %s (set by %s)" % (self.value, self.set_by)
return desc return desc
# A representation of a configuration macro. It handles both macros without a value (MACRO) class ConfigMacro(object):
# and with a value (MACRO=VALUE) """ A representation of a configuration macro. It handles both macros
class ConfigMacro: without a value (MACRO) and with a value (MACRO=VALUE)
"""
def __init__(self, name, unit_name, unit_kind): def __init__(self, name, unit_name, unit_kind):
self.name = name self.name = name
self.defined_by = ConfigParameter.get_display_name(unit_name, unit_kind) self.defined_by = ConfigParameter.get_display_name(unit_name, unit_kind)
if name.find("=") != -1: if name.find("=") != -1:
tmp = name.split("=") tmp = name.split("=")
if len(tmp) != 2: if len(tmp) != 2:
raise ValueError("Invalid macro definition '%s' in '%s'" % (name, self.defined_by)) raise ValueError("Invalid macro definition '%s' in '%s'" %
(name, self.defined_by))
self.macro_name = tmp[0] self.macro_name = tmp[0]
self.macro_value = tmp[1] self.macro_value = tmp[1]
else: else:
self.macro_name = name self.macro_name = name
self.macro_value = None self.macro_value = None
# Representation of overrides for cumulative attributes class ConfigCumulativeOverride(object):
class ConfigCumulativeOverride: """Representation of overrides for cumulative attributes"""
def __init__(self, name, additions=set(), removals=set(), strict=False): def __init__(self, name, additions=None, removals=None, strict=False):
self.name = name self.name = name
if additions:
self.additions = set(additions) self.additions = set(additions)
else:
self.additions = set()
if removals:
self.removals = set(removals) self.removals = set(removals)
else:
self.removals = set()
self.strict = strict self.strict = strict
# Add attr to the cumulative override
def remove_cumulative_overrides(self, overrides): def remove_cumulative_overrides(self, overrides):
"""Add attr to the cumulative override"""
for override in overrides: for override in overrides:
if override in self.additions: if override in self.additions:
raise ConfigException("Configuration conflict. The %s %s both added and removed." % (self.name[:-1], override)) raise ConfigException(
"Configuration conflict. The %s %s both added and removed."
% (self.name[:-1], override))
self.removals |= set(overrides) self.removals |= set(overrides)
# Remove attr from the cumulative overrides
def add_cumulative_overrides(self, overrides): def add_cumulative_overrides(self, overrides):
"""Remove attr from the cumulative overrides"""
for override in overrides: for override in overrides:
if (override in self.removals or (self.strict and override not in self.additions)): if override in self.removals or \
raise ConfigException("Configuration conflict. The %s %s both added and removed." % (self.name[:-1], override)) (self.strict and override not in self.additions):
raise ConfigException(
"Configuration conflict. The %s %s both added and removed."
% (self.name[:-1], override))
self.additions |= set(overrides) self.additions |= set(overrides)
# Enable strict set of cumulative overrides for the specified attr
def strict_cumulative_overrides(self, overrides): def strict_cumulative_overrides(self, overrides):
"""Enable strict set of cumulative overrides for the specified attr"""
self.remove_cumulative_overrides(self.additions - set(overrides)) self.remove_cumulative_overrides(self.additions - set(overrides))
self.add_cumulative_overrides(overrides) self.add_cumulative_overrides(overrides)
self.strict = True self.strict = True
def update_target(self, target): def update_target(self, target):
setattr(target, self.name, list( """Update the attributes of a target based on this override"""
(set(getattr(target, self.name, [])) | self.additions) - self.removals)) setattr(target, self.name,
list((set(getattr(target, self.name, []))
| self.additions) - self.removals))
def _process_config_parameters(data, params, unit_name, unit_kind):
"""Process a "config_parameters" section in either a target, a library
or the application
# 'Config' implements the mbed configuration mechanism data: a dictionary with the configuration parameters
class Config: params: storage for the discovered configuration parameters
# Libraries and applications have different names for their configuration files unit_name: the unit (target/library/application) that defines this parameter
unit_kind: the kind of the unit ("target", "library" or "application")
"""
for name, val in data.items():
full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind)
# If the parameter was already defined, raise an error
if full_name in params:
raise ConfigException(
"Parameter name '%s' defined in both '%s' and '%s'" %
(name, ConfigParameter.get_display_name(unit_name, unit_kind),
params[full_name].defined_by))
# Otherwise add it to the list of known parameters
# If "val" is not a dictionary, this is a shortcut definition,
# otherwise it is a full definition
params[full_name] = ConfigParameter(name, val if isinstance(val, dict)
else {"value": val}, unit_name,
unit_kind)
return params
def _process_macros(mlist, macros, unit_name, unit_kind):
"""Process a macro definition, checking for incompatible duplicate
definitions
mlist: list of macro names to process
macros: dictionary with currently discovered macros
unit_name: the unit (library/application) that defines this macro
unit_kind: the kind of the unit ("library" or "application")
"""
for mname in mlist:
macro = ConfigMacro(mname, unit_name, unit_kind)
if (macro.macro_name in macros) and \
(macros[macro.macro_name].name != mname):
# Found an incompatible definition of the macro in another module,
# so raise an error
full_unit_name = ConfigParameter.get_display_name(unit_name,
unit_kind)
raise ConfigException(
("Macro '%s' defined in both '%s' and '%s'"
% (macro.macro_name, macros[macro.macro_name].defined_by,
full_unit_name)) +
" with incompatible values")
macros[macro.macro_name] = macro
class Config(object):
"""'Config' implements the mbed configuration mechanism"""
# Libraries and applications have different names for their configuration
# files
__mbed_app_config_name = "mbed_app.json" __mbed_app_config_name = "mbed_app.json"
__mbed_lib_config_name = "mbed_lib.json" __mbed_lib_config_name = "mbed_lib.json"
# Allowed keys in configuration dictionaries # Allowed keys in configuration dictionaries
# (targets can have any kind of keys, so this validation is not applicable to them) # (targets can have any kind of keys, so this validation is not applicable
# to them)
__allowed_keys = { __allowed_keys = {
"library": set(["name", "config", "target_overrides", "macros", "__config_path"]), "library": set(["name", "config", "target_overrides", "macros",
"application": set(["config", "custom_targets", "target_overrides", "macros", "__config_path"]) "__config_path"]),
"application": set(["config", "custom_targets", "target_overrides",
"macros", "__config_path"])
} }
# Allowed features in configurations # Allowed features in configurations
@ -191,29 +295,43 @@ class Config:
"UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE" "UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE"
] ]
# The initialization arguments for Config are: def __init__(self, target, top_level_dirs=None):
# target: the name of the mbed target used for this configuration instance """
# top_level_dirs: a list of top level source directories (where mbed_abb_config.json could be found) The initialization arguments for Config are:
# __init__ will look for the application configuration file in top_level_dirs. target: the name of the mbed target used for this configuration
# If found once, it'll parse it and check if it has a custom_targets function. instance
# If it does, it'll update the list of targets if need. top_level_dirs: a list of top level source directories (where
# If found more than once, an exception is raised mbed_abb_config.json could be found)
# top_level_dirs can be None (in this case, mbed_app_config.json will not be searched) __init__ will look for the application configuration file in
def __init__(self, target, top_level_dirs = []): top_level_dirs.
If found once, it'll parse it and check if it has a custom_targets
function.
If it does, it'll update the list of targets if need.
If found more than once, an exception is raised
top_level_dirs can be None (in this case, mbed_app_config.json will not
be searched)
"""
app_config_location = None app_config_location = None
for s in (top_level_dirs or []): for directory in top_level_dirs or []:
full_path = os.path.join(s, self.__mbed_app_config_name) full_path = os.path.join(directory, self.__mbed_app_config_name)
if os.path.isfile(full_path): if os.path.isfile(full_path):
if app_config_location is not None: if app_config_location is not None:
raise ConfigException("Duplicate '%s' file in '%s' and '%s'" % (self.__mbed_app_config_name, app_config_location, full_path)) raise ConfigException("Duplicate '%s' file in '%s' and '%s'"
% (self.__mbed_app_config_name,
app_config_location, full_path))
else: else:
app_config_location = full_path app_config_location = full_path
self.app_config_data = json_file_to_dict(app_config_location) if app_config_location else {} self.app_config_data = json_file_to_dict(app_config_location) \
if app_config_location else {}
# Check the keys in the application configuration data # Check the keys in the application configuration data
unknown_keys = set(self.app_config_data.keys()) - self.__allowed_keys["application"] unknown_keys = set(self.app_config_data.keys()) - \
self.__allowed_keys["application"]
if unknown_keys: if unknown_keys:
raise ConfigException("Unknown key(s) '%s' in %s" % (",".join(unknown_keys), self.__mbed_app_config_name)) raise ConfigException("Unknown key(s) '%s' in %s" %
# Update the list of targets with the ones defined in the application config, if applicable (",".join(unknown_keys),
self.__mbed_app_config_name))
# Update the list of targets with the ones defined in the application
# config, if applicable
Target.add_py_targets(self.app_config_data.get("custom_targets", {})) Target.add_py_targets(self.app_config_data.get("custom_targets", {}))
self.lib_config_data = {} self.lib_config_data = {}
# Make sure that each config is processed only once # Make sure that each config is processed only once
@ -221,229 +339,281 @@ class Config:
self.target = target if isinstance(target, basestring) else target.name self.target = target if isinstance(target, basestring) else target.name
self.target_labels = Target.get_target(self.target).get_labels() self.target_labels = Target.get_target(self.target).get_labels()
self.cumulative_overrides = { key: ConfigCumulativeOverride(key) self.cumulative_overrides = {key: ConfigCumulativeOverride(key)
for key in Target._Target__cumulative_attributes } for key in
Target._Target__cumulative_attributes}
self._process_config_and_overrides(self.app_config_data, {}, "app", "application") self._process_config_and_overrides(self.app_config_data, {}, "app",
"application")
self.target_labels = Target.get_target(self.target).get_labels() self.target_labels = Target.get_target(self.target).get_labels()
self.config_errors = None
# Add one or more configuration files
def add_config_files(self, flist): def add_config_files(self, flist):
for f in flist: """Add one or more configuration files"""
if not f.endswith(self.__mbed_lib_config_name): for config_file in flist:
if not config_file.endswith(self.__mbed_lib_config_name):
continue continue
full_path = os.path.normpath(os.path.abspath(f)) full_path = os.path.normpath(os.path.abspath(config_file))
# Check that we didn't already process this file # Check that we didn't already process this file
if self.processed_configs.has_key(full_path): if self.processed_configs.has_key(full_path):
continue continue
self.processed_configs[full_path] = True self.processed_configs[full_path] = True
# Read the library configuration and add a "__full_config_path" attribute to it # Read the library configuration and add a "__full_config_path"
cfg = json_file_to_dict(f) # attribute to it
cfg = json_file_to_dict(config_file)
cfg["__config_path"] = full_path cfg["__config_path"] = full_path
if "name" not in cfg: if "name" not in cfg:
raise ConfigException("Library configured at %s has no name field." % full_path) raise ConfigException(
# If there's already a configuration for a module with the same name, exit with error "Library configured at %s has no name field." % full_path)
# If there's already a configuration for a module with the same
# name, exit with error
if self.lib_config_data.has_key(cfg["name"]): if self.lib_config_data.has_key(cfg["name"]):
raise ConfigException("Library name '%s' is not unique (defined in '%s' and '%s')" % (cfg["name"], full_path, self.lib_config_data[cfg["name"]]["__config_path"])) raise ConfigException(
"Library name '%s' is not unique (defined in '%s' and '%s')"
% (cfg["name"], full_path,
self.lib_config_data[cfg["name"]]["__config_path"]))
self.lib_config_data[cfg["name"]] = cfg self.lib_config_data[cfg["name"]] = cfg
# Helper function: process a "config_parameters" section in either a target, a library or the application
# data: a dictionary with the configuration parameters
# params: storage for the discovered configuration parameters
# unit_name: the unit (target/library/application) that defines this parameter
# unit_kind: the kind of the unit ("target", "library" or "application")
def _process_config_parameters(self, data, params, unit_name, unit_kind):
for name, v in data.items():
full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind)
# If the parameter was already defined, raise an error
if full_name in params:
raise ConfigException("Parameter name '%s' defined in both '%s' and '%s'" % (name, ConfigParameter.get_display_name(unit_name, unit_kind), params[full_name].defined_by))
# Otherwise add it to the list of known parameters
# If "v" is not a dictionary, this is a shortcut definition, otherwise it is a full definition
params[full_name] = ConfigParameter(name, v if isinstance(v, dict) else {"value": v}, unit_name, unit_kind)
return params
# Helper function: process "config_parameters" and "target_config_overrides" in a given dictionary
# data: the configuration data of the library/appliation
# params: storage for the discovered configuration parameters
# unit_name: the unit (library/application) that defines this parameter
# unit_kind: the kind of the unit ("library" or "application")
def _process_config_and_overrides(self, data, params, unit_name, unit_kind): def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
"""Process "config_parameters" and "target_config_overrides" in a given
dictionary
data: the configuration data of the library/appliation
params: storage for the discovered configuration parameters
unit_name: the unit (library/application) that defines this parameter
unit_kind: the kind of the unit ("library" or "application")
"""
self.config_errors = [] self.config_errors = []
self._process_config_parameters(data.get("config", {}), params, unit_name, unit_kind) _process_config_parameters(data.get("config", {}), params, unit_name,
unit_kind)
for label, overrides in data.get("target_overrides", {}).items(): for label, overrides in data.get("target_overrides", {}).items():
# If the label is defined by the target or it has the special value "*", process the overrides # If the label is defined by the target or it has the special value
# "*", process the overrides
if (label == '*') or (label in self.target_labels): if (label == '*') or (label in self.target_labels):
# Check for invalid cumulative overrides in libraries # Check for invalid cumulative overrides in libraries
if (unit_kind == 'library' and if (unit_kind == 'library' and
any(attr.startswith('target.extra_labels') for attr in overrides.iterkeys())): any(attr.startswith('target.extra_labels') for attr
raise ConfigException("Target override '%s' in '%s' is only allowed at the application level" in overrides.iterkeys())):
% ("target.extra_labels", ConfigParameter.get_display_name(unit_name, unit_kind, label))) raise ConfigException(
"Target override 'target.extra_labels' in " +
ConfigParameter.get_display_name(unit_name, unit_kind,
label) +
" is only allowed at the application level")
# Parse out cumulative overrides # Parse out cumulative overrides
for attr, cumulatives in self.cumulative_overrides.iteritems(): for attr, cumulatives in self.cumulative_overrides.iteritems():
if 'target.'+attr in overrides: if 'target.'+attr in overrides:
cumulatives.strict_cumulative_overrides(overrides['target.'+attr]) cumulatives.strict_cumulative_overrides(
overrides['target.'+attr])
del overrides['target.'+attr] del overrides['target.'+attr]
if 'target.'+attr+'_add' in overrides: if 'target.'+attr+'_add' in overrides:
cumulatives.add_cumulative_overrides(overrides['target.'+attr+'_add']) cumulatives.add_cumulative_overrides(
overrides['target.'+attr+'_add'])
del overrides['target.'+attr+'_add'] del overrides['target.'+attr+'_add']
if 'target.'+attr+'_remove' in overrides: if 'target.'+attr+'_remove' in overrides:
cumulatives.remove_cumulative_overrides(overrides['target.'+attr+'_remove']) cumulatives.remove_cumulative_overrides(
overrides['target.'+attr+'_remove'])
del overrides['target.'+attr+'_remove'] del overrides['target.'+attr+'_remove']
# Consider the others as overrides # Consider the others as overrides
for name, v in overrides.items(): for name, val in overrides.items():
# Get the full name of the parameter # Get the full name of the parameter
full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind, label) full_name = ConfigParameter.get_full_name(name, unit_name,
unit_kind, label)
if full_name in params: if full_name in params:
params[full_name].set_value(v, unit_name, unit_kind, label) params[full_name].set_value(val, unit_name, unit_kind,
label)
else: else:
self.config_errors.append(ConfigException("Attempt to override undefined parameter '%s' in '%s'" self.config_errors.append(
% (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label)))) ConfigException(
"Attempt to override undefined parameter" +
(" '%s' in '%s'"
% (full_name,
ConfigParameter.get_display_name(unit_name,
unit_kind,
label)))))
for cumulatives in self.cumulative_overrides.itervalues(): for cumulatives in self.cumulative_overrides.itervalues():
cumulatives.update_target(Target.get_target(self.target)) cumulatives.update_target(Target.get_target(self.target))
return params return params
# Read and interpret configuration data defined by targets
def get_target_config_data(self): def get_target_config_data(self):
# We consider the resolution order for our target and sort it by level reversed, """Read and interpret configuration data defined by targets.
# so that we first look at the top level target (the parent), then its direct children,
# then the children's children and so on, until we reach self.target We consider the resolution order for our target and sort it by level
# TODO: this might not work so well in some multiple inheritance scenarios reversed, so that we first look at the top level target (the parent),
# At each step, look at two keys of the target data: then its direct children, then the children's children and so on,
# - config_parameters: used to define new configuration parameters until we reach self.target
# - config_overrides: used to override already defined configuration parameters TODO: this might not work so well in some multiple inheritance scenarios
At each step, look at two keys of the target data:
- config_parameters: used to define new configuration parameters
- config_overrides: used to override already defined configuration
parameters
"""
params, json_data = {}, Target.get_json_target_data() params, json_data = {}, Target.get_json_target_data()
resolution_order = [e[0] for e in sorted(Target.get_target(self.target).resolution_order, key = lambda e: e[1], reverse = True)] resolution_order = [e[0] for e
in sorted(
Target.get_target(self.target).resolution_order,
key=lambda e: e[1], reverse=True)]
for tname in resolution_order: for tname in resolution_order:
# Read the target data directly from its description # Read the target data directly from its description
t = json_data[tname] target_data = json_data[tname]
# Process definitions first # Process definitions first
self._process_config_parameters(t.get("config", {}), params, tname, "target") _process_config_parameters(target_data.get("config", {}), params,
tname, "target")
# Then process overrides # Then process overrides
for name, v in t.get("overrides", {}).items(): for name, val in target_data.get("overrides", {}).items():
full_name = ConfigParameter.get_full_name(name, tname, "target") full_name = ConfigParameter.get_full_name(name, tname, "target")
# If the parameter name is not defined or if there isn't a path from this target to the target where the # If the parameter name is not defined or if there isn't a path
# parameter was defined in the target inheritance tree, raise an error # from this target to the target where the parameter was defined
# We need to use 'defined_by[7:]' to remove the "target:" prefix from defined_by # in the target inheritance tree, raise an error We need to use
if (not full_name in params) or (not params[full_name].defined_by[7:] in Target.get_target(tname).resolution_order_names): # 'defined_by[7:]' to remove the "target:" prefix from
raise ConfigException("Attempt to override undefined parameter '%s' in '%s'" % (name, ConfigParameter.get_display_name(tname, "target"))) # defined_by
if (full_name not in params) or \
(params[full_name].defined_by[7:] not in
Target.get_target(tname).resolution_order_names):
raise ConfigException(
"Attempt to override undefined parameter '%s' in '%s'"
% (name,
ConfigParameter.get_display_name(tname, "target")))
# Otherwise update the value of the parameter # Otherwise update the value of the parameter
params[full_name].set_value(v, tname, "target") params[full_name].set_value(val, tname, "target")
return params return params
# Helper function: process a macro definition, checking for incompatible duplicate definitions
# mlist: list of macro names to process
# macros: dictionary with currently discovered macros
# unit_name: the unit (library/application) that defines this macro
# unit_kind: the kind of the unit ("library" or "application")
def _process_macros(self, mlist, macros, unit_name, unit_kind):
for mname in mlist:
m = ConfigMacro(mname, unit_name, unit_kind)
if (m.macro_name in macros) and (macros[m.macro_name].name != mname):
# Found an incompatible definition of the macro in another module, so raise an error
full_unit_name = ConfigParameter.get_display_name(unit_name, unit_kind)
raise ConfigException("Macro '%s' defined in both '%s' and '%s' with incompatible values" % (m.macro_name, macros[m.macro_name].defined_by, full_unit_name))
macros[m.macro_name] = m
# Read and interpret configuration data defined by libs
# It is assumed that "add_config_files" above was already called and the library configuration data
# exists in self.lib_config_data
def get_lib_config_data(self): def get_lib_config_data(self):
""" Read and interpret configuration data defined by libs It is assumed
that "add_config_files" above was already called and the library
configuration data exists in self.lib_config_data
"""
all_params, macros = {}, {} all_params, macros = {}, {}
for lib_name, lib_data in self.lib_config_data.items(): for lib_name, lib_data in self.lib_config_data.items():
unknown_keys = set(lib_data.keys()) - self.__allowed_keys["library"] unknown_keys = set(lib_data.keys()) - self.__allowed_keys["library"]
if unknown_keys: if unknown_keys:
raise ConfigException("Unknown key(s) '%s' in %s" % (",".join(unknown_keys), lib_name)) raise ConfigException("Unknown key(s) '%s' in %s" %
all_params.update(self._process_config_and_overrides(lib_data, {}, lib_name, "library")) (",".join(unknown_keys), lib_name))
self._process_macros(lib_data.get("macros", []), macros, lib_name, "library") all_params.update(self._process_config_and_overrides(lib_data, {},
lib_name,
"library"))
_process_macros(lib_data.get("macros", []), macros, lib_name,
"library")
return all_params, macros return all_params, macros
# Read and interpret the configuration data defined by the target
# The target can override any configuration parameter, as well as define its own configuration data
# params: the dictionary with configuration parameters found so far (in the target and in libraries)
# macros: the list of macros defined in the configuration
def get_app_config_data(self, params, macros): def get_app_config_data(self, params, macros):
app_cfg = self.app_config_data """ Read and interpret the configuration data defined by the target The
# The application can have a "config_parameters" and a "target_config_overrides" section just like a library target can override any configuration parameter, as well as define its
self._process_config_and_overrides(app_cfg, params, "app", "application") own configuration data
# The application can also defined macros
self._process_macros(app_cfg.get("macros", []), macros, "app", "application") params: the dictionary with configuration parameters found so far (in
the target and in libraries)
macros: the list of macros defined in the configuration
"""
app_cfg = self.app_config_data
# The application can have a "config_parameters" and a
# "target_config_overrides" section just like a library
self._process_config_and_overrides(app_cfg, params, "app",
"application")
# The application can also defined macros
_process_macros(app_cfg.get("macros", []), macros, "app",
"application")
# Return the configuration data in two parts:
# - params: a dictionary with (name, ConfigParam) entries
# - macros: the list of macros defined with "macros" in libraries and in the application (as ConfigMacro instances)
def get_config_data(self): def get_config_data(self):
""" Return the configuration data in two parts:
- params: a dictionary with (name, ConfigParam) entries
- macros: the list of macros defined with "macros" in libraries and
in the application (as ConfigMacro instances)
"""
all_params = self.get_target_config_data() all_params = self.get_target_config_data()
lib_params, macros = self.get_lib_config_data() lib_params, macros = self.get_lib_config_data()
all_params.update(lib_params) all_params.update(lib_params)
self.get_app_config_data(all_params, macros) self.get_app_config_data(all_params, macros)
return all_params, macros return all_params, macros
# Helper: verify if there are any required parameters without a value in 'params'
@staticmethod @staticmethod
def _check_required_parameters(params): def _check_required_parameters(params):
for p in params.values(): """verify if there are any required parameters without a value in
if p.required and (p.value is None): 'params'
raise ConfigException("Required parameter '%s' defined by '%s' doesn't have a value" % (p.name, p.defined_by)) """
for param in params.values():
if param.required and (param.value is None):
raise ConfigException("Required parameter '" + param.name +
"' defined by '" + param.defined_by +
"' doesn't have a value")
# Return the macro definitions generated for a dictionary of configuration parameters
# params: a dictionary of (name, ConfigParameters instance) mappings
@staticmethod @staticmethod
def parameters_to_macros(params): def parameters_to_macros(params):
return ['%s=%s' % (m.macro_name, m.value) for m in params.values() if m.value is not None] """ Return the macro definitions generated for a dictionary of
configuration parameters
params: a dictionary of (name, ConfigParameters instance) mappings
"""
return ['%s=%s' % (m.macro_name, m.value) for m in params.values()
if m.value is not None]
# Return the macro definitions generated for a dictionary of ConfigMacros (as returned by get_config_data)
# params: a dictionary of (name, ConfigMacro instance) mappings
@staticmethod @staticmethod
def config_macros_to_macros(macros): def config_macros_to_macros(macros):
""" Return the macro definitions generated for a dictionary of
ConfigMacros (as returned by get_config_data)
params: a dictionary of (name, ConfigMacro instance) mappings
"""
return [m.name for m in macros.values()] return [m.name for m in macros.values()]
# Return the configuration data converted to a list of C macros
# config - configuration data as (ConfigParam instances, ConfigMacro instances) tuple
# (as returned by get_config_data())
@staticmethod @staticmethod
def config_to_macros(config): def config_to_macros(config):
""" Return the configuration data converted to a list of C macros
config - configuration data as (ConfigParam instances, ConfigMacro
instances) tuple (as returned by get_config_data())
"""
params, macros = config[0], config[1] params, macros = config[0], config[1]
Config._check_required_parameters(params) Config._check_required_parameters(params)
return Config.config_macros_to_macros(macros) + Config.parameters_to_macros(params) return Config.config_macros_to_macros(macros) + \
Config.parameters_to_macros(params)
# Return the configuration data converted to a list of C macros
def get_config_data_macros(self): def get_config_data_macros(self):
""" Return the configuration data converted to a list of C macros
"""
return self.config_to_macros(self.get_config_data()) return self.config_to_macros(self.get_config_data())
# Returns any features in the configuration data
def get_features(self): def get_features(self):
""" Returns any features in the configuration data
"""
params, _ = self.get_config_data() params, _ = self.get_config_data()
self._check_required_parameters(params) self._check_required_parameters(params)
self.cumulative_overrides['features'].update_target(Target.get_target(self.target)) self.cumulative_overrides['features']\
.update_target(Target.get_target(self.target))
features = Target.get_target(self.target).features features = Target.get_target(self.target).features
for feature in features: for feature in features:
if feature not in self.__allowed_features: if feature not in self.__allowed_features:
raise ConfigException("Feature '%s' is not a supported features" % feature) raise ConfigException(
"Feature '%s' is not a supported features" % feature)
return features return features
# Validate configuration settings. This either returns True or raises an exception
def validate_config(self): def validate_config(self):
""" Validate configuration settings. This either returns True or
raises an exception
"""
if self.config_errors: if self.config_errors:
raise self.config_errors[0] raise self.config_errors[0]
return True return True
# Loads configuration data from resources. Also expands resources based on defined features settings
def load_resources(self, resources): def load_resources(self, resources):
""" Loads configuration data from resources. Also expands resources
based on defined features settings
"""
# Update configuration files until added features creates no changes # Update configuration files until added features creates no changes
prev_features = set() prev_features = set()
while True: while True:
# Add/update the configuration with any .json files found while scanning # Add/update the configuration with any .json files found while
# scanning
self.add_config_files(resources.json_files) self.add_config_files(resources.json_files)
# Add features while we find new ones # Add features while we find new ones
@ -460,14 +630,18 @@ class Config:
return resources return resources
# Return the configuration data converted to the content of a C header file,
# meant to be included to a C/C++ file. The content is returned as a string.
# If 'fname' is given, the content is also written to the file called "fname".
# WARNING: if 'fname' names an existing file, that file will be overwritten!
# config - configuration data as (ConfigParam instances, ConfigMacro instances) tuple
# (as returned by get_config_data())
@staticmethod @staticmethod
def config_to_header(config, fname = None): def config_to_header(config, fname=None):
""" Return the configuration data converted to the content of a C header
file, meant to be included to a C/C++ file. The content is returned as a
string. If 'fname' is given, the content is also written to the file
called "fname".
WARNING: if 'fname' names an existing file, that file will be
overwritten!
config - configuration data as (ConfigParam instances, ConfigMacro
instances) tuple (as returned by get_config_data())
"""
params, macros = config[0], config[1] params, macros = config[0], config[1]
Config._check_required_parameters(params) Config._check_required_parameters(params)
header_data = "// Automatically generated configuration file.\n" header_data = "// Automatically generated configuration file.\n"
@ -475,37 +649,62 @@ class Config:
header_data += "#ifndef __MBED_CONFIG_DATA__\n" header_data += "#ifndef __MBED_CONFIG_DATA__\n"
header_data += "#define __MBED_CONFIG_DATA__\n\n" header_data += "#define __MBED_CONFIG_DATA__\n\n"
# Compute maximum length of macro names for proper alignment # Compute maximum length of macro names for proper alignment
max_param_macro_name_len = max([len(m.macro_name) for m in params.values() if m.value is not None]) if params else 0 max_param_macro_name_len = (max([len(m.macro_name) for m
max_direct_macro_name_len = max([len(m.macro_name) for m in macros.values()]) if macros else 0 in params.values()
max_macro_name_len = max(max_param_macro_name_len, max_direct_macro_name_len) if m.value is not None])
if params else 0)
max_direct_macro_name_len = (max([len(m.macro_name) for m
in macros.values()])
if macros else 0)
max_macro_name_len = max(max_param_macro_name_len,
max_direct_macro_name_len)
# Compute maximum length of macro values for proper alignment # Compute maximum length of macro values for proper alignment
max_param_macro_val_len = max([len(str(m.value)) for m in params.values() if m.value is not None]) if params else 0 max_param_macro_val_len = (max([len(str(m.value)) for m
max_direct_macro_val_len = max([len(m.macro_value or "") for m in macros.values()]) if macros else 0 in params.values()
max_macro_val_len = max(max_param_macro_val_len, max_direct_macro_val_len) if m.value is not None])
if params else 0)
max_direct_macro_val_len = max([len(m.macro_value or "") for m
in macros.values()]) if macros else 0
max_macro_val_len = max(max_param_macro_val_len,
max_direct_macro_val_len)
# Generate config parameters first # Generate config parameters first
if params: if params:
header_data += "// Configuration parameters\n" header_data += "// Configuration parameters\n"
for m in params.values(): for macro in params.values():
if m.value is not None: if macro.value is not None:
header_data += "#define {0:<{1}} {2!s:<{3}} // set by {4}\n".format(m.macro_name, max_macro_name_len, m.value, max_macro_val_len, m.set_by) header_data += ("#define {0:<{1}} {2!s:<{3}} " +
"// set by {4}\n")\
.format(macro.macro_name, max_macro_name_len,
macro.value, max_macro_val_len, macro.set_by)
# Then macros # Then macros
if macros: if macros:
header_data += "// Macros\n" header_data += "// Macros\n"
for m in macros.values(): for macro in macros.values():
if m.macro_value: if macro.macro_value:
header_data += "#define {0:<{1}} {2!s:<{3}} // defined by {4}\n".format(m.macro_name, max_macro_name_len, m.macro_value, max_macro_val_len, m.defined_by) header_data += ("#define {0:<{1}} {2!s:<{3}}" +
" // defined by {4}\n")\
.format(m.macro_name, max_macro_name_len, m.macro_value,
max_macro_val_len, m.defined_by)
else: else:
header_data += "#define {0:<{1}} // defined by {2}\n".format(m.macro_name, max_macro_name_len + max_macro_val_len + 1, m.defined_by) header_data += ("#define {0:<{1}}" +
" // defined by {2}\n")\
.format(m.macro_name,
max_macro_name_len + max_macro_val_len + 1,
m.defined_by)
header_data += "\n#endif\n" header_data += "\n#endif\n"
# If fname is given, write "header_data" to it # If fname is given, write "header_data" to it
if fname: if fname:
with open(fname, "wt") as f: with open(fname, "w+") as file_desc:
f.write(header_data) file_desc.write(header_data)
return header_data return header_data
# Return the configuration data converted to the content of a C header file, def get_config_data_header(self, fname=None):
# meant to be included to a C/C++ file. The content is returned as a string. """ Return the configuration data converted to the content of a C
# If 'fname' is given, the content is also written to the file called "fname". header file, meant to be included to a C/C++ file. The content is
# WARNING: if 'fname' names an existing file, that file will be overwritten! returned as a string. If 'fname' is given, the content is also written
def get_config_data_header(self, fname = None): to the file called "fname".
WARNING: if 'fname' names an existing file, that file will be
overwritten!
"""
return self.config_to_header(self.get_config_data(), fname) return self.config_to_header(self.get_config_data(), fname)