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