directory_layout: factor out an ExtensionsLayout class
This commit is contained in:
parent
650ca7db9e
commit
b9e8402104
@ -88,37 +88,6 @@ def check_installed(self, spec):
|
||||
"""
|
||||
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 @@ def remove_install_directory(self, spec):
|
||||
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 @@ def __init__(self, root, **kwargs):
|
||||
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 @@ def specs_by_hash(self):
|
||||
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 @@ def _extension_map(self, spec):
|
||||
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 @@ def _extension_map(self, spec):
|
||||
|
||||
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 _write_extensions(self, spec, extensions):
|
||||
path = self.extension_file_path(spec)
|
||||
|
||||
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)
|
||||
# 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 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)
|
||||
# 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')
|
||||
|
||||
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 remove_extension(self, spec, ext_spec):
|
||||
_check_concrete(spec)
|
||||
_check_concrete(ext_spec)
|
||||
|
||||
# Make sure it's installed before removing.
|
||||
exts = self._extension_map(spec)
|
||||
self.check_activated(spec, ext_spec)
|
||||
|
||||
# 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):
|
||||
|
Loading…
Reference in New Issue
Block a user