summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/config.py71
-rw-r--r--lib/spack/spack/test/config.py87
2 files changed, 141 insertions, 17 deletions
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index 7ed47340bf..445d62d2ab 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -280,6 +280,7 @@ class InternalConfigScope(ConfigScope):
self.sections = syaml.syaml_dict()
if data:
+ data = InternalConfigScope._process_dict_keyname_overrides(data)
for section in data:
dsec = data[section]
validate({section: dsec}, section_schemas[section])
@@ -306,6 +307,25 @@ class InternalConfigScope(ConfigScope):
def __repr__(self):
return '<InternalConfigScope: %s>' % self.name
+ @staticmethod
+ def _process_dict_keyname_overrides(data):
+ """Turn a trailing `:' in a key name into an override attribute."""
+ result = {}
+ for sk, sv in iteritems(data):
+ if sk.endswith(':'):
+ key = syaml.syaml_str(sk[:-1])
+ key.override = True
+ else:
+ key = sk
+
+ if isinstance(sv, dict):
+ result[key]\
+ = InternalConfigScope._process_dict_keyname_overrides(sv)
+ else:
+ result[key] = copy.copy(sv)
+
+ return result
+
class Configuration(object):
"""A full Spack configuration, from a hierarchy of config files.
@@ -505,14 +525,14 @@ class Configuration(object):
Accepts the path syntax described in ``get()``.
"""
- section, _, rest = path.partition(':')
+ parts = _process_config_path(path)
+ section = parts.pop(0)
- if not rest:
+ if not parts:
self.update_config(section, value, scope=scope)
else:
section_data = self.get_config(section, scope=scope)
- parts = rest.split(':')
data = section_data
while len(parts) > 1:
key = parts.pop(0)
@@ -612,7 +632,7 @@ def _config():
"""Singleton Configuration instance.
This constructs one instance associated with this module and returns
- it. It is bundled inside a function so that configuratoin can be
+ it. It is bundled inside a function so that configuration can be
initialized lazily.
Return:
@@ -763,17 +783,12 @@ def _merge_yaml(dest, source):
Config file authors can optionally end any attribute in a dict
with `::` instead of `:`, and the key will override that of the
parent instead of merging.
-
"""
def they_are(t):
return isinstance(dest, t) and isinstance(source, t)
- # If both are None, handle specially and return None.
- if source is None and dest is None:
- return None
-
# If source is None, overwrite with source.
- elif source is None:
+ if source is None:
return None
# Source list is prepended (for precedence)
@@ -799,8 +814,9 @@ def _merge_yaml(dest, source):
# to copy mark information on source keys to dest.
key_marks[sk] = sk
- # ensure that keys are marked in the destination. the key_marks dict
- # ensures we can get the actual source key objects from dest keys
+ # ensure that keys are marked in the destination. The
+ # key_marks dict ensures we can get the actual source key
+ # objects from dest keys
for dk in list(dest.keys()):
if dk in key_marks and syaml.markable(dk):
syaml.mark(dk, key_marks[dk])
@@ -812,9 +828,34 @@ def _merge_yaml(dest, source):
return dest
- # In any other case, overwrite with a copy of the source value.
- else:
- return copy.copy(source)
+ # If we reach here source and dest are either different types or are
+ # not both lists or dicts: replace with source.
+ return copy.copy(source)
+
+
+#
+# Process a path argument to config.set() that may contain overrides ('::' or
+# trailing ':')
+#
+def _process_config_path(path):
+ result = []
+ if path.startswith(':'):
+ raise syaml.SpackYAMLError("Illegal leading `:' in path `{0}'".
+ format(path), '')
+ seen_override_in_path = False
+ while path:
+ front, sep, path = path.partition(':')
+ if (sep and not path) or path.startswith(':'):
+ if seen_override_in_path:
+ raise syaml.SpackYAMLError("Meaningless second override"
+ " indicator `::' in path `{0}'".
+ format(path), '')
+ path = path.lstrip(':')
+ front = syaml.syaml_str(front)
+ front.override = True
+ seen_override_in_path = True
+ result.append(front)
+ return result
#
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
index feb2b9cae4..b8598616d5 100644
--- a/lib/spack/spack/test/config.py
+++ b/lib/spack/spack/test/config.py
@@ -46,7 +46,19 @@ config_merge_list = {
config_override_list = {
'config': {
- 'build_stage:': ['patha', 'pathb']}}
+ 'build_stage:': ['pathd', 'pathe']}}
+
+config_merge_dict = {
+ 'config': {
+ 'info': {
+ 'a': 3,
+ 'b': 4}}}
+
+config_override_dict = {
+ 'config': {
+ 'info:': {
+ 'a': 7,
+ 'c': 9}}}
@pytest.fixture()
@@ -382,7 +394,7 @@ def test_read_config_override_list(mock_low_high_config, write_config_file):
write_config_file('config', config_override_list, 'high')
assert spack.config.get('config') == {
'install_tree': 'install_tree_path',
- 'build_stage': ['patha', 'pathb']
+ 'build_stage': config_override_list['config']['build_stage:']
}
@@ -857,3 +869,74 @@ def test_dotkit_in_config_does_not_raise(
# we throw a a deprecation warning without raising
assert '_sp_sys_type' in captured[0] # stdout
assert 'Warning' in captured[1] # stderr
+
+
+def test_internal_config_section_override(mock_low_high_config,
+ write_config_file):
+ write_config_file('config', config_merge_list, 'low')
+ wanted_list = config_override_list['config']['build_stage:']
+ mock_low_high_config.push_scope(spack.config.InternalConfigScope
+ ('high', {
+ 'config:': {
+ 'build_stage': wanted_list
+ }
+ }))
+ assert mock_low_high_config.get('config:build_stage') == wanted_list
+
+
+def test_internal_config_dict_override(mock_low_high_config,
+ write_config_file):
+ write_config_file('config', config_merge_dict, 'low')
+ wanted_dict = config_override_dict['config']['info:']
+ mock_low_high_config.push_scope(spack.config.InternalConfigScope
+ ('high', config_override_dict))
+ assert mock_low_high_config.get('config:info') == wanted_dict
+
+
+def test_internal_config_list_override(mock_low_high_config,
+ write_config_file):
+ write_config_file('config', config_merge_list, 'low')
+ wanted_list = config_override_list['config']['build_stage:']
+ mock_low_high_config.push_scope(spack.config.InternalConfigScope
+ ('high', config_override_list))
+ assert mock_low_high_config.get('config:build_stage') == wanted_list
+
+
+def test_set_section_override(mock_low_high_config, write_config_file):
+ write_config_file('config', config_merge_list, 'low')
+ wanted_list = config_override_list['config']['build_stage:']
+ with spack.config.override('config::build_stage', wanted_list):
+ assert mock_low_high_config.get('config:build_stage') == wanted_list
+ assert config_merge_list['config']['build_stage'] == \
+ mock_low_high_config.get('config:build_stage')
+
+
+def test_set_list_override(mock_low_high_config, write_config_file):
+ write_config_file('config', config_merge_list, 'low')
+ wanted_list = config_override_list['config']['build_stage:']
+ with spack.config.override('config:build_stage:', wanted_list):
+ assert wanted_list == mock_low_high_config.get('config:build_stage')
+ assert config_merge_list['config']['build_stage'] == \
+ mock_low_high_config.get('config:build_stage')
+
+
+def test_set_dict_override(mock_low_high_config, write_config_file):
+ write_config_file('config', config_merge_dict, 'low')
+ wanted_dict = config_override_dict['config']['info:']
+ with spack.config.override('config:info:', wanted_dict):
+ assert wanted_dict == mock_low_high_config.get('config:info')
+ assert config_merge_dict['config']['info'] == \
+ mock_low_high_config.get('config:info')
+
+
+def test_set_bad_path(config):
+ with pytest.raises(syaml.SpackYAMLError, match='Illegal leading'):
+ with spack.config.override(':bad:path', ''):
+ pass
+
+
+def test_bad_path_double_override(config):
+ with pytest.raises(syaml.SpackYAMLError,
+ match='Meaningless second override'):
+ with spack.config.override('bad::double:override::directive', ''):
+ pass