extensions file now in YAML format

This commit is contained in:
Todd Gamblin 2015-05-10 02:56:50 -07:00
parent 2f3b0481de
commit 29e833dfef
3 changed files with 69 additions and 38 deletions

View File

@ -173,12 +173,13 @@ def __init__(self, root, **kwargs):
self.metadata_dir = kwargs.get('metadata_dir', '.spack')
self.hash_len = kwargs.get('hash_len', None)
self.spec_file_name = 'spec'
self.extension_file_name = 'extensions'
self.spec_file_name = 'spec.yaml'
self.extension_file_name = 'extensions.yaml'
# Cache of already written/read extension maps.
self._extension_maps = {}
@property
def hidden_file_paths(self):
return (self.metadata_dir)
@ -208,14 +209,13 @@ def write_spec(self, spec, path):
"""Write a spec out to a file."""
_check_concrete(spec)
with open(path, 'w') as f:
f.write(spec.to_yaml())
spec.to_yaml(f)
def read_spec(self, path):
"""Read the contents of a file and parse them as a spec"""
with open(path) as f:
yaml_text = f.read()
spec = Spec.from_yaml(yaml_text)
spec = Spec.from_yaml(f)
# Specs read from actual installations are always concrete
spec._normal = True
@ -262,18 +262,51 @@ def create_install_directory(self, spec):
def all_specs(self):
if not os.path.isdir(self.root):
return []
spec_files = glob.glob("%s/*/*/*/.spack/spec" % self.root)
pattern = join_path(
self.root, '*', '*', '*', self.metadata_dir, self.spec_file_name)
spec_files = glob.glob(pattern)
return [self.read_spec(s) for s in spec_files]
@memoized
def specs_by_hash(self):
by_hash = {}
for spec in self.all_specs():
by_hash[spec.dag_hash()] = spec
return by_hash
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)
def _write_extensions(self, spec, extensions):
path = self.extension_file_path(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)
# 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)
# Atomic update by moving tmpfile on top of old one.
os.rename(tmp.name, path)
def _extension_map(self, spec):
"""Get a dict<name -> spec> for all extensions currnetly
"""Get a dict<name -> spec> for all extensions currently
installed for this package."""
_check_concrete(spec)
@ -283,16 +316,26 @@ def _extension_map(self, spec):
self._extension_maps[spec] = {}
else:
by_hash = self.specs_by_hash()
exts = {}
with 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))
yaml_file = yaml.load(ext_file)
for entry in yaml_file['extensions']:
name = next(iter(entry))
dag_hash = entry[name]['hash']
prefix = entry[name]['path']
if not dag_hash in by_hash:
raise InvalidExtensionSpecError(
"Spec %s not found in %s." % (dag_hash, prefix))
ext_spec = by_hash[dag_hash]
if not prefix == ext_spec.prefix:
raise InvalidExtensionSpecError(
"Prefix %s does not match spec with hash %s: %s"
% (prefix, dag_hash, ext_spec))
exts[ext_spec.name] = ext_spec
self._extension_maps[spec] = exts
return self._extension_maps[spec]
@ -300,6 +343,7 @@ def _extension_map(self, spec):
def extension_map(self, spec):
"""Defensive copying version of _extension_map() for external API."""
_check_concrete(spec)
return self._extension_map(spec).copy()
@ -319,23 +363,6 @@ def check_activated(self, spec, ext_spec):
raise NoSuchExtensionError(spec, ext_spec)
def _write_extensions(self, spec, extensions):
path = self.extension_file_path(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)
# Write temp file.
with 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, ext_spec):
_check_concrete(spec)
_check_concrete(ext_spec)
@ -362,7 +389,6 @@ def remove_extension(self, spec, ext_spec):
self._write_extensions(spec, exts)
class DirectoryLayoutError(SpackError):
"""Superclass for directory layout errors."""
def __init__(self, message):

View File

@ -603,13 +603,14 @@ def to_node_dict(self):
return { self.name : d }
def to_yaml(self):
def to_yaml(self, stream=None):
node_list = []
for s in self.traverse(order='pre'):
node = s.to_node_dict()
node[s.name]['hash'] = s.dag_hash()
node_list.append(node)
return yaml.dump({ 'spec' : node_list }, default_flow_style=False)
return yaml.dump({ 'spec' : node_list },
stream=stream, default_flow_style=False)
@staticmethod
@ -633,9 +634,12 @@ def from_node_dict(node):
@staticmethod
def from_yaml(string):
def from_yaml(stream):
"""Construct a spec from YAML.
Parameters:
stream -- string or file object to read from.
TODO: currently discards hashes. Include hashes when they
represent more than the DAG does.
@ -644,7 +648,7 @@ def from_yaml(string):
spec = None
try:
yfile = yaml.load(string)
yfile = yaml.load(stream)
except MarkedYAMLError, e:
raise SpackYAMLError("error parsing YMAL spec:", str(e))

View File

@ -152,7 +152,8 @@ def test_handle_unknown_package(self):
# Now check that even without the package files, we know
# enough to read a spec from the spec file.
for spec, path in installed_specs.items():
spec_from_file = self.layout.read_spec(join_path(path, '.spack', 'spec'))
spec_from_file = self.layout.read_spec(
join_path(path, '.spack', 'spec.yaml'))
# To satisfy these conditions, directory layouts need to
# read in concrete specs from their install dirs somehow.