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