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:
		@@ -334,31 +334,41 @@ def install(parser, args, **kwargs):
 | 
			
		||||
        tty.error('The `spack install` command requires a spec to install.')
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
        if args.log_format is not None:
 | 
			
		||||
            # Compute the filename for logging
 | 
			
		||||
            log_filename = args.log_file
 | 
			
		||||
            if not log_filename:
 | 
			
		||||
                log_filename = default_log_file(spec)
 | 
			
		||||
 | 
			
		||||
            # Create the test suite in which to log results
 | 
			
		||||
            test_suite = TestSuite(spec)
 | 
			
		||||
            # Decorate PackageBase.do_install to get installation status
 | 
			
		||||
            PackageBase.do_install = junit_output(
 | 
			
		||||
                spec, test_suite
 | 
			
		||||
            )(PackageBase.do_install)
 | 
			
		||||
 | 
			
		||||
            # Temporarily decorate PackageBase.do_install to monitor
 | 
			
		||||
            # recursive calls.
 | 
			
		||||
            decorator = junit_output(spec, test_suite)
 | 
			
		||||
 | 
			
		||||
        # Do the actual installation
 | 
			
		||||
        if args.things_to_install == 'dependencies':
 | 
			
		||||
            # Install dependencies as-if they were installed
 | 
			
		||||
            # for root (explicit=False in the DB)
 | 
			
		||||
            kwargs['explicit'] = False
 | 
			
		||||
            for s in spec.dependencies():
 | 
			
		||||
                p = spack.repo.get(s)
 | 
			
		||||
                p.do_install(**kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            package = spack.repo.get(spec)
 | 
			
		||||
            kwargs['explicit'] = True
 | 
			
		||||
            package.do_install(**kwargs)
 | 
			
		||||
        try:
 | 
			
		||||
            # decorate the install if necessary
 | 
			
		||||
            PackageBase.do_install = decorator(PackageBase.do_install)
 | 
			
		||||
 | 
			
		||||
            if args.things_to_install == 'dependencies':
 | 
			
		||||
                # Install dependencies as-if they were installed
 | 
			
		||||
                # for root (explicit=False in the DB)
 | 
			
		||||
                kwargs['explicit'] = False
 | 
			
		||||
                for s in spec.dependencies():
 | 
			
		||||
                    p = spack.repo.get(s)
 | 
			
		||||
                    p.do_install(**kwargs)
 | 
			
		||||
            else:
 | 
			
		||||
                package = spack.repo.get(spec)
 | 
			
		||||
                kwargs['explicit'] = True
 | 
			
		||||
                package.do_install(**kwargs)
 | 
			
		||||
        finally:
 | 
			
		||||
            PackageBase.do_install = saved_do_install
 | 
			
		||||
 | 
			
		||||
        # Dump log file if asked to
 | 
			
		||||
        if args.log_format is not None:
 | 
			
		||||
 
 | 
			
		||||
@@ -760,10 +760,6 @@ def _make_stage(self):
 | 
			
		||||
            # Append the item to the composite
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -772,6 +768,12 @@ def stage(self):
 | 
			
		||||
            raise ValueError("Can only get a stage for a concrete package.")
 | 
			
		||||
        if self._stage is None:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
    @stage.setter
 | 
			
		||||
@@ -1390,6 +1392,11 @@ def build_process():
 | 
			
		||||
            if not keep_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(
 | 
			
		||||
            self, keep_prefix=False, restage=False):
 | 
			
		||||
        """Check for leftover files from partially-completed prior install to
 | 
			
		||||
 
 | 
			
		||||
@@ -236,6 +236,10 @@ def __init__(
 | 
			
		||||
 | 
			
		||||
            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):
 | 
			
		||||
        """
 | 
			
		||||
        Entering a stage context will create the stage directory
 | 
			
		||||
@@ -521,6 +525,7 @@ def create(self):
 | 
			
		||||
                mkdirp(self.path)
 | 
			
		||||
        # 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."""
 | 
			
		||||
@@ -532,6 +537,9 @@ def destroy(self):
 | 
			
		||||
        except OSError:
 | 
			
		||||
            os.chdir(os.path.dirname(self.path))
 | 
			
		||||
 | 
			
		||||
        # mark as destroyed
 | 
			
		||||
        self.created = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResourceStage(Stage):
 | 
			
		||||
 | 
			
		||||
@@ -573,8 +581,9 @@ def expand_archive(self):
 | 
			
		||||
                shutil.move(source_path, destination_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pattern.composite(method_list=['fetch', 'create', 'check', 'expand_archive',
 | 
			
		||||
                                'restage', 'destroy', 'cache_local'])
 | 
			
		||||
@pattern.composite(method_list=[
 | 
			
		||||
    'fetch', 'create', 'created', 'check', 'expand_archive', 'restage',
 | 
			
		||||
    'destroy', 'cache_local'])
 | 
			
		||||
class StageComposite:
 | 
			
		||||
    """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
 | 
			
		||||
@@ -623,6 +632,7 @@ def __init__(self, path):
 | 
			
		||||
        self.archive_file = None
 | 
			
		||||
        self.path = path
 | 
			
		||||
        self.source_path = path
 | 
			
		||||
        self.created = True
 | 
			
		||||
 | 
			
		||||
    def chdir(self):
 | 
			
		||||
        if os.path.isdir(self.path):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user