Make install command reusable within single Spack run
- install and probably other commands were designed to run once, but now we can to test them from within Spack with SpackCommand - cmd/install.py assumed that it could modify do_install in PackageBase and leave it that way; this makes the decorator temporary - package.py didn't properly initialize its stage if the same package had been built successfully before (and the stage removed). - manage stage lifecycle better and remember when Package needs to re-create the stage
This commit is contained in:
parent
fa1faa61c4
commit
f51b541ef8
@ -334,20 +334,28 @@ def install(parser, args, **kwargs):
|
|||||||
tty.error('The `spack install` command requires a spec to install.')
|
tty.error('The `spack install` command requires a spec to install.')
|
||||||
|
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
|
saved_do_install = PackageBase.do_install
|
||||||
|
decorator = lambda fn: fn
|
||||||
|
|
||||||
# Check if we were asked to produce some log for dashboards
|
# Check if we were asked to produce some log for dashboards
|
||||||
if args.log_format is not None:
|
if args.log_format is not None:
|
||||||
# Compute the filename for logging
|
# Compute the filename for logging
|
||||||
log_filename = args.log_file
|
log_filename = args.log_file
|
||||||
if not log_filename:
|
if not log_filename:
|
||||||
log_filename = default_log_file(spec)
|
log_filename = default_log_file(spec)
|
||||||
|
|
||||||
# Create the test suite in which to log results
|
# Create the test suite in which to log results
|
||||||
test_suite = TestSuite(spec)
|
test_suite = TestSuite(spec)
|
||||||
# Decorate PackageBase.do_install to get installation status
|
|
||||||
PackageBase.do_install = junit_output(
|
# Temporarily decorate PackageBase.do_install to monitor
|
||||||
spec, test_suite
|
# recursive calls.
|
||||||
)(PackageBase.do_install)
|
decorator = junit_output(spec, test_suite)
|
||||||
|
|
||||||
# Do the actual installation
|
# Do the actual installation
|
||||||
|
try:
|
||||||
|
# decorate the install if necessary
|
||||||
|
PackageBase.do_install = decorator(PackageBase.do_install)
|
||||||
|
|
||||||
if args.things_to_install == 'dependencies':
|
if args.things_to_install == 'dependencies':
|
||||||
# Install dependencies as-if they were installed
|
# Install dependencies as-if they were installed
|
||||||
# for root (explicit=False in the DB)
|
# for root (explicit=False in the DB)
|
||||||
@ -359,6 +367,8 @@ def install(parser, args, **kwargs):
|
|||||||
package = spack.repo.get(spec)
|
package = spack.repo.get(spec)
|
||||||
kwargs['explicit'] = True
|
kwargs['explicit'] = True
|
||||||
package.do_install(**kwargs)
|
package.do_install(**kwargs)
|
||||||
|
finally:
|
||||||
|
PackageBase.do_install = saved_do_install
|
||||||
|
|
||||||
# Dump log file if asked to
|
# Dump log file if asked to
|
||||||
if args.log_format is not None:
|
if args.log_format is not None:
|
||||||
|
@ -760,10 +760,6 @@ def _make_stage(self):
|
|||||||
# Append the item to the composite
|
# Append the item to the composite
|
||||||
composite_stage.append(stage)
|
composite_stage.append(stage)
|
||||||
|
|
||||||
# Create stage on first access. Needed because fetch, stage,
|
|
||||||
# patch, and install can be called independently of each
|
|
||||||
# other, so `with self.stage:` in do_install isn't sufficient.
|
|
||||||
composite_stage.create()
|
|
||||||
return composite_stage
|
return composite_stage
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -772,6 +768,12 @@ def stage(self):
|
|||||||
raise ValueError("Can only get a stage for a concrete package.")
|
raise ValueError("Can only get a stage for a concrete package.")
|
||||||
if self._stage is None:
|
if self._stage is None:
|
||||||
self._stage = self._make_stage()
|
self._stage = self._make_stage()
|
||||||
|
|
||||||
|
# Create stage on first access. Needed because fetch, stage,
|
||||||
|
# patch, and install can be called independently of each
|
||||||
|
# other, so `with self.stage:` in do_install isn't sufficient.
|
||||||
|
self._stage.create()
|
||||||
|
|
||||||
return self._stage
|
return self._stage
|
||||||
|
|
||||||
@stage.setter
|
@stage.setter
|
||||||
@ -1390,6 +1392,11 @@ def build_process():
|
|||||||
if not keep_prefix:
|
if not keep_prefix:
|
||||||
self.remove_prefix()
|
self.remove_prefix()
|
||||||
|
|
||||||
|
# The subprocess *may* have removed the build stage. Mark it
|
||||||
|
# not created so that the next time self.stage is invoked, we
|
||||||
|
# check the filesystem for it.
|
||||||
|
self.stage.created = False
|
||||||
|
|
||||||
def check_for_unfinished_installation(
|
def check_for_unfinished_installation(
|
||||||
self, keep_prefix=False, restage=False):
|
self, keep_prefix=False, restage=False):
|
||||||
"""Check for leftover files from partially-completed prior install to
|
"""Check for leftover files from partially-completed prior install to
|
||||||
|
@ -236,6 +236,10 @@ def __init__(
|
|||||||
|
|
||||||
self._lock = Stage.stage_locks[self.name]
|
self._lock = Stage.stage_locks[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):
|
def __enter__(self):
|
||||||
"""
|
"""
|
||||||
Entering a stage context will create the stage directory
|
Entering a stage context will create the stage directory
|
||||||
@ -521,6 +525,7 @@ def create(self):
|
|||||||
mkdirp(self.path)
|
mkdirp(self.path)
|
||||||
# Make sure we can actually do something with the stage we made.
|
# Make sure we can actually do something with the stage we made.
|
||||||
ensure_access(self.path)
|
ensure_access(self.path)
|
||||||
|
self.created = True
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Removes this stage directory."""
|
"""Removes this stage directory."""
|
||||||
@ -532,6 +537,9 @@ def destroy(self):
|
|||||||
except OSError:
|
except OSError:
|
||||||
os.chdir(os.path.dirname(self.path))
|
os.chdir(os.path.dirname(self.path))
|
||||||
|
|
||||||
|
# mark as destroyed
|
||||||
|
self.created = False
|
||||||
|
|
||||||
|
|
||||||
class ResourceStage(Stage):
|
class ResourceStage(Stage):
|
||||||
|
|
||||||
@ -573,8 +581,9 @@ def expand_archive(self):
|
|||||||
shutil.move(source_path, destination_path)
|
shutil.move(source_path, destination_path)
|
||||||
|
|
||||||
|
|
||||||
@pattern.composite(method_list=['fetch', 'create', 'check', 'expand_archive',
|
@pattern.composite(method_list=[
|
||||||
'restage', 'destroy', 'cache_local'])
|
'fetch', 'create', 'created', 'check', 'expand_archive', 'restage',
|
||||||
|
'destroy', 'cache_local'])
|
||||||
class StageComposite:
|
class StageComposite:
|
||||||
"""Composite for Stage type objects. The first item in this composite is
|
"""Composite for Stage type objects. The first item in this composite is
|
||||||
considered to be the root package, and operations that return a value are
|
considered to be the root package, and operations that return a value are
|
||||||
@ -623,6 +632,7 @@ def __init__(self, path):
|
|||||||
self.archive_file = None
|
self.archive_file = None
|
||||||
self.path = path
|
self.path = path
|
||||||
self.source_path = path
|
self.source_path = path
|
||||||
|
self.created = True
|
||||||
|
|
||||||
def chdir(self):
|
def chdir(self):
|
||||||
if os.path.isdir(self.path):
|
if os.path.isdir(self.path):
|
||||||
|
Loading…
Reference in New Issue
Block a user