diff options
author | Harmen Stoppels <me@harmenstoppels.nl> | 2024-07-07 07:02:25 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-06 22:02:25 -0700 |
commit | 8a430f89b33d0aa75db74a771fc9ec8226b9eaff (patch) | |
tree | 1f1aa6db7c027d08a1711afdab7ec32a95204bcb | |
parent | aeaa922eef45b14199b486fa2c188252df8c9544 (diff) | |
download | spack-8a430f89b33d0aa75db74a771fc9ec8226b9eaff.tar.gz spack-8a430f89b33d0aa75db74a771fc9ec8226b9eaff.tar.bz2 spack-8a430f89b33d0aa75db74a771fc9ec8226b9eaff.tar.xz spack-8a430f89b33d0aa75db74a771fc9ec8226b9eaff.zip |
spack -C <env>: use env config w/o activation (#45046)
Precedence:
1. Named environment
2. Anonymous environment
3. Generic directory
-rw-r--r-- | lib/spack/spack/config.py | 33 | ||||
-rw-r--r-- | lib/spack/spack/environment/environment.py | 16 | ||||
-rw-r--r-- | lib/spack/spack/main.py | 5 | ||||
-rw-r--r-- | lib/spack/spack/test/config.py | 65 | ||||
-rwxr-xr-x | share/spack/spack-completion.fish | 2 |
5 files changed, 78 insertions, 43 deletions
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 54038ade93..d850a24959 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -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 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) + name = f"cmd_scope_{i}" + + 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: diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py index 501663322e..f3df7e115a 100644 --- a/lib/spack/spack/environment/environment.py +++ b/lib/spack/spack/environment/environment.py @@ -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 @@ class Environment: 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 @@ class EnvironmentManifestFile(collections.abc.Mapping): 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 @@ class EnvironmentManifestFile(collections.abc.Mapping): # 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 @@ class EnvironmentManifestFile(collections.abc.Mapping): 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( diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py index 655fae2f17..78fda27c46 100644 --- a/lib/spack/spack/main.py +++ b/lib/spack/spack/main.py @@ -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", diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index 26a0b37dba..817cfeb40d 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -13,7 +13,7 @@ from datetime import date 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 @@ 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(): diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index d2d883ff93..f02926e449 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -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 |