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.
This commit is contained in:
parent
22cb3815fe
commit
ec517b40e9
@ -53,6 +53,7 @@ def setup_parser(subparser):
|
|||||||
"-S", "--stages", action="store_true", help="top level stage directory"
|
"-S", "--stages", action="store_true", help="top level stage directory"
|
||||||
)
|
)
|
||||||
directories.add_argument(
|
directories.add_argument(
|
||||||
|
"-c",
|
||||||
"--source-dir",
|
"--source-dir",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="source directory for a spec (requires it to be staged first)",
|
help="source directory for a spec (requires it to be staged first)",
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
install_test_root,
|
install_test_root,
|
||||||
)
|
)
|
||||||
from spack.installer import InstallError, PackageInstaller
|
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.executable import ProcessError, which
|
||||||
from spack.util.package_hash import package_hash
|
from spack.util.package_hash import package_hash
|
||||||
from spack.version import GitVersion, StandardVersion
|
from spack.version import GitVersion, StandardVersion
|
||||||
@ -1075,7 +1075,12 @@ def _make_stage(self):
|
|||||||
# If it's a dev package (not transitively), use a DIY stage object
|
# If it's a dev package (not transitively), use a DIY stage object
|
||||||
dev_path_var = self.spec.variants.get("dev_path", None)
|
dev_path_var = self.spec.variants.get("dev_path", None)
|
||||||
if dev_path_var:
|
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
|
# To fetch the current version
|
||||||
source_stage = self._make_root_stage(self.fetcher)
|
source_stage = self._make_root_stage(self.fetcher)
|
||||||
@ -1407,7 +1412,7 @@ def do_fetch(self, mirror_only=False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
checksum = spack.config.get("config:checksum")
|
checksum = spack.config.get("config:checksum")
|
||||||
fetch = self.stage.managed_by_spack
|
fetch = self.stage.needs_fetching
|
||||||
if (
|
if (
|
||||||
checksum
|
checksum
|
||||||
and fetch
|
and fetch
|
||||||
@ -1480,9 +1485,6 @@ def do_stage(self, mirror_only=False):
|
|||||||
if self.has_code:
|
if self.has_code:
|
||||||
self.do_fetch(mirror_only)
|
self.do_fetch(mirror_only)
|
||||||
self.stage.expand_archive()
|
self.stage.expand_archive()
|
||||||
|
|
||||||
if not os.listdir(self.stage.path):
|
|
||||||
raise spack.error.FetchError("Archive was empty for %s" % self.name)
|
|
||||||
else:
|
else:
|
||||||
# Support for post-install hooks requires a stage.source_path
|
# Support for post-install hooks requires a stage.source_path
|
||||||
fsys.mkdirp(self.stage.source_path)
|
fsys.mkdirp(self.stage.source_path)
|
||||||
@ -1516,7 +1518,7 @@ def do_patch(self):
|
|||||||
# If we encounter an archive that failed to patch, restage it
|
# If we encounter an archive that failed to patch, restage it
|
||||||
# so that we can apply all the patches again.
|
# so that we can apply all the patches again.
|
||||||
if os.path.isfile(bad_file):
|
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.")
|
tty.debug("Patching failed last time. Restaging.")
|
||||||
self.stage.restage()
|
self.stage.restage()
|
||||||
else:
|
else:
|
||||||
@ -1537,6 +1539,8 @@ def do_patch(self):
|
|||||||
tty.msg("No patches needed for {0}".format(self.name))
|
tty.msg("No patches needed for {0}".format(self.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
# Apply all the patches for specs that match this one
|
# Apply all the patches for specs that match this one
|
||||||
patched = False
|
patched = False
|
||||||
for patch in patches:
|
for patch in patches:
|
||||||
@ -1546,12 +1550,16 @@ def do_patch(self):
|
|||||||
tty.msg("Applied patch {0}".format(patch.path_or_url))
|
tty.msg("Applied patch {0}".format(patch.path_or_url))
|
||||||
patched = True
|
patched = True
|
||||||
except spack.error.SpackError as e:
|
except spack.error.SpackError as e:
|
||||||
tty.debug(e)
|
|
||||||
|
|
||||||
# Touch bad file if anything goes wrong.
|
# Touch bad file if anything goes wrong.
|
||||||
tty.msg("Patch %s failed." % patch.path_or_url)
|
|
||||||
fsys.touch(bad_file)
|
fsys.touch(bad_file)
|
||||||
|
error_msg = f"Patch {patch.path_or_url} failed."
|
||||||
|
if self.stage.requires_patch_success:
|
||||||
|
tty.msg(error_msg)
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
tty.debug(error_msg)
|
||||||
|
tty.debug(e)
|
||||||
|
errors.append(e)
|
||||||
|
|
||||||
if has_patch_fun:
|
if has_patch_fun:
|
||||||
try:
|
try:
|
||||||
@ -1569,16 +1577,21 @@ def do_patch(self):
|
|||||||
# printed a message for each patch.
|
# printed a message for each patch.
|
||||||
tty.msg("No patches needed for {0}".format(self.name))
|
tty.msg("No patches needed for {0}".format(self.name))
|
||||||
except spack.error.SpackError as e:
|
except spack.error.SpackError as e:
|
||||||
tty.debug(e)
|
|
||||||
|
|
||||||
# Touch bad file if anything goes wrong.
|
# Touch bad file if anything goes wrong.
|
||||||
tty.msg("patch() function failed for {0}".format(self.name))
|
|
||||||
fsys.touch(bad_file)
|
fsys.touch(bad_file)
|
||||||
|
error_msg = f"patch() function failed for {self.name}"
|
||||||
|
if self.stage.requires_patch_success:
|
||||||
|
tty.msg(error_msg)
|
||||||
raise
|
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
|
# Get rid of any old failed file -- patches have either succeeded
|
||||||
# or are not needed. This is mostly defensive -- it's needed
|
# 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 we didn't restage
|
||||||
if os.path.isfile(bad_file):
|
if os.path.isfile(bad_file):
|
||||||
os.remove(bad_file)
|
os.remove(bad_file)
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@
|
|||||||
"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]
|
"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]
|
||||||
},
|
},
|
||||||
"stage_name": {"type": "string"},
|
"stage_name": {"type": "string"},
|
||||||
|
"develop_stage_link": {"type": "string"},
|
||||||
"test_stage": {"type": "string"},
|
"test_stage": {"type": "string"},
|
||||||
"extensions": {"type": "array", "items": {"type": "string"}},
|
"extensions": {"type": "array", "items": {"type": "string"}},
|
||||||
"template_dirs": {"type": "array", "items": {"type": "string"}},
|
"template_dirs": {"type": "array", "items": {"type": "string"}},
|
||||||
|
@ -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.
|
"""Manages a temporary stage directory for building.
|
||||||
|
|
||||||
A Stage object is a context manager that handles a directory where
|
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.
|
#: Most staging is managed by Spack. DIYStage is one exception.
|
||||||
managed_by_spack = True
|
needs_fetching = True
|
||||||
|
requires_patch_success = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -297,6 +394,8 @@ def __init__(
|
|||||||
The search function that provides the fetch strategy
|
The search function that provides the fetch strategy
|
||||||
instance.
|
instance.
|
||||||
"""
|
"""
|
||||||
|
super().__init__(name, path, keep, lock)
|
||||||
|
|
||||||
# TODO: fetch/stage coupling needs to be reworked -- the logic
|
# TODO: fetch/stage coupling needs to be reworked -- the logic
|
||||||
# TODO: here is convoluted and not modular enough.
|
# TODO: here is convoluted and not modular enough.
|
||||||
if isinstance(url_or_fetch_strategy, str):
|
if isinstance(url_or_fetch_strategy, str):
|
||||||
@ -314,72 +413,8 @@ def __init__(
|
|||||||
|
|
||||||
self.srcdir = None
|
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
|
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
|
@property
|
||||||
def expected_archive_files(self):
|
def expected_archive_files(self):
|
||||||
"""Possible archive file paths."""
|
"""Possible archive file paths."""
|
||||||
@ -631,21 +666,6 @@ def restage(self):
|
|||||||
"""
|
"""
|
||||||
self.fetcher.reset()
|
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):
|
def destroy(self):
|
||||||
"""Removes this stage directory."""
|
"""Removes this stage directory."""
|
||||||
remove_linked_tree(self.path)
|
remove_linked_tree(self.path)
|
||||||
@ -752,7 +772,8 @@ def __init__(self):
|
|||||||
"cache_mirror",
|
"cache_mirror",
|
||||||
"steal_source",
|
"steal_source",
|
||||||
"disable_mirrors",
|
"disable_mirrors",
|
||||||
"managed_by_spack",
|
"needs_fetching",
|
||||||
|
"requires_patch_success",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -808,8 +829,8 @@ class DIYStage:
|
|||||||
directory naming convention.
|
directory naming convention.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""DIY staging is, by definition, not managed by Spack."""
|
needs_fetching = False
|
||||||
managed_by_spack = False
|
requires_patch_success = False
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
if path is None:
|
if path is None:
|
||||||
@ -857,6 +878,65 @@ def cache_local(self):
|
|||||||
tty.debug("Sources for DIY stages are not cached")
|
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):
|
def ensure_access(file):
|
||||||
"""Ensure we can access a directory and die with an error if we can't."""
|
"""Ensure we can access a directory and die with an error if we can't."""
|
||||||
if not can_access(file):
|
if not can_access(file):
|
||||||
|
@ -41,6 +41,7 @@ def test_dev_build_basics(tmpdir, install_mockery):
|
|||||||
assert os.path.exists(str(tmpdir))
|
assert os.path.exists(str(tmpdir))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.disable_clean_stage_check
|
||||||
def test_dev_build_before(tmpdir, install_mockery):
|
def test_dev_build_before(tmpdir, install_mockery):
|
||||||
spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized()
|
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)
|
assert not os.path.exists(spec.prefix)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.disable_clean_stage_check
|
||||||
def test_dev_build_until(tmpdir, install_mockery):
|
def test_dev_build_until(tmpdir, install_mockery):
|
||||||
spec = spack.spec.Spec(f"dev-build-test-install@0.0.0 dev_path={tmpdir}").concretized()
|
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)
|
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):
|
def test_dev_build_until_last_phase(tmpdir, install_mockery):
|
||||||
# Test that we ignore the last_phase argument if it is already last
|
# 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()
|
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))
|
assert os.path.exists(str(tmpdir))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.disable_clean_stage_check
|
||||||
def test_dev_build_before_until(tmpdir, install_mockery, capsys):
|
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()
|
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
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.disable_clean_stage_check
|
||||||
def test_dev_build_drop_in(tmpdir, mock_packages, monkeypatch, install_mockery, working_env):
|
def test_dev_build_drop_in(tmpdir, mock_packages, monkeypatch, install_mockery, working_env):
|
||||||
monkeypatch.setattr(os, "execvp", print_spack_cc)
|
monkeypatch.setattr(os, "execvp", print_spack_cc)
|
||||||
monkeypatch.setattr(spack.build_environment, "module", mock_module_noop)
|
monkeypatch.setattr(spack.build_environment, "module", mock_module_noop)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import spack.spec
|
import spack.spec
|
||||||
from spack.main import SpackCommand
|
from spack.main import SpackCommand
|
||||||
|
|
||||||
|
add = SpackCommand("add")
|
||||||
develop = SpackCommand("develop")
|
develop = SpackCommand("develop")
|
||||||
env = SpackCommand("env")
|
env = SpackCommand("env")
|
||||||
|
|
||||||
@ -192,14 +193,16 @@ def test_develop_full_git_repo(
|
|||||||
finally:
|
finally:
|
||||||
spec.package.do_clean()
|
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,
|
# sure the git repo pulled includes the full branch history (or rather,
|
||||||
# more than just one commit).
|
# more than just one commit).
|
||||||
env("create", "test")
|
env("create", "test")
|
||||||
with ev.read("test"):
|
with ev.read("test") as e:
|
||||||
|
add("git-test-commit")
|
||||||
develop("git-test-commit@1.2")
|
develop("git-test-commit@1.2")
|
||||||
|
|
||||||
location = SpackCommand("location")
|
e.concretize()
|
||||||
develop_stage_dir = location("git-test-commit").strip()
|
spec = e.all_specs()[0]
|
||||||
commits = _git_commit_list(develop_stage_dir)
|
develop_dir = spec.variants["dev_path"].value
|
||||||
|
commits = _git_commit_list(develop_dir)
|
||||||
assert len(commits) > 1
|
assert len(commits) > 1
|
||||||
|
@ -268,7 +268,7 @@ def trigger_bad_patch(pkg):
|
|||||||
|
|
||||||
|
|
||||||
def test_patch_failure_develop_spec_exits_gracefully(
|
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
|
ensure that a failing patch does not trigger exceptions
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import spack.util.executable
|
import spack.util.executable
|
||||||
import spack.util.url as url_util
|
import spack.util.url as url_util
|
||||||
from spack.resource import Resource
|
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
|
from spack.util.path import canonicalize_path
|
||||||
|
|
||||||
# The following values are used for common fetch and stage mocking fixtures:
|
# 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)
|
assert not os.path.exists(stage_path)
|
||||||
|
|
||||||
# tmp stage needs to remove tmp dir too.
|
# 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)
|
target = os.path.realpath(stage_path)
|
||||||
assert not os.path.exists(target)
|
assert not os.path.exists(target)
|
||||||
|
|
||||||
@ -857,6 +857,73 @@ def test_diystage_preserve_file(self, tmpdir):
|
|||||||
_file.read() == _readme_contents
|
_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):
|
def test_stage_create_replace_path(tmp_build_stage_dir):
|
||||||
"""Ensure stage creation replaces a non-directory path."""
|
"""Ensure stage creation replaces a non-directory path."""
|
||||||
_, test_stage_path = tmp_build_stage_dir
|
_, test_stage_path = tmp_build_stage_dir
|
||||||
|
@ -668,7 +668,7 @@ _spack_buildcache_rebuild_index() {
|
|||||||
_spack_cd() {
|
_spack_cd() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
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
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
@ -1347,7 +1347,7 @@ _spack_load() {
|
|||||||
_spack_location() {
|
_spack_location() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
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
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
|
@ -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'
|
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
|
# 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_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 -f -a help
|
||||||
complete -c spack -n '__fish_spack_using_command cd' -s h -l help -d 'show this help message and exit'
|
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 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 -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' -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' -s c -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 -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 -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 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
|
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`'
|
complete -c spack -n '__fish_spack_using_command load' -l list -d 'show loaded packages: same as `spack find --loaded`'
|
||||||
|
|
||||||
# spack location
|
# 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_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 -f -a help
|
||||||
complete -c spack -n '__fish_spack_using_command location' -s h -l help -d 'show this help message and exit'
|
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 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 -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' -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' -s c -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 -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 -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 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
|
complete -c spack -n '__fish_spack_using_command location' -s e -l env -r -f -a location_env
|
||||||
|
Loading…
Reference in New Issue
Block a user