spack -C <env>: use env config w/o activation (#45046)

Precedence:

1. Named environment
2. Anonymous environment
3. Generic directory
This commit is contained in:
Harmen Stoppels 2024-07-07 07:02:25 +02:00 committed by GitHub
parent aeaa922eef
commit 8a430f89b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 77 additions and 42 deletions

View File

@ -796,22 +796,27 @@ def config_paths_from_entry_points() -> List[Tuple[str, str]]:
def _add_command_line_scopes(
cfg: Union[Configuration, lang.Singleton], command_line_scopes: List[str]
) -> None:
"""Add additional scopes from the --config-scope argument.
"""Add additional scopes from the --config-scope argument, either envs or dirs."""
import spack.environment.environment as env # circular import
Command line scopes are named after their position in the arg list.
"""
for i, path in enumerate(command_line_scopes):
# We ensure that these scopes exist and are readable, as they are
# provided on the command line by the user.
if not os.path.isdir(path):
raise ConfigError(f"config scope is not a directory: '{path}'")
elif not os.access(path, os.R_OK):
raise ConfigError(f"config scope is not readable: '{path}'")
name = f"cmd_scope_{i}"
# name based on order on the command line
name = f"cmd_scope_{i:d}"
cfg.push_scope(DirectoryConfigScope(name, path, writable=False))
_add_platform_scope(cfg, name, path, writable=False)
if env.exists(path): # managed environment
manifest = env.EnvironmentManifestFile(env.root(path))
elif env.is_env_dir(path): # anonymous environment
manifest = env.EnvironmentManifestFile(path)
elif os.path.isdir(path): # directory with config files
cfg.push_scope(DirectoryConfigScope(name, path, writable=False))
_add_platform_scope(cfg, name, path, writable=False)
continue
else:
raise ConfigError(f"Invalid configuration scope: {path}")
for scope in manifest.env_config_scopes:
scope.name = f"{name}:{scope.name}"
scope.writable = False
cfg.push_scope(scope)
def create() -> Configuration:

View File

@ -269,9 +269,7 @@ def root(name):
def exists(name):
"""Whether an environment with this name exists or not."""
if not valid_env_name(name):
return False
return os.path.isdir(root(name))
return valid_env_name(name) and os.path.isdir(_root(name))
def active(name):
@ -922,7 +920,7 @@ def __init__(self, manifest_dir: Union[str, pathlib.Path]) -> None:
def _load_manifest_file(self):
"""Instantiate and load the manifest file contents into memory."""
with lk.ReadTransaction(self.txlock):
self.manifest = EnvironmentManifestFile(self.path)
self.manifest = EnvironmentManifestFile(self.path, self.name)
with self.manifest.use_config():
self._read()
@ -2753,10 +2751,11 @@ def from_lockfile(manifest_dir: Union[pathlib.Path, str]) -> "EnvironmentManifes
manifest.flush()
return manifest
def __init__(self, manifest_dir: Union[pathlib.Path, str]) -> None:
def __init__(self, manifest_dir: Union[pathlib.Path, str], name: Optional[str] = None) -> None:
self.manifest_dir = pathlib.Path(manifest_dir)
self.name = name or str(manifest_dir)
self.manifest_file = self.manifest_dir / manifest_name
self.scope_name = f"env:{environment_name(self.manifest_dir)}"
self.scope_name = f"env:{self.name}"
self.config_stage_dir = os.path.join(env_subdir_path(manifest_dir), "config")
#: Configuration scopes associated with this environment. Note that these are not
@ -3033,7 +3032,6 @@ def included_config_scopes(self) -> List[spack.config.ConfigScope]:
# load config scopes added via 'include:', in reverse so that
# highest-precedence scopes are last.
includes = self[TOP_LEVEL_KEY].get("include", [])
env_name = environment_name(self.manifest_dir)
missing = []
for i, config_path in enumerate(reversed(includes)):
# allow paths to contain spack config/environment variables, etc.
@ -3096,12 +3094,12 @@ def included_config_scopes(self) -> List[spack.config.ConfigScope]:
if os.path.isdir(config_path):
# directories are treated as regular ConfigScopes
config_name = "env:%s:%s" % (env_name, os.path.basename(config_path))
config_name = f"env:{self.name}:{os.path.basename(config_path)}"
tty.debug(f"Creating DirectoryConfigScope {config_name} for '{config_path}'")
scopes.append(spack.config.DirectoryConfigScope(config_name, config_path))
elif os.path.exists(config_path):
# files are assumed to be SingleFileScopes
config_name = "env:%s:%s" % (env_name, config_path)
config_name = f"env:{self.name}:{config_path}"
tty.debug(f"Creating SingleFileScope {config_name} for '{config_path}'")
scopes.append(
spack.config.SingleFileScope(

View File

@ -444,8 +444,9 @@ def make_argument_parser(**kwargs):
"--config-scope",
dest="config_scopes",
action="append",
metavar="DIR",
help="add a custom configuration scope",
metavar="DIR|ENV",
help="add directory or environment as read-only configuration scope, without activating "
"the environment.",
)
parser.add_argument(
"-d",

View File

@ -13,7 +13,7 @@
import pytest
import llnl.util.tty as tty
from llnl.util.filesystem import getuid, join_path, mkdirp, touch, touchp
from llnl.util.filesystem import join_path, touch, touchp
import spack.config
import spack.directory_layout
@ -864,26 +864,18 @@ def test_bad_config_section(mock_low_high_config):
spack.config.get("foobar")
@pytest.mark.not_on_windows("chmod not supported on Windows")
@pytest.mark.skipif(getuid() == 0, reason="user is root")
def test_bad_command_line_scopes(tmpdir, config):
def test_bad_command_line_scopes(tmp_path, config):
cfg = spack.config.Configuration()
file_path = tmp_path / "file_instead_of_dir"
non_existing_path = tmp_path / "non_existing_dir"
with tmpdir.as_cwd():
with pytest.raises(spack.config.ConfigError):
spack.config._add_command_line_scopes(cfg, ["bad_path"])
file_path.write_text("")
touch("unreadable_file")
with pytest.raises(spack.config.ConfigError):
spack.config._add_command_line_scopes(cfg, ["unreadable_file"])
with pytest.raises(spack.config.ConfigError):
spack.config._add_command_line_scopes(cfg, [str(file_path)])
mkdirp("unreadable_dir")
with pytest.raises(spack.config.ConfigError):
try:
os.chmod("unreadable_dir", 0)
spack.config._add_command_line_scopes(cfg, ["unreadable_dir"])
finally:
os.chmod("unreadable_dir", 0o700) # so tmpdir can be removed
with pytest.raises(spack.config.ConfigError):
spack.config._add_command_line_scopes(cfg, [str(non_existing_path)])
def test_add_command_line_scopes(tmpdir, mutable_config):
@ -898,6 +890,45 @@ def test_add_command_line_scopes(tmpdir, mutable_config):
)
spack.config._add_command_line_scopes(mutable_config, [str(tmpdir)])
assert mutable_config.get("config:verify_ssl") is False
assert mutable_config.get("config:dirty") is False
def test_add_command_line_scope_env(tmp_path, mutable_mock_env_path):
"""Test whether --config-scope <env> works, either by name or path."""
managed_env = ev.create("example").manifest_path
with open(managed_env, "w") as f:
f.write(
"""\
spack:
config:
install_tree:
root: /tmp/first
"""
)
with open(tmp_path / "spack.yaml", "w") as f:
f.write(
"""\
spack:
config:
install_tree:
root: /tmp/second
"""
)
config = spack.config.Configuration()
spack.config._add_command_line_scopes(config, ["example", str(tmp_path)])
assert len(config.scopes) == 2
assert config.get("config:install_tree:root") == "/tmp/second"
config = spack.config.Configuration()
spack.config._add_command_line_scopes(config, [str(tmp_path), "example"])
assert len(config.scopes) == 2
assert config.get("config:install_tree:root") == "/tmp/first"
assert ev.active_environment() is None # shouldn't cause an environment to be activated
def test_nested_override():

View File

@ -438,7 +438,7 @@ complete -c spack -n '__fish_spack_using_command ' -l color -r -d 'when to color
complete -c spack -n '__fish_spack_using_command ' -s c -l config -r -f -a config_vars
complete -c spack -n '__fish_spack_using_command ' -s c -l config -r -d 'add one or more custom, one off config settings'
complete -c spack -n '__fish_spack_using_command ' -s C -l config-scope -r -f -a config_scopes
complete -c spack -n '__fish_spack_using_command ' -s C -l config-scope -r -d 'add a custom configuration scope'
complete -c spack -n '__fish_spack_using_command ' -s C -l config-scope -r -d 'add directory or environment as read-only configuration scope, without activating the environment.'
complete -c spack -n '__fish_spack_using_command ' -s d -l debug -f -a debug
complete -c spack -n '__fish_spack_using_command ' -s d -l debug -d 'write out debug messages'
complete -c spack -n '__fish_spack_using_command ' -l timestamp -f -a timestamp