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.
Diff: mbed-os/tools/config/__init__.py
- Revision:
- 0:380207fcb5c1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os/tools/config/__init__.py Thu Mar 29 07:02:09 2018 +0000
@@ -0,0 +1,1083 @@
+"""
+mbed SDK
+Copyright (c) 2016 ARM Limited
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from __future__ import print_function, division, absolute_import
+
+from copy import deepcopy
+from six import moves
+import json
+import six
+import os
+from os.path import dirname, abspath, exists, join, isabs
+import sys
+from collections import namedtuple
+from os.path import splitext, relpath
+from intelhex import IntelHex
+from jinja2 import FileSystemLoader, StrictUndefined
+from jinja2.environment import Environment
+from jsonschema import Draft4Validator, RefResolver
+
+from ..utils import json_file_to_dict, intelhex_offset, integer
+from ..arm_pack_manager import Cache
+from ..targets import (CUMULATIVE_ATTRIBUTES, TARGET_MAP, generate_py_target,
+ get_resolution_order, Target)
+
+try:
+ unicode
+except NameError:
+ unicode = str
+PATH_OVERRIDES = set(["target.bootloader_img"])
+BOOTLOADER_OVERRIDES = set(["target.bootloader_img", "target.restrict_size",
+ "target.header_format", "target.header_offset",
+ "target.app_offset",
+ "target.mbed_app_start", "target.mbed_app_size"])
+
+
+# Base class for all configuration exceptions
+class ConfigException(Exception):
+ """Config system only exception. Makes it easier to distinguish config
+ errors"""
+ pass
+
+class ConfigParameter(object):
+ """This class keeps information about a single configuration parameter"""
+
+ def __init__(self, name, data, unit_name, unit_kind):
+ """Construct a ConfigParameter
+
+ Positional arguments:
+ name - the name of the configuration parameter
+ data - the data associated with the configuration parameter
+ unit_name - the unit (target/library/application) that defines this
+ parameter
+ unit_ kind - the kind of the unit ("target", "library" or "application")
+ """
+ self.name = self.get_full_name(name, unit_name, unit_kind,
+ allow_prefix=False)
+ self.defined_by = self.get_display_name(unit_name, unit_kind)
+ self.set_value(data.get("value", None), unit_name, unit_kind)
+ self.help_text = data.get("help", None)
+ self.required = data.get("required", False)
+ self.macro_name = data.get("macro_name", "MBED_CONF_%s" %
+ self.sanitize(self.name.upper()))
+ self.config_errors = []
+
+ @staticmethod
+ def get_full_name(name, unit_name, unit_kind, label=None,
+ allow_prefix=True):
+ """Return the full (prefixed) name of a parameter. If the parameter
+ already has a prefix, check if it is valid
+
+ Positional arguments:
+ name - the simple (unqualified) name of the parameter
+ unit_name - the unit (target/library/application) that defines this
+ parameter
+ unit_kind - the kind of the unit ("target", "library" or "application")
+
+ Keyword arguments:
+ label - the name of the label in the 'target_config_overrides' section
+ allow_prefix - True to allow the original name to have a prefix, False
+ otherwise
+ """
+ if name.find('.') == -1: # the name is not prefixed
+ if unit_kind == "target":
+ prefix = "target."
+ elif unit_kind == "application":
+ prefix = "app."
+ else:
+ prefix = unit_name + '.'
+ return prefix + name
+ if name in BOOTLOADER_OVERRIDES:
+ return name
+ # The name has a prefix, so check if it is valid
+ if not allow_prefix:
+ raise ConfigException("Invalid parameter name '%s' in '%s'" %
+ (name, ConfigParameter.get_display_name(
+ unit_name, unit_kind, label)))
+ temp = name.split(".")
+ # Check if the parameter syntax is correct (must be
+ # unit_name.parameter_name)
+ if len(temp) != 2:
+ raise ConfigException("Invalid parameter name '%s' in '%s'" %
+ (name, ConfigParameter.get_display_name(
+ unit_name, unit_kind, label)))
+ prefix = temp[0]
+ # Check if the given parameter prefix matches the expected prefix
+ if (unit_kind == "library" and prefix != unit_name) or \
+ (unit_kind == "target" and prefix != "target"):
+ raise ConfigException(
+ "Invalid prefix '%s' for parameter name '%s' in '%s'" %
+ (prefix, name, ConfigParameter.get_display_name(
+ unit_name, unit_kind, label)))
+ return name
+
+ @staticmethod
+ def get_display_name(unit_name, unit_kind, label=None):
+ """Return the name displayed for a unit when interrogating the origin
+ and the last set place of a parameter
+
+ Positional arguments:
+ unit_name - the unit (target/library/application) that defines this
+ parameter
+ unit_kind - the kind of the unit ("target", "library" or "application")
+
+ Keyword arguments:
+ label - the name of the label in the 'target_config_overrides' section
+ """
+ if unit_kind == "target":
+ return "target:" + unit_name
+ elif unit_kind == "application":
+ return "application%s" % ("[%s]" % label if label else "")
+ else: # library
+ return "library:%s%s" % (unit_name, "[%s]" % label if label else "")
+
+ @staticmethod
+ def sanitize(name):
+ """ "Sanitize" a name so that it is a valid C macro name. Currently it
+ simply replaces '.' and '-' with '_'.
+
+ Positional arguments:
+ name - the name to make into a valid C macro
+ """
+ return name.replace('.', '_').replace('-', '_')
+
+ def set_value(self, value, unit_name, unit_kind, label=None):
+ """ Sets a value for this parameter, remember the place where it was
+ set. If the value is a Boolean, it is converted to 1 (for True) or
+ to 0 (for False).
+
+ Positional arguments:
+ value - the value of the parameter
+ unit_name - the unit (target/library/application) that defines this
+ parameter
+ unit_kind - the kind of the unit ("target", "library" or "application")
+
+ Keyword arguments:
+ label - the name of the label in the 'target_config_overrides' section
+ (optional)
+ """
+ self.value = int(value) if isinstance(value, bool) else value
+ self.set_by = self.get_display_name(unit_name, unit_kind, label)
+
+ def __str__(self):
+ """Return the string representation of this configuration parameter
+
+ Arguments: None
+ """
+ if self.value is not None:
+ return '%s = %s (macro name: "%s")' % \
+ (self.name, self.value, self.macro_name)
+ else:
+ return '%s has no value' % self.name
+
+ def get_verbose_description(self):
+ """Return a verbose description of this configuration parameter as a
+ string
+
+ Arguments: None
+ """
+ desc = "Name: %s%s\n" % \
+ (self.name, " (required parameter)" if self.required else "")
+ if self.help_text:
+ desc += " Description: %s\n" % self.help_text
+ desc += " Defined by: %s\n" % self.defined_by
+ if not self.value:
+ return desc + " No value set"
+ desc += " Macro name: %s\n" % self.macro_name
+ desc += " Value: %s (set by %s)" % (self.value, self.set_by)
+ return desc
+
+class ConfigMacro(object):
+ """ A representation of a configuration macro. It handles both macros
+ without a value (MACRO) and with a value (MACRO=VALUE)
+ """
+ def __init__(self, name, unit_name, unit_kind):
+ """Construct a ConfigMacro object
+
+ Positional arguments:
+ name - the macro's name
+ unit_name - the location where the macro was defined
+ unit_kind - the type of macro this is
+ """
+ self.name = name
+ self.defined_by = ConfigParameter.get_display_name(unit_name, unit_kind)
+ if name.find("=") != -1:
+ tmp = name.split("=")
+ if len(tmp) != 2:
+ raise ValueError("Invalid macro definition '%s' in '%s'" %
+ (name, self.defined_by))
+ self.macro_name = tmp[0]
+ self.macro_value = tmp[1]
+ else:
+ self.macro_name = name
+ self.macro_value = None
+
+class ConfigCumulativeOverride(object):
+ """Representation of overrides for cumulative attributes"""
+ def __init__(self, name, additions=None, removals=None, strict=False):
+ """Construct a ConfigCumulativeOverride object
+
+ Positional arguments:
+ name - the name of the config file this came from ?
+
+ Keyword arguments:
+ additions - macros to add to the overrides
+ removals - macros to remove from the overrides
+ strict - Boolean indicating that attempting to remove from an override
+ that does not exist should error
+ """
+ self.name = name
+ if additions:
+ self.additions = set(additions)
+ else:
+ self.additions = set()
+ if removals:
+ self.removals = set(removals)
+ else:
+ self.removals = set()
+ self.strict = strict
+
+ def remove_cumulative_overrides(self, overrides):
+ """Extend the list of override removals.
+
+ Positional arguments:
+ overrides - a list of names that, when the override is evaluated, will
+ be removed
+ """
+ for override in overrides:
+ if override in self.additions:
+ raise ConfigException(
+ "Configuration conflict. The %s %s both added and removed."
+ % (self.name[:-1], override))
+
+ self.removals |= set(overrides)
+
+ def add_cumulative_overrides(self, overrides):
+ """Extend the list of override additions.
+
+ Positional arguments:
+ overrides - a list of a names that, when the override is evaluated, will
+ be added to the list
+ """
+ for override in overrides:
+ if override in self.removals or \
+ (self.strict and override not in self.additions):
+ raise ConfigException(
+ "Configuration conflict. The %s %s both added and removed."
+ % (self.name[:-1], override))
+
+ self.additions |= set(overrides)
+
+ def strict_cumulative_overrides(self, overrides):
+ """Remove all overrides that are not the specified ones
+
+ Positional arguments:
+ overrides - a list of names that will replace the entire attribute when
+ this override is evaluated.
+ """
+ self.remove_cumulative_overrides(self.additions - set(overrides))
+ self.add_cumulative_overrides(overrides)
+ self.strict = True
+
+ def update_target(self, target):
+ """Update the attributes of a target based on this override"""
+ setattr(target, self.name,
+ list((set(getattr(target, self.name, []))
+ | self.additions) - self.removals))
+
+
+def _process_config_parameters(data, params, unit_name, unit_kind):
+ """Process a "config_parameters" section in either a target, a library,
+ or the application.
+
+ Positional arguments:
+ data - a dictionary with the configuration parameters
+ params - storage for the discovered configuration parameters
+ unit_name - the unit (target/library/application) that defines this
+ parameter
+ unit_kind - the kind of the unit ("target", "library" or "application")
+ """
+ for name, val in data.items():
+ full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind)
+ # If the parameter was already defined, raise an error
+ if full_name in params:
+ raise ConfigException(
+ "Parameter name '%s' defined in both '%s' and '%s'" %
+ (name, ConfigParameter.get_display_name(unit_name, unit_kind),
+ params[full_name].defined_by))
+ # Otherwise add it to the list of known parameters
+ # If "val" is not a dictionary, this is a shortcut definition,
+ # otherwise it is a full definition
+ params[full_name] = ConfigParameter(name, val if isinstance(val, dict)
+ else {"value": val}, unit_name,
+ unit_kind)
+ return params
+
+
+def _process_macros(mlist, macros, unit_name, unit_kind):
+ """Process a macro definition and check for incompatible duplicate
+ definitions.
+
+ Positional arguments:
+ mlist - list of macro names to process
+ macros - dictionary with currently discovered macros
+ unit_name - the unit (library/application) that defines this macro
+ unit_kind - the kind of the unit ("library" or "application")
+ """
+ for mname in mlist:
+ macro = ConfigMacro(mname, unit_name, unit_kind)
+ if (macro.macro_name in macros) and \
+ (macros[macro.macro_name].name != mname):
+ # Found an incompatible definition of the macro in another module,
+ # so raise an error
+ full_unit_name = ConfigParameter.get_display_name(unit_name,
+ unit_kind)
+ raise ConfigException(
+ ("Macro '%s' defined in both '%s' and '%s'"
+ % (macro.macro_name, macros[macro.macro_name].defined_by,
+ full_unit_name)) +
+ " with incompatible values")
+ macros[macro.macro_name] = macro
+
+
+Region = namedtuple("Region", "name start size active filename")
+
+class Config(object):
+ """'Config' implements the mbed configuration mechanism"""
+
+ # Libraries and applications have different names for their configuration
+ # files
+ __mbed_app_config_name = "mbed_app.json"
+ __mbed_lib_config_name = "mbed_lib.json"
+
+ __unused_overrides = set(["target.bootloader_img", "target.restrict_size",
+ "target.mbed_app_start", "target.mbed_app_size"])
+
+ # Allowed features in configurations
+ __allowed_features = [
+ "UVISOR", "BLE", "CLIENT", "IPV4", "LWIP", "COMMON_PAL", "STORAGE", "NANOSTACK",
+ # Nanostack configurations
+ "LOWPAN_BORDER_ROUTER", "LOWPAN_HOST", "LOWPAN_ROUTER", "NANOSTACK_FULL", "THREAD_BORDER_ROUTER", "THREAD_END_DEVICE", "THREAD_ROUTER", "ETHERNET_HOST"
+ ]
+
+ @classmethod
+ def find_app_config(cls, top_level_dirs):
+ app_config_location = None
+ for directory in top_level_dirs:
+ full_path = os.path.join(directory, cls.__mbed_app_config_name)
+ if os.path.isfile(full_path):
+ if app_config_location is not None:
+ raise ConfigException("Duplicate '%s' file in '%s' and '%s'"
+ % (cls.__mbed_app_config_name,
+ cls.app_config_location, full_path))
+ else:
+ app_config_location = full_path
+ return app_config_location
+
+ def format_validation_error(self, error, path):
+ if error.context:
+ return self.format_validation_error(error.context[0], path)
+ else:
+ return "in {} element {}: {}".format(
+ path, str(".".join(str(p) for p in error.absolute_path)), error.message)
+
+ def __init__(self, tgt, top_level_dirs=None, app_config=None):
+ """Construct a mbed configuration
+
+ Positional arguments:
+ target - the name of the mbed target used for this configuration
+ instance
+
+ Keyword argumets:
+ top_level_dirs - a list of top level source directories (where
+ mbed_app_config.json could be found)
+ app_config - location of a chosen mbed_app.json file
+
+ NOTE: Construction of a Config object will look for the application
+ configuration file in top_level_dirs. If found once, it'll parse it.
+ top_level_dirs may be None (in this case, the constructor will not
+ search for a configuration file).
+ """
+ config_errors = []
+ self.app_config_location = app_config
+ if self.app_config_location is None and top_level_dirs:
+ self.app_config_location = self.find_app_config(top_level_dirs)
+ try:
+ self.app_config_data = json_file_to_dict(self.app_config_location) \
+ if self.app_config_location else {}
+ except ValueError as exc:
+ self.app_config_data = {}
+ config_errors.append(
+ ConfigException("Could not parse mbed app configuration from %s"
+ % self.app_config_location))
+
+
+ if self.app_config_location is not None:
+ # Validate the format of the JSON file based on schema_app.json
+ schema_root = os.path.dirname(os.path.abspath(__file__))
+ schema_path = os.path.join(schema_root, "schema_app.json")
+ schema = json_file_to_dict(schema_path)
+
+ url = moves.urllib.request.pathname2url(schema_path)
+ uri = moves.urllib_parse.urljoin("file://", url)
+
+ resolver = RefResolver(uri, schema)
+ validator = Draft4Validator(schema, resolver=resolver)
+
+ errors = sorted(validator.iter_errors(self.app_config_data))
+
+ if errors:
+ raise ConfigException("; ".join(
+ self.format_validation_error(x, self.app_config_location)
+ for x in errors))
+
+ # Update the list of targets with the ones defined in the application
+ # config, if applicable
+ self.lib_config_data = {}
+ # Make sure that each config is processed only once
+ self.processed_configs = {}
+ if isinstance(tgt, Target):
+ self.target = tgt
+ else:
+ if tgt in TARGET_MAP:
+ self.target = TARGET_MAP[tgt]
+ else:
+ self.target = generate_py_target(
+ self.app_config_data.get("custom_targets", {}), tgt)
+ self.target = deepcopy(self.target)
+ self.target_labels = self.target.labels
+ for override in BOOTLOADER_OVERRIDES:
+ _, attr = override.split(".")
+ setattr(self.target, attr, None)
+
+ self.cumulative_overrides = {key: ConfigCumulativeOverride(key)
+ for key in CUMULATIVE_ATTRIBUTES}
+
+ self._process_config_and_overrides(self.app_config_data, {}, "app",
+ "application")
+ self.config_errors = config_errors
+
+ def add_config_files(self, flist):
+ """Add configuration files
+
+ Positional arguments:
+ flist - a list of files to add to this configuration
+ """
+ for config_file in flist:
+ if not config_file.endswith(self.__mbed_lib_config_name):
+ continue
+ full_path = os.path.normpath(os.path.abspath(config_file))
+ # Check that we didn't already process this file
+ if full_path in self.processed_configs:
+ continue
+ self.processed_configs[full_path] = True
+ # Read the library configuration and add a "__full_config_path"
+ # attribute to it
+ try:
+ cfg = json_file_to_dict(config_file)
+ except ValueError as exc:
+ sys.stderr.write(str(exc) + "\n")
+ continue
+
+ # Validate the format of the JSON file based on the schema_lib.json
+ schema_root = os.path.dirname(os.path.abspath(__file__))
+ schema_path = os.path.join(schema_root, "schema_lib.json")
+ schema_file = json_file_to_dict(schema_path)
+
+ url = moves.urllib.request.pathname2url(schema_path)
+ uri = moves.urllib_parse.urljoin("file://", url)
+
+ resolver = RefResolver(uri, schema_file)
+ validator = Draft4Validator(schema_file, resolver=resolver)
+
+ errors = sorted(validator.iter_errors(cfg))
+
+ if errors:
+ raise ConfigException("; ".join(
+ self.format_validation_error(x, config_file)
+ for x in errors))
+
+ cfg["__config_path"] = full_path
+
+ # If there's already a configuration for a module with the same
+ # name, exit with error
+ if cfg["name"] in self.lib_config_data:
+ raise ConfigException(
+ "Library name '%s' is not unique (defined in '%s' and '%s')"
+ % (cfg["name"], full_path,
+ self.lib_config_data[cfg["name"]]["__config_path"]))
+ self.lib_config_data[cfg["name"]] = cfg
+
+ @property
+ def has_regions(self):
+ """Does this config have regions defined?"""
+ for override in BOOTLOADER_OVERRIDES:
+ _, attr = override.split(".")
+ if getattr(self.target, attr, None):
+ return True
+ return False
+
+ @property
+ def sectors(self):
+ """Return a list of tuples of sector start,size"""
+ cache = Cache(False, False)
+ if self.target.device_name not in cache.index:
+ raise ConfigException("Bootloader not supported on this target: "
+ "targets.json `device_name` not found in "
+ "arm_pack_manager index.")
+ cmsis_part = cache.index[self.target.device_name]
+ sectors = cmsis_part['sectors']
+ if sectors:
+ return sectors
+ raise ConfigException("No sector info available")
+
+ @property
+ def regions(self):
+ """Generate a list of regions from the config"""
+ if not self.target.bootloader_supported:
+ raise ConfigException("Bootloader not supported on this target.")
+ if not hasattr(self.target, "device_name"):
+ raise ConfigException("Bootloader not supported on this target: "
+ "targets.json `device_name` not specified.")
+ cache = Cache(False, False)
+ if self.target.device_name not in cache.index:
+ raise ConfigException("Bootloader not supported on this target: "
+ "targets.json `device_name` not found in "
+ "arm_pack_manager index.")
+ cmsis_part = cache.index[self.target.device_name]
+ if ((self.target.bootloader_img or self.target.restrict_size) and
+ (self.target.mbed_app_start or self.target.mbed_app_size)):
+ raise ConfigException(
+ "target.bootloader_img and target.restirct_size are "
+ "incompatible with target.mbed_app_start and "
+ "target.mbed_app_size")
+ try:
+ rom_size = int(cmsis_part['memory']['IROM1']['size'], 0)
+ rom_start = int(cmsis_part['memory']['IROM1']['start'], 0)
+ except KeyError:
+ try:
+ rom_size = int(cmsis_part['memory']['PROGRAM_FLASH']['size'], 0)
+ rom_start = int(cmsis_part['memory']['PROGRAM_FLASH']['start'], 0)
+ except KeyError:
+ raise ConfigException("Not enough information in CMSIS packs to "
+ "build a bootloader project")
+ if self.target.bootloader_img or self.target.restrict_size:
+ return self._generate_bootloader_build(rom_start, rom_size)
+ elif self.target.mbed_app_start or self.target.mbed_app_size:
+ return self._generate_linker_overrides(rom_start, rom_size)
+ else:
+ raise ConfigException(
+ "Bootloader build requested but no bootlader configuration")
+
+ @staticmethod
+ def header_member_size(member):
+ _, _, subtype, _ = member
+ try:
+ return int(subtype[:-2]) // 8
+ except:
+ if subtype.startswith("CRCITT32"):
+ return 32 // 8
+ elif subtype == "SHA256":
+ return 256 // 8
+ elif subtype == "SHA512":
+ return 512 // 8
+ else:
+ raise ValueError("target.header_format: subtype %s is not "
+ "understood" % subtype)
+
+ @staticmethod
+ def _header_size(format):
+ return sum(Config.header_member_size(m) for m in format)
+
+ def _make_header_region(self, start, header_format, offset=None):
+ size = self._header_size(header_format)
+ region = Region("header", start, size, False, None)
+ start += size
+ start = ((start + 7) // 8) * 8
+ return (start, region)
+
+ @staticmethod
+ def _assign_new_offset(rom_start, start, new_offset, region_name):
+ newstart = rom_start + integer(new_offset, 0)
+ if newstart < start:
+ raise ConfigException(
+ "Can not place % region inside previous region" % region_name)
+ return newstart
+
+ def _generate_bootloader_build(self, rom_start, rom_size):
+ start = rom_start
+ rom_end = rom_start + rom_size
+ if self.target.bootloader_img:
+ if isabs(self.target.bootloader_img):
+ filename = self.target.bootloader_img
+ else:
+ basedir = abspath(dirname(self.app_config_location))
+ filename = join(basedir, self.target.bootloader_img)
+ if not exists(filename):
+ raise ConfigException("Bootloader %s not found" % filename)
+ part = intelhex_offset(filename, offset=rom_start)
+ if part.minaddr() != rom_start:
+ raise ConfigException("bootloader executable does not "
+ "start at 0x%x" % rom_start)
+ part_size = (part.maxaddr() - part.minaddr()) + 1
+ part_size = Config._align_ceiling(rom_start + part_size, self.sectors) - rom_start
+ yield Region("bootloader", rom_start, part_size, False,
+ filename)
+ start = rom_start + part_size
+ if self.target.header_format:
+ if self.target.header_offset:
+ start = self._assign_new_offset(
+ rom_start, start, self.target.header_offset, "header")
+ start, region = self._make_header_region(
+ start, self.target.header_format)
+ yield region._replace(filename=self.target.header_format)
+ if self.target.restrict_size is not None:
+ new_size = int(self.target.restrict_size, 0)
+ new_size = Config._align_floor(start + new_size, self.sectors) - start
+ yield Region("application", start, new_size, True, None)
+ start += new_size
+ if self.target.header_format:
+ if self.target.header_offset:
+ start = self._assign_new_offset(
+ rom_start, start, self.target.header_offset, "header")
+ start, region = self._make_header_region(
+ start, self.target.header_format)
+ yield region
+ if self.target.app_offset:
+ start = self._assign_new_offset(
+ rom_start, start, self.target.app_offset, "application")
+ yield Region("post_application", start, rom_end - start,
+ False, None)
+ else:
+ if self.target.app_offset:
+ start = self._assign_new_offset(
+ rom_start, start, self.target.app_offset, "application")
+ yield Region("application", start, rom_end - start,
+ True, None)
+ if start > rom_start + rom_size:
+ raise ConfigException("Not enough memory on device to fit all "
+ "application regions")
+
+ @staticmethod
+ def _find_sector(address, sectors):
+ target_size = -1
+ target_start = -1
+ for (start, size) in sectors:
+ if address < start:
+ break
+ target_start = start
+ target_size = size
+ if (target_size < 0):
+ raise ConfigException("No valid sector found")
+ return target_start, target_size
+
+ @staticmethod
+ def _align_floor(address, sectors):
+ target_start, target_size = Config._find_sector(address, sectors)
+ sector_num = (address - target_start) // target_size
+ return target_start + (sector_num * target_size)
+
+ @staticmethod
+ def _align_ceiling(address, sectors):
+ target_start, target_size = Config._find_sector(address, sectors)
+ sector_num = ((address - target_start) + target_size - 1) // target_size
+ return target_start + (sector_num * target_size)
+
+ @property
+ def report(self):
+ return {'app_config': self.app_config_location,
+ 'library_configs': map(relpath, self.processed_configs.keys())}
+
+ def _generate_linker_overrides(self, rom_start, rom_size):
+ if self.target.mbed_app_start is not None:
+ start = int(self.target.mbed_app_start, 0)
+ else:
+ start = rom_start
+ if self.target.mbed_app_size is not None:
+ size = int(self.target.mbed_app_size, 0)
+ else:
+ size = (rom_size + rom_start) - start
+ if start < rom_start:
+ raise ConfigException("Application starts before ROM")
+ if size + start > rom_size + rom_start:
+ raise ConfigException("Application ends after ROM")
+ yield Region("application", start, size, True, None)
+
+ def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
+ """Process "config_parameters" and "target_config_overrides" into a
+ given dictionary
+
+ Positional arguments:
+ data - the configuration data of the library/appliation
+ params - storage for the discovered configuration parameters
+ unit_name - the unit (library/application) that defines this parameter
+ unit_kind - the kind of the unit ("library" or "application")
+ """
+ self.config_errors = []
+ _process_config_parameters(data.get("config", {}), params, unit_name,
+ unit_kind)
+ for label, overrides in data.get("target_overrides", {}).items():
+ # If the label is defined by the target or it has the special value
+ # "*", process the overrides
+ if (label == '*') or (label in self.target_labels):
+ # Check for invalid cumulative overrides in libraries
+ if (unit_kind == 'library' and
+ any(attr.startswith('target.extra_labels') for attr
+ in overrides.keys())):
+ raise ConfigException(
+ "Target override 'target.extra_labels' in " +
+ ConfigParameter.get_display_name(unit_name, unit_kind,
+ label) +
+ " is only allowed at the application level")
+
+ # Parse out cumulative overrides
+ for attr, cumulatives in self.cumulative_overrides.items():
+ if 'target.'+attr in overrides:
+ key = 'target.' + attr
+ if not isinstance(overrides[key], list):
+ raise ConfigException(
+ "The value of %s.%s is not of type %s" %
+ (unit_name, "target_overrides." + key,
+ "list"))
+ cumulatives.strict_cumulative_overrides(overrides[key])
+ del overrides[key]
+
+ if 'target.'+attr+'_add' in overrides:
+ key = 'target.' + attr + "_add"
+ if not isinstance(overrides[key], list):
+ raise ConfigException(
+ "The value of %s.%s is not of type %s" %
+ (unit_name, "target_overrides." + key,
+ "list"))
+ cumulatives.add_cumulative_overrides(overrides[key])
+ del overrides[key]
+
+ if 'target.'+attr+'_remove' in overrides:
+ key = 'target.' + attr + "_remove"
+ if not isinstance(overrides[key], list):
+ raise ConfigException(
+ "The value of %s.%s is not of type %s" %
+ (unit_name, "target_overrides." + key,
+ "list"))
+ cumulatives.remove_cumulative_overrides(overrides[key])
+ del overrides[key]
+
+ # Consider the others as overrides
+ for name, val in overrides.items():
+ if (name in PATH_OVERRIDES and "__config_path" in data):
+ val = os.path.join(
+ os.path.dirname(data["__config_path"]), val)
+
+ # Get the full name of the parameter
+ full_name = ConfigParameter.get_full_name(name, unit_name,
+ unit_kind, label)
+ if full_name in params:
+ params[full_name].set_value(val, unit_name, unit_kind,
+ label)
+ elif (name.startswith("target.") and
+ (unit_kind is "application" or
+ name in BOOTLOADER_OVERRIDES)):
+ _, attribute = name.split(".")
+ setattr(self.target, attribute, val)
+ continue
+ else:
+ self.config_errors.append(
+ ConfigException(
+ "Attempt to override undefined parameter" +
+ (" '%s' in '%s'"
+ % (full_name,
+ ConfigParameter.get_display_name(unit_name,
+ unit_kind,
+ label)))))
+
+ for cumulatives in self.cumulative_overrides.values():
+ cumulatives.update_target(self.target)
+
+ return params
+
+ def get_target_config_data(self):
+ """Read and interpret configuration data defined by targets.
+
+ We consider the resolution order for our target and sort it by level
+ reversed, so that we first look at the top level target (the parent),
+ then its direct children, then the children of those children and so on,
+ until we reach self.target
+ TODO: this might not work so well in some multiple inheritance scenarios
+ At each step, look at two keys of the target data:
+ - config_parameters: used to define new configuration parameters
+ - config_overrides: used to override already defined configuration
+ parameters
+
+ Arguments: None
+ """
+ params, json_data = {}, self.target.json_data
+ resolution_order = [e[0] for e
+ in sorted(
+ self.target.resolution_order,
+ key=lambda e: e[1], reverse=True)]
+ for tname in resolution_order:
+ # Read the target data directly from its description
+ target_data = json_data[tname]
+ # Process definitions first
+ _process_config_parameters(target_data.get("config", {}), params,
+ tname, "target")
+ # Then process overrides
+ for name, val in target_data.get("overrides", {}).items():
+ full_name = ConfigParameter.get_full_name(name, tname, "target")
+ # If the parameter name is not defined or if there isn't a path
+ # from this target to the target where the parameter was defined
+ # in the target inheritance tree, raise an error We need to use
+ # 'defined_by[7:]' to remove the "target:" prefix from
+ # defined_by
+ rel_names = [tgt for tgt, _ in
+ get_resolution_order(self.target.json_data, tname,
+ [])]
+ if full_name in BOOTLOADER_OVERRIDES:
+ continue
+ if (full_name not in params) or \
+ (params[full_name].defined_by[7:] not in rel_names):
+ raise ConfigException(
+ "Attempt to override undefined parameter '%s' in '%s'"
+ % (name,
+ ConfigParameter.get_display_name(tname, "target")))
+ # Otherwise update the value of the parameter
+ params[full_name].set_value(val, tname, "target")
+ return params
+
+ def get_lib_config_data(self):
+ """ Read and interpret configuration data defined by libraries. It is
+ assumed that "add_config_files" above was already called and the library
+ configuration data exists in self.lib_config_data
+
+ Arguments: None
+ """
+ all_params, macros = {}, {}
+ for lib_name, lib_data in self.lib_config_data.items():
+ all_params.update(self._process_config_and_overrides(lib_data, {},
+ lib_name,
+ "library"))
+ _process_macros(lib_data.get("macros", []), macros, lib_name,
+ "library")
+ return all_params, macros
+
+ def get_app_config_data(self, params, macros):
+ """ Read and interpret the configuration data defined by the target. The
+ target can override any configuration parameter, as well as define its
+ own configuration data.
+
+ Positional arguments.
+ params - the dictionary with configuration parameters found so far (in
+ the target and in libraries)
+ macros - the list of macros defined in the configuration
+ """
+ app_cfg = self.app_config_data
+ # The application can have a "config_parameters" and a
+ # "target_config_overrides" section just like a library
+ self._process_config_and_overrides(app_cfg, params, "app",
+ "application")
+ # The application can also defined macros
+ _process_macros(app_cfg.get("macros", []), macros, "app",
+ "application")
+
+ def get_config_data(self):
+ """ Return the configuration data in two parts: (params, macros)
+ params - a dictionary with mapping a name to a ConfigParam
+ macros - the list of macros defined with "macros" in libraries and in
+ the application (as ConfigMacro instances)
+
+ Arguments: None
+ """
+ all_params = self.get_target_config_data()
+ lib_params, macros = self.get_lib_config_data()
+ all_params.update(lib_params)
+ self.get_app_config_data(all_params, macros)
+ return all_params, macros
+
+ @staticmethod
+ def _check_required_parameters(params):
+ """Check that there are no required parameters without a value
+
+ Positional arguments:
+ params - the list of parameters to check
+
+ NOTE: This function does not return. Instead, it throws a
+ ConfigException when any of the required parameters are missing values
+ """
+ for param in params.values():
+ if param.required and (param.value is None):
+ raise ConfigException("Required parameter '" + param.name +
+ "' defined by '" + param.defined_by +
+ "' doesn't have a value")
+
+ @staticmethod
+ def parameters_to_macros(params):
+ """ Encode the configuration parameters as C macro definitions.
+
+ Positional arguments:
+ params - a dictionary mapping a name to a ConfigParameter
+
+ Return: a list of strings that encode the configuration parameters as
+ C pre-processor macros
+ """
+ return ['%s=%s' % (m.macro_name, m.value) for m in params.values()
+ if m.value is not None]
+
+ @staticmethod
+ def config_macros_to_macros(macros):
+ """ Return the macro definitions generated for a dictionary of
+ ConfigMacros (as returned by get_config_data).
+
+ Positional arguments:
+ params - a dictionary mapping a name to a ConfigMacro instance
+
+ Return: a list of strings that are the C pre-processor macros
+ """
+ return [m.name for m in macros.values()]
+
+ @staticmethod
+ def config_to_macros(config):
+ """Convert the configuration data to a list of C macros
+
+ Positional arguments:
+ config - configuration data as (ConfigParam instances, ConfigMacro
+ instances) tuple (as returned by get_config_data())
+ """
+ params, macros = config[0], config[1]
+ Config._check_required_parameters(params)
+ return Config.config_macros_to_macros(macros) + \
+ Config.parameters_to_macros(params)
+
+ def get_config_data_macros(self):
+ """ Convert a Config object to a list of C macros
+
+ Arguments: None
+ """
+ return self.config_to_macros(self.get_config_data())
+
+ def get_features(self):
+ """ Extract any features from the configuration data
+
+ Arguments: None
+ """
+ params, _ = self.get_config_data()
+ self._check_required_parameters(params)
+ self.cumulative_overrides['features']\
+ .update_target(self.target)
+
+ for feature in self.target.features:
+ if feature not in self.__allowed_features:
+ raise ConfigException(
+ "Feature '%s' is not a supported features" % feature)
+
+ return self.target.features
+
+ def validate_config(self):
+ """ Validate configuration settings. This either returns True or
+ raises an exception
+
+ Arguments: None
+ """
+ if self.config_errors:
+ raise self.config_errors[0]
+ return True
+
+
+ @property
+ def name(self):
+ if "artifact_name" in self.app_config_data:
+ return self.app_config_data["artifact_name"]
+ else:
+ return None
+
+ def load_resources(self, resources):
+ """ Load configuration data from a Resources instance and expand it
+ based on defined features.
+
+ Positional arguments:
+ resources - the resources object to load from and expand
+ """
+ # Update configuration files until added features creates no changes
+ prev_features = set()
+ self.validate_config()
+ while True:
+ # Add/update the configuration with any .json files found while
+ # scanning
+ self.add_config_files(resources.json_files)
+
+ # Add features while we find new ones
+ features = set(self.get_features())
+ if features == prev_features:
+ break
+
+ for feature in features:
+ if feature in resources.features:
+ resources.add(resources.features[feature])
+
+ prev_features = features
+ self.validate_config()
+
+ return resources
+
+ @staticmethod
+ def config_to_header(config, fname=None):
+ """ Convert the configuration data to the content of a C header file,
+ meant to be included to a C/C++ file. The content is returned as a
+ string.
+
+ Positional arguments:
+ config - configuration data as (ConfigParam instances, ConfigMacro
+ instances) tuple (as returned by get_config_data())
+
+ Keyword arguments:
+ fname - also write the content is to the file called "fname".
+ WARNING: if 'fname' names an existing file, it will be
+ overwritten!
+ """
+ params, macros = config[0] or {}, config[1] or {}
+ Config._check_required_parameters(params)
+ params_with_values = [p for p in params.values() if p.value is not None]
+ ctx = {
+ "cfg_params" : [(p.macro_name, str(p.value), p.set_by)
+ for p in params_with_values],
+ "macros": [(m.macro_name, str(m.macro_value or ""), m.defined_by)
+ for m in macros.values()],
+ "name_len": max([len(m.macro_name) for m in macros.values()] +
+ [len(m.macro_name) for m in params_with_values]
+ + [0]),
+ "val_len" : max([len(str(m.value)) for m in params_with_values] +
+ [len(m.macro_value or "") for m in macros.values()]
+ + [0]),
+ }
+ jinja_loader = FileSystemLoader(dirname(abspath(__file__)))
+ jinja_environment = Environment(loader=jinja_loader,
+ undefined=StrictUndefined)
+ header_data = jinja_environment.get_template("header.tmpl").render(ctx)
+ # If fname is given, write "header_data" to it
+ if fname:
+ with open(fname, "w+") as file_desc:
+ file_desc.write(header_data)
+ return header_data
+
+ def get_config_data_header(self, fname=None):
+ """ Convert a Config instance to the content of a C header file, meant
+ to be included to a C/C++ file. The content is returned as a string.
+
+ Keyword arguments:
+ fname - also write the content to the file called "fname".
+ WARNING: if 'fname' names an existing file, it will be
+ overwritten!
+ """
+ return self.config_to_header(self.get_config_data(), fname)