the other jimmy / mbed-sdk-tools

Fork of mbed-sdk-tools by mbed official

Revision:
29:1210849dba19
Parent:
24:25bff2709c20
Child:
30:f12ce67666d0
--- a/config.py	Mon Aug 29 10:55:42 2016 +0100
+++ b/config.py	Mon Aug 29 11:18:36 2016 +0100
@@ -16,40 +16,56 @@
 """
 
 # 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)
+        """Construct a ConfigParameter
+
+        Positional arguments:
+        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
+
+        Positional arguments:
+        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")
+
+        Keyword arguments:
+        label - the name of the label in the 'target_config_overrides' section
+        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 +76,39 @@
             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
+
+        Positional arguments:
+        unit_name - the unit (target/library/application) that defines this
+                    parameter
+        unit_kind - the kind of the unit ("target", "library" or "application")
+
+        Keyword arguments:
+        label - the name of the label in the 'target_config_overrides' section
+        """
         if unit_kind == "target":
             return "target:" + unit_name
         elif unit_kind == "application":
@@ -85,33 +116,53 @@
         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 '_'.
+
+        Positional arguments:
+        name - the name to make into a valid C macro
+        """
         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).
+
+        Positional arguments:
+        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")
+
+        Keyword arguments:
+        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
+
+        Arguments: 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:
             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 parameter as a
+        string
+
+        Arguments: None
+        """
+        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 +172,175 @@
         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):
+        """Construct a ConfigMacro object
+
+        Positional arguments:
+        name - the macro's name
+        unit_name - the location where the macro was defined
+        unit_kind - the type of macro this is
+        """
         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):
+        """Construct a ConfigCumulativeOverride object
+
+        Positional arguments:
+        name - the name of the config file this came from ?
+
+        Keyword arguments:
+        additions - macros to add to the overrides
+        removals - macros to remove from the overrides
+        strict - Boolean indicating that attempting to remove from an override
+                 that does not exist should error
+        """
         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):
+        """Extend the list of override removals.
+
+        Positional arguments:
+        overrides - a list of names that, when the override is evaluated, will
+                    be removed
+        """
         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):
+        """Extend the list of override additions.
+
+        Positional arguments:
+        overrides - a list of a names that, when the override is evaluated, will
+                    be added to the list
+        """
         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):
+        """Remove all overrides that are not the specified ones
+
+        Positional arguments:
+        overrides - a list of names that will replace the entire attribute when
+                    this override is evaluated.
+        """
         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
+    Positional arguments:
+    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 and check for incompatible duplicate
+    definitions.
+
+    Positional arguments:
+    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 +348,45 @@
         "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):
+        """Construct a mbed configuration
+
+        Positional arguments:
+        target - the name of the mbed target used for this configuration
+                 instance
+
+        Keyword argumets:
+        top_level_dirs - a list of top level source directories (where
+                         mbed_abb_config.json could be found)
+
+        NOTE: Construction of a Config object 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 as needed. If more than one config file is found, an
+        exception is raised. top_level_dirs may be None (in this case,
+        the constructor will not search for a configuration file)
+        """
         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,230 +394,318 @@
         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.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 configuration files
+
+        Positional arguments:
+        flist - a list of files to add to this configuration
+        """
+        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 there's already a configuration for a module with the same name, exit with error
+
+            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
             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
+
+    def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
+        """Process "config_parameters" and "target_config_overrides" into a
+        given dictionary
 
-    # 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):
+        Positional arguments:
+        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 of those 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
+
+        Arguments: None
+        """
         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
+    def get_lib_config_data(self):
+        """ Read and interpret configuration data defined by libraries. It is
+        assumed that "add_config_files" above was already called and the library
+        configuration data exists in self.lib_config_data
 
-    # 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):
+        Arguments: None
+        """
         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):
+        """ 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.
+
+        Positional arguments.
+        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 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")
+        _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, macros)
+        params - a dictionary with mapping a name to a ConfigParam
+        macros - the list of macros defined with "macros" in libraries and in
+                 the application (as ConfigMacro instances)
+
+        Arguments: None
+        """
         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))
+        """Check that there are no required parameters without a value
+
+        Positional arguments:
+        params - the list of parameters to check
 
-    # Return the macro definitions generated for a dictionary of configuration parameters
-    # params: a dictionary of (name, ConfigParameters instance) mappings
+        NOTE: This function does not return. Instead, it throws a
+        ConfigException when any of the required parameters are missing values
+        """
+        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")
+
     @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]
+        """ Encode the configuration parameters as C macro definitions.
+
+        Positional arguments:
+        params - a dictionary mapping a name to a ConfigParameter
 
-    # 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: a list of strings that encode the configuration parameters as
+        C pre-processor macros
+        """
+        return ['%s=%s' % (m.macro_name, m.value) for m in params.values()
+                if m.value is not None]
+
     @staticmethod
     def config_macros_to_macros(macros):
+        """ Return the macro definitions generated for a dictionary of
+        ConfigMacros (as returned by get_config_data).
+
+        Positional arguments:
+        params - a dictionary mapping a name to a ConfigMacro instance
+
+        Return: a list of strings that are the C pre-processor 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):
+        """Convert the configuration data to a list of C macros
+
+        Positional arguments:
+        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):
+        """ Convert a Config object to a list of C macros
+
+        Arguments: None
+        """
         return self.config_to_macros(self.get_config_data())
 
-    # Returns any features in the configuration data
     def get_features(self):
+        """ Extract any features from the configuration data
+
+        Arguments: None
+        """
         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
+
+        Arguments: None
+        """
         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):
+        """ Load configuration data from a Resources instance and expand it
+        based on defined features.
+
+        Positional arguments:
+        resources - the resources object to load from and expand
+        """
         # 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
-            features = self.get_features()
+            features = set(self.get_features())
             if features == prev_features:
                 break
 
@@ -457,52 +718,85 @@
 
         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):
+        """ Convert the configuration data to the content of a C header file,
+        meant to be included to a C/C++ file. The content is returned as a
+        string.
+
+        Positional arguments:
+        config - configuration data as (ConfigParam instances, ConfigMacro
+                 instances) tuple (as returned by get_config_data())
+
+        Keyword arguments:
+        fname -  also write the content is to the file called "fname".
+                 WARNING: if 'fname' names an existing file, it will be
+                 overwritten!
+        """
         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(macro.macro_name, max_macro_name_len,
+                                macro.macro_value, max_macro_val_len,
+                                macro.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(macro.macro_name,
+                                max_macro_name_len + max_macro_val_len + 1,
+                                macro.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):
+        """ Convert a Config instance to the content of a C header file, meant
+        to be included to a C/C++ file. The content is returned as a string.
+
+        Keyword arguments:
+        fname - also write the content to the file called "fname".
+                WARNING: if 'fname' names an existing file, it will be
+                overwritten!
+        """
         return self.config_to_header(self.get_config_data(), fname)