From 29e833dfefe47fa82d3115d23299921643997fbd Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Sun, 10 May 2015 02:56:50 -0700 Subject: extensions file now in YAML format --- lib/spack/spack/directory_layout.py | 92 ++++++++++++++++++++------------ lib/spack/spack/spec.py | 12 +++-- lib/spack/spack/test/directory_layout.py | 3 +- 3 files changed, 69 insertions(+), 38 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py index 67708c47b5..c2e2ea4deb 100644 --- a/lib/spack/spack/directory_layout.py +++ b/lib/spack/spack/directory_layout.py @@ -173,12 +173,13 @@ class YamlDirectoryLayout(DirectoryLayout): 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 @@ class YamlDirectoryLayout(DirectoryLayout): """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 @@ class YamlDirectoryLayout(DirectoryLayout): 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 spec> for all extensions currnetly + """Get a dict spec> for all extensions currently installed for this package.""" _check_concrete(spec) @@ -283,16 +316,26 @@ class YamlDirectoryLayout(DirectoryLayout): 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 @@ class YamlDirectoryLayout(DirectoryLayout): 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 @@ class YamlDirectoryLayout(DirectoryLayout): 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 @@ class YamlDirectoryLayout(DirectoryLayout): self._write_extensions(spec, exts) - class DirectoryLayoutError(SpackError): """Superclass for directory layout errors.""" def __init__(self, message): diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index e36a1d6f0b..21e36de14d 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -603,13 +603,14 @@ class Spec(object): 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 @@ class Spec(object): @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 @@ class Spec(object): spec = None try: - yfile = yaml.load(string) + yfile = yaml.load(stream) except MarkedYAMLError, e: raise SpackYAMLError("error parsing YMAL spec:", str(e)) diff --git a/lib/spack/spack/test/directory_layout.py b/lib/spack/spack/test/directory_layout.py index 34374628be..7ca84090f2 100644 --- a/lib/spack/spack/test/directory_layout.py +++ b/lib/spack/spack/test/directory_layout.py @@ -152,7 +152,8 @@ class DirectoryLayoutTest(unittest.TestCase): # 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. -- cgit v1.2.3-60-g2f50