summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarmen Stoppels <me@harmenstoppels.nl>2024-07-07 07:02:25 +0200
committerGitHub <noreply@github.com>2024-07-06 22:02:25 -0700
commit8a430f89b33d0aa75db74a771fc9ec8226b9eaff (patch)
tree1f1aa6db7c027d08a1711afdab7ec32a95204bcb
parentaeaa922eef45b14199b486fa2c188252df8c9544 (diff)
downloadspack-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.py33
-rw-r--r--lib/spack/spack/environment/environment.py16
-rw-r--r--lib/spack/spack/main.py5
-rw-r--r--lib/spack/spack/test/config.py65
-rwxr-xr-xshare/spack/spack-completion.fish2
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