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