Better activate/deactivate logic.

spack activate
  - now activates dependency extensions
  - ensures dependencies are activated in the python installation.
  - -f/--force option still allows the old activate behavior.

spack deactivate
  - checks for dependents before deactivating (like uninstall)
  - deactivate -a/--all <extension> will deactviate a package and ALL
    of its dependency extensions.
  - deactivate -a/--all <extendee> activates all extensions of <extendee>
    e.g.: spack deactivate -a python
  - deactivate -f/--force option allows removing regardless of dependents.
    - deactivate -f can be run EVEN if a package is not activated.
    - allows for clenup of activations gone wrong.
This commit is contained in:
Todd Gamblin 2015-02-17 00:24:58 -08:00
parent 57f331e2ac
commit d800c23cec
6 changed files with 95 additions and 19 deletions

View File

@ -30,6 +30,9 @@
description = "Activate a package extension." description = "Activate a package extension."
def setup_parser(subparser): def setup_parser(subparser):
subparser.add_argument(
'-f', '--force', action='store_true',
help="Activate without first activating dependencies.")
subparser.add_argument( subparser.add_argument(
'spec', nargs=argparse.REMAINDER, help="spec of package extension to activate.") 'spec', nargs=argparse.REMAINDER, help="spec of package extension to activate.")
@ -44,6 +47,9 @@ def activate(parser, args):
spack.db.get(specs[0]) spack.db.get(specs[0])
spec = spack.cmd.disambiguate_spec(specs[0]) spec = spack.cmd.disambiguate_spec(specs[0])
if not spec.package.is_extension:
tty.die("%s is not an extension." % spec.name)
if spec.package.activated: if spec.package.activated:
tty.die("Package %s is already activated." % specs[0].short_spec) tty.die("Package %s is already activated." % specs[0].short_spec)

View File

@ -24,8 +24,10 @@
############################################################################## ##############################################################################
from external import argparse from external import argparse
import llnl.util.tty as tty import llnl.util.tty as tty
import spack import spack
import spack.cmd import spack.cmd
from spack.graph import topological_sort
description = "Deactivate a package extension." description = "Deactivate a package extension."
@ -33,6 +35,10 @@ def setup_parser(subparser):
subparser.add_argument( subparser.add_argument(
'-f', '--force', action='store_true', '-f', '--force', action='store_true',
help="Run deactivation even if spec is NOT currently activated.") help="Run deactivation even if spec is NOT currently activated.")
subparser.add_argument(
'-a', '--all', action='store_true',
help="Deactivate all extensions of an extendable pacakge, or "
"deactivate an extension AND its dependencies.")
subparser.add_argument( subparser.add_argument(
'spec', nargs=argparse.REMAINDER, help="spec of package extension to deactivate.") 'spec', nargs=argparse.REMAINDER, help="spec of package extension to deactivate.")
@ -42,12 +48,52 @@ def deactivate(parser, args):
if len(specs) != 1: if len(specs) != 1:
tty.die("deactivate requires one spec. %d given." % len(specs)) tty.die("deactivate requires one spec. %d given." % len(specs))
# TODO: remove this hack when DAG info is stored in dir layout. # TODO: remove this hack when DAG info is stored properly.
# This ensures the ext spec is always normalized properly. # This ensures the ext spec is always normalized properly.
spack.db.get(specs[0]) spack.db.get(specs[0])
spec = spack.cmd.disambiguate_spec(specs[0]) spec = spack.cmd.disambiguate_spec(specs[0])
if not args.force and not spec.package.activated: pkg = spec.package
tty.die("Package %s is not activated." % specs[0].short_spec)
spec.package.do_deactivate(force=args.force) if args.all:
if pkg.extendable:
tty.msg("Deactivating all extensions of %s" % pkg.spec.short_spec)
ext_pkgs = spack.db.installed_extensions_for(spec)
for ext_pkg in ext_pkgs:
ext_pkg.spec.normalize()
if ext_pkg.activated:
ext_pkg.do_deactivate(force=True)
elif pkg.is_extension:
# TODO: store DAG info properly (see above)
spec.normalize()
tty.msg("Deactivating %s and all dependencies." % pkg.spec.short_spec)
topo_order = topological_sort(spec)
index = spec.index()
for name in topo_order:
espec = index[name]
epkg = espec.package
# TODO: store DAG info properly (see above)
epkg.spec.normalize()
if epkg.extends(pkg.extendee_spec):
if epkg.activated or args.force:
epkg.do_deactivate(force=args.force)
else:
tty.die("spack deactivate --all requires an extendable package or an extension.")
else:
if not pkg.is_extension:
tty.die("spack deactivate requires an extension.",
"Did you mean 'spack deactivate --all'?")
if not args.force and not spec.package.activated:
tty.die("Package %s is not activated." % specs[0].short_spec)
spec.package.do_deactivate(force=args.force)

View File

@ -85,7 +85,7 @@ def display_specs(specs, **kwargs):
elif mode == 'deps': elif mode == 'deps':
for spec in specs: for spec in specs:
print spec.tree(indent=4, format='$_$@$+', color=True), print spec.tree(indent=4, format='$_$@$+$#', color=True),
elif mode in ('short', 'long'): elif mode in ('short', 'long'):
fmt = '$-_$@$+' fmt = '$-_$@$+'

View File

@ -371,7 +371,7 @@ def add_extension(self, spec, ext_spec):
_check_concrete(ext_spec) _check_concrete(ext_spec)
# Check whether it's already installed or if it's a conflict. # Check whether it's already installed or if it's a conflict.
exts = self.extension_map(spec) exts = self._extension_map(spec)
self.check_extension_conflict(spec, ext_spec) self.check_extension_conflict(spec, ext_spec)
# do the actual adding. # do the actual adding.
@ -384,7 +384,7 @@ def remove_extension(self, spec, ext_spec):
_check_concrete(ext_spec) _check_concrete(ext_spec)
# Make sure it's installed before removing. # Make sure it's installed before removing.
exts = self.extension_map(spec) exts = self._extension_map(spec)
self.check_activated(spec, ext_spec) self.check_activated(spec, ext_spec)
# do the actual removing. # do the actual removing.
@ -450,10 +450,10 @@ def __init__(self, spec, ext_spec, conflict):
class NoSuchExtensionError(DirectoryLayoutError): class NoSuchExtensionError(DirectoryLayoutError):
"""Raised when an extension isn't there on remove.""" """Raised when an extension isn't there on deactivate."""
def __init__(self, spec, ext_spec): def __init__(self, spec, ext_spec):
super(NoSuchExtensionError, self).__init__( super(NoSuchExtensionError, self).__init__(
"%s cannot be removed from %s because it's not installed."% ( "%s cannot be removed from %s because it's not activated."% (
ext_spec.short_spec, spec.short_spec)) ext_spec.short_spec, spec.short_spec))

View File

@ -33,4 +33,4 @@ def pre_uninstall(pkg):
if pkg.is_extension: if pkg.is_extension:
if pkg.activated: if pkg.activated:
pkg.do_deactivate() pkg.do_deactivate(force=True)

View File

@ -529,11 +529,8 @@ def extends(self, spec):
@property @property
def activated(self): def activated(self):
if not self.spec.concrete:
raise ValueError("Only concrete package extensions can be activated.")
if not self.is_extension: if not self.is_extension:
raise ValueError("is_extension called on package that is not an extension.") raise ValueError("is_extension called on package that is not an extension.")
exts = spack.install_layout.extension_map(self.extendee_spec) exts = spack.install_layout.extension_map(self.extendee_spec)
return (self.name in exts) and (exts[self.name] == self.spec) return (self.name in exts) and (exts[self.name] == self.spec)
@ -956,20 +953,33 @@ def _sanity_check_extension(self):
raise ValueError("%s does not extend %s!" % (self.name, self.extendee.name)) raise ValueError("%s does not extend %s!" % (self.name, self.extendee.name))
def do_activate(self): def do_activate(self, **kwargs):
"""Called on an etension to invoke the extendee's activate method. """Called on an etension to invoke the extendee's activate method.
Commands should call this routine, and should not call Commands should call this routine, and should not call
activate() directly. activate() directly.
""" """
self._sanity_check_extension() self._sanity_check_extension()
force = kwargs.get('force', False)
# TODO: get rid of this normalize - DAG handling.
self.spec.normalize()
spack.install_layout.check_extension_conflict(self.extendee_spec, self.spec) spack.install_layout.check_extension_conflict(self.extendee_spec, self.spec)
if not force:
for spec in self.spec.traverse(root=False):
if spec.package.extends(self.extendee_spec):
# TODO: fix this normalize() requirement -- revisit DAG handling.
spec.package.spec.normalize()
if not spec.package.activated:
spec.package.do_activate(**kwargs)
self.extendee_spec.package.activate(self, **self.extendee_args) self.extendee_spec.package.activate(self, **self.extendee_args)
spack.install_layout.add_extension(self.extendee_spec, self.spec) spack.install_layout.add_extension(self.extendee_spec, self.spec)
tty.msg("Activated extension %s for %s." tty.msg("Activated extension %s for %s."
% (self.spec.short_spec, self.extendee_spec.short_spec)) % (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@")))
def activate(self, extension, **kwargs): def activate(self, extension, **kwargs):
@ -994,15 +1004,21 @@ def ignore(filename):
def do_deactivate(self, **kwargs): def do_deactivate(self, **kwargs):
"""Called on the extension to invoke extendee's deactivate() method.""" """Called on the extension to invoke extendee's deactivate() method."""
force = kwargs.get('force', False)
self._sanity_check_extension() self._sanity_check_extension()
force = kwargs.get('force', False)
# Allow a force deactivate to happen. This can unlink # Allow a force deactivate to happen. This can unlink
# spurious files if something was corrupted. # spurious files if something was corrupted.
if not force: if not force:
spack.install_layout.check_activated(self.extendee_spec, self.spec) spack.install_layout.check_activated(self.extendee_spec, self.spec)
activated = spack.install_layout.extension_map(self.extendee_spec)
for name, aspec in activated.items():
if aspec != self.spec and self.spec in aspec:
raise ActivationError(
"Cannot deactivate %s beacuse %s is activated and depends on it."
% (self.spec.short_spec, aspec.short_spec))
self.extendee_spec.package.deactivate(self, **self.extendee_args) self.extendee_spec.package.deactivate(self, **self.extendee_args)
# redundant activation check -- makes SURE the spec is not # redundant activation check -- makes SURE the spec is not
@ -1011,7 +1027,7 @@ def do_deactivate(self, **kwargs):
spack.install_layout.remove_extension(self.extendee_spec, self.spec) spack.install_layout.remove_extension(self.extendee_spec, self.spec)
tty.msg("Deactivated extension %s for %s." tty.msg("Deactivated extension %s for %s."
% (self.spec.short_spec, self.extendee_spec.short_spec)) % (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@")))
def deactivate(self, extension, **kwargs): def deactivate(self, extension, **kwargs):
@ -1236,7 +1252,15 @@ def __init__(self, cls):
"Package %s has no version with a URL." % cls.__name__) "Package %s has no version with a URL." % cls.__name__)
class ExtensionConflictError(PackageError): class ExtensionError(PackageError): pass
class ExtensionConflictError(ExtensionError):
def __init__(self, path): def __init__(self, path):
super(ExtensionConflictError, self).__init__( super(ExtensionConflictError, self).__init__(
"Extension blocked by file: %s" % path) "Extension blocked by file: %s" % path)
class ActivationError(ExtensionError):
def __init__(self, msg, long_msg=None):
super(ActivationError, self).__init__(msg, long_msg)