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 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)
Generated on Tue Aug 9 2022 00:37:00 by
1.7.2