Nathan Yonkee / Mbed 2 deprecated Nucleo_sinewave_output_copy

Dependencies:   mbed

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