Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
__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)
Generated on Tue Jul 12 2022 17:12:47 by
