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.')
 | 
					        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
 | 
				
			||||||
        if args.things_to_install == 'dependencies':
 | 
					        try:
 | 
				
			||||||
            # Install dependencies as-if they were installed
 | 
					            # decorate the install if necessary
 | 
				
			||||||
            # for root (explicit=False in the DB)
 | 
					            PackageBase.do_install = decorator(PackageBase.do_install)
 | 
				
			||||||
            kwargs['explicit'] = False
 | 
					
 | 
				
			||||||
            for s in spec.dependencies():
 | 
					            if args.things_to_install == 'dependencies':
 | 
				
			||||||
                p = spack.repo.get(s)
 | 
					                # Install dependencies as-if they were installed
 | 
				
			||||||
                p.do_install(**kwargs)
 | 
					                # for root (explicit=False in the DB)
 | 
				
			||||||
        else:
 | 
					                kwargs['explicit'] = False
 | 
				
			||||||
            package = spack.repo.get(spec)
 | 
					                for s in spec.dependencies():
 | 
				
			||||||
            kwargs['explicit'] = True
 | 
					                    p = spack.repo.get(s)
 | 
				
			||||||
            package.do_install(**kwargs)
 | 
					                    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
 | 
					        # 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):
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user