summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/directory_layout.py210
1 files changed, 118 insertions, 92 deletions
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 54fffeabfb..4bc7a26926 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -88,37 +88,6 @@ class DirectoryLayout(object):
"""
raise NotImplementedError()
- 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, ext_spec):
- """Add to the list of currently installed extensions."""
- raise NotImplementedError()
-
- def remove_extension(self, spec, ext_spec):
- """Remove from the list of currently installed extensions."""
- raise NotImplementedError()
-
def path_for_spec(self, spec):
"""Return absolute path from the root to a directory for the spec."""
_check_concrete(spec)
@@ -149,6 +118,50 @@ class DirectoryLayout(object):
path = os.path.dirname(path)
+class ExtensionsLayout(object):
+ """A directory layout is used to associate unique paths with specs for
+ package extensions.
+ Keeps track of which extensions are activated for what package.
+ Depending on the use case, this can mean globally activated extensions
+ directly in the installation folder - or extensions activated in
+ filesystem views.
+ """
+ def __init__(self, root, **kwargs):
+ self.root = root
+ self.link = kwargs.get("link", os.symlink)
+
+ def add_extension(self, spec, ext_spec):
+ """Add to the list of currently installed extensions."""
+ 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 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 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 remove_extension(self, spec, ext_spec):
+ """Remove from the list of currently installed extensions."""
+ raise NotImplementedError()
+
+
class YamlDirectoryLayout(DirectoryLayout):
"""By default lays out installation directories like this::
<install root>/
@@ -184,9 +197,6 @@ class YamlDirectoryLayout(DirectoryLayout):
self.build_env_name = 'build.env' # build environment
self.packages_dir = 'repos' # archive of package.py files
- # Cache of already written/read extension maps.
- self._extension_maps = {}
-
@property
def hidden_file_paths(self):
return (self.metadata_dir,)
@@ -297,31 +307,69 @@ class YamlDirectoryLayout(DirectoryLayout):
by_hash[spec.dag_hash()] = spec
return by_hash
+
+class YamlExtensionsLayout(ExtensionsLayout):
+ """Implements globally activated extensions within a YamlDirectoryLayout.
+ """
+ def __init__(self, root, layout):
+ """layout is the corresponding YamlDirectoryLayout object for which
+ we implement extensions.
+ """
+ super(YamlExtensionsLayout, self).__init__(root)
+ self.layout = layout
+ self.extension_file_name = 'extensions.yaml'
+
+ # Cache of already written/read extension maps.
+ self._extension_maps = {}
+
+ def add_extension(self, spec, ext_spec):
+ _check_concrete(spec)
+ _check_concrete(ext_spec)
+
+ # Check whether it's already installed or if it's a conflict.
+ exts = self._extension_map(spec)
+ self.check_extension_conflict(spec, ext_spec)
+
+ # do the actual adding.
+ exts[ext_spec.name] = ext_spec
+ self._write_extensions(spec, exts)
+
+ 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 (ext_spec.name not in exts) or (ext_spec != exts[ext_spec.name]):
+ raise NoSuchExtensionError(spec, ext_spec)
+
def extension_file_path(self, spec):
"""Gets full path to an installed package's extension file"""
_check_concrete(spec)
- return join_path(self.metadata_path(spec), self.extension_file_name)
+ return join_path(self.layout.metadata_path(spec),
+ self.extension_file_name)
- def _write_extensions(self, spec, extensions):
- path = self.extension_file_path(spec)
+ def extension_map(self, spec):
+ """Defensive copying version of _extension_map() for external API."""
+ _check_concrete(spec)
+ return self._extension_map(spec).copy()
- # 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)
+ def remove_extension(self, spec, ext_spec):
+ _check_concrete(spec)
+ _check_concrete(ext_spec)
- # write tmp file
- with tmp:
- yaml.dump({
- 'extensions': [
- {ext.name: {
- 'hash': ext.dag_hash(),
- 'path': str(ext.prefix)
- }} for ext in sorted(extensions.values())]
- }, tmp, default_flow_style=False)
+ # Make sure it's installed before removing.
+ exts = self._extension_map(spec)
+ self.check_activated(spec, ext_spec)
- # Atomic update by moving tmpfile on top of old one.
- os.rename(tmp.name, path)
+ # do the actual removing.
+ del exts[ext_spec.name]
+ self._write_extensions(spec, exts)
def _extension_map(self, spec):
"""Get a dict<name -> spec> for all extensions currently
@@ -334,7 +382,7 @@ class YamlDirectoryLayout(DirectoryLayout):
self._extension_maps[spec] = {}
else:
- by_hash = self.specs_by_hash()
+ by_hash = self.layout.specs_by_hash()
exts = {}
with open(path) as ext_file:
yaml_file = yaml.load(ext_file)
@@ -358,48 +406,26 @@ class YamlDirectoryLayout(DirectoryLayout):
return self._extension_maps[spec]
- def extension_map(self, spec):
- """Defensive copying version of _extension_map() for external API."""
- _check_concrete(spec)
- 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 (ext_spec.name not in exts) or (ext_spec != exts[ext_spec.name]):
- raise NoSuchExtensionError(spec, ext_spec)
-
- def add_extension(self, spec, ext_spec):
- _check_concrete(spec)
- _check_concrete(ext_spec)
-
- # Check whether it's already installed or if it's a conflict.
- exts = self._extension_map(spec)
- self.check_extension_conflict(spec, ext_spec)
-
- # do the actual adding.
- exts[ext_spec.name] = ext_spec
- self._write_extensions(spec, exts)
+ def _write_extensions(self, spec, extensions):
+ path = self.extension_file_path(spec)
- def remove_extension(self, spec, ext_spec):
- _check_concrete(spec)
- _check_concrete(ext_spec)
+ # 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)
- # Make sure it's installed before removing.
- exts = self._extension_map(spec)
- self.check_activated(spec, ext_spec)
+ # write tmp file
+ with tmp:
+ yaml.dump({
+ 'extensions': [
+ {ext.name: {
+ 'hash': ext.dag_hash(),
+ 'path': str(ext.prefix)
+ }} for ext in sorted(extensions.values())]
+ }, tmp, default_flow_style=False, encoding='utf-8')
- # do the actual removing.
- del exts[ext_spec.name]
- self._write_extensions(spec, exts)
+ # Atomic update by moving tmpfile on top of old one.
+ os.rename(tmp.name, path)
class DirectoryLayoutError(SpackError):