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