Rtos API example

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers __init__.py Source File

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