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:
- 8:a8ac6ed29081
- Child:
- 10:2511036308b8
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/config.py Tue Jun 07 11:35:02 2016 +0100
@@ -0,0 +1,325 @@
+"""
+mbed SDK
+Copyright (c) 2016 ARM Limited
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+# Implementation of mbed configuration mechanism
+from copy import deepcopy
+from collections import OrderedDict
+from tools.utils import json_file_to_dict, ToolException
+from tools.targets import Target
+import os
+
+# Base class for all configuration exceptions
+class ConfigException(Exception):
+ pass
+
+# This class keeps information about a single configuration parameter
+class ConfigParameter:
+ # 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):
+ 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.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()))
+
+ # 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
+ def get_full_name(name, unit_name, unit_kind, label = None, allow_prefix = True):
+ if name.find('.') == -1: # the name is not prefixed
+ if unit_kind == "target":
+ prefix = "target."
+ elif unit_kind == "application":
+ prefix = "app."
+ else:
+ prefix = unit_name + '.'
+ return prefix + name
+ # The name has a prefix, so check if it is valid
+ if not allow_prefix:
+ raise ConfigException("Invalid parameter name '%s' in '%s'" % (name, ConfigParameter.get_display_name(unit_name, unit_kind, label)))
+ temp = name.split(".")
+ # Check if the parameter syntax is correct (must be unit_name.parameter_name)
+ if len(temp) != 2:
+ raise ConfigException("Invalid parameter name '%s' in '%s'" % (name, ConfigParameter.get_display_name(unit_name, unit_kind, label)))
+ prefix = temp[0]
+ # 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"):
+ 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 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
+ def get_display_name(unit_name, unit_kind, label = None):
+ if unit_kind == "target":
+ return "target:" + unit_name
+ elif unit_kind == "application":
+ return "application%s" % ("[%s]" % label if label else "")
+ else: # library
+ 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
+ def sanitize(name):
+ return name.replace('.', '_').replace('-', '_')
+
+ # Sets a value for this parameter, remember the place where it was set
+ # 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.set_by = self.get_display_name(unit_name, unit_kind, label)
+
+ # Return the string representation of this configuration parameter
+ def __str__(self):
+ if self.value is not None:
+ return '%s = %s (macro name: "%s")' % (self.name, self.value, self.macro_name)
+ else:
+ return '%s has no value' % self.name
+
+ # Return a verbose description of this configuration paramater as a string
+ def get_verbose_description(self):
+ desc = "Name: %s%s\n" % (self.name, " (required parameter)" if self.required else "")
+ if self.help_text:
+ desc += " Description: %s\n" % self.help_text
+ desc += " Defined by: %s\n" % self.defined_by
+ if not self.value:
+ return desc + " No value set"
+ desc += " Macro name: %s\n" % self.macro_name
+ desc += " Value: %s (set by %s)" % (self.value, self.set_by)
+ return desc
+
+# A representation of a configuration macro. It handles both macros without a value (MACRO)
+# and with a value (MACRO=VALUE)
+class ConfigMacro:
+ def __init__(self, name, unit_name, unit_kind):
+ self.name = name
+ self.defined_by = ConfigParameter.get_display_name(unit_name, unit_kind)
+ if name.find("=") != -1:
+ tmp = name.split("=")
+ if len(tmp) != 2:
+ raise ValueError("Invalid macro definition '%s' in '%s'" % (name, self.defined_by))
+ self.macro_name = tmp[0]
+ else:
+ self.macro_name = name
+
+# 'Config' implements the mbed configuration mechanism
+class Config:
+ # Libraries and applications have different names for their configuration files
+ __mbed_app_config_name = "mbed_app.json"
+ __mbed_lib_config_name = "mbed_lib.json"
+
+ # Allowed keys in configuration dictionaries
+ # (targets can have any kind of keys, so this validation is not applicable to them)
+ __allowed_keys = {
+ "library": set(["name", "config", "target_overrides", "macros", "__config_path"]),
+ "application": set(["config", "custom_targets", "target_overrides", "macros", "__config_path"])
+ }
+
+ # 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)
+ # __init__ will look for the application configuration file in 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)
+ def __init__(self, target, top_level_dirs = []):
+ app_config_location = None
+ for s in (top_level_dirs or []):
+ full_path = os.path.join(s, self.__mbed_app_config_name)
+ if os.path.isfile(full_path):
+ 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))
+ else:
+ app_config_location = full_path
+ self.app_config_data = json_file_to_dict(app_config_location) if app_config_location else {}
+ # Check the keys in the application configuration data
+ unknown_keys = set(self.app_config_data.keys()) - self.__allowed_keys["application"]
+ if unknown_keys:
+ raise ConfigException("Unknown key(s) '%s' in %s" % (",".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", {}))
+ 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_labels = Target.get_target(self.target).get_labels()
+
+ # Add one or more configuration files
+ def add_config_files(self, flist):
+ for f in flist:
+ if not f.endswith(self.__mbed_lib_config_name):
+ continue
+ full_path = os.path.normpath(os.path.abspath(f))
+ # Check that we didn't already process this file
+ if self.processed_configs.has_key(full_path):
+ continue
+ self.processed_configs[full_path] = True
+ # Read the library configuration and add a "__full_config_path" attribute to it
+ cfg = json_file_to_dict(f)
+ cfg["__config_path"] = 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"]):
+ 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
+
+ # 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):
+ 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):
+ 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)
+ return params
+
+ # Read and interpret configuration data defined by targets
+ def get_target_config_data(self):
+ # We consider the resolution order for our target and sort it by level reversed,
+ # 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
+ # 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()
+ 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:
+ # Read the target data directly from its description
+ t = json_data[tname]
+ # Process definitions first
+ self._process_config_parameters(t.get("config", {}), params, tname, "target")
+ # Then process overrides
+ for name, v in t.get("overrides", {}).items():
+ 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
+ # parameter was defined in the target inheritance tree, raise an error
+ # We need to use 'defined_by[7:]' to remove the "target:" prefix from defined_by
+ if (not full_name in params) or (not params[full_name].defined_by[7:] 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
+ params[full_name].set_value(v, tname, "target")
+ 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):
+ all_params, macros = {}, {}
+ for lib_name, lib_data in self.lib_config_data.items():
+ unknown_keys = set(lib_data.keys()) - self.__allowed_keys["library"]
+ if unknown_keys:
+ raise ConfigException("Unknown key(s) '%s' in %s" % (",".join(unknown_keys), lib_name))
+ all_params.update(self._process_config_and_overrides(lib_data, {}, lib_name, "library"))
+ self._process_macros(lib_data.get("macros", []), macros, lib_name, "library")
+ 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):
+ 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
+ self._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
+ 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()]
+
+ # Helper: verify if there are any required parameters without a value in 'params'
+ def _check_required_parameters(self, 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))
+
+ # Return the macro definitions generated for a dictionary of configuration parameters
+ # params: a dictionary of (name, ConfigParameters instance) mappings
+ @staticmethod
+ 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 configuration data converted to a list of C macros
+ def get_config_data_macros(self):
+ params, macros = self.get_config_data()
+ self._check_required_parameters(params)
+ return macros + self.parameters_to_macros(params)
