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