summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/llnl/util/lang.py31
-rw-r--r--lib/spack/llnl/util/link_tree.py4
-rw-r--r--lib/spack/spack/build_environment.py56
-rw-r--r--lib/spack/spack/cmd/activate.py6
-rw-r--r--lib/spack/spack/cmd/deactivate.py57
-rw-r--r--lib/spack/spack/cmd/extensions.py4
-rw-r--r--lib/spack/spack/cmd/find.py5
-rw-r--r--lib/spack/spack/directory_layout.py165
-rw-r--r--lib/spack/spack/hooks/extensions.py2
-rw-r--r--lib/spack/spack/package.py112
-rw-r--r--lib/spack/spack/packages.py12
11 files changed, 353 insertions, 101 deletions
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index db15da0506..332367f537 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -291,6 +291,37 @@ def check_kwargs(kwargs, fun):
% (next(kwargs.iterkeys()), fun.__name__))
+def match_predicate(*args):
+ """Utility function for making string matching predicates.
+
+ Each arg can be a:
+ - regex
+ - list or tuple of regexes
+ - predicate that takes a string.
+
+ This returns a predicate that is true if:
+ - any arg regex matches
+ - any regex in a list or tuple of regexes matches.
+ - any predicate in args matches.
+ """
+ def match(string):
+ for arg in args:
+ if isinstance(arg, basestring):
+ if re.search(arg, string):
+ return True
+ elif isinstance(arg, list) or isinstance(arg, tuple):
+ if any(re.search(i, string) for i in arg):
+ return True
+ elif callable(arg):
+ if arg(string):
+ return True
+ else:
+ raise ValueError("args to match_predicate must be regex, "
+ "list of regexes, or callable.")
+ return False
+ return match
+
+
class RequiredAttributeError(ValueError):
def __init__(self, message):
super(RequiredAttributeError, self).__init__(message)
diff --git a/lib/spack/llnl/util/link_tree.py b/lib/spack/llnl/util/link_tree.py
index 4e4e48316e..4d778eca1e 100644
--- a/lib/spack/llnl/util/link_tree.py
+++ b/lib/spack/llnl/util/link_tree.py
@@ -175,6 +175,10 @@ class LinkTree(object):
kwargs['order'] = 'post'
for src, dest in traverse_tree(self._root, dest_root, **kwargs):
if os.path.isdir(src):
+ # Skip non-existing links.
+ if not os.path.exists(dest):
+ continue
+
if not os.path.isdir(dest):
raise ValueError("File blocks directory: %s" % dest)
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 5e8c52cb3c..59b25d96e7 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -28,6 +28,7 @@ Skimming this module is a nice way to get acquainted with the types of
calls you can make from within the install() function.
"""
import os
+import sys
import shutil
import multiprocessing
import platform
@@ -226,3 +227,58 @@ def setup_package(pkg):
for dep_spec in pkg.spec.traverse(root=False):
dep_spec.package.setup_dependent_environment(
pkg.module, dep_spec, pkg.spec)
+
+
+def fork(pkg, function):
+ """Fork a child process to do part of a spack build.
+
+ Arguments:
+
+ pkg -- pkg whose environemnt we should set up the
+ forked process for.
+ function -- arg-less function to run in the child process.
+
+ Usage:
+ def child_fun():
+ # do stuff
+ build_env.fork(pkg, child_fun)
+
+ Forked processes are run with the build environemnt set up by
+ spack.build_environment. This allows package authors to have
+ full control over the environment, etc. without offecting
+ other builds that might be executed in the same spack call.
+
+ If something goes wrong, the child process is expected toprint
+ the error and the parent process will exit with error as
+ well. If things go well, the child exits and the parent
+ carries on.
+ """
+ try:
+ pid = os.fork()
+ except OSError, e:
+ raise InstallError("Unable to fork build process: %s" % e)
+
+ if pid == 0:
+ # Give the child process the package's build environemnt.
+ setup_package(pkg)
+
+ try:
+ # call the forked function.
+ function()
+
+ # Use os._exit here to avoid raising a SystemExit exception,
+ # which interferes with unit tests.
+ os._exit(0)
+ except:
+ # Child doesn't raise or return to main spack code.
+ # Just runs default exception handler and exits.
+ sys.excepthook(*sys.exc_info())
+ os._exit(1)
+
+ else:
+ # Parent process just waits for the child to complete. If the
+ # child exited badly, assume it already printed an appropriate
+ # message. Just make the parent exit with an error code.
+ pid, returncode = os.waitpid(pid, 0)
+ if returncode != 0:
+ sys.exit(1)
diff --git a/lib/spack/spack/cmd/activate.py b/lib/spack/spack/cmd/activate.py
index c1e23852d6..71eca4f453 100644
--- a/lib/spack/spack/cmd/activate.py
+++ b/lib/spack/spack/cmd/activate.py
@@ -31,6 +31,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)
diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py
index fd13f051df..bfec618c8e 100644
--- a/lib/spack/spack/cmd/deactivate.py
+++ b/lib/spack/spack/cmd/deactivate.py
@@ -24,13 +24,22 @@
##############################################################################
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."
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.")
@@ -39,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 spec.package.activated:
- tty.die("Package %s is not activated." % specs[0].short_spec)
+ pkg = spec.package
+
+ 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()
+ spec.package.do_deactivate(force=args.force)
diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py
index ae73d8ac55..fc8e6842c3 100644
--- a/lib/spack/spack/cmd/extensions.py
+++ b/lib/spack/spack/cmd/extensions.py
@@ -89,10 +89,10 @@ def extensions(parser, args):
spack.cmd.find.display_specs(installed, mode=args.mode)
# List specs of activated extensions.
- activated = spack.install_layout.get_extensions(spec)
+ activated = spack.install_layout.extension_map(spec)
print
if not activated:
tty.msg("None activated.")
return
tty.msg("%d currently activated:" % len(activated))
- spack.cmd.find.display_specs(activated, mode=args.mode)
+ spack.cmd.find.display_specs(activated.values(), mode=args.mode)
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index f6f503afe5..70b10edb4e 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -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 = '$-_$@$+'
@@ -122,5 +122,8 @@ def find(parser, args):
if not args.mode:
args.mode = 'short'
+
+ if sys.stdout.isatty():
+ tty.msg("%d installed packages." % len(specs))
display_specs(specs, mode=args.mode)
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 37740720a2..b2cf5dc801 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -27,9 +27,11 @@ import os
import exceptions
import hashlib
import shutil
+import tempfile
from contextlib import closing
import llnl.util.tty as tty
+from llnl.util.lang import memoized
from llnl.util.filesystem import join_path, mkdirp
import spack
@@ -84,17 +86,38 @@ class DirectoryLayout(object):
raise NotImplementedError()
- def get_extensions(self, spec):
- """Get a set of currently installed extension packages for a spec."""
+ def extension_map(self, spec):
+ """Get a dict of currently installed extension packages for a spec.
+
+ Dict maps { name : extension_spec }
+ Modifying dict does not affect internals of this layout.
+ """
+ raise NotImplementedError()
+
+
+ def check_extension_conflict(self, spec, ext_spec):
+ """Ensure that ext_spec can be activated in spec.
+
+ If not, raise ExtensionAlreadyInstalledError or
+ ExtensionConflictError.
+ """
+ raise NotImplementedError()
+
+
+ def check_activated(self, spec, ext_spec):
+ """Ensure that ext_spec can be removed from spec.
+
+ If not, raise NoSuchExtensionError.
+ """
raise NotImplementedError()
- def add_extension(self, spec, extension_spec):
+ def add_extension(self, spec, ext_spec):
"""Add to the list of currently installed extensions."""
raise NotImplementedError()
- def remove_extension(self, spec, extension_spec):
+ def remove_extension(self, spec, ext_spec):
"""Remove from the list of currently installed extensions."""
raise NotImplementedError()
@@ -173,6 +196,8 @@ class SpecHashDirectoryLayout(DirectoryLayout):
self.spec_file_name = spec_file_name
self.extension_file_name = extension_file_name
+ # Cache of already written/read extension maps.
+ self._extension_maps = {}
@property
def hidden_file_paths(self):
@@ -199,6 +224,9 @@ class SpecHashDirectoryLayout(DirectoryLayout):
if all(spack.db.exists(s.name) for s in spec.traverse()):
copy = spec.copy()
+
+ # TODO: It takes a lot of time to normalize every spec on read.
+ # TODO: Storing graph info with spec files would fix this.
copy.normalize()
if copy.concrete:
return copy # These are specs spack still understands.
@@ -252,17 +280,20 @@ class SpecHashDirectoryLayout(DirectoryLayout):
self.write_spec(spec, spec_file_path)
+ @memoized
def all_specs(self):
if not os.path.isdir(self.root):
- return
+ return []
+ specs = []
for path in traverse_dirs_at_depth(self.root, 3):
arch, compiler, last_dir = path
spec_file_path = join_path(
self.root, arch, compiler, last_dir, self.spec_file_name)
if os.path.exists(spec_file_path):
spec = self.read_spec(spec_file_path)
- yield spec
+ specs.append(spec)
+ return specs
def extension_file_path(self, spec):
@@ -271,54 +302,94 @@ class SpecHashDirectoryLayout(DirectoryLayout):
return join_path(self.path_for_spec(spec), self.extension_file_name)
- def get_extensions(self, spec):
+ def _extension_map(self, spec):
+ """Get a dict<name -> spec> for all extensions currnetly
+ installed for this package."""
_check_concrete(spec)
- extensions = set()
- path = self.extension_file_path(spec)
- if os.path.exists(path):
- with closing(open(path)) as ext_file:
- for line in ext_file:
- try:
- extensions.add(Spec(line.strip()))
- except spack.error.SpackError, e:
- raise InvalidExtensionSpecError(str(e))
- return extensions
+ if not spec in self._extension_maps:
+ path = self.extension_file_path(spec)
+ if not os.path.exists(path):
+ self._extension_maps[spec] = {}
+
+ else:
+ exts = {}
+ with closing(open(path)) as ext_file:
+ for line in ext_file:
+ try:
+ spec = Spec(line.strip())
+ exts[spec.name] = spec
+ except spack.error.SpackError, e:
+ # TODO: do something better here -- should be
+ # resilient to corrupt files.
+ raise InvalidExtensionSpecError(str(e))
+ self._extension_maps[spec] = exts
+
+ return self._extension_maps[spec]
+
+
+ def extension_map(self, spec):
+ """Defensive copying version of _extension_map() for external API."""
+ return self._extension_map(spec).copy()
+
+
+ def check_extension_conflict(self, spec, ext_spec):
+ exts = self._extension_map(spec)
+ if ext_spec.name in exts:
+ installed_spec = exts[ext_spec.name]
+ if ext_spec == installed_spec:
+ raise ExtensionAlreadyInstalledError(spec, ext_spec)
+ else:
+ raise ExtensionConflictError(spec, ext_spec, installed_spec)
- def write_extensions(self, spec, extensions):
+ def check_activated(self, spec, ext_spec):
+ exts = self._extension_map(spec)
+ if (not ext_spec.name in exts) or (ext_spec != exts[ext_spec.name]):
+ raise NoSuchExtensionError(spec, ext_spec)
+
+
+ def _write_extensions(self, spec, extensions):
path = self.extension_file_path(spec)
- with closing(open(path, 'w')) as spec_file:
- for extension in sorted(extensions):
- spec_file.write("%s\n" % extension)
+
+ # Create a temp file in the same directory as the actual file.
+ dirname, basename = os.path.split(path)
+ tmp = tempfile.NamedTemporaryFile(
+ prefix=basename, dir=dirname, delete=False)
+
+ # Write temp file.
+ with closing(tmp):
+ for extension in sorted(extensions.values()):
+ tmp.write("%s\n" % extension)
+
+ # Atomic update by moving tmpfile on top of old one.
+ os.rename(tmp.name, path)
- def add_extension(self, spec, extension_spec):
+ def add_extension(self, spec, ext_spec):
_check_concrete(spec)
- _check_concrete(extension_spec)
+ _check_concrete(ext_spec)
- exts = self.get_extensions(spec)
- if extension_spec in exts:
- raise ExtensionAlreadyInstalledError(spec, extension_spec)
- else:
- for already_installed in exts:
- if spec.name == extension_spec.name:
- raise ExtensionConflictError(spec, extension_spec, already_installed)
+ # Check whether it's already installed or if it's a conflict.
+ exts = self._extension_map(spec)
+ self.check_extension_conflict(spec, ext_spec)
- exts.add(extension_spec)
- self.write_extensions(spec, exts)
+ # do the actual adding.
+ exts[ext_spec.name] = ext_spec
+ self._write_extensions(spec, exts)
- def remove_extension(self, spec, extension_spec):
+ def remove_extension(self, spec, ext_spec):
_check_concrete(spec)
- _check_concrete(extension_spec)
+ _check_concrete(ext_spec)
- exts = self.get_extensions(spec)
- if not extension_spec in exts:
- raise NoSuchExtensionError(spec, extension_spec)
+ # Make sure it's installed before removing.
+ exts = self._extension_map(spec)
+ self.check_activated(spec, ext_spec)
- exts.remove(extension_spec)
- self.write_extensions(spec, exts)
+ # do the actual removing.
+ del exts[ext_spec.name]
+ self._write_extensions(spec, exts)
class DirectoryLayoutError(SpackError):
@@ -365,24 +436,24 @@ class InvalidExtensionSpecError(DirectoryLayoutError):
class ExtensionAlreadyInstalledError(DirectoryLayoutError):
"""Raised when an extension is added to a package that already has it."""
- def __init__(self, spec, extension_spec):
+ def __init__(self, spec, ext_spec):
super(ExtensionAlreadyInstalledError, self).__init__(
- "%s is already installed in %s" % (extension_spec.short_spec, spec.short_spec))
+ "%s is already installed in %s" % (ext_spec.short_spec, spec.short_spec))
class ExtensionConflictError(DirectoryLayoutError):
"""Raised when an extension is added to a package that already has it."""
- def __init__(self, spec, extension_spec, conflict):
+ def __init__(self, spec, ext_spec, conflict):
super(ExtensionConflictError, self).__init__(
"%s cannot be installed in %s because it conflicts with %s."% (
- extension_spec.short_spec, spec.short_spec, conflict.short_spec))
+ ext_spec.short_spec, spec.short_spec, conflict.short_spec))
class NoSuchExtensionError(DirectoryLayoutError):
- """Raised when an extension isn't there on remove."""
- def __init__(self, spec, extension_spec):
+ """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."% (
- extension_spec.short_spec, spec.short_spec))
+ "%s cannot be removed from %s because it's not activated."% (
+ ext_spec.short_spec, spec.short_spec))
diff --git a/lib/spack/spack/hooks/extensions.py b/lib/spack/spack/hooks/extensions.py
index 9d6fa23d03..cf87a78c8c 100644
--- a/lib/spack/spack/hooks/extensions.py
+++ b/lib/spack/spack/hooks/extensions.py
@@ -33,4 +33,4 @@ def pre_uninstall(pkg):
if pkg.is_extension:
if pkg.activated:
- pkg.do_deactivate()
+ pkg.do_deactivate(force=True)
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index b18d054990..bc8541a184 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -529,12 +529,10 @@ class Package(object):
@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.")
-
- return self.spec in spack.install_layout.get_extensions(self.extendee_spec)
+ exts = spack.install_layout.extension_map(self.extendee_spec)
+ return (self.name in exts) and (exts[self.name] == self.spec)
def preorder_traversal(self, visited=None, **kwargs):
@@ -803,32 +801,21 @@ class Package(object):
if not fake_install:
self.do_patch()
- # Fork a child process to do the build. This allows each
- # package authors to have full control over their environment,
- # etc. without offecting other builds that might be executed
- # in the same spack call.
- try:
- pid = os.fork()
- except OSError, e:
- raise InstallError("Unable to fork build process: %s" % e)
+ # create the install directory. The install layout
+ # handles this in case so that it can use whatever
+ # package naming scheme it likes.
+ spack.install_layout.make_path_for_spec(self.spec)
- if pid == 0:
+ def real_work():
try:
tty.msg("Building %s." % self.name)
- # create the install directory. The install layout
- # handles this in case so that it can use whatever
- # package naming scheme it likes.
- spack.install_layout.make_path_for_spec(self.spec)
-
# Run the pre-install hook in the child process after
# the directory is created.
spack.hooks.pre_install(self)
# Set up process's build environment before running install.
self.stage.chdir_to_source()
- build_env.setup_package(self)
-
if fake_install:
self.do_fake_install()
else:
@@ -847,14 +834,10 @@ class Package(object):
build_time = self._total_time - self._fetch_time
tty.msg("Successfully installed %s." % self.name,
- "Fetch: %.2f sec. Build: %.2f sec. Total: %.2f sec."
- % (self._fetch_time, build_time, self._total_time))
+ "Fetch: %s. Build: %s. Total: %s."
+ % (_hms(self._fetch_time), _hms(build_time), _hms(self._total_time)))
print_pkg(self.prefix)
- # Use os._exit here to avoid raising a SystemExit exception,
- # which interferes with unit tests.
- os._exit(0)
-
except:
if not keep_prefix:
# If anything goes wrong, remove the install prefix
@@ -864,24 +847,14 @@ class Package(object):
"Spack will think this package is installed." +
"Manually remove this directory to fix:",
self.prefix)
+ raise
- # Child doesn't raise or return to main spack code.
- # Just runs default exception handler and exits.
- sys.excepthook(*sys.exc_info())
- os._exit(1)
-
- # Parent process just waits for the child to complete. If the
- # child exited badly, assume it already printed an appropriate
- # message. Just make the parent exit with an error code.
- pid, returncode = os.waitpid(pid, 0)
- if returncode != 0:
- sys.exit(1)
+ build_env.fork(self, real_work)
# Once everything else is done, run post install hooks
spack.hooks.post_install(self)
-
def _sanity_check_install(self):
installed = set(os.listdir(self.prefix))
installed.difference_update(spack.install_layout.hidden_file_paths)
@@ -980,18 +953,33 @@ class Package(object):
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):
@@ -1014,16 +1002,32 @@ class Package(object):
tree.merge(self.prefix, ignore=ignore)
- def do_deactivate(self):
+ def do_deactivate(self, **kwargs):
"""Called on the extension to invoke extendee's deactivate() method."""
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)
- if self.spec in spack.install_layout.get_extensions(self.extendee_spec):
+ # redundant activation check -- makes SURE the spec is not
+ # still activated even if something was wrong above.
+ if self.activated:
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):
@@ -1188,6 +1192,18 @@ def print_pkg(message):
print message
+def _hms(seconds):
+ """Convert time in seconds to hours, minutes, seconds."""
+ m, s = divmod(seconds, 60)
+ h, m = divmod(m, 60)
+
+ parts = []
+ if h: parts.append("%dh" % h)
+ if m: parts.append("%dm" % m)
+ if s: parts.append("%.2fs" % s)
+ return ' '.join(parts)
+
+
class FetchError(spack.error.SpackError):
"""Raised when something goes wrong during fetch."""
def __init__(self, message, long_msg=None):
@@ -1236,7 +1252,15 @@ class NoURLError(PackageError):
"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)
diff --git a/lib/spack/spack/packages.py b/lib/spack/spack/packages.py
index 7ef8135c1a..43c4c191c1 100644
--- a/lib/spack/spack/packages.py
+++ b/lib/spack/spack/packages.py
@@ -119,8 +119,15 @@ class PackageDB(object):
@_autospec
def installed_extensions_for(self, extendee_spec):
- return [s.package for s in self.installed_package_specs()
- if s.package.extends(extendee_spec)]
+ for s in self.installed_package_specs():
+ try:
+ if s.package.extends(extendee_spec):
+ yield s.package
+ except UnknownPackageError, e:
+ # Skip packages we know nothing about
+ continue
+ # TODO: add some conditional way to do this instead of
+ # catching exceptions.
def dirname_for_package_name(self, pkg_name):
@@ -185,6 +192,7 @@ class PackageDB(object):
yield self.get(name)
+ @memoized
def exists(self, pkg_name):
"""Whether a package with the supplied name exists ."""
return os.path.exists(self.filename_for_package_name(pkg_name))