Better extension activation/deactivation
This commit is contained in:
		@@ -89,10 +89,10 @@ def extensions(parser, args):
 | 
				
			|||||||
    spack.cmd.find.display_specs(installed, mode=args.mode)
 | 
					    spack.cmd.find.display_specs(installed, mode=args.mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # List specs of activated extensions.
 | 
					    # List specs of activated extensions.
 | 
				
			||||||
    activated  = spack.install_layout.get_extensions(spec)
 | 
					    activated = spack.install_layout.extension_map(spec)
 | 
				
			||||||
    print
 | 
					    print
 | 
				
			||||||
    if not activated:
 | 
					    if not activated:
 | 
				
			||||||
        tty.msg("None activated.")
 | 
					        tty.msg("None activated.")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    tty.msg("%d currently activated:" % len(activated))
 | 
					    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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@
 | 
				
			|||||||
import exceptions
 | 
					import exceptions
 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
from contextlib import closing
 | 
					from contextlib import closing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import llnl.util.tty as tty
 | 
					import llnl.util.tty as tty
 | 
				
			||||||
@@ -84,17 +85,38 @@ def make_path_for_spec(self, spec):
 | 
				
			|||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_extensions(self, spec):
 | 
					    def extension_map(self, spec):
 | 
				
			||||||
        """Get a set of currently installed extension packages for a 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()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_extension(self, spec, extension_spec):
 | 
					    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, ext_spec):
 | 
				
			||||||
        """Add to the list of currently installed extensions."""
 | 
					        """Add to the list of currently installed extensions."""
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def remove_extension(self, spec, extension_spec):
 | 
					    def remove_extension(self, spec, ext_spec):
 | 
				
			||||||
        """Remove from the list of currently installed extensions."""
 | 
					        """Remove from the list of currently installed extensions."""
 | 
				
			||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -173,6 +195,8 @@ def __init__(self, root, **kwargs):
 | 
				
			|||||||
        self.spec_file_name = spec_file_name
 | 
					        self.spec_file_name = spec_file_name
 | 
				
			||||||
        self.extension_file_name = extension_file_name
 | 
					        self.extension_file_name = extension_file_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Cache of already written/read extension maps.
 | 
				
			||||||
 | 
					        self._extension_maps = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def hidden_file_paths(self):
 | 
					    def hidden_file_paths(self):
 | 
				
			||||||
@@ -271,54 +295,94 @@ def extension_file_path(self, spec):
 | 
				
			|||||||
        return join_path(self.path_for_spec(spec), self.extension_file_name)
 | 
					        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)
 | 
					        _check_concrete(spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        extensions = set()
 | 
					        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 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)
 | 
					        path = self.extension_file_path(spec)
 | 
				
			||||||
        if os.path.exists(path):
 | 
					
 | 
				
			||||||
            with closing(open(path)) as ext_file:
 | 
					        # Create a temp file in the same directory as the actual file.
 | 
				
			||||||
                for line in ext_file:
 | 
					        dirname, basename = os.path.split(path)
 | 
				
			||||||
                    try:
 | 
					        tmp = tempfile.NamedTemporaryFile(
 | 
				
			||||||
                        extensions.add(Spec(line.strip()))
 | 
					            prefix=basename, dir=dirname, delete=False)
 | 
				
			||||||
                    except spack.error.SpackError, e:
 | 
					
 | 
				
			||||||
                        raise InvalidExtensionSpecError(str(e))
 | 
					        # Write temp file.
 | 
				
			||||||
        return extensions
 | 
					        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 write_extensions(self, spec, extensions):
 | 
					    def add_extension(self, spec, ext_spec):
 | 
				
			||||||
        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)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def add_extension(self, spec, extension_spec):
 | 
					 | 
				
			||||||
        _check_concrete(spec)
 | 
					        _check_concrete(spec)
 | 
				
			||||||
        _check_concrete(extension_spec)
 | 
					        _check_concrete(ext_spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exts = self.get_extensions(spec)
 | 
					        # Check whether it's already installed or if it's a conflict.
 | 
				
			||||||
        if extension_spec in exts:
 | 
					        exts = self.extension_map(spec)
 | 
				
			||||||
            raise ExtensionAlreadyInstalledError(spec, extension_spec)
 | 
					        self.check_extension_conflict(spec, ext_spec)
 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            for already_installed in exts:
 | 
					 | 
				
			||||||
                if spec.name == extension_spec.name:
 | 
					 | 
				
			||||||
                    raise ExtensionConflictError(spec, extension_spec, already_installed)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exts.add(extension_spec)
 | 
					        # do the actual adding.
 | 
				
			||||||
        self.write_extensions(spec, exts)
 | 
					        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(spec)
 | 
				
			||||||
        _check_concrete(extension_spec)
 | 
					        _check_concrete(ext_spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exts = self.get_extensions(spec)
 | 
					        # Make sure it's installed before removing.
 | 
				
			||||||
        if not extension_spec in exts:
 | 
					        exts = self.extension_map(spec)
 | 
				
			||||||
            raise NoSuchExtensionError(spec, extension_spec)
 | 
					        self.check_activated(spec, ext_spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        exts.remove(extension_spec)
 | 
					        # do the actual removing.
 | 
				
			||||||
        self.write_extensions(spec, exts)
 | 
					        del exts[ext_spec.name]
 | 
				
			||||||
 | 
					        self._write_extensions(spec, exts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DirectoryLayoutError(SpackError):
 | 
					class DirectoryLayoutError(SpackError):
 | 
				
			||||||
@@ -365,24 +429,24 @@ def __init__(self, message):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ExtensionAlreadyInstalledError(DirectoryLayoutError):
 | 
					class ExtensionAlreadyInstalledError(DirectoryLayoutError):
 | 
				
			||||||
    """Raised when an extension is added to a package that already has it."""
 | 
					    """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__(
 | 
					        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):
 | 
					class ExtensionConflictError(DirectoryLayoutError):
 | 
				
			||||||
    """Raised when an extension is added to a package that already has it."""
 | 
					    """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__(
 | 
					        super(ExtensionConflictError, self).__init__(
 | 
				
			||||||
            "%s cannot be installed in %s because it conflicts with %s."% (
 | 
					            "%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):
 | 
					class NoSuchExtensionError(DirectoryLayoutError):
 | 
				
			||||||
    """Raised when an extension isn't there on remove."""
 | 
					    """Raised when an extension isn't there on remove."""
 | 
				
			||||||
    def __init__(self, spec, extension_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 installed."% (
 | 
				
			||||||
                extension_spec.short_spec, spec.short_spec))
 | 
					                ext_spec.short_spec, spec.short_spec))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -534,7 +534,8 @@ def activated(self):
 | 
				
			|||||||
        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.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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):
 | 
					    def preorder_traversal(self, visited=None, **kwargs):
 | 
				
			||||||
@@ -987,6 +988,8 @@ def do_activate(self):
 | 
				
			|||||||
        activate() directly.
 | 
					        activate() directly.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self._sanity_check_extension()
 | 
					        self._sanity_check_extension()
 | 
				
			||||||
 | 
					        spack.install_layout.check_extension_conflict(self.extendee_spec, self.spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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)
 | 
				
			||||||
@@ -1014,12 +1017,22 @@ def ignore(filename):
 | 
				
			|||||||
        tree.merge(self.prefix, ignore=ignore)
 | 
					        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."""
 | 
					        """Called on the extension to invoke extendee's deactivate() method."""
 | 
				
			||||||
 | 
					        force = kwargs.get('force', False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._sanity_check_extension()
 | 
					        self._sanity_check_extension()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.extendee_spec.package.deactivate(self, **self.extendee_args)
 | 
					        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)
 | 
					            spack.install_layout.remove_extension(self.extendee_spec, self.spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tty.msg("Deactivated extension %s for %s."
 | 
					        tty.msg("Deactivated extension %s for %s."
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,9 +98,9 @@ def ignore(filename):
 | 
				
			|||||||
        return ignore
 | 
					        return ignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def write_easy_install_pth(self, extensions):
 | 
					    def write_easy_install_pth(self, exts):
 | 
				
			||||||
        paths = []
 | 
					        paths = []
 | 
				
			||||||
        for ext in extensions:
 | 
					        for ext in sorted(exts.values()):
 | 
				
			||||||
            ext_site_packages = os.path.join(ext.prefix, self.site_packages_dir)
 | 
					            ext_site_packages = os.path.join(ext.prefix, self.site_packages_dir)
 | 
				
			||||||
            easy_pth = "%s/easy-install.pth" % ext_site_packages
 | 
					            easy_pth = "%s/easy-install.pth" % ext_site_packages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,15 +139,15 @@ def activate(self, ext_pkg, **args):
 | 
				
			|||||||
        args.update(ignore=self.python_ignore(ext_pkg, args))
 | 
					        args.update(ignore=self.python_ignore(ext_pkg, args))
 | 
				
			||||||
        super(Python, self).activate(ext_pkg, **args)
 | 
					        super(Python, self).activate(ext_pkg, **args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        extensions = set(spack.install_layout.get_extensions(self.spec))
 | 
					        exts = spack.install_layout.extension_map(self.spec)
 | 
				
			||||||
        extensions.add(ext_pkg.spec)
 | 
					        exts[ext_pkg.name] = ext_pkg.spec
 | 
				
			||||||
        self.write_easy_install_pth(extensions)
 | 
					        self.write_easy_install_pth(exts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def deactivate(self, ext_pkg, **args):
 | 
					    def deactivate(self, ext_pkg, **args):
 | 
				
			||||||
        args.update(ignore=self.python_ignore(ext_pkg, args))
 | 
					        args.update(ignore=self.python_ignore(ext_pkg, args))
 | 
				
			||||||
        super(Python, self).deactivate(ext_pkg, **args)
 | 
					        super(Python, self).deactivate(ext_pkg, **args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        extensions = set(spack.install_layout.get_extensions(self.spec))
 | 
					        exts = spack.install_layout.extension_map(self.spec)
 | 
				
			||||||
        extensions.remove(ext_pkg.spec)
 | 
					        del exts[ext_pkg.name]
 | 
				
			||||||
        self.write_easy_install_pth(extensions)
 | 
					        self.write_easy_install_pth(exts)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user