Rizky Ardi Maulana / mbed-os
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers config.py Source File

config.py

00001 """
00002 mbed SDK
00003 Copyright (c) 2016 ARM Limited
00004 
00005 Licensed under the Apache License, Version 2.0 (the "License");
00006 you may not use this file except in compliance with the License.
00007 You may obtain a copy of the License at
00008 
00009 http://www.apache.org/licenses/LICENSE-2.0
00010 
00011 Unless required by applicable law or agreed to in writing, software
00012 distributed under the License is distributed on an "AS IS" BASIS,
00013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014 See the License for the specific language governing permissions and
00015 limitations under the License.
00016 """
00017 
00018 from copy import deepcopy
00019 import os
00020 # Implementation of mbed configuration mechanism
00021 from tools.utils import json_file_to_dict
00022 from tools.targets import CUMULATIVE_ATTRIBUTES, TARGET_MAP, \
00023     generate_py_target, get_resolution_order
00024 
00025 # Base class for all configuration exceptions
00026 class ConfigException (Exception):
00027     """Config system only exception. Makes it easier to distinguish config
00028     errors"""
00029     pass
00030 
00031 class ConfigParameter (object):
00032     """This class keeps information about a single configuration parameter"""
00033 
00034     def __init__ (self, name, data, unit_name, unit_kind):
00035         """Construct a ConfigParameter
00036 
00037         Positional arguments:
00038         name - the name of the configuration parameter
00039         data - the data associated with the configuration parameter
00040         unit_name - the unit (target/library/application) that defines this
00041                     parameter
00042         unit_ kind - the kind of the unit ("target", "library" or "application")
00043         """
00044         self.name  = self.get_full_name (name, unit_name, unit_kind,
00045                                        allow_prefix=False)
00046         self.defined_by  = self.get_display_name (unit_name, unit_kind)
00047         self.set_value (data.get("value", None), unit_name, unit_kind)
00048         self.help_text  = data.get("help", None)
00049         self.required  = data.get("required", False)
00050         self.macro_name  = data.get("macro_name", "MBED_CONF_%s" %
00051                                    self.sanitize (self.name .upper()))
00052         self.config_errors  = []
00053 
00054     @staticmethod
00055     def get_full_name (name, unit_name, unit_kind, label=None,
00056                       allow_prefix=True):
00057         """Return the full (prefixed) name of a parameter. If the parameter
00058         already has a prefix, check if it is valid
00059 
00060         Positional arguments:
00061         name - the simple (unqualified) name of the parameter
00062         unit_name - the unit (target/library/application) that defines this
00063                     parameter
00064         unit_kind - the kind of the unit ("target", "library" or "application")
00065 
00066         Keyword arguments:
00067         label - the name of the label in the 'target_config_overrides' section
00068         allow_prefix - True to allow the original name to have a prefix, False
00069                        otherwise
00070         """
00071         if name.find('.') == -1: # the name is not prefixed
00072             if unit_kind == "target":
00073                 prefix = "target."
00074             elif unit_kind == "application":
00075                 prefix = "app."
00076             else:
00077                 prefix = unit_name + '.'
00078             return prefix + name
00079         # The name has a prefix, so check if it is valid
00080         if not allow_prefix:
00081             raise ConfigException("Invalid parameter name '%s' in '%s'" %
00082                                   (name, ConfigParameter.get_display_name(
00083                                       unit_name, unit_kind, label)))
00084         temp = name.split(".")
00085         # Check if the parameter syntax is correct (must be
00086         # unit_name.parameter_name)
00087         if len(temp) != 2:
00088             raise ConfigException("Invalid parameter name '%s' in '%s'" %
00089                                   (name, ConfigParameter.get_display_name(
00090                                       unit_name, unit_kind, label)))
00091         prefix = temp[0]
00092         # Check if the given parameter prefix matches the expected prefix
00093         if (unit_kind == "library" and prefix != unit_name) or \
00094            (unit_kind == "target" and prefix != "target"):
00095             raise ConfigException(
00096                 "Invalid prefix '%s' for parameter name '%s' in '%s'" %
00097                 (prefix, name, ConfigParameter.get_display_name(
00098                     unit_name, unit_kind, label)))
00099         return name
00100 
00101     @staticmethod
00102     def get_display_name (unit_name, unit_kind, label=None):
00103         """Return the name displayed for a unit when interrogating the origin
00104         and the last set place of a parameter
00105 
00106         Positional arguments:
00107         unit_name - the unit (target/library/application) that defines this
00108                     parameter
00109         unit_kind - the kind of the unit ("target", "library" or "application")
00110 
00111         Keyword arguments:
00112         label - the name of the label in the 'target_config_overrides' section
00113         """
00114         if unit_kind == "target":
00115             return "target:" + unit_name
00116         elif unit_kind == "application":
00117             return "application%s" % ("[%s]" % label if label else "")
00118         else: # library
00119             return "library:%s%s" % (unit_name, "[%s]" % label if label else "")
00120 
00121     @staticmethod
00122     def sanitize (name):
00123         """ "Sanitize" a name so that it is a valid C macro name. Currently it
00124         simply replaces '.' and '-' with '_'.
00125 
00126         Positional arguments:
00127         name - the name to make into a valid C macro
00128         """
00129         return name.replace('.', '_').replace('-', '_')
00130 
00131     def set_value (self, value, unit_name, unit_kind, label=None):
00132         """ Sets a value for this parameter, remember the place where it was
00133         set.  If the value is a Boolean, it is converted to 1 (for True) or
00134         to 0 (for False).
00135 
00136         Positional arguments:
00137         value - the value of the parameter
00138         unit_name - the unit (target/library/application) that defines this
00139                    parameter
00140         unit_kind - the kind of the unit ("target", "library" or "application")
00141 
00142         Keyword arguments:
00143         label - the name of the label in the 'target_config_overrides' section
00144                (optional)
00145         """
00146         self.value  = int(value) if isinstance(value, bool) else value
00147         self.set_by  = self.get_display_name (unit_name, unit_kind, label)
00148 
00149     def __str__ (self):
00150         """Return the string representation of this configuration parameter
00151 
00152         Arguments: None
00153         """
00154         if self.value  is not None:
00155             return '%s = %s (macro name: "%s")' % \
00156                 (self.name , self.value , self.macro_name )
00157         else:
00158             return '%s has no value' % self.name 
00159 
00160     def get_verbose_description (self):
00161         """Return a verbose description of this configuration parameter as a
00162         string
00163 
00164         Arguments: None
00165         """
00166         desc = "Name: %s%s\n" % \
00167                (self.name , " (required parameter)" if self.required  else "")
00168         if self.help_text :
00169             desc += "    Description: %s\n" % self.help_text 
00170         desc += "    Defined by: %s\n" % self.defined_by 
00171         if not self.value :
00172             return desc + "    No value set"
00173         desc += "    Macro name: %s\n" % self.macro_name 
00174         desc += "    Value: %s (set by %s)" % (self.value , self.set_by )
00175         return desc
00176 
00177 class ConfigMacro (object):
00178     """ A representation of a configuration macro. It handles both macros
00179     without a value (MACRO) and with a value (MACRO=VALUE)
00180     """
00181     def __init__ (self, name, unit_name, unit_kind):
00182         """Construct a ConfigMacro object
00183 
00184         Positional arguments:
00185         name - the macro's name
00186         unit_name - the location where the macro was defined
00187         unit_kind - the type of macro this is
00188         """
00189         self.name  = name
00190         self.defined_by  = ConfigParameter.get_display_name(unit_name, unit_kind)
00191         if name.find("=") != -1:
00192             tmp = name.split("=")
00193             if len(tmp) != 2:
00194                 raise ValueError("Invalid macro definition '%s' in '%s'" %
00195                                  (name, self.defined_by ))
00196             self.macro_name  = tmp[0]
00197             self.macro_value  = tmp[1]
00198         else:
00199             self.macro_name  = name
00200             self.macro_value  = None
00201 
00202 class ConfigCumulativeOverride (object):
00203     """Representation of overrides for cumulative attributes"""
00204     def __init__ (self, name, additions=None, removals=None, strict=False):
00205         """Construct a ConfigCumulativeOverride object
00206 
00207         Positional arguments:
00208         name - the name of the config file this came from ?
00209 
00210         Keyword arguments:
00211         additions - macros to add to the overrides
00212         removals - macros to remove from the overrides
00213         strict - Boolean indicating that attempting to remove from an override
00214                  that does not exist should error
00215         """
00216         self.name  = name
00217         if additions:
00218             self.additions  = set(additions)
00219         else:
00220             self.additions  = set()
00221         if removals:
00222             self.removals  = set(removals)
00223         else:
00224             self.removals  = set()
00225         self.strict  = strict
00226 
00227     def remove_cumulative_overrides (self, overrides):
00228         """Extend the list of override removals.
00229 
00230         Positional arguments:
00231         overrides - a list of names that, when the override is evaluated, will
00232                     be removed
00233         """
00234         for override in overrides:
00235             if override in self.additions :
00236                 raise ConfigException(
00237                     "Configuration conflict. The %s %s both added and removed."
00238                     % (self.name [:-1], override))
00239 
00240         self.removals  |= set(overrides)
00241 
00242     def add_cumulative_overrides (self, overrides):
00243         """Extend the list of override additions.
00244 
00245         Positional arguments:
00246         overrides - a list of a names that, when the override is evaluated, will
00247                     be added to the list
00248         """
00249         for override in overrides:
00250             if override in self.removals  or \
00251                (self.strict  and override not in self.additions ):
00252                 raise ConfigException(
00253                     "Configuration conflict. The %s %s both added and removed."
00254                     % (self.name [:-1], override))
00255 
00256         self.additions  |= set(overrides)
00257 
00258     def strict_cumulative_overrides (self, overrides):
00259         """Remove all overrides that are not the specified ones
00260 
00261         Positional arguments:
00262         overrides - a list of names that will replace the entire attribute when
00263                     this override is evaluated.
00264         """
00265         self.remove_cumulative_overrides (self.additions  - set(overrides))
00266         self.add_cumulative_overrides (overrides)
00267         self.strict  = True
00268 
00269     def update_target (self, target):
00270         """Update the attributes of a target based on this override"""
00271         setattr(target, self.name ,
00272                 list((set(getattr(target, self.name , []))
00273                       | self.additions ) - self.removals ))
00274 
00275 
00276 def _process_config_parameters(data, params, unit_name, unit_kind):
00277     """Process a "config_parameters" section in either a target, a library,
00278     or the application.
00279 
00280     Positional arguments:
00281     data - a dictionary with the configuration parameters
00282     params - storage for the discovered configuration parameters
00283     unit_name - the unit (target/library/application) that defines this
00284                 parameter
00285     unit_kind - the kind of the unit ("target", "library" or "application")
00286     """
00287     for name, val in data.items():
00288         full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind)
00289         # If the parameter was already defined, raise an error
00290         if full_name in params:
00291             raise ConfigException(
00292                 "Parameter name '%s' defined in both '%s' and '%s'" %
00293                 (name, ConfigParameter.get_display_name(unit_name, unit_kind),
00294                  params[full_name].defined_by))
00295         # Otherwise add it to the list of known parameters
00296         # If "val" is not a dictionary, this is a shortcut definition,
00297         # otherwise it is a full definition
00298         params[full_name] = ConfigParameter(name, val if isinstance(val, dict)
00299                                             else {"value": val}, unit_name,
00300                                             unit_kind)
00301     return params
00302 
00303 
00304 def _process_macros(mlist, macros, unit_name, unit_kind):
00305     """Process a macro definition and check for incompatible duplicate
00306     definitions.
00307 
00308     Positional arguments:
00309     mlist - list of macro names to process
00310     macros - dictionary with currently discovered macros
00311     unit_name - the unit (library/application) that defines this macro
00312     unit_kind - the kind of the unit ("library" or "application")
00313     """
00314     for mname in mlist:
00315         macro = ConfigMacro(mname, unit_name, unit_kind)
00316         if (macro.macro_name in macros) and \
00317            (macros[macro.macro_name].name != mname):
00318             # Found an incompatible definition of the macro in another module,
00319             # so raise an error
00320             full_unit_name = ConfigParameter.get_display_name(unit_name,
00321                                                               unit_kind)
00322             raise ConfigException(
00323                 ("Macro '%s' defined in both '%s' and '%s'"
00324                  % (macro.macro_name, macros[macro.macro_name].defined_by,
00325                     full_unit_name)) +
00326                 " with incompatible values")
00327         macros[macro.macro_name] = macro
00328 
00329 
00330 class Config (object):
00331     """'Config' implements the mbed configuration mechanism"""
00332 
00333     # Libraries and applications have different names for their configuration
00334     # files
00335     __mbed_app_config_name = "mbed_app.json"
00336     __mbed_lib_config_name = "mbed_lib.json"
00337 
00338     # Allowed keys in configuration dictionaries
00339     # (targets can have any kind of keys, so this validation is not applicable
00340     # to them)
00341     __allowed_keys = {
00342         "library": set(["name", "config", "target_overrides", "macros",
00343                         "__config_path"]),
00344         "application": set(["config", "target_overrides",
00345                             "macros", "__config_path"])
00346     }
00347 
00348     # Allowed features in configurations
00349     __allowed_features = [
00350         "UVISOR", "BLE", "CLIENT", "IPV4", "LWIP", "COMMON_PAL", "STORAGE", "NANOSTACK",
00351         # Nanostack configurations
00352         "LOWPAN_BORDER_ROUTER", "LOWPAN_HOST", "LOWPAN_ROUTER", "NANOSTACK_FULL", "THREAD_BORDER_ROUTER", "THREAD_END_DEVICE", "THREAD_ROUTER"
00353         ]
00354 
00355     def __init__ (self, tgt, top_level_dirs=None, app_config=None):
00356         """Construct a mbed configuration
00357 
00358         Positional arguments:
00359         target - the name of the mbed target used for this configuration
00360                  instance
00361 
00362         Keyword argumets:
00363         top_level_dirs - a list of top level source directories (where
00364                          mbed_app_config.json could be found)
00365         app_config - location of a chosen mbed_app.json file
00366 
00367         NOTE: Construction of a Config object will look for the application
00368         configuration file in top_level_dirs. If found once, it'll parse it.
00369         top_level_dirs may be None (in this case, the constructor will not
00370         search for a configuration file).
00371         """
00372         app_config_location = app_config
00373         if app_config_location is None:
00374             for directory in top_level_dirs or []:
00375                 full_path = os.path.join(directory, self.__mbed_app_config_name )
00376                 if os.path.isfile(full_path):
00377                     if app_config_location is not None:
00378                         raise ConfigException("Duplicate '%s' file in '%s' and '%s'"
00379                                               % (self.__mbed_app_config_name ,
00380                                                  app_config_location, full_path))
00381                     else:
00382                         app_config_location = full_path
00383         try:
00384             self.app_config_data  = json_file_to_dict(app_config_location) \
00385                                    if app_config_location else {}
00386         except ValueError as exc:
00387             sys.stderr.write(str(exc) + "\n")
00388             self.app_config_data  = {}
00389 
00390         # Check the keys in the application configuration data
00391         unknown_keys = set(self.app_config_data .keys()) - \
00392                        self.__allowed_keys ["application"]
00393         if unknown_keys:
00394             raise ConfigException("Unknown key(s) '%s' in %s" %
00395                                   (",".join(unknown_keys),
00396                                    self.__mbed_app_config_name ))
00397         # Update the list of targets with the ones defined in the application
00398         # config, if applicable
00399         self.lib_config_data  = {}
00400         # Make sure that each config is processed only once
00401         self.processed_configs  = {}
00402         if isinstance(tgt, basestring):
00403             if tgt in TARGET_MAP:
00404                 self.target  = TARGET_MAP[tgt]
00405             else:
00406                 self.target  = generate_py_target(
00407                     self.app_config_data .get("custom_targets", {}), tgt)
00408 
00409         else:
00410             self.target  = tgt
00411         self.target  = deepcopy(self.target )
00412         self.target_labels  = self.target .labels
00413 
00414         self.cumulative_overrides  = {key: ConfigCumulativeOverride(key)
00415                                      for key in CUMULATIVE_ATTRIBUTES}
00416 
00417         self._process_config_and_overrides (self.app_config_data , {}, "app",
00418                                            "application")
00419         self.config_errors  = None
00420 
00421     def add_config_files (self, flist):
00422         """Add configuration files
00423 
00424         Positional arguments:
00425         flist - a list of files to add to this configuration
00426         """
00427         for config_file in flist:
00428             if not config_file.endswith(self.__mbed_lib_config_name ):
00429                 continue
00430             full_path = os.path.normpath(os.path.abspath(config_file))
00431             # Check that we didn't already process this file
00432             if self.processed_configs .has_key(full_path):
00433                 continue
00434             self.processed_configs [full_path] = True
00435             # Read the library configuration and add a "__full_config_path"
00436             # attribute to it
00437             try:
00438                 cfg = json_file_to_dict(config_file)
00439             except ValueError as exc:
00440                 sys.stderr.write(str(exc) + "\n")
00441                 continue
00442 
00443             cfg["__config_path"] = full_path
00444 
00445             if "name" not in cfg:
00446                 raise ConfigException(
00447                     "Library configured at %s has no name field." % full_path)
00448             # If there's already a configuration for a module with the same
00449             # name, exit with error
00450             if self.lib_config_data .has_key(cfg["name"]):
00451                 raise ConfigException(
00452                     "Library name '%s' is not unique (defined in '%s' and '%s')"
00453                     % (cfg["name"], full_path,
00454                        self.lib_config_data [cfg["name"]]["__config_path"]))
00455             self.lib_config_data [cfg["name"]] = cfg
00456 
00457 
00458     def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
00459         """Process "config_parameters" and "target_config_overrides" into a
00460         given dictionary
00461 
00462         Positional arguments:
00463         data - the configuration data of the library/appliation
00464         params - storage for the discovered configuration parameters
00465         unit_name - the unit (library/application) that defines this parameter
00466         unit_kind - the kind of the unit ("library" or "application")
00467         """
00468         self.config_errors  = []
00469         _process_config_parameters(data.get("config", {}), params, unit_name,
00470                                    unit_kind)
00471         for label, overrides in data.get("target_overrides", {}).items():
00472             # If the label is defined by the target or it has the special value
00473             # "*", process the overrides
00474             if (label == '*') or (label in self.target_labels ):
00475                 # Check for invalid cumulative overrides in libraries
00476                 if (unit_kind == 'library' and
00477                     any(attr.startswith('target.extra_labels') for attr
00478                         in overrides.iterkeys())):
00479                     raise ConfigException(
00480                         "Target override 'target.extra_labels' in " +
00481                         ConfigParameter.get_display_name(unit_name, unit_kind,
00482                                                          label) +
00483                         " is only allowed at the application level")
00484 
00485                 # Parse out cumulative overrides
00486                 for attr, cumulatives in self.cumulative_overrides .iteritems():
00487                     if 'target.'+attr in overrides:
00488                         cumulatives.strict_cumulative_overrides(
00489                             overrides['target.'+attr])
00490                         del overrides['target.'+attr]
00491 
00492                     if 'target.'+attr+'_add' in overrides:
00493                         cumulatives.add_cumulative_overrides(
00494                             overrides['target.'+attr+'_add'])
00495                         del overrides['target.'+attr+'_add']
00496 
00497                     if 'target.'+attr+'_remove' in overrides:
00498                         cumulatives.remove_cumulative_overrides(
00499                             overrides['target.'+attr+'_remove'])
00500                         del overrides['target.'+attr+'_remove']
00501 
00502                 # Consider the others as overrides
00503                 for name, val in overrides.items():
00504                     # Get the full name of the parameter
00505                     full_name = ConfigParameter.get_full_name(name, unit_name,
00506                                                               unit_kind, label)
00507                     if full_name in params:
00508                         params[full_name].set_value(val, unit_name, unit_kind,
00509                                                     label)
00510                     else:
00511                         self.config_errors .append(
00512                             ConfigException(
00513                                 "Attempt to override undefined parameter" +
00514                                 (" '%s' in '%s'"
00515                                  % (full_name,
00516                                     ConfigParameter.get_display_name(unit_name,
00517                                                                      unit_kind,
00518                                                                      label)))))
00519 
00520         for cumulatives in self.cumulative_overrides .itervalues():
00521             cumulatives.update_target(self.target )
00522 
00523         return params
00524 
00525     def get_target_config_data (self):
00526         """Read and interpret configuration data defined by targets.
00527 
00528         We consider the resolution order for our target and sort it by level
00529         reversed, so that we first look at the top level target (the parent),
00530         then its direct children, then the children of those children and so on,
00531         until we reach self.target
00532         TODO: this might not work so well in some multiple inheritance scenarios
00533         At each step, look at two keys of the target data:
00534           - config_parameters: used to define new configuration parameters
00535           - config_overrides: used to override already defined configuration
00536                               parameters
00537 
00538         Arguments: None
00539         """
00540         params, json_data = {}, self.target .json_data
00541         resolution_order = [e[0] for e
00542                             in sorted(
00543                                 self.target .resolution_order,
00544                                 key=lambda e: e[1], reverse=True)]
00545         for tname in resolution_order:
00546             # Read the target data directly from its description
00547             target_data = json_data[tname]
00548             # Process definitions first
00549             _process_config_parameters(target_data.get("config", {}), params,
00550                                        tname, "target")
00551             # Then process overrides
00552             for name, val in target_data.get("overrides", {}).items():
00553                 full_name = ConfigParameter.get_full_name(name, tname, "target")
00554                 # If the parameter name is not defined or if there isn't a path
00555                 # from this target to the target where the parameter was defined
00556                 # in the target inheritance tree, raise an error We need to use
00557                 # 'defined_by[7:]' to remove the "target:" prefix from
00558                 # defined_by
00559                 rel_names = [tgt for tgt, _ in
00560                              get_resolution_order(self.target .json_data, tname,
00561                                                   [])]
00562                 if (full_name not in params) or \
00563                    (params[full_name].defined_by[7:] not in rel_names):
00564                     raise ConfigException(
00565                         "Attempt to override undefined parameter '%s' in '%s'"
00566                         % (name,
00567                            ConfigParameter.get_display_name(tname, "target")))
00568                 # Otherwise update the value of the parameter
00569                 params[full_name].set_value(val, tname, "target")
00570         return params
00571 
00572     def get_lib_config_data (self):
00573         """ Read and interpret configuration data defined by libraries. It is
00574         assumed that "add_config_files" above was already called and the library
00575         configuration data exists in self.lib_config_data
00576 
00577         Arguments: None
00578         """
00579         all_params, macros = {}, {}
00580         for lib_name, lib_data in self.lib_config_data .items():
00581             unknown_keys = set(lib_data.keys()) - self.__allowed_keys ["library"]
00582             if unknown_keys:
00583                 raise ConfigException("Unknown key(s) '%s' in %s" %
00584                                       (",".join(unknown_keys), lib_name))
00585             all_params.update(self._process_config_and_overrides (lib_data, {},
00586                                                                  lib_name,
00587                                                                  "library"))
00588             _process_macros(lib_data.get("macros", []), macros, lib_name,
00589                             "library")
00590         return all_params, macros
00591 
00592     def get_app_config_data (self, params, macros):
00593         """ Read and interpret the configuration data defined by the target. The
00594         target can override any configuration parameter, as well as define its
00595         own configuration data.
00596 
00597         Positional arguments.
00598         params - the dictionary with configuration parameters found so far (in
00599                  the target and in libraries)
00600         macros - the list of macros defined in the configuration
00601         """
00602         app_cfg = self.app_config_data 
00603         # The application can have a "config_parameters" and a
00604         # "target_config_overrides" section just like a library
00605         self._process_config_and_overrides (app_cfg, params, "app",
00606                                            "application")
00607         # The application can also defined macros
00608         _process_macros(app_cfg.get("macros", []), macros, "app",
00609                         "application")
00610 
00611     def get_config_data (self):
00612         """ Return the configuration data in two parts: (params, macros)
00613         params - a dictionary with mapping a name to a ConfigParam
00614         macros - the list of macros defined with "macros" in libraries and in
00615                  the application (as ConfigMacro instances)
00616 
00617         Arguments: None
00618         """
00619         all_params = self.get_target_config_data ()
00620         lib_params, macros = self.get_lib_config_data ()
00621         all_params.update(lib_params)
00622         self.get_app_config_data (all_params, macros)
00623         return all_params, macros
00624 
00625     @staticmethod
00626     def _check_required_parameters(params):
00627         """Check that there are no required parameters without a value
00628 
00629         Positional arguments:
00630         params - the list of parameters to check
00631 
00632         NOTE: This function does not return. Instead, it throws a
00633         ConfigException when any of the required parameters are missing values
00634         """
00635         for param in params.values():
00636             if param.required and (param.value is None):
00637                 raise ConfigException("Required parameter '" + param.name +
00638                                       "' defined by '" + param.defined_by +
00639                                       "' doesn't have a value")
00640 
00641     @staticmethod
00642     def parameters_to_macros (params):
00643         """ Encode the configuration parameters as C macro definitions.
00644 
00645         Positional arguments:
00646         params - a dictionary mapping a name to a ConfigParameter
00647 
00648         Return: a list of strings that encode the configuration parameters as
00649         C pre-processor macros
00650         """
00651         return ['%s=%s' % (m.macro_name, m.value) for m in params.values()
00652                 if m.value is not None]
00653 
00654     @staticmethod
00655     def config_macros_to_macros (macros):
00656         """ Return the macro definitions generated for a dictionary of
00657         ConfigMacros (as returned by get_config_data).
00658 
00659         Positional arguments:
00660         params - a dictionary mapping a name to a ConfigMacro instance
00661 
00662         Return: a list of strings that are the C pre-processor macros
00663         """
00664         return [m.name for m in macros.values()]
00665 
00666     @staticmethod
00667     def config_to_macros (config):
00668         """Convert the configuration data to a list of C macros
00669 
00670         Positional arguments:
00671         config - configuration data as (ConfigParam instances, ConfigMacro
00672                  instances) tuple (as returned by get_config_data())
00673         """
00674         params, macros = config[0], config[1]
00675         Config._check_required_parameters(params)
00676         return Config.config_macros_to_macros(macros) + \
00677             Config.parameters_to_macros(params)
00678 
00679     def get_config_data_macros (self):
00680         """ Convert a Config object to a list of C macros
00681 
00682         Arguments: None
00683         """
00684         return self.config_to_macros (self.get_config_data ())
00685 
00686     def get_features (self):
00687         """ Extract any features from the configuration data
00688 
00689         Arguments: None
00690         """
00691         params, _ = self.get_config_data ()
00692         self._check_required_parameters (params)
00693         self.cumulative_overrides ['features']\
00694             .update_target(self.target )
00695 
00696         for feature in self.target .features:
00697             if feature not in self.__allowed_features :
00698                 raise ConfigException(
00699                     "Feature '%s' is not a supported features" % feature)
00700 
00701         return self.target .features
00702 
00703     def validate_config (self):
00704         """ Validate configuration settings. This either returns True or
00705         raises an exception
00706 
00707         Arguments: None
00708         """
00709         if self.config_errors :
00710             raise self.config_errors [0]
00711         return True
00712 
00713 
00714     def load_resources (self, resources):
00715         """ Load configuration data from a Resources instance and expand it
00716         based on defined features.
00717 
00718         Positional arguments:
00719         resources - the resources object to load from and expand
00720         """
00721         # Update configuration files until added features creates no changes
00722         prev_features = set()
00723         while True:
00724             # Add/update the configuration with any .json files found while
00725             # scanning
00726             self.add_config_files (resources.json_files)
00727 
00728             # Add features while we find new ones
00729             features = set(self.get_features ())
00730             if features == prev_features:
00731                 break
00732 
00733             for feature in features:
00734                 if feature in resources.features:
00735                     resources.add(resources.features[feature])
00736 
00737             prev_features = features
00738         self.validate_config ()
00739 
00740         return resources
00741 
00742     @staticmethod
00743     def config_to_header (config, fname=None):
00744         """ Convert the configuration data to the content of a C header file,
00745         meant to be included to a C/C++ file. The content is returned as a
00746         string.
00747 
00748         Positional arguments:
00749         config - configuration data as (ConfigParam instances, ConfigMacro
00750                  instances) tuple (as returned by get_config_data())
00751 
00752         Keyword arguments:
00753         fname -  also write the content is to the file called "fname".
00754                  WARNING: if 'fname' names an existing file, it will be
00755                  overwritten!
00756         """
00757         params, macros = config[0], config[1]
00758         Config._check_required_parameters(params)
00759         header_data = "// Automatically generated configuration file.\n"
00760         header_data += "// DO NOT EDIT, content will be overwritten.\n\n"
00761         header_data += "#ifndef __MBED_CONFIG_DATA__\n"
00762         header_data += "#define __MBED_CONFIG_DATA__\n\n"
00763         # Compute maximum length of macro names for proper alignment
00764         max_param_macro_name_len = (max([len(m.macro_name) for m
00765                                          in params.values()
00766                                          if m.value is not None])
00767                                     if params else 0)
00768         max_direct_macro_name_len = (max([len(m.macro_name) for m
00769                                          in macros.values()])
00770                                      if macros else 0)
00771         max_macro_name_len = max(max_param_macro_name_len,
00772                                  max_direct_macro_name_len)
00773         # Compute maximum length of macro values for proper alignment
00774         max_param_macro_val_len = (max([len(str(m.value)) for m
00775                                        in params.values()
00776                                        if m.value is not None])
00777                                    if params else 0)
00778         max_direct_macro_val_len = max([len(m.macro_value or "") for m
00779                                         in macros.values()]) if macros else 0
00780         max_macro_val_len = max(max_param_macro_val_len,
00781                                 max_direct_macro_val_len)
00782         # Generate config parameters first
00783         if params:
00784             header_data += "// Configuration parameters\n"
00785             for macro in params.values():
00786                 if macro.value is not None:
00787                     header_data += ("#define {0:<{1}} {2!s:<{3}} " +
00788                                     "// set by {4}\n")\
00789                         .format(macro.macro_name, max_macro_name_len,
00790                                 macro.value, max_macro_val_len, macro.set_by)
00791         # Then macros
00792         if macros:
00793             header_data += "// Macros\n"
00794             for macro in macros.values():
00795                 if macro.macro_value:
00796                     header_data += ("#define {0:<{1}} {2!s:<{3}}" +
00797                                     " // defined by {4}\n")\
00798                         .format(macro.macro_name, max_macro_name_len,
00799                                 macro.macro_value, max_macro_val_len,
00800                                 macro.defined_by)
00801                 else:
00802                     header_data += ("#define {0:<{1}}" +
00803                                     " // defined by {2}\n")\
00804                         .format(macro.macro_name,
00805                                 max_macro_name_len + max_macro_val_len + 1,
00806                                 macro.defined_by)
00807         header_data += "\n#endif\n"
00808         # If fname is given, write "header_data" to it
00809         if fname:
00810             with open(fname, "w+") as file_desc:
00811                 file_desc.write(header_data)
00812         return header_data
00813 
00814     def get_config_data_header (self, fname=None):
00815         """ Convert a Config instance to the content of a C header file, meant
00816         to be included to a C/C++ file. The content is returned as a string.
00817 
00818         Keyword arguments:
00819         fname - also write the content to the file called "fname".
00820                 WARNING: if 'fname' names an existing file, it will be
00821                 overwritten!
00822         """
00823         return self.config_to_header (self.get_config_data (), fname)