diff --git a/tools/config.py b/tools/config.py index 053c7d850b..f729386657 100644 --- a/tools/config.py +++ b/tools/config.py @@ -16,40 +16,52 @@ 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.utils import json_file_to_dict from tools.targets import Target import os # Base class for all configuration exceptions class ConfigException(Exception): + """Config system only exception. Makes it easier to distinguish config + errors""" 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") +class ConfigParameter(object): + """This class keeps information about a single configuration parameter""" + 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.set_value(data.get("value", None), unit_name, unit_kind) self.help_text = data.get("help", None) 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 = [] - # 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): + 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 unit_kind == "target": prefix = "target." @@ -60,24 +72,37 @@ class ConfigParameter: 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))) + 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) + # 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))) + 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))) + 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): + 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": return "target:" + unit_name elif unit_kind == "application": @@ -85,33 +110,42 @@ class ConfigParameter: 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): + """ "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('-', '_') - # 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): + def set_value(self, value, unit_name, unit_kind, label=None): + """ 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) + """ 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 def __str__(self): + """Return the string representation of this configuration parameter""" 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: 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 "") + """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: desc += " Description: %s\n" % self.help_text 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) return desc -# A representation of a configuration macro. It handles both macros without a value (MACRO) -# and with a value (MACRO=VALUE) -class ConfigMacro: +class ConfigMacro(object): + """ A representation of a configuration macro. It handles both macros + without a value (MACRO) and with a value (MACRO=VALUE) + """ 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)) + 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 -# Representation of overrides for cumulative attributes -class ConfigCumulativeOverride: - def __init__(self, name, additions=set(), removals=set(), strict=False): +class ConfigCumulativeOverride(object): + """Representation of overrides for cumulative attributes""" + def __init__(self, name, additions=None, removals=None, strict=False): self.name = name - self.additions = set(additions) - self.removals = set(removals) + if additions: + self.additions = set(additions) + else: + self.additions = set() + if removals: + self.removals = set(removals) + else: + self.removals = set() self.strict = strict - # Add attr to the cumulative override def remove_cumulative_overrides(self, overrides): + """Add attr to the cumulative override""" for override in overrides: 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) - # Remove attr from the cumulative overrides def add_cumulative_overrides(self, overrides): + """Remove attr from the cumulative overrides""" for override in overrides: - if (override in self.removals or (self.strict and override not in self.additions)): - raise ConfigException("Configuration conflict. The %s %s both added and removed." % (self.name[:-1], override)) + if override in self.removals or \ + (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) - # Enable strict set of cumulative overrides for the specified attr 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.add_cumulative_overrides(overrides) self.strict = True def update_target(self, target): - setattr(target, self.name, list( - (set(getattr(target, self.name, [])) | self.additions) - self.removals)) + """Update the attributes of a target based on this override""" + 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 -class Config: - # Libraries and applications have different names for their configuration files + 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") + """ + 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_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) + # (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"]) + "library": set(["name", "config", "target_overrides", "macros", + "__config_path"]), + "application": set(["config", "custom_targets", "target_overrides", + "macros", "__config_path"]) } # Allowed features in configurations @@ -191,29 +295,43 @@ class Config: "UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE" ] - # 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 = []): + def __init__(self, target, top_level_dirs=None): + """ + 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) + """ app_config_location = None - for s in (top_level_dirs or []): - full_path = os.path.join(s, self.__mbed_app_config_name) + for directory in top_level_dirs or []: + full_path = os.path.join(directory, 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)) + 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 {} + 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"] + 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 + 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 @@ -221,229 +339,281 @@ class Config: self.target = target if isinstance(target, basestring) else target.name self.target_labels = Target.get_target(self.target).get_labels() - self.cumulative_overrides = { key: ConfigCumulativeOverride(key) - for key in Target._Target__cumulative_attributes } + self.cumulative_overrides = {key: ConfigCumulativeOverride(key) + 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.config_errors = None - # 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): + """Add one or more configuration files""" + for config_file in flist: + if not config_file.endswith(self.__mbed_lib_config_name): 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 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) + # Read the library configuration and add a "__full_config_path" + # attribute to it + cfg = json_file_to_dict(config_file) cfg["__config_path"] = full_path if "name" not in cfg: - raise ConfigException("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 + raise ConfigException( + "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"]): - 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 - # 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): + """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._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(): - # 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): # Check for invalid cumulative overrides in libraries - if (unit_kind == 'library' and - any(attr.startswith('target.extra_labels') for attr in overrides.iterkeys())): - raise ConfigException("Target override '%s' in '%s' is only allowed at the application level" - % ("target.extra_labels", ConfigParameter.get_display_name(unit_name, unit_kind, label))) + if (unit_kind == 'library' and + any(attr.startswith('target.extra_labels') for attr + in overrides.iterkeys())): + 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 for attr, cumulatives in self.cumulative_overrides.iteritems(): if 'target.'+attr in overrides: - cumulatives.strict_cumulative_overrides(overrides['target.'+attr]) + cumulatives.strict_cumulative_overrides( + overrides['target.'+attr]) del overrides['target.'+attr] 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'] 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'] # Consider the others as overrides - for name, v in overrides.items(): + for name, val in overrides.items(): # 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: - params[full_name].set_value(v, unit_name, unit_kind, label) + params[full_name].set_value(val, 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)))) + self.config_errors.append( + 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(): cumulatives.update_target(Target.get_target(self.target)) 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 + """Read and interpret configuration data defined by targets. + + 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)] + 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] + target_data = json_data[tname] # 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 - 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") - # 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"))) + # 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 (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 - params[full_name].set_value(v, tname, "target") + params[full_name].set_value(val, 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): + """ 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 = {}, {} 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") + 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")) + _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") + """ 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 + """ + 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): + """ 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() lib_params, macros = self.get_lib_config_data() all_params.update(lib_params) self.get_app_config_data(all_params, macros) return all_params, macros - # Helper: verify if there are any required parameters without a value in '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)) + """verify if there are any required parameters without a value in + 'params' + """ + 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 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 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 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): + """ 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] 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): + """ Return the configuration data converted to a list of C macros + """ return self.config_to_macros(self.get_config_data()) - # Returns any features in the configuration data def get_features(self): + """ Returns any features in the configuration data + """ params, _ = self.get_config_data() 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 for feature in 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 - # Validate configuration settings. This either returns True or raises an exception def validate_config(self): + """ Validate configuration settings. This either returns True or + raises an exception + """ 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): + """ Loads configuration data from resources. Also expands resources + based on defined features settings + """ # 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 + # 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 @@ -460,52 +630,81 @@ class 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): + 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] Config._check_required_parameters(params) - header_data = "// Automatically generated configuration file.\n" + 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) + 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) + 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) + for macro in params.values(): + if macro.value is not None: + 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 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) + for macro in macros.values(): + 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) 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" # If fname is given, write "header_data" to it if fname: - with open(fname, "wt") as f: - f.write(header_data) + with open(fname, "w+") as file_desc: + file_desc.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): + def get_config_data_header(self, 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! + """ return self.config_to_header(self.get_config_data(), fname)