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