Clone of official tools
Diff: config.py
- Revision:
- 13:ab47a20b66f0
- Parent:
- 10:2511036308b8
- Child:
- 24:25bff2709c20
--- a/config.py Tue Jun 14 11:33:06 2016 +0100 +++ b/config.py Thu Jul 14 20:21:19 2016 +0100 @@ -35,11 +35,11 @@ def __init__(self, name, data, unit_name, unit_kind): 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_by = self.defined_by + self.set_value(data.get("value", None), unit_name, unit_kind) self.help_text = data.get("help", None) - self.value = data.get("value", 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 = [] # Return the full (prefixed) name of a parameter. # If the parameter already has a prefix, check if it is valid @@ -92,13 +92,14 @@ def sanitize(name): return name.replace('.', '_').replace('-', '_') - # Sets a value for this parameter, remember the place where it was set + # 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). # 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") # label: the name of the label in the 'target_config_overrides' section (optional) def set_value(self, value, unit_name, unit_kind, label = None): - self.value = value + self.value = int(value) if isinstance(value, bool) else value self.set_by = self.get_display_name(unit_name, unit_kind, label) # Return the string representation of this configuration parameter @@ -131,8 +132,10 @@ 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 # 'Config' implements the mbed configuration mechanism class Config: @@ -147,6 +150,11 @@ "application": set(["config", "custom_targets", "target_overrides", "macros", "__config_path"]) } + # Allowed features in configurations + __allowed_features = [ + "UVISOR", "BLE", "CLIENT", "IPV4", "IPV6" + ] + # The initialization arguments for Config are: # target: the name of the mbed target used for this configuration instance # top_level_dirs: a list of top level source directories (where mbed_abb_config.json could be found) @@ -174,9 +182,11 @@ self.lib_config_data = {} # Make sure that each config is processed only once self.processed_configs = {} - self.target = target if isinstance(target, str) else target.name + self.target = target if isinstance(target, basestring) else target.name self.target_labels = Target.get_target(self.target).get_labels() - self.target_instance = Target.get_target(self.target) + self.added_features = set() + self.removed_features = set() + self.removed_unecessary_features = False # Add one or more configuration files def add_config_files(self, flist): @@ -212,44 +222,59 @@ params[full_name] = ConfigParameter(name, v if isinstance(v, dict) else {"value": v}, unit_name, unit_kind) return params + # Add features to the available features + def remove_features(self, features): + for feature in features: + if feature in self.added_features: + raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature) + + self.removed_features |= set(features) + + # Remove features from the available features + def add_features(self, features): + for feature in features: + if (feature in self.removed_features + or (self.removed_unecessary_features and feature not in self.added_features)): + raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature) + + self.added_features |= set(features) + # Helper function: process "config_parameters" and "target_config_overrides" in a given dictionary # 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") def _process_config_and_overrides(self, data, params, unit_name, unit_kind): + self.config_errors = [] self._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): - # Parse out cumulative attributes - for attr in Target._Target__cumulative_attributes: - attrs = getattr(self.target_instance, attr) - - if attr in overrides: - del attrs[:] - attrs.extend(overrides[attr]) - del overrides[attr] + # Parse out features + if 'target.features' in overrides: + features = overrides['target.features'] + self.remove_features(self.added_features - set(features)) + self.add_features(features) + self.removed_unecessary_features = True + del overrides['target.features'] - if attr+'_add' in overrides: - attrs.extend(overrides[attr+'_add']) - del overrides[attr+'_add'] + if 'target.features_add' in overrides: + self.add_features(overrides['target.features_add']) + del overrides['target.features_add'] - if attr+'_remove' in overrides: - for a in overrides[attr+'_remove']: - attrs.remove(a) - del overrides[attr+'_remove'] - - setattr(self.target_instance, attr, attrs) + if 'target.features_remove' in overrides: + self.remove_features(overrides['target.features_remove']) + del overrides['target.features_remove'] # Consider the others as overrides for name, v in overrides.items(): # Get the full name of the parameter full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind, label) - # If an attempt is made to override a parameter that isn't defined, raise an error - if not full_name in params: - raise ConfigException("Attempt to override undefined parameter '%s' in '%s'" % (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label))) - params[full_name].set_value(v, unit_name, unit_kind, label) + if full_name in params: + params[full_name].set_value(v, unit_name, unit_kind, label) + 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)))) return params # Read and interpret configuration data defined by targets @@ -320,16 +345,17 @@ # Return the configuration data in two parts: # - params: a dictionary with (name, ConfigParam) entries - # - macros: the list of macros defined with "macros" in libraries and in the application + # - macros: the list of macros defined with "macros" in libraries and in the application (as ConfigMacro instances) def get_config_data(self): 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, [m.name for m in macros.values()] + return all_params, macros # Helper: verify if there are any required parameters without a value in 'params' - def _check_required_parameters(self, params): + @staticmethod + def _check_required_parameters(params): for p in params.values(): if p.required and (p.value is None): raise ConfigException("Required parameter '%s' defined by '%s' doesn't have a value" % (p.name, p.defined_by)) @@ -340,8 +366,113 @@ def parameters_to_macros(params): return ['%s=%s' % (m.macro_name, m.value) for m in params.values() if m.value is not None] + # Return the macro definitions generated for a dictionary of ConfigMacros (as returned by get_config_data) + # params: a dictionary of (name, ConfigMacro instance) mappings + @staticmethod + def config_macros_to_macros(macros): + return [m.name for m in macros.values()] + + # Return the configuration data converted to a list of C macros + # config - configuration data as (ConfigParam instances, ConfigMacro instances) tuple + # (as returned by get_config_data()) + @staticmethod + def config_to_macros(config): + params, macros = config[0], config[1] + Config._check_required_parameters(params) + return Config.config_macros_to_macros(macros) + Config.parameters_to_macros(params) + # Return the configuration data converted to a list of C macros def get_config_data_macros(self): - params, macros = self.get_config_data() + return self.config_to_macros(self.get_config_data()) + + # Returns any features in the configuration data + def get_features(self): + params, _ = self.get_config_data() self._check_required_parameters(params) - return macros + self.parameters_to_macros(params) + features = ((set(Target.get_target(self.target).features) + | self.added_features) - self.removed_features) + + for feature in features: + if feature not in self.__allowed_features: + raise ConfigException("Feature '%s' is not a supported features" % feature) + + return features + + # Validate configuration settings. This either returns True or raises an exception + def validate_config(self): + if self.config_errors: + raise self.config_errors[0] + return True + + + # Loads configuration data from resources. Also expands resources based on defined features settings + def load_resources(self, resources): + # Update configuration files until added features creates no changes + prev_features = set() + 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 = 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 + + # Return the configuration data converted to the content of a C header file, + # meant to be included to a C/C++ file. The content is returned as a string. + # If 'fname' is given, the content is also written to the file called "fname". + # WARNING: if 'fname' names an existing file, that file will be overwritten! + # config - configuration data as (ConfigParam instances, ConfigMacro instances) tuple + # (as returned by get_config_data()) + @staticmethod + def config_to_header(config, fname = None): + params, macros = config[0], config[1] + Config._check_required_parameters(params) + header_data = "// Automatically generated configuration file.\n" + header_data += "// DO NOT EDIT, content will be overwritten.\n\n" + header_data += "#ifndef __MBED_CONFIG_DATA__\n" + header_data += "#define __MBED_CONFIG_DATA__\n\n" + # Compute maximum length of macro names for proper alignment + max_param_macro_name_len = max([len(m.macro_name) for m in params.values() if m.value is not None]) if params else 0 + max_direct_macro_name_len = max([len(m.macro_name) for m in macros.values()]) if macros else 0 + max_macro_name_len = max(max_param_macro_name_len, max_direct_macro_name_len) + # Compute maximum length of macro values for proper alignment + max_param_macro_val_len = max([len(str(m.value)) for m in params.values() if m.value is not None]) if params else 0 + max_direct_macro_val_len = max([len(m.macro_value or "") for m in macros.values()]) if macros else 0 + max_macro_val_len = max(max_param_macro_val_len, max_direct_macro_val_len) + # Generate config parameters first + if params: + header_data += "// Configuration parameters\n" + for m in params.values(): + if m.value is not None: + header_data += "#define {0:<{1}} {2!s:<{3}} // set by {4}\n".format(m.macro_name, max_macro_name_len, m.value, max_macro_val_len, m.set_by) + # Then macros + if macros: + header_data += "// Macros\n" + for m in macros.values(): + if m.macro_value: + header_data += "#define {0:<{1}} {2!s:<{3}} // defined by {4}\n".format(m.macro_name, max_macro_name_len, m.macro_value, max_macro_val_len, m.defined_by) + else: + header_data += "#define {0:<{1}} // defined by {2}\n".format(m.macro_name, max_macro_name_len + max_macro_val_len + 1, m.defined_by) + header_data += "\n#endif\n" + # If fname is given, write "header_data" to it + if fname: + with open(fname, "wt") as f: + f.write(header_data) + return header_data + + # Return the configuration data converted to the content of a C header file, + # meant to be included to a C/C++ file. The content is returned as a string. + # If 'fname' is given, the content is also written to the file called "fname". + # WARNING: if 'fname' names an existing file, that file will be overwritten! + def get_config_data_header(self, fname = None): + return self.config_to_header(self.get_config_data(), fname)