takashi kadono / Mbed OS Nucleo_446

Dependencies:   ssd1331

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             if not hasattr(self.target , attr):
00499                 setattr(self.target , attr, None)
00500 
00501         self.cumulative_overrides  = {key: ConfigCumulativeOverride(key)
00502                                      for key in CUMULATIVE_ATTRIBUTES}
00503 
00504         self._process_config_and_overrides (self.app_config_data , {}, "app",
00505                                            "application")
00506         self.config_errors  = config_errors
00507 
00508     def add_config_files (self, flist):
00509         """Add configuration files
00510 
00511         Positional arguments:
00512         flist - a list of files to add to this configuration
00513         """
00514         for config_file in flist:
00515             if not config_file.endswith(self.__mbed_lib_config_name ):
00516                 continue
00517             full_path = os.path.normpath(os.path.abspath(config_file))
00518             # Check that we didn't already process this file
00519             if full_path in self.processed_configs :
00520                 continue
00521             self.processed_configs [full_path] = True
00522             # Read the library configuration and add a "__full_config_path"
00523             # attribute to it
00524             try:
00525                 cfg = json_file_to_dict(config_file)
00526             except ValueError as exc:
00527                 raise ConfigException(str(exc))
00528 
00529             # Validate the format of the JSON file based on the schema_lib.json
00530             schema_root = os.path.dirname(os.path.abspath(__file__))
00531             schema_path = os.path.join(schema_root, "schema_lib.json")
00532             schema_file = json_file_to_dict(schema_path)
00533 
00534             url = moves.urllib.request.pathname2url(schema_path)
00535             uri = moves.urllib_parse.urljoin("file://", url)
00536 
00537             resolver = RefResolver(uri, schema_file)
00538             validator = Draft4Validator(schema_file, resolver=resolver)
00539 
00540             errors = sorted(validator.iter_errors(cfg))
00541 
00542             if errors:
00543                 raise ConfigException("; ".join(
00544                     self.format_validation_error (x, config_file)
00545                     for x in errors))
00546 
00547             cfg["__config_path"] = full_path
00548 
00549             # If there's already a configuration for a module with the same
00550             # name, exit with error
00551             if cfg["name"] in self.lib_config_data :
00552                 raise ConfigException(
00553                     "Library name '%s' is not unique (defined in '%s' and '%s')"
00554                     % (cfg["name"], full_path,
00555                        self.lib_config_data [cfg["name"]]["__config_path"]))
00556             self.lib_config_data [cfg["name"]] = cfg
00557 
00558     @property
00559     def has_regions (self):
00560         """Does this config have regions defined?"""
00561         for override in ROM_OVERRIDES:
00562             _, attr = override.split(".")
00563             if getattr(self.target , attr, None):
00564                 return True
00565         return False
00566 
00567     @property
00568     def has_ram_regions (self):
00569         """Does this config have regions defined?"""
00570         for override in RAM_OVERRIDES:
00571             _, attr = override.split(".")
00572             if getattr(self.target , attr, None):
00573                 return True
00574         return False
00575 
00576     @property
00577     def sectors (self):
00578         """Return a list of tuples of sector start,size"""
00579         cache = Cache(False, False)
00580         if self.target .device_name not in cache.index:
00581             raise ConfigException("Bootloader not supported on this target: "
00582                                   "targets.json `device_name` not found in "
00583                                   "arm_pack_manager index.")
00584         cmsis_part = cache.index[self.target .device_name]
00585         sectors = cmsis_part['sectors']
00586         if sectors:
00587             return sectors
00588         raise ConfigException("No sector info available")
00589 
00590     def _get_cmsis_part(self):
00591         if not getattr(self.target , "bootloader_supported", False):
00592             raise ConfigException("Bootloader not supported on this target.")
00593         if not hasattr(self.target , "device_name"):
00594             raise ConfigException("Bootloader not supported on this target: "
00595                                   "targets.json `device_name` not specified.")
00596         cache = Cache(False, False)
00597         if self.target .device_name not in cache.index:
00598             raise ConfigException("Bootloader not supported on this target: "
00599                                   "targets.json `device_name` not found in "
00600                                   "arm_pack_manager index.")
00601         return cache.index[self.target .device_name]
00602 
00603     def _get_mem_specs(self, memories, cmsis_part, exception_text):
00604         for memory in memories:
00605             try:
00606                 size = cmsis_part['memory'][memory]['size']
00607                 start = cmsis_part['memory'][memory]['start']
00608                 return (start, size)
00609             except KeyError:
00610                 continue
00611         raise ConfigException(exception_text)
00612 
00613     @property
00614     def rom (self):
00615         """Get rom information as a pair of start_addr, size"""
00616         # Override rom_start/rom_size
00617         #
00618         # This is usually done for a target which:
00619         # 1. Doesn't support CMSIS pack, or
00620         # 2. Supports TrustZone and user needs to change its flash partition
00621         cmsis_part = self._get_cmsis_part ()
00622         rom_start, rom_size = self._get_mem_specs (
00623             ["IROM1", "PROGRAM_FLASH"],
00624             cmsis_part,
00625             "Not enough information in CMSIS packs to build a bootloader "
00626             "project"
00627         )
00628         rom_start = int(getattr(self.target , "mbed_rom_start", False) or rom_start, 0)
00629         rom_size = int(getattr(self.target , "mbed_rom_size", False) or rom_size, 0)
00630         return (rom_start, rom_size)
00631 
00632     @property
00633     def ram_regions (self):
00634         """Generate a list of ram regions from the config"""
00635         cmsis_part = self._get_cmsis_part ()
00636         ram_start, ram_size = self._get_mem_specs (
00637             ["IRAM1", "SRAM0"],
00638             cmsis_part,
00639             "Not enough information in CMSIS packs to build a ram sharing project"
00640         )
00641         # Override ram_start/ram_size
00642         #
00643         # This is usually done for a target which:
00644         # 1. Doesn't support CMSIS pack, or
00645         # 2. Supports TrustZone and user needs to change its flash partition
00646         ram_start = getattr(self.target , "mbed_ram_start", False) or ram_start
00647         ram_size = getattr(self.target , "mbed_ram_size", False) or ram_size
00648         return [RamRegion("application_ram", int(ram_start, 0), int(ram_size, 0), True)]
00649 
00650     @property
00651     def regions (self):
00652         """Generate a list of regions from the config"""
00653         if  ((self.target .bootloader_img or self.target .restrict_size) and
00654              (self.target .mbed_app_start or self.target .mbed_app_size)):
00655             raise ConfigException(
00656                 "target.bootloader_img and target.restirct_size are "
00657                 "incompatible with target.mbed_app_start and "
00658                 "target.mbed_app_size")
00659         if self.target .bootloader_img or self.target .restrict_size:
00660             return self._generate_bootloader_build (*self.rom )
00661         else:
00662             return self._generate_linker_overrides (*self.rom )
00663 
00664     @staticmethod
00665     def header_member_size(member):
00666         _, _, subtype, _ = member
00667         try:
00668             return int(subtype[:-2]) // 8
00669         except:
00670             if subtype.startswith("CRCITT32"):
00671                 return 32 // 8
00672             elif subtype == "SHA256":
00673                 return 256 // 8
00674             elif subtype == "SHA512":
00675                 return 512 // 8
00676             else:
00677                 raise ValueError("target.header_format: subtype %s is not "
00678                                  "understood" % subtype)
00679 
00680     @staticmethod
00681     def _header_size(format):
00682         return sum(Config.header_member_size(m) for m in format)
00683 
00684     def _make_header_region(self, start, header_format, offset=None):
00685         size = self._header_size (header_format)
00686         region = Region("header", start, size, False, None)
00687         start += size
00688         start = ((start + 7) // 8) * 8
00689         return (start, region)
00690 
00691     @staticmethod
00692     def _assign_new_offset(rom_start, start, new_offset, region_name):
00693         newstart = rom_start + integer(new_offset, 0)
00694         if newstart < start:
00695             raise ConfigException(
00696                 "Can not place % region inside previous region" % region_name)
00697         return newstart
00698 
00699     def _generate_bootloader_build(self, rom_start, rom_size):
00700         start = rom_start
00701         rom_end = rom_start + rom_size
00702         if self.target .bootloader_img:
00703             if isabs(self.target .bootloader_img):
00704                 filename = self.target .bootloader_img
00705             else:
00706                 basedir = abspath(dirname(self.app_config_location ))
00707                 filename = join(basedir, self.target .bootloader_img)
00708             if not exists(filename):
00709                 raise ConfigException("Bootloader %s not found" % filename)
00710             part = intelhex_offset(filename, offset=rom_start)
00711             if part.minaddr() != rom_start:
00712                 raise ConfigException("bootloader executable does not "
00713                                       "start at 0x%x" % rom_start)
00714             part_size = (part.maxaddr() - part.minaddr()) + 1
00715             part_size = Config._align_ceiling(rom_start + part_size, self.sectors ) - rom_start
00716             yield Region("bootloader", rom_start, part_size, False,
00717                          filename)
00718             start = rom_start + part_size
00719             if self.target .header_format:
00720                 if self.target .header_offset:
00721                     start = self._assign_new_offset (
00722                         rom_start, start, self.target .header_offset, "header")
00723                 start, region = self._make_header_region (
00724                     start, self.target .header_format)
00725                 yield region._replace(filename=self.target .header_format)
00726         if self.target .restrict_size is not None:
00727             new_size = int(self.target .restrict_size, 0)
00728             new_size = Config._align_floor(start + new_size, self.sectors ) - start
00729             yield Region("application", start, new_size, True, None)
00730             start += new_size
00731             if self.target .header_format and not self.target .bootloader_img:
00732                 if self.target .header_offset:
00733                     start = self._assign_new_offset (
00734                         rom_start, start, self.target .header_offset, "header")
00735                 start, region = self._make_header_region (
00736                     start, self.target .header_format)
00737                 yield region
00738             if self.target .app_offset:
00739                 start = self._assign_new_offset (
00740                     rom_start, start, self.target .app_offset, "application")
00741             yield Region("post_application", start, rom_end - start,
00742                          False, None)
00743         else:
00744             if self.target .app_offset:
00745                 start = self._assign_new_offset (
00746                     rom_start, start, self.target .app_offset, "application")
00747             yield Region("application", start, rom_end - start,
00748                          True, None)
00749         if start > rom_start + rom_size:
00750             raise ConfigException("Not enough memory on device to fit all "
00751                                   "application regions")
00752     
00753     @staticmethod
00754     def _find_sector(address, sectors):
00755         target_size = -1
00756         target_start = -1
00757         for (start, size) in sectors:
00758             if address < start:
00759                 break
00760             target_start = start
00761             target_size = size
00762         if (target_size < 0):
00763             raise ConfigException("No valid sector found")
00764         return target_start, target_size
00765         
00766     @staticmethod
00767     def _align_floor(address, sectors):
00768         target_start, target_size = Config._find_sector(address, sectors)
00769         sector_num = (address - target_start) // target_size
00770         return target_start + (sector_num * target_size)
00771     
00772     @staticmethod
00773     def _align_ceiling(address, sectors):
00774         target_start, target_size = Config._find_sector(address, sectors)
00775         sector_num = ((address - target_start) + target_size - 1) // target_size
00776         return target_start + (sector_num * target_size)
00777 
00778     @property
00779     def report(self):
00780         return {'app_config': self.app_config_location ,
00781                 'library_configs': map(relpath, self.processed_configs .keys())}
00782 
00783     def _generate_linker_overrides(self, rom_start, rom_size):
00784         if self.target .mbed_app_start is not None:
00785             start = int(self.target .mbed_app_start, 0)
00786         else:
00787             start = rom_start
00788         if self.target .mbed_app_size is not None:
00789             size = int(self.target .mbed_app_size, 0)
00790         else:
00791             size = (rom_size + rom_start) - start
00792         if start < rom_start:
00793             raise ConfigException("Application starts before ROM")
00794         if size + start > rom_size + rom_start:
00795             raise ConfigException("Application ends after ROM")
00796         yield Region("application", start, size, True, None)
00797 
00798     def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
00799         """Process "config_parameters" and "target_config_overrides" into a
00800         given dictionary
00801 
00802         Positional arguments:
00803         data - the configuration data of the library/appliation
00804         params - storage for the discovered configuration parameters
00805         unit_name - the unit (library/application) that defines this parameter
00806         unit_kind - the kind of the unit ("library" or "application")
00807         """
00808         _process_config_parameters(data.get("config", {}), params, unit_name,
00809                                    unit_kind)
00810         for label, overrides in data.get("target_overrides", {}).items():
00811             # If the label is defined by the target or it has the special value
00812             # "*", process the overrides
00813             if (label == '*') or (label in self.target_labels ):
00814                 # Check for invalid cumulative overrides in libraries
00815                 if (unit_kind == 'library' and
00816                     any(attr.startswith('target.extra_labels') for attr
00817                         in overrides.keys())):
00818                     raise ConfigException(
00819                         "Target override 'target.extra_labels' in " +
00820                         ConfigParameter.get_display_name(unit_name, unit_kind,
00821                                                          label) +
00822                         " is only allowed at the application level")
00823 
00824                 # Parse out cumulative overrides
00825                 for attr, cumulatives in self.cumulative_overrides .items():
00826                     if 'target.'+attr in overrides:
00827                         key = 'target.' + attr
00828                         if not isinstance(overrides[key], list):
00829                             raise ConfigException(
00830                                 "The value of %s.%s is not of type %s" %
00831                                 (unit_name, "target_overrides." + key,
00832                                  "list"))
00833                         cumulatives.strict_cumulative_overrides(overrides[key])
00834                         del overrides[key]
00835 
00836                     if 'target.'+attr+'_add' in overrides:
00837                         key = 'target.' + attr + "_add"
00838                         if not isinstance(overrides[key], list):
00839                             raise ConfigException(
00840                                 "The value of %s.%s is not of type %s" %
00841                                 (unit_name, "target_overrides." + key,
00842                                  "list"))
00843                         cumulatives.add_cumulative_overrides(overrides[key])
00844                         del overrides[key]
00845 
00846                     if 'target.'+attr+'_remove' in overrides:
00847                         key = 'target.' + attr + "_remove"
00848                         if not isinstance(overrides[key], list):
00849                             raise ConfigException(
00850                                 "The value of %s.%s is not of type %s" %
00851                                 (unit_name, "target_overrides." + key,
00852                                  "list"))
00853                         cumulatives.remove_cumulative_overrides(overrides[key])
00854                         del overrides[key]
00855 
00856                 # Consider the others as overrides
00857                 for name, val in overrides.items():
00858                     if (name in PATH_OVERRIDES and "__config_path" in data):
00859                         val = os.path.join(
00860                             os.path.dirname(data["__config_path"]), val)
00861 
00862                     # Get the full name of the parameter
00863                     full_name = ConfigParameter.get_full_name(name, unit_name,
00864                                                               unit_kind, label)
00865                     if full_name in params:
00866                         params[full_name].set_value(val, unit_name, unit_kind,
00867                                                     label)
00868                     elif (name.startswith("target.") and
00869                         (unit_kind is "application" or
00870                          name in BOOTLOADER_OVERRIDES)):
00871                         _, attribute = name.split(".")
00872                         setattr(self.target , attribute, val)
00873                         continue
00874                     else:
00875                         self.config_errors .append(
00876                             UndefinedParameter(
00877                                 full_name, unit_name, unit_kind, label))
00878 
00879         for cumulatives in self.cumulative_overrides .values():
00880             cumulatives.update_target(self.target )
00881 
00882         return params
00883 
00884     def get_target_config_data (self):
00885         """Read and interpret configuration data defined by targets.
00886 
00887         We consider the resolution order for our target and sort it by level
00888         reversed, so that we first look at the top level target (the parent),
00889         then its direct children, then the children of those children and so on,
00890         until we reach self.target
00891         TODO: this might not work so well in some multiple inheritance scenarios
00892         At each step, look at two keys of the target data:
00893           - config_parameters: used to define new configuration parameters
00894           - config_overrides: used to override already defined configuration
00895                               parameters
00896 
00897         Arguments: None
00898         """
00899         params, json_data = {}, self.target .json_data
00900         resolution_order = [e[0] for e
00901                             in sorted(
00902                                 self.target .resolution_order,
00903                                 key=lambda e: e[1], reverse=True)]
00904         for tname in resolution_order:
00905             # Read the target data directly from its description
00906             target_data = json_data[tname]
00907             # Process definitions first
00908             _process_config_parameters(target_data.get("config", {}), params,
00909                                        tname, "target")
00910             # Then process overrides
00911             for name, val in target_data.get("overrides", {}).items():
00912                 full_name = ConfigParameter.get_full_name(name, tname, "target")
00913                 # If the parameter name is not defined or if there isn't a path
00914                 # from this target to the target where the parameter was defined
00915                 # in the target inheritance tree, raise an error We need to use
00916                 # 'defined_by[7:]' to remove the "target:" prefix from
00917                 # defined_by
00918                 rel_names = [tgt for tgt, _ in
00919                              get_resolution_order(self.target .json_data, tname,
00920                                                   [])]
00921                 if full_name in BOOTLOADER_OVERRIDES:
00922                     continue
00923                 if (full_name not in params) or \
00924                    (params[full_name].defined_by[7:] not in rel_names):
00925                     raise UndefinedParameter(name, tname, "target", "")
00926                 # Otherwise update the value of the parameter
00927                 params[full_name].set_value(val, tname, "target")
00928         return params
00929 
00930     def get_lib_config_data (self, target_data):
00931         """ Read and interpret configuration data defined by libraries. It is
00932         assumed that "add_config_files" above was already called and the library
00933         configuration data exists in self.lib_config_data
00934 
00935         Arguments: None
00936         """
00937         macros = {}
00938         for lib_name, lib_data in self.lib_config_data .items():
00939             self._process_config_and_overrides (
00940                 lib_data, target_data, lib_name, "library")
00941             _process_macros(lib_data.get("macros", []), macros, lib_name,
00942                             "library")
00943         return target_data, macros
00944 
00945     def get_app_config_data (self, params, macros):
00946         """ Read and interpret the configuration data defined by the target. The
00947         target can override any configuration parameter, as well as define its
00948         own configuration data.
00949 
00950         Positional arguments.
00951         params - the dictionary with configuration parameters found so far (in
00952                  the target and in libraries)
00953         macros - the list of macros defined in the configuration
00954         """
00955         app_cfg = self.app_config_data 
00956         # The application can have a "config_parameters" and a
00957         # "target_config_overrides" section just like a library
00958         self._process_config_and_overrides (app_cfg, params, "app",
00959                                            "application")
00960         # The application can also defined macros
00961         _process_macros(app_cfg.get("macros", []), macros, "app",
00962                         "application")
00963 
00964     def get_config_data (self):
00965         """ Return the configuration data in two parts: (params, macros)
00966         params - a dictionary with mapping a name to a ConfigParam
00967         macros - the list of macros defined with "macros" in libraries and in
00968                  the application (as ConfigMacro instances)
00969 
00970         Arguments: None
00971         """
00972         all_params = self.get_target_config_data ()
00973         lib_params, macros = self.get_lib_config_data (all_params)
00974         self.get_app_config_data (lib_params, macros)
00975         return lib_params, macros
00976 
00977     @staticmethod
00978     def _check_required_parameters(params):
00979         """Check that there are no required parameters without a value
00980 
00981         Positional arguments:
00982         params - the list of parameters to check
00983 
00984         NOTE: This function does not return. Instead, it throws a
00985         ConfigException when any of the required parameters are missing values
00986         """
00987         for param in params.values():
00988             if param.required and (param.value is None):
00989                 raise ConfigException("Required parameter '" + param.name +
00990                                       "' defined by '" + param.defined_by +
00991                                       "' doesn't have a value")
00992 
00993     @staticmethod
00994     def parameters_to_macros (params):
00995         """ Encode the configuration parameters as C macro definitions.
00996 
00997         Positional arguments:
00998         params - a dictionary mapping a name to a ConfigParameter
00999 
01000         Return: a list of strings that encode the configuration parameters as
01001         C pre-processor macros
01002         """
01003         return ['%s=%s' % (m.macro_name, m.value) for m in params.values()
01004                 if m.value is not None]
01005 
01006     @staticmethod
01007     def config_macros_to_macros (macros):
01008         """ Return the macro definitions generated for a dictionary of
01009         ConfigMacros (as returned by get_config_data).
01010 
01011         Positional arguments:
01012         params - a dictionary mapping a name to a ConfigMacro instance
01013 
01014         Return: a list of strings that are the C pre-processor macros
01015         """
01016         return [m.name for m in macros.values()]
01017 
01018     @staticmethod
01019     def config_to_macros (config):
01020         """Convert the configuration data to a list of C macros
01021 
01022         Positional arguments:
01023         config - configuration data as (ConfigParam instances, ConfigMacro
01024                  instances) tuple (as returned by get_config_data())
01025         """
01026         params, macros = config[0], config[1]
01027         Config._check_required_parameters(params)
01028         return Config.config_macros_to_macros(macros) + \
01029             Config.parameters_to_macros(params)
01030 
01031     def get_config_data_macros (self):
01032         """ Convert a Config object to a list of C macros
01033 
01034         Arguments: None
01035         """
01036         return self.config_to_macros (self.get_config_data ())
01037 
01038     def get_features (self):
01039         """ Extract any features from the configuration data
01040 
01041         Arguments: None
01042         """
01043         params, _ = self.get_config_data ()
01044         self._check_required_parameters (params)
01045         self.cumulative_overrides ['features']\
01046             .update_target(self.target )
01047 
01048         for feature in self.target .features:
01049             if feature not in ALLOWED_FEATURES:
01050                 raise ConfigException(
01051                     "Feature '%s' is not a supported features" % feature)
01052 
01053         return self.target .features
01054 
01055     def validate_config (self):
01056         """ Validate configuration settings. This either returns True or
01057         raises an exception
01058 
01059         Arguments: None
01060         """
01061         params, _ = self.get_config_data ()
01062         for error in self.config_errors :
01063             if  (isinstance(error, UndefinedParameter) and
01064                  error.param in params):
01065                 continue
01066             else:
01067                 raise error
01068         return True
01069 
01070 
01071     @property
01072     def name(self):
01073         if "artifact_name" in self.app_config_data :
01074             return self.app_config_data ["artifact_name"]
01075         else:
01076             return None
01077 
01078     def load_resources (self, resources):
01079         """ Load configuration data from a Resources instance and expand it
01080         based on defined features.
01081 
01082         Positional arguments:
01083         resources - the resources object to load from and expand
01084         """
01085         # Update configuration files until added features creates no changes
01086         prev_features = set()
01087         while True:
01088             # Add/update the configuration with any .json files found while
01089             # scanning
01090             self.add_config_files (
01091                 f.path for f in resources.get_file_refs(FileType.JSON)
01092             )
01093 
01094             # Add features while we find new ones
01095             features = set(self.get_features ())
01096             if features == prev_features:
01097                 break
01098 
01099             resources.add_features(features)
01100 
01101             prev_features = features
01102         self.validate_config ()
01103 
01104         if  (hasattr(self.target , "release_versions") and
01105              "5" not in self.target .release_versions and
01106              "rtos" in self.lib_config_data ):
01107             raise NotSupportedException("Target does not support mbed OS 5")
01108 
01109     @staticmethod
01110     def config_to_header (config, fname=None):
01111         """ Convert the configuration data to the content of a C header file,
01112         meant to be included to a C/C++ file. The content is returned as a
01113         string.
01114 
01115         Positional arguments:
01116         config - configuration data as (ConfigParam instances, ConfigMacro
01117                  instances) tuple (as returned by get_config_data())
01118 
01119         Keyword arguments:
01120         fname -  also write the content is to the file called "fname".
01121                  WARNING: if 'fname' names an existing file, it will be
01122                  overwritten!
01123         """
01124         params, macros = config[0] or {}, config[1] or {}
01125         Config._check_required_parameters(params)
01126         params_with_values = [p for p in params.values() if p.value is not None]
01127         ctx = {
01128             "cfg_params": sorted([
01129                 (p.macro_name, str(p.value), p.set_by)
01130                 for p in params_with_values
01131             ]),
01132             "macros": sorted([
01133                 (m.macro_name, str(m.macro_value or ""), m.defined_by)
01134                 for m in macros.values()
01135             ]),
01136             "name_len":  max([len(m.macro_name) for m in macros.values()] +
01137                              [len(m.macro_name) for m in params_with_values]
01138                              + [0]),
01139             "val_len" : max([len(str(m.value)) for m in params_with_values] +
01140                             [len(m.macro_value or "") for m in macros.values()]
01141                             + [0]),
01142         }
01143         jinja_loader = FileSystemLoader(dirname(abspath(__file__)))
01144         jinja_environment = Environment(loader=jinja_loader,
01145                                         undefined=StrictUndefined)
01146         header_data = jinja_environment.get_template("header.tmpl").render(ctx)
01147         # If fname is given, write "header_data" to it
01148         if fname:
01149             with open(fname, "w+") as file_desc:
01150                 file_desc.write(header_data)
01151         return header_data
01152 
01153     def get_config_data_header (self, fname=None):
01154         """ Convert a Config instance to the content of a C header file, meant
01155         to be included to a C/C++ file. The content is returned as a string.
01156 
01157         Keyword arguments:
01158         fname - also write the content to the file called "fname".
01159                 WARNING: if 'fname' names an existing file, it will be
01160                 overwritten!
01161         """
01162         return self.config_to_header (self.get_config_data (), fname)