Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: config.py
- Revision:
- 13:ab47a20b66f0
- Parent:
- 10:2511036308b8
- Child:
- 24:25bff2709c20
diff -r f2e8a005c7d3 -r ab47a20b66f0 config.py
--- a/config.py Tue Jun 14 11:33:06 2016 +0100
+++ b/config.py Thu Jul 14 20:21:19 2016 +0100
@@ -35,11 +35,11 @@
def __init__(self, name, data, unit_name, unit_kind):
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.set_by = self.defined_by
+ self.set_value(data.get("value", None), unit_name, unit_kind)
self.help_text = data.get("help", None)
- self.value = data.get("value", None)
self.required = data.get("required", False)
self.macro_name = data.get("macro_name", "MBED_CONF_%s" % self.sanitize(self.name.upper()))
+ self.config_errors = []
# Return the full (prefixed) name of a parameter.
# If the parameter already has a prefix, check if it is valid
@@ -92,13 +92,14 @@
def sanitize(name):
return name.replace('.', '_').replace('-', '_')
- # Sets a value for this parameter, remember the place where it was set
+ # Sets a value for this parameter, remember the place where it was set.
+ # If the value is a boolean, it is converted to 1 (for True) or to 0 (for False).
# value: the value 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)
def set_value(self, value, unit_name, unit_kind, label = None):
- self.value = value
+ self.value = int(value) if isinstance(value, bool) else value
self.set_by = self.get_display_name(unit_name, unit_kind, label)
# Return the string representation of this configuration parameter
@@ -131,8 +132,10 @@
if len(tmp) != 2:
raise ValueError("Invalid macro definition '%s' in '%s'" % (name, self.defined_by))
self.macro_name = tmp[0]
+ self.macro_value = tmp[1]
else:
self.macro_name = name
+ self.macro_value = None
# 'Config' implements the mbed configuration mechanism
class Config:
@@ -147,6 +150,11 @@
"application": set(["config", "custom_targets", "target_overrides", "macros", "__config_path"])
}
+ # Allowed features in configurations
+ __allowed_features = [
+ "UVISOR", "BLE", "CLIENT", "IPV4", "IPV6"
+ ]
+
# The initialization arguments for Config are:
# 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)
@@ -174,9 +182,11 @@
self.lib_config_data = {}
# Make sure that each config is processed only once
self.processed_configs = {}
- self.target = target if isinstance(target, str) 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_instance = Target.get_target(self.target)
+ self.added_features = set()
+ self.removed_features = set()
+ self.removed_unecessary_features = False
# Add one or more configuration files
def add_config_files(self, flist):
@@ -212,44 +222,59 @@
params[full_name] = ConfigParameter(name, v if isinstance(v, dict) else {"value": v}, unit_name, unit_kind)
return params
+ # Add features to the available features
+ def remove_features(self, features):
+ for feature in features:
+ if feature in self.added_features:
+ raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
+
+ self.removed_features |= set(features)
+
+ # Remove features from the available features
+ def add_features(self, features):
+ for feature in features:
+ if (feature in self.removed_features
+ or (self.removed_unecessary_features and feature not in self.added_features)):
+ raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
+
+ self.added_features |= set(features)
+
# 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):
+ self.config_errors = []
self._process_config_parameters(data.get("config", {}), params, unit_name, unit_kind)
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 (label == '*') or (label in self.target_labels):
- # Parse out cumulative attributes
- for attr in Target._Target__cumulative_attributes:
- attrs = getattr(self.target_instance, attr)
-
- if attr in overrides:
- del attrs[:]
- attrs.extend(overrides[attr])
- del overrides[attr]
+ # Parse out features
+ if 'target.features' in overrides:
+ features = overrides['target.features']
+ self.remove_features(self.added_features - set(features))
+ self.add_features(features)
+ self.removed_unecessary_features = True
+ del overrides['target.features']
- if attr+'_add' in overrides:
- attrs.extend(overrides[attr+'_add'])
- del overrides[attr+'_add']
+ if 'target.features_add' in overrides:
+ self.add_features(overrides['target.features_add'])
+ del overrides['target.features_add']
- if attr+'_remove' in overrides:
- for a in overrides[attr+'_remove']:
- attrs.remove(a)
- del overrides[attr+'_remove']
-
- setattr(self.target_instance, attr, attrs)
+ if 'target.features_remove' in overrides:
+ self.remove_features(overrides['target.features_remove'])
+ del overrides['target.features_remove']
# Consider the others as overrides
for name, v in overrides.items():
# Get the full name of the parameter
full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind, label)
- # If an attempt is made to override a parameter that isn't defined, raise an error
- if not full_name in params:
- raise ConfigException("Attempt to override undefined parameter '%s' in '%s'" % (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label)))
- params[full_name].set_value(v, unit_name, unit_kind, label)
+ if full_name in params:
+ params[full_name].set_value(v, unit_name, unit_kind, label)
+ else:
+ self.config_errors.append(ConfigException("Attempt to override undefined parameter '%s' in '%s'"
+ % (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label))))
return params
# Read and interpret configuration data defined by targets
@@ -320,16 +345,17 @@
# 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
+ # - macros: the list of macros defined with "macros" in libraries and in the application (as ConfigMacro instances)
def get_config_data(self):
all_params = self.get_target_config_data()
lib_params, macros = self.get_lib_config_data()
all_params.update(lib_params)
self.get_app_config_data(all_params, macros)
- return all_params, [m.name for m in macros.values()]
+ return all_params, macros
# Helper: verify if there are any required parameters without a value in 'params'
- def _check_required_parameters(self, params):
+ @staticmethod
+ def _check_required_parameters(params):
for p in params.values():
if p.required and (p.value is None):
raise ConfigException("Required parameter '%s' defined by '%s' doesn't have a value" % (p.name, p.defined_by))
@@ -340,8 +366,113 @@
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 ConfigMacros (as returned by get_config_data)
+ # params: a dictionary of (name, ConfigMacro instance) mappings
+ @staticmethod
+ def config_macros_to_macros(macros):
+ 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
+ def config_to_macros(config):
+ params, macros = config[0], config[1]
+ Config._check_required_parameters(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):
- params, 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):
+ params, _ = self.get_config_data()
self._check_required_parameters(params)
- return macros + self.parameters_to_macros(params)
+ features = ((set(Target.get_target(self.target).features)
+ | self.added_features) - self.removed_features)
+
+ for feature in features:
+ if feature not in self.__allowed_features:
+ raise ConfigException("Feature '%s' is not a supported features" % feature)
+
+ return features
+
+ # Validate configuration settings. This either returns True or raises an exception
+ def validate_config(self):
+ if self.config_errors:
+ raise self.config_errors[0]
+ return True
+
+
+ # Loads configuration data from resources. Also expands resources based on defined features settings
+ def load_resources(self, resources):
+ # Update configuration files until added features creates no changes
+ prev_features = set()
+ while True:
+ # Add/update the configuration with any .json files found while scanning
+ self.add_config_files(resources.json_files)
+
+ # Add features while we find new ones
+ features = self.get_features()
+ if features == prev_features:
+ break
+
+ for feature in features:
+ if feature in resources.features:
+ resources.add(resources.features[feature])
+
+ prev_features = features
+ self.validate_config()
+
+ 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
+ def config_to_header(config, fname = None):
+ params, macros = config[0], config[1]
+ Config._check_required_parameters(params)
+ header_data = "// Automatically generated configuration file.\n"
+ header_data += "// DO NOT EDIT, content will be overwritten.\n\n"
+ header_data += "#ifndef __MBED_CONFIG_DATA__\n"
+ header_data += "#define __MBED_CONFIG_DATA__\n\n"
+ # 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_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
+ 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_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
+ if params:
+ header_data += "// Configuration parameters\n"
+ for m in params.values():
+ if m.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)
+ # Then macros
+ if macros:
+ header_data += "// Macros\n"
+ for m in macros.values():
+ if m.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)
+ 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 += "\n#endif\n"
+ # If fname is given, write "header_data" to it
+ if fname:
+ with open(fname, "wt") as f:
+ f.write(header_data)
+ return header_data
+
+ # 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!
+ def get_config_data_header(self, fname = None):
+ return self.config_to_header(self.get_config_data(), fname)
