Move context manager to swap the current configuration into spack.config

The context manager can be used to swap the current
configuration temporarily, for any use case that may need it.
This commit is contained in:
Massimiliano Culpo 2021-02-01 17:27:00 +01:00 committed by Todd Gamblin
parent cb2c233a97
commit 553d37a6d6
3 changed files with 78 additions and 64 deletions

View File

@ -29,6 +29,7 @@
""" """
import collections import collections
import contextlib
import copy import copy
import functools import functools
import os import os
@ -49,6 +50,7 @@
import spack.paths import spack.paths
import spack.architecture import spack.architecture
import spack.compilers
import spack.schema import spack.schema
import spack.schema.compilers import spack.schema.compilers
import spack.schema.mirrors import spack.schema.mirrors
@ -804,22 +806,6 @@ def _config():
config = llnl.util.lang.Singleton(_config) config = llnl.util.lang.Singleton(_config)
def replace_config(configuration):
"""Replace the current global configuration with the instance passed as
argument.
Args:
configuration (Configuration): the new configuration to be used.
Returns:
The old configuration that has been removed
"""
global config
config.clear_caches(), configuration.clear_caches()
old_config, config = config, configuration
return old_config
def get(path, default=None, scope=None): def get(path, default=None, scope=None):
"""Module-level wrapper for ``Configuration.get()``.""" """Module-level wrapper for ``Configuration.get()``."""
return config.get(path, default, scope) return config.get(path, default, scope)
@ -1134,6 +1120,55 @@ def ensure_latest_format_fn(section):
return update_fn return update_fn
@contextlib.contextmanager
def use_configuration(*scopes_or_paths):
"""Use the configuration scopes passed as arguments within the
context manager.
Args:
*scopes_or_paths: scope objects or paths to be used
Returns:
Configuration object associated with the scopes passed as arguments
"""
global config
# Normalize input and construct a Configuration object
configuration = _config_from(scopes_or_paths)
config.clear_caches(), configuration.clear_caches()
# Save and clear the current compiler cache
saved_compiler_cache = spack.compilers._cache_config_file
spack.compilers._cache_config_file = []
saved_config, config = config, configuration
yield configuration
# Restore previous config files
spack.compilers._cache_config_file = saved_compiler_cache
config = saved_config
@llnl.util.lang.memoized
def _config_from(scopes_or_paths):
scopes = []
for scope_or_path in scopes_or_paths:
# If we have a config scope we are already done
if isinstance(scope_or_path, ConfigScope):
scopes.append(scope_or_path)
continue
# Otherwise we need to construct it
path = os.path.normpath(scope_or_path)
assert os.path.isdir(path), '"{0}" must be a directory'.format(path)
name = os.path.basename(path)
scopes.append(ConfigScope(name, path))
configuration = Configuration(*scopes)
return configuration
class ConfigError(SpackError): class ConfigError(SpackError):
"""Superclass for all Spack config related errors.""" """Superclass for all Spack config related errors."""

View File

@ -8,10 +8,10 @@
import pytest import pytest
import spack.config
import spack.main import spack.main
import spack.modules import spack.modules
import spack.store import spack.store
from spack.test.conftest import use_configuration
module = spack.main.SpackCommand('module') module = spack.main.SpackCommand('module')
@ -19,11 +19,12 @@
#: make sure module files are generated for all the tests here #: make sure module files are generated for all the tests here
@pytest.fixture(scope='module', autouse=True) @pytest.fixture(scope='module', autouse=True)
def ensure_module_files_are_there( def ensure_module_files_are_there(
mock_repo_path, mock_store, mock_configuration): mock_repo_path, mock_store, mock_configuration_scopes
):
"""Generate module files for module tests.""" """Generate module files for module tests."""
module = spack.main.SpackCommand('module') module = spack.main.SpackCommand('module')
with spack.store.use_store(mock_store): with spack.store.use_store(mock_store):
with use_configuration(mock_configuration): with spack.config.use_configuration(*mock_configuration_scopes):
with spack.repo.use_repositories(mock_repo_path): with spack.repo.use_repositories(mock_repo_path):
module('tcl', 'refresh', '-y') module('tcl', 'refresh', '-y')

View File

@ -4,7 +4,6 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections import collections
import contextlib
import errno import errno
import inspect import inspect
import itertools import itertools
@ -336,7 +335,7 @@ def test_platform():
# #
# Context managers used by fixtures # Note on context managers used by fixtures
# #
# Because these context managers modify global state, they should really # Because these context managers modify global state, they should really
# ONLY be used persistently (i.e., around yield statements) in # ONLY be used persistently (i.e., around yield statements) in
@ -357,23 +356,6 @@ def test_platform():
# *USE*, or things can get really confusing. # *USE*, or things can get really confusing.
# #
@contextlib.contextmanager
def use_configuration(config):
"""Context manager to swap out the global Spack configuration."""
saved = spack.config.replace_config(config)
# Avoid using real spack configuration that has been cached by other
# tests, and avoid polluting the cache with spack test configuration
# (including modified configuration)
saved_compiler_cache = spack.compilers._cache_config_file
spack.compilers._cache_config_file = []
yield
spack.config.replace_config(saved)
spack.compilers._cache_config_file = saved_compiler_cache
# #
# Test-specific fixtures # Test-specific fixtures
# #
@ -430,9 +412,7 @@ def default_config():
This ensures we can test the real default configuration without having This ensures we can test the real default configuration without having
tests fail when the user overrides the defaults that we test against.""" tests fail when the user overrides the defaults that we test against."""
defaults_path = os.path.join(spack.paths.etc_path, 'spack', 'defaults') defaults_path = os.path.join(spack.paths.etc_path, 'spack', 'defaults')
defaults_scope = spack.config.ConfigScope('defaults', defaults_path) with spack.config.use_configuration(defaults_path) as defaults_config:
defaults_config = spack.config.Configuration(defaults_scope)
with use_configuration(defaults_config):
yield defaults_config yield defaults_config
@ -507,7 +487,7 @@ def configuration_dir(tmpdir_factory, linux_os):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def mock_configuration(configuration_dir): def mock_configuration_scopes(configuration_dir):
"""Create a persistent Configuration object from the configuration_dir.""" """Create a persistent Configuration object from the configuration_dir."""
defaults = spack.config.InternalConfigScope( defaults = spack.config.InternalConfigScope(
'_builtin', spack.config.config_defaults '_builtin', spack.config.config_defaults
@ -518,14 +498,14 @@ def mock_configuration(configuration_dir):
for name in ['site', 'system', 'user']] for name in ['site', 'system', 'user']]
test_scopes.append(spack.config.InternalConfigScope('command_line')) test_scopes.append(spack.config.InternalConfigScope('command_line'))
yield spack.config.Configuration(*test_scopes) yield test_scopes
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def config(mock_configuration): def config(mock_configuration_scopes):
"""This fixture activates/deactivates the mock configuration.""" """This fixture activates/deactivates the mock configuration."""
with use_configuration(mock_configuration): with spack.config.use_configuration(*mock_configuration_scopes) as config:
yield mock_configuration yield config
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
@ -534,11 +514,10 @@ def mutable_config(tmpdir_factory, configuration_dir):
mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp') mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp')
configuration_dir.copy(mutable_dir) configuration_dir.copy(mutable_dir)
cfg = spack.config.Configuration( scopes = [spack.config.ConfigScope(name, str(mutable_dir.join(name)))
*[spack.config.ConfigScope(name, str(mutable_dir.join(name))) for name in ['site', 'system', 'user']]
for name in ['site', 'system', 'user']])
with use_configuration(cfg): with spack.config.use_configuration(*scopes) as cfg:
yield cfg yield cfg
@ -546,23 +525,20 @@ def mutable_config(tmpdir_factory, configuration_dir):
def mutable_empty_config(tmpdir_factory, configuration_dir): def mutable_empty_config(tmpdir_factory, configuration_dir):
"""Empty configuration that can be modified by the tests.""" """Empty configuration that can be modified by the tests."""
mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp') mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp')
scopes = [spack.config.ConfigScope(name, str(mutable_dir.join(name)))
for name in ['site', 'system', 'user']]
cfg = spack.config.Configuration( with spack.config.use_configuration(*scopes) as cfg:
*[spack.config.ConfigScope(name, str(mutable_dir.join(name)))
for name in ['site', 'system', 'user']])
with use_configuration(cfg):
yield cfg yield cfg
@pytest.fixture() @pytest.fixture()
def mock_low_high_config(tmpdir): def mock_low_high_config(tmpdir):
"""Mocks two configuration scopes: 'low' and 'high'.""" """Mocks two configuration scopes: 'low' and 'high'."""
config = spack.config.Configuration( scopes = [spack.config.ConfigScope(name, str(tmpdir.join(name)))
*[spack.config.ConfigScope(name, str(tmpdir.join(name))) for name in ['low', 'high']]
for name in ['low', 'high']])
with use_configuration(config): with spack.config.use_configuration(*scopes) as config:
yield config yield config
@ -611,7 +587,7 @@ def _store_dir_and_cache(tmpdir_factory):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def mock_store(tmpdir_factory, mock_repo_path, mock_configuration, def mock_store(tmpdir_factory, mock_repo_path, mock_configuration_scopes,
_store_dir_and_cache): _store_dir_and_cache):
"""Creates a read-only mock database with some packages installed note """Creates a read-only mock database with some packages installed note
that the ref count for dyninst here will be 3, as it's recycled that the ref count for dyninst here will be 3, as it's recycled
@ -625,7 +601,7 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
# If the cache does not exist populate the store and create it # If the cache does not exist populate the store and create it
if not os.path.exists(str(store_cache.join('.spack-db'))): if not os.path.exists(str(store_cache.join('.spack-db'))):
with use_configuration(mock_configuration): with spack.config.use_configuration(*mock_configuration_scopes):
with spack.store.use_store(str(store_path)) as store: with spack.store.use_store(str(store_path)) as store:
with spack.repo.use_repositories(mock_repo_path): with spack.repo.use_repositories(mock_repo_path):
_populate(store.db) _populate(store.db)
@ -640,8 +616,10 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def mutable_mock_store(tmpdir_factory, mock_repo_path, mock_configuration, def mutable_mock_store(
_store_dir_and_cache): tmpdir_factory, mock_repo_path, mock_configuration_scopes,
_store_dir_and_cache
):
"""Creates a read-only mock database with some packages installed note """Creates a read-only mock database with some packages installed note
that the ref count for dyninst here will be 3, as it's recycled that the ref count for dyninst here will be 3, as it's recycled
across each install. across each install.
@ -654,7 +632,7 @@ def mutable_mock_store(tmpdir_factory, mock_repo_path, mock_configuration,
# If the cache does not exist populate the store and create it # If the cache does not exist populate the store and create it
if not os.path.exists(str(store_cache.join('.spack-db'))): if not os.path.exists(str(store_cache.join('.spack-db'))):
with use_configuration(mock_configuration): with spack.config.use_configuration(*mock_configuration_scopes):
with spack.store.use_store(str(store_path)) as store: with spack.store.use_store(str(store_path)) as store:
with spack.repo.use_repositories(mock_repo_path): with spack.repo.use_repositories(mock_repo_path):
_populate(store.db) _populate(store.db)