config: create internal config scope for commands to use.

This commit is contained in:
Todd Gamblin 2018-04-13 23:10:25 -07:00 committed by scheibelp
parent eee502cc3b
commit c9ea957563
5 changed files with 87 additions and 13 deletions

View file

@ -117,10 +117,11 @@ def init_compiler_config():
def compiler_config_files(): def compiler_config_files():
config_files = list() config_files = list()
config = spack.config.get_configuration() config = spack.config.get_configuration()
for scope in config.scopes: for scope in config.file_scopes:
compiler_config = config.get_config('compilers', scope=scope) name = scope.name
compiler_config = config.get_config('compilers', scope=name)
if compiler_config: if compiler_config:
config_files.append(config.get_config_filename(scope, 'compilers')) config_files.append(config.get_config_filename(name, 'compilers'))
return config_files return config_files

View file

@ -109,6 +109,10 @@
scopes_metavar = '{defaults,system,site,user}[/PLATFORM]' scopes_metavar = '{defaults,system,site,user}[/PLATFORM]'
#: config scopes only used by Spack internally
internal_scopes = ['commands']
def _extend_with_default(validator_class): def _extend_with_default(validator_class):
"""Add support for the 'default' attr for properties and patternProperties. """Add support for the 'default' attr for properties and patternProperties.
@ -199,6 +203,37 @@ def __repr__(self):
return '<ConfigScope: %s: %s>' % (self.name, self.path) return '<ConfigScope: %s: %s>' % (self.name, self.path)
class InternalConfigScope(ConfigScope):
"""An internal configuration scope that is not persisted to a file.
This is for spack internal use so that command-line options and
config file settings are accessed the same way, and Spack can easily
override settings from files.
"""
def __init__(self, name):
self.name = name
self.sections = syaml.syaml_dict()
def get_section_filename(self, section):
raise NotImplementedError(
"Cannot get filename for InternalConfigScope.")
def get_section(self, section):
"""Just reads from an internal dictionary."""
if section not in self.sections:
self.sections[section] = None
return self.sections[section]
def write_section(self, section):
"""This only validates, as the data is already in memory."""
data = self.get_section(section)
if data is not None:
_validate_section(data, section_schemas[section])
def __repr__(self):
return '<InternalConfigScope: %s>' % self.name
class Configuration(object): class Configuration(object):
"""A full Spack configuration, from a hierarchy of config files. """A full Spack configuration, from a hierarchy of config files.
@ -226,10 +261,15 @@ def pop_scope(self):
name, scope = self.scopes.popitem(last=True) name, scope = self.scopes.popitem(last=True)
return scope return scope
@property
def file_scopes(self):
"""List of scopes with an associated file (non-internal scopes)."""
return [s for s in self.scopes.values()
if not isinstance(s, InternalConfigScope)]
def highest_precedence_scope(self): def highest_precedence_scope(self):
"""Get the scope with highest precedence (prefs will override others). """Non-internal scope with highest precedence."""
""" return next(reversed(self.file_scopes), None)
return list(self.scopes.values())[-1]
def _validate_scope(self, scope): def _validate_scope(self, scope):
"""Ensure that scope is valid in this configuration. """Ensure that scope is valid in this configuration.
@ -371,17 +411,19 @@ def get_configuration():
# configuration directory. # configuration directory.
platform = spack.architecture.platform().name platform = spack.architecture.platform().name
scopes = [] _configuration = Configuration()
for name, path in configuration_paths: for name, path in configuration_paths:
# add the regular scope # add the regular scope
scopes.append(ConfigScope(name, path)) _configuration.push_scope(ConfigScope(name, path))
# add platform-specific scope # add platform-specific scope
plat_name = '%s/%s' % (name, platform) plat_name = '%s/%s' % (name, platform)
plat_path = os.path.join(path, platform) plat_path = os.path.join(path, platform)
scopes.append(ConfigScope(plat_name, plat_path)) _configuration.push_scope(ConfigScope(plat_name, plat_path))
_configuration = Configuration(*scopes) # we make a special scope for spack commands so that they can
# override configuration options.
_configuration.push_scope(InternalConfigScope('commands'))
return _configuration return _configuration

View file

@ -52,6 +52,7 @@
from llnl.util.filesystem import working_dir, mkdirp, join_path from llnl.util.filesystem import working_dir, mkdirp, join_path
import spack import spack
import spack.config
import spack.error import spack.error
import spack.util.crypto as crypto import spack.util.crypto as crypto
import spack.util.pattern as pattern import spack.util.pattern as pattern

View file

@ -38,7 +38,7 @@ def pre_run():
def check_compiler_yaml_version(): def check_compiler_yaml_version():
config = spack.config.get_configuration() config = spack.config.get_configuration()
for scope in config: for scope in config.file_scopes:
file_name = os.path.join(scope.path, 'compilers.yaml') file_name = os.path.join(scope.path, 'compilers.yaml')
data = None data = None
if os.path.isfile(file_name): if os.path.isfile(file_name):

View file

@ -64,9 +64,10 @@ def config(tmpdir):
real_configuration = spack.config._configuration real_configuration = spack.config._configuration
scopes = [spack.config.ConfigScope(name, str(tmpdir.join(name))) scopes = [spack.config.ConfigScope(name, str(tmpdir.join(name)))
for name in ['low', 'high']] for name in ['low', 'high']]
spack.config._configuration = spack.config.Configuration(*scopes) config = spack.config.Configuration(*scopes)
spack.config._configuration = config
yield yield config
spack.config._configuration = real_configuration spack.config._configuration = real_configuration
@ -420,6 +421,35 @@ def test_read_config_override_list(config, write_config_file):
} }
def test_internal_config_update(config, write_config_file):
write_config_file('config', config_low, 'low')
before = config.get_config('config')
assert before['install_tree'] == 'install_tree_path'
# add an internal configuration scope
scope = spack.config.InternalConfigScope('commands')
assert 'InternalConfigScope' in repr(scope)
config.push_scope(scope)
command_config = config.get_config('config', scope='commands')
command_config['install_tree'] = 'foo/bar'
config.update_config('config', command_config, scope='commands')
after = config.get_config('config')
assert after['install_tree'] == 'foo/bar'
def test_internal_config_filename(config, write_config_file):
write_config_file('config', config_low, 'low')
config.push_scope(spack.config.InternalConfigScope('commands'))
with pytest.raises(NotImplementedError):
config.get_config_filename('commands', 'config')
def test_keys_are_ordered(): def test_keys_are_ordered():
expected_order = ( expected_order = (