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."
def setup_parser(subparser):
subparser.add_argument(
'-f', '--force', action='store_true',
help="Activate without first activating dependencies.")
subparser.add_argument(
'spec', nargs=argparse.REMAINDER, help="spec of package extension to activate.")
@ -44,6 +47,9 @@ def activate(parser, args):
spack.db.get(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:
tty.die("Package %s is already activated." % specs[0].short_spec)

View file

@ -24,8 +24,10 @@
##############################################################################
from external import argparse
import llnl.util.tty as tty
import spack
import spack.cmd
from spack.graph import topological_sort
description = "Deactivate a package extension."
@ -33,6 +35,10 @@ def setup_parser(subparser):
subparser.add_argument(
'-f', '--force', action='store_true',
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(
'spec', nargs=argparse.REMAINDER, help="spec of package extension to deactivate.")
@ -42,12 +48,52 @@ def deactivate(parser, args):
if len(specs) != 1:
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.
spack.db.get(specs[0])
spec = spack.cmd.disambiguate_spec(specs[0])
if not args.force and not spec.package.activated:
tty.die("Package %s is not activated." % specs[0].short_spec)
pkg = spec.package
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':
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'):
fmt = '$-_$@$+'

View file

@ -371,7 +371,7 @@ def add_extension(self, spec, ext_spec):
_check_concrete(ext_spec)
# 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)
# do the actual adding.
@ -384,7 +384,7 @@ def remove_extension(self, spec, ext_spec):
_check_concrete(ext_spec)
# Make sure it's installed before removing.
exts = self.extension_map(spec)
exts = self._extension_map(spec)
self.check_activated(spec, ext_spec)
# do the actual removing.
@ -450,10 +450,10 @@ def __init__(self, spec, ext_spec, conflict):
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):
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))

View file

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

View file

@ -529,11 +529,8 @@ def extends(self, spec):
@property
def activated(self):
if not self.spec.concrete:
raise ValueError("Only concrete package extensions can be activated.")
if not self.is_extension:
raise ValueError("is_extension called on package that is not an extension.")
exts = spack.install_layout.extension_map(self.extendee_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))
def do_activate(self):
def do_activate(self, **kwargs):
"""Called on an etension to invoke the extendee's activate method.
Commands should call this routine, and should not call
activate() directly.
"""
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)
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)
spack.install_layout.add_extension(self.extendee_spec, self.spec)
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):
@ -994,15 +1004,21 @@ def ignore(filename):
def do_deactivate(self, **kwargs):
"""Called on the extension to invoke extendee's deactivate() method."""
force = kwargs.get('force', False)
self._sanity_check_extension()
force = kwargs.get('force', False)
# Allow a force deactivate to happen. This can unlink
# spurious files if something was corrupted.
if not force:
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)
# 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)
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):
@ -1236,7 +1252,15 @@ def __init__(self, cls):
"Package %s has no version with a URL." % cls.__name__)
class ExtensionConflictError(PackageError):
class ExtensionError(PackageError): pass
class ExtensionConflictError(ExtensionError):
def __init__(self, path):
super(ExtensionConflictError, self).__init__(
"Extension blocked by file: %s" % path)
class ActivationError(ExtensionError):
def __init__(self, msg, long_msg=None):
super(ActivationError, self).__init__(msg, long_msg)