diff options
author | Peter Scheibel <scheibel1@llnl.gov> | 2024-03-14 13:32:01 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-14 13:32:01 -0700 |
commit | ec517b40e98dabb5759553f1d2599e50f245018a (patch) | |
tree | e6bf9f099f9446970f68608c1bdb27fd2f057b64 | |
parent | 22cb3815fe2df7090eb735cc472a7d37c3beb8ac (diff) | |
download | spack-ec517b40e98dabb5759553f1d2599e50f245018a.tar.gz spack-ec517b40e98dabb5759553f1d2599e50f245018a.tar.bz2 spack-ec517b40e98dabb5759553f1d2599e50f245018a.tar.xz spack-ec517b40e98dabb5759553f1d2599e50f245018a.zip |
`spack develop`: stage build artifacts in same root as non-dev builds (#41373)
Currently (outside of this PR) when you `spack develop` a path, this path is treated as the staging
directory (this means that for example all build artifacts are placed in the develop path).
This PR creates a separate staging directory for all `spack develop`ed builds. It looks like
```
# the stage root
/the-stage-root-for-all-spack-builds/
spack-stage-<hash>
# Spack packages inheriting CMakePackage put their build artifacts here
spack-build-<hash>/
```
Unlike non-develop builds, there is no `spack-src` directory, `source_path` is the provided `dev_path`.
Instead, separately, in the `dev_path`, we have:
```
/dev/path/for/foo/
build-{arch}-<hash> -> /the-stage-root-for-all-spack-builds/spack-stage-<hash>/
```
The main benefit of this is that build artifacts for out-of-source builds that are relative to
`Stage.path` are easily identified (and you can delete them with `spack clean`).
Other behavior added here:
- [x] A symlink is made from the `dev_path` to the stage directory. This symlink name incorporates
spec details, so that multiple Spack environments that develop the same path will not conflict
with one another
- [x] `spack cd` and `spack location` have added a `-c` shorthand for `--source-dir`
Spack builds can still change the develop path (in particular to keep track of applied patches),
and for in-source builds, this doesn't change much (although logs would not be written into
the develop path). Packages inheriting from `CMakePackage` should get this benefit
automatically though.
-rw-r--r-- | lib/spack/spack/cmd/location.py | 1 | ||||
-rw-r--r-- | lib/spack/spack/package_base.py | 67 | ||||
-rw-r--r-- | lib/spack/spack/schema/config.py | 1 | ||||
-rw-r--r-- | lib/spack/spack/stage.py | 248 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/dev_build.py | 5 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/develop.py | 13 | ||||
-rw-r--r-- | lib/spack/spack/test/patch.py | 2 | ||||
-rw-r--r-- | lib/spack/spack/test/stage.py | 71 | ||||
-rwxr-xr-x | share/spack/spack-completion.bash | 4 | ||||
-rwxr-xr-x | share/spack/spack-completion.fish | 12 |
10 files changed, 297 insertions, 127 deletions
diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py index d2bac9b9ba..d774784622 100644 --- a/lib/spack/spack/cmd/location.py +++ b/lib/spack/spack/cmd/location.py @@ -53,6 +53,7 @@ def setup_parser(subparser): "-S", "--stages", action="store_true", help="top level stage directory" ) directories.add_argument( + "-c", "--source-dir", action="store_true", help="source directory for a spec (requires it to be staged first)", diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index 9e9f4728e1..2f8e0ae003 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -64,7 +64,7 @@ from spack.install_test import ( install_test_root, ) from spack.installer import InstallError, PackageInstaller -from spack.stage import DIYStage, ResourceStage, Stage, StageComposite, compute_stage_name +from spack.stage import DevelopStage, ResourceStage, Stage, StageComposite, compute_stage_name from spack.util.executable import ProcessError, which from spack.util.package_hash import package_hash from spack.version import GitVersion, StandardVersion @@ -1075,7 +1075,12 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): # If it's a dev package (not transitively), use a DIY stage object dev_path_var = self.spec.variants.get("dev_path", None) if dev_path_var: - return DIYStage(dev_path_var.value) + dev_path = dev_path_var.value + link_format = spack.config.get("config:develop_stage_link") + if not link_format: + link_format = "build-{arch}-{hash:7}" + stage_link = self.spec.format_path(link_format) + return DevelopStage(compute_stage_name(self.spec), dev_path, stage_link) # To fetch the current version source_stage = self._make_root_stage(self.fetcher) @@ -1407,7 +1412,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): return checksum = spack.config.get("config:checksum") - fetch = self.stage.managed_by_spack + fetch = self.stage.needs_fetching if ( checksum and fetch @@ -1480,9 +1485,6 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): if self.has_code: self.do_fetch(mirror_only) self.stage.expand_archive() - - if not os.listdir(self.stage.path): - raise spack.error.FetchError("Archive was empty for %s" % self.name) else: # Support for post-install hooks requires a stage.source_path fsys.mkdirp(self.stage.source_path) @@ -1516,7 +1518,7 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): # If we encounter an archive that failed to patch, restage it # so that we can apply all the patches again. if os.path.isfile(bad_file): - if self.stage.managed_by_spack: + if self.stage.requires_patch_success: tty.debug("Patching failed last time. Restaging.") self.stage.restage() else: @@ -1537,6 +1539,8 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): tty.msg("No patches needed for {0}".format(self.name)) return + errors = [] + # Apply all the patches for specs that match this one patched = False for patch in patches: @@ -1546,12 +1550,16 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): tty.msg("Applied patch {0}".format(patch.path_or_url)) patched = True except spack.error.SpackError as e: - tty.debug(e) - # Touch bad file if anything goes wrong. - tty.msg("Patch %s failed." % patch.path_or_url) fsys.touch(bad_file) - raise + error_msg = f"Patch {patch.path_or_url} failed." + if self.stage.requires_patch_success: + tty.msg(error_msg) + raise + else: + tty.debug(error_msg) + tty.debug(e) + errors.append(e) if has_patch_fun: try: @@ -1569,24 +1577,29 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): # printed a message for each patch. tty.msg("No patches needed for {0}".format(self.name)) except spack.error.SpackError as e: - tty.debug(e) - # Touch bad file if anything goes wrong. - tty.msg("patch() function failed for {0}".format(self.name)) fsys.touch(bad_file) - raise - - # Get rid of any old failed file -- patches have either succeeded - # or are not needed. This is mostly defensive -- it's needed - # if the restage() method doesn't clean *everything* (e.g., for a repo) - if os.path.isfile(bad_file): - os.remove(bad_file) - - # touch good or no patches file so that we skip next time. - if patched: - fsys.touch(good_file) - else: - fsys.touch(no_patches_file) + error_msg = f"patch() function failed for {self.name}" + if self.stage.requires_patch_success: + tty.msg(error_msg) + raise + else: + tty.debug(error_msg) + tty.debug(e) + errors.append(e) + + if not errors: + # Get rid of any old failed file -- patches have either succeeded + # or are not needed. This is mostly defensive -- it's needed + # if we didn't restage + if os.path.isfile(bad_file): + os.remove(bad_file) + + # touch good or no patches file so that we skip next time. + if patched: + fsys.touch(good_file) + else: + fsys.touch(no_patches_file) @classmethod def all_patches(cls): diff --git a/lib/spack/spack/schema/config.py b/lib/spack/spack/schema/config.py index 2a6499058d..b7fca09938 100644 --- a/lib/spack/spack/schema/config.py +++ b/lib/spack/spack/schema/config.py @@ -63,6 +63,7 @@ properties: Dict[str, Any] = { "oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}] }, "stage_name": {"type": "string"}, + "develop_stage_link": {"type": "string"}, "test_stage": {"type": "string"}, "extensions": {"type": "array", "items": {"type": "string"}}, "template_dirs": {"type": "array", "items": {"type": "string"}}, diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py index b756255bd6..46848c0016 100644 --- a/lib/spack/spack/stage.py +++ b/lib/spack/spack/stage.py @@ -208,7 +208,103 @@ def _mirror_roots(): ] -class Stage: +class LockableStagingDir: + """A directory whose lifetime can be managed with a context + manager (but persists if the user requests it). Instances can have + a specified name and if they do, then for all instances that have + the same name, only one can enter the context manager at a time. + """ + + def __init__(self, name, path, keep, lock): + # TODO: This uses a protected member of tempfile, but seemed the only + # TODO: way to get a temporary name. It won't be the same as the + # TODO: temporary stage area in _stage_root. + self.name = name + if name is None: + self.name = stage_prefix + next(tempfile._get_candidate_names()) + + # Use the provided path or construct an optionally named stage path. + if path is not None: + self.path = path + else: + self.path = os.path.join(get_stage_root(), self.name) + + # Flag to decide whether to delete the stage folder on exit or not + self.keep = keep + + # File lock for the stage directory. We use one file for all + # stage locks. See spack.database.Database.prefix_locker.lock for + # details on this approach. + self._lock = None + self._use_locks = lock + + # When stages are reused, we need to know whether to re-create + # it. This marks whether it has been created/destroyed. + self.created = False + + def _get_lock(self): + if not self._lock: + sha1 = hashlib.sha1(self.name.encode("utf-8")).digest() + lock_id = prefix_bits(sha1, bit_length(sys.maxsize)) + stage_lock_path = os.path.join(get_stage_root(), ".lock") + self._lock = spack.util.lock.Lock( + stage_lock_path, start=lock_id, length=1, desc=self.name + ) + return self._lock + + def __enter__(self): + """ + Entering a stage context will create the stage directory + + Returns: + self + """ + if self._use_locks: + self._get_lock().acquire_write(timeout=60) + self.create() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Exiting from a stage context will delete the stage directory unless: + - it was explicitly requested not to do so + - an exception has been raised + + Args: + exc_type: exception type + exc_val: exception value + exc_tb: exception traceback + + Returns: + Boolean + """ + # Delete when there are no exceptions, unless asked to keep. + if exc_type is None and not self.keep: + self.destroy() + + if self._use_locks: + self._get_lock().release_write() + + def create(self): + """ + Ensures the top-level (config:build_stage) directory exists. + """ + # User has full permissions and group has only read permissions + if not os.path.exists(self.path): + mkdirp(self.path, mode=stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) + elif not os.path.isdir(self.path): + os.remove(self.path) + mkdirp(self.path, mode=stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) + + # Make sure we can actually do something with the stage we made. + ensure_access(self.path) + self.created = True + + def destroy(self): + raise NotImplementedError(f"{self.__class__.__name__} is abstract") + + +class Stage(LockableStagingDir): """Manages a temporary stage directory for building. A Stage object is a context manager that handles a directory where @@ -251,7 +347,8 @@ class Stage: """ #: Most staging is managed by Spack. DIYStage is one exception. - managed_by_spack = True + needs_fetching = True + requires_patch_success = True def __init__( self, @@ -297,6 +394,8 @@ class Stage: The search function that provides the fetch strategy instance. """ + super().__init__(name, path, keep, lock) + # TODO: fetch/stage coupling needs to be reworked -- the logic # TODO: here is convoluted and not modular enough. if isinstance(url_or_fetch_strategy, str): @@ -314,72 +413,8 @@ class Stage: self.srcdir = None - # TODO: This uses a protected member of tempfile, but seemed the only - # TODO: way to get a temporary name. It won't be the same as the - # TODO: temporary stage area in _stage_root. - self.name = name - if name is None: - self.name = stage_prefix + next(tempfile._get_candidate_names()) self.mirror_paths = mirror_paths - # Use the provided path or construct an optionally named stage path. - if path is not None: - self.path = path - else: - self.path = os.path.join(get_stage_root(), self.name) - - # Flag to decide whether to delete the stage folder on exit or not - self.keep = keep - - # File lock for the stage directory. We use one file for all - # stage locks. See spack.database.Database.prefix_locker.lock for - # details on this approach. - self._lock = None - if lock: - sha1 = hashlib.sha1(self.name.encode("utf-8")).digest() - lock_id = prefix_bits(sha1, bit_length(sys.maxsize)) - stage_lock_path = os.path.join(get_stage_root(), ".lock") - self._lock = spack.util.lock.Lock( - stage_lock_path, start=lock_id, length=1, desc=self.name - ) - - # When stages are reused, we need to know whether to re-create - # it. This marks whether it has been created/destroyed. - self.created = False - - def __enter__(self): - """ - Entering a stage context will create the stage directory - - Returns: - self - """ - if self._lock is not None: - self._lock.acquire_write(timeout=60) - self.create() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """ - Exiting from a stage context will delete the stage directory unless: - - it was explicitly requested not to do so - - an exception has been raised - - Args: - exc_type: exception type - exc_val: exception value - exc_tb: exception traceback - - Returns: - Boolean - """ - # Delete when there are no exceptions, unless asked to keep. - if exc_type is None and not self.keep: - self.destroy() - - if self._lock is not None: - self._lock.release_write() - @property def expected_archive_files(self): """Possible archive file paths.""" @@ -631,21 +666,6 @@ class Stage: """ self.fetcher.reset() - def create(self): - """ - Ensures the top-level (config:build_stage) directory exists. - """ - # User has full permissions and group has only read permissions - if not os.path.exists(self.path): - mkdirp(self.path, mode=stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) - elif not os.path.isdir(self.path): - os.remove(self.path) - mkdirp(self.path, mode=stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP) - - # Make sure we can actually do something with the stage we made. - ensure_access(self.path) - self.created = True - def destroy(self): """Removes this stage directory.""" remove_linked_tree(self.path) @@ -752,7 +772,8 @@ class StageComposite(pattern.Composite): "cache_mirror", "steal_source", "disable_mirrors", - "managed_by_spack", + "needs_fetching", + "requires_patch_success", ] ) @@ -808,8 +829,8 @@ class DIYStage: directory naming convention. """ - """DIY staging is, by definition, not managed by Spack.""" - managed_by_spack = False + needs_fetching = False + requires_patch_success = False def __init__(self, path): if path is None: @@ -857,6 +878,65 @@ class DIYStage: tty.debug("Sources for DIY stages are not cached") +class DevelopStage(LockableStagingDir): + needs_fetching = False + requires_patch_success = False + + def __init__(self, name, dev_path, reference_link): + super().__init__(name=name, path=None, keep=False, lock=True) + self.dev_path = dev_path + self.source_path = dev_path + + # The path of a link that will point to this stage + if os.path.isabs(reference_link): + link_path = reference_link + else: + link_path = os.path.join(self.source_path, reference_link) + if not os.path.isdir(os.path.dirname(link_path)): + raise StageError(f"The directory containing {link_path} must exist") + self.reference_link = link_path + + @property + def archive_file(self): + return None + + def fetch(self, *args, **kwargs): + tty.debug("No fetching needed for develop stage.") + + def check(self): + tty.debug("No checksum needed for develop stage.") + + def expand_archive(self): + tty.debug("No expansion needed for develop stage.") + + @property + def expanded(self): + """Returns True since the source_path must exist.""" + return True + + def create(self): + super().create() + try: + llnl.util.symlink.symlink(self.path, self.reference_link) + except (llnl.util.symlink.AlreadyExistsError, FileExistsError): + pass + + def destroy(self): + # Destroy all files, but do not follow symlinks + try: + shutil.rmtree(self.path) + except FileNotFoundError: + pass + self.created = False + + def restage(self): + self.destroy() + self.create() + + def cache_local(self): + tty.debug("Sources for Develop stages are not cached") + + def ensure_access(file): """Ensure we can access a directory and die with an error if we can't.""" if not can_access(file): diff --git a/lib/spack/spack/test/cmd/dev_build.py b/lib/spack/spack/test/cmd/dev_build.py index 0a717fa747..3e6631cf51 100644 --- a/lib/spack/spack/test/cmd/dev_build.py +++ b/lib/spack/spack/test/cmd/dev_build.py @@ -41,6 +41,7 @@ def test_dev_build_basics(tmpdir, install_mockery): assert os.path.exists(str(tmpdir)) +@pytest.mark.disable_clean_stage_check def test_dev_build_before(tmpdir, install_mockery): spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized() @@ -57,6 +58,7 @@ def test_dev_build_before(tmpdir, install_mockery): assert not os.path.exists(spec.prefix) +@pytest.mark.disable_clean_stage_check def test_dev_build_until(tmpdir, install_mockery): spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized() @@ -74,6 +76,7 @@ def test_dev_build_until(tmpdir, install_mockery): assert not spack.store.STORE.db.query(spec, installed=True) +@pytest.mark.disable_clean_stage_check def test_dev_build_until_last_phase(tmpdir, install_mockery): # Test that we ignore the last_phase argument if it is already last spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized() @@ -93,6 +96,7 @@ def test_dev_build_until_last_phase(tmpdir, install_mockery): assert os.path.exists(str(tmpdir)) +@pytest.mark.disable_clean_stage_check def test_dev_build_before_until(tmpdir, install_mockery, capsys): spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized() @@ -130,6 +134,7 @@ def mock_module_noop(*args): pass +@pytest.mark.disable_clean_stage_check def test_dev_build_drop_in(tmpdir, mock_packages, monkeypatch, install_mockery, working_env): monkeypatch.setattr(os, "execvp", print_spack_cc) monkeypatch.setattr(spack.build_environment, "module", mock_module_noop) diff --git a/lib/spack/spack/test/cmd/develop.py b/lib/spack/spack/test/cmd/develop.py index 7d01853fd4..b090f0d21b 100644 --- a/lib/spack/spack/test/cmd/develop.py +++ b/lib/spack/spack/test/cmd/develop.py @@ -14,6 +14,7 @@ import spack.environment as ev import spack.spec from spack.main import SpackCommand +add = SpackCommand("add") develop = SpackCommand("develop") env = SpackCommand("env") @@ -192,14 +193,16 @@ def test_develop_full_git_repo( finally: spec.package.do_clean() - # Now use "spack develop": look at the resulting stage directory and make + # Now use "spack develop": look at the resulting dev_path and make # sure the git repo pulled includes the full branch history (or rather, # more than just one commit). env("create", "test") - with ev.read("test"): + with ev.read("test") as e: + add("git-test-commit") develop("git-test-commit@1.2") - location = SpackCommand("location") - develop_stage_dir = location("git-test-commit").strip() - commits = _git_commit_list(develop_stage_dir) + e.concretize() + spec = e.all_specs()[0] + develop_dir = spec.variants["dev_path"].value + commits = _git_commit_list(develop_dir) assert len(commits) > 1 diff --git a/lib/spack/spack/test/patch.py b/lib/spack/spack/test/patch.py index c8e9e8ece1..3710c58303 100644 --- a/lib/spack/spack/test/patch.py +++ b/lib/spack/spack/test/patch.py @@ -268,7 +268,7 @@ def trigger_bad_patch(pkg): def test_patch_failure_develop_spec_exits_gracefully( - mock_packages, config, install_mockery, mock_fetch, tmpdir + mock_packages, config, install_mockery, mock_fetch, tmpdir, mock_stage ): """ ensure that a failing patch does not trigger exceptions diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py index 024d195daa..37e8fd4559 100644 --- a/lib/spack/spack/test/stage.py +++ b/lib/spack/spack/test/stage.py @@ -22,7 +22,7 @@ import spack.stage import spack.util.executable import spack.util.url as url_util from spack.resource import Resource -from spack.stage import DIYStage, ResourceStage, Stage, StageComposite +from spack.stage import DevelopStage, DIYStage, ResourceStage, Stage, StageComposite from spack.util.path import canonicalize_path # The following values are used for common fetch and stage mocking fixtures: @@ -145,7 +145,7 @@ def check_destroy(stage, stage_name): assert not os.path.exists(stage_path) # tmp stage needs to remove tmp dir too. - if not stage.managed_by_spack: + if not isinstance(stage, DIYStage): target = os.path.realpath(stage_path) assert not os.path.exists(target) @@ -857,6 +857,73 @@ class TestStage: _file.read() == _readme_contents +def _create_files_from_tree(base, tree): + for name, content in tree.items(): + sub_base = os.path.join(base, name) + if isinstance(content, dict): + os.mkdir(sub_base) + _create_files_from_tree(sub_base, content) + else: + assert (content is None) or (isinstance(content, str)) + with open(sub_base, "w") as f: + if content: + f.write(content) + + +def _create_tree_from_dir_recursive(path): + if os.path.islink(path): + return os.readlink(path) + elif os.path.isdir(path): + tree = {} + for name in os.listdir(path): + sub_path = os.path.join(path, name) + tree[name] = _create_tree_from_dir_recursive(sub_path) + return tree + else: + with open(path, "r") as f: + content = f.read() or None + return content + + +@pytest.fixture +def develop_path(tmpdir): + dir_structure = {"a1": {"b1": None, "b2": "b1content"}, "a2": None} + srcdir = str(tmpdir.join("test-src")) + os.mkdir(srcdir) + _create_files_from_tree(srcdir, dir_structure) + yield dir_structure, srcdir + + +class TestDevelopStage: + def test_sanity_check_develop_path(self, develop_path): + _, srcdir = develop_path + with open(os.path.join(srcdir, "a1", "b2")) as f: + assert f.read() == "b1content" + + assert os.path.exists(os.path.join(srcdir, "a2")) + + def test_develop_stage(self, develop_path, tmp_build_stage_dir): + """Check that (a) develop stages update the given + `dev_path` with a symlink that points to the stage dir and + (b) that destroying the stage does not destroy `dev_path` + """ + devtree, srcdir = develop_path + stage = DevelopStage("test-stage", srcdir, reference_link="link-to-stage") + stage.create() + srctree1 = _create_tree_from_dir_recursive(stage.source_path) + assert os.path.samefile(srctree1["link-to-stage"], stage.path) + del srctree1["link-to-stage"] + assert srctree1 == devtree + + stage.destroy() + # Make sure destroying the stage doesn't change anything + # about the path + assert not os.path.exists(stage.path) + srctree2 = _create_tree_from_dir_recursive(srcdir) + del srctree2["link-to-stage"] # Note the symlink persists but is broken + assert srctree2 == devtree + + def test_stage_create_replace_path(tmp_build_stage_dir): """Ensure stage creation replaces a non-directory path.""" _, test_stage_path = tmp_build_stage_dir diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index bdc2907d42..90d17c5f48 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -668,7 +668,7 @@ _spack_buildcache_rebuild_index() { _spack_cd() { if $list_options then - SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages --source-dir -b --build-dir -e --env --first" + SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -c --source-dir -b --build-dir -e --env --first" else _all_packages fi @@ -1347,7 +1347,7 @@ _spack_load() { _spack_location() { if $list_options then - SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages --source-dir -b --build-dir -e --env --first" + SPACK_COMPREPLY="-h --help -m --module-dir -r --spack-root -i --install-dir -p --package-dir -P --packages -s --stage-dir -S --stages -c --source-dir -b --build-dir -e --env --first" else _all_packages fi diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index 600213454b..84e1a4721e 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -880,7 +880,7 @@ complete -c spack -n '__fish_spack_using_command buildcache rebuild-index' -s k complete -c spack -n '__fish_spack_using_command buildcache rebuild-index' -s k -l keys -d 'if provided, key index will be updated as well as package index' # spack cd -set -g __fish_spack_optspecs_spack_cd h/help m/module-dir r/spack-root i/install-dir p/package-dir P/packages s/stage-dir S/stages source-dir b/build-dir e/env= first +set -g __fish_spack_optspecs_spack_cd h/help m/module-dir r/spack-root i/install-dir p/package-dir P/packages s/stage-dir S/stages c/source-dir b/build-dir e/env= first complete -c spack -n '__fish_spack_using_command_pos_remainder 0 cd' -f -k -a '(__fish_spack_specs)' complete -c spack -n '__fish_spack_using_command cd' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command cd' -s h -l help -d 'show this help message and exit' @@ -898,8 +898,8 @@ complete -c spack -n '__fish_spack_using_command cd' -s s -l stage-dir -f -a sta complete -c spack -n '__fish_spack_using_command cd' -s s -l stage-dir -d 'stage directory for a spec' complete -c spack -n '__fish_spack_using_command cd' -s S -l stages -f -a stages complete -c spack -n '__fish_spack_using_command cd' -s S -l stages -d 'top level stage directory' -complete -c spack -n '__fish_spack_using_command cd' -l source-dir -f -a source_dir -complete -c spack -n '__fish_spack_using_command cd' -l source-dir -d 'source directory for a spec (requires it to be staged first)' +complete -c spack -n '__fish_spack_using_command cd' -s c -l source-dir -f -a source_dir +complete -c spack -n '__fish_spack_using_command cd' -s c -l source-dir -d 'source directory for a spec (requires it to be staged first)' complete -c spack -n '__fish_spack_using_command cd' -s b -l build-dir -f -a build_dir complete -c spack -n '__fish_spack_using_command cd' -s b -l build-dir -d 'build directory for a spec (requires it to be staged first)' complete -c spack -n '__fish_spack_using_command cd' -s e -l env -r -f -a location_env @@ -2089,7 +2089,7 @@ complete -c spack -n '__fish_spack_using_command load' -l list -f -a list complete -c spack -n '__fish_spack_using_command load' -l list -d 'show loaded packages: same as `spack find --loaded`' # spack location -set -g __fish_spack_optspecs_spack_location h/help m/module-dir r/spack-root i/install-dir p/package-dir P/packages s/stage-dir S/stages source-dir b/build-dir e/env= first +set -g __fish_spack_optspecs_spack_location h/help m/module-dir r/spack-root i/install-dir p/package-dir P/packages s/stage-dir S/stages c/source-dir b/build-dir e/env= first complete -c spack -n '__fish_spack_using_command_pos_remainder 0 location' -f -k -a '(__fish_spack_specs)' complete -c spack -n '__fish_spack_using_command location' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command location' -s h -l help -d 'show this help message and exit' @@ -2107,8 +2107,8 @@ complete -c spack -n '__fish_spack_using_command location' -s s -l stage-dir -f complete -c spack -n '__fish_spack_using_command location' -s s -l stage-dir -d 'stage directory for a spec' complete -c spack -n '__fish_spack_using_command location' -s S -l stages -f -a stages complete -c spack -n '__fish_spack_using_command location' -s S -l stages -d 'top level stage directory' -complete -c spack -n '__fish_spack_using_command location' -l source-dir -f -a source_dir -complete -c spack -n '__fish_spack_using_command location' -l source-dir -d 'source directory for a spec (requires it to be staged first)' +complete -c spack -n '__fish_spack_using_command location' -s c -l source-dir -f -a source_dir +complete -c spack -n '__fish_spack_using_command location' -s c -l source-dir -d 'source directory for a spec (requires it to be staged first)' complete -c spack -n '__fish_spack_using_command location' -s b -l build-dir -f -a build_dir complete -c spack -n '__fish_spack_using_command location' -s b -l build-dir -d 'build directory for a spec (requires it to be staged first)' complete -c spack -n '__fish_spack_using_command location' -s e -l env -r -f -a location_env |