summaryrefslogtreecommitdiff
path: root/lib/spack/spack/spec.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r--lib/spack/spack/spec.py124
1 files changed, 110 insertions, 14 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 7eb9d42cd1..f2625ae596 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1,5 +1,5 @@
##############################################################################
-# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Copyright (c) 2013-2015, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
@@ -93,8 +93,11 @@ expansion when it is the first character in an id typed on the command line.
import sys
import itertools
import hashlib
+import base64
from StringIO import StringIO
from operator import attrgetter
+from external import yaml
+from external.yaml.error import MarkedYAMLError
import llnl.util.tty as tty
from llnl.util.lang import *
@@ -120,6 +123,7 @@ architecture_color = '@m'
enabled_variant_color = '@B'
disabled_variant_color = '@r'
dependency_color = '@.'
+hash_color = '@K'
"""This map determines the coloring of specs when using color output.
We make the fields different colors to enhance readability.
@@ -129,7 +133,8 @@ color_formats = {'%' : compiler_color,
'=' : architecture_color,
'+' : enabled_variant_color,
'~' : disabled_variant_color,
- '^' : dependency_color }
+ '^' : dependency_color,
+ '#' : hash_color }
"""Regex used for splitting by spec field separators."""
_separators = '[%s]' % ''.join(color_formats.keys())
@@ -258,6 +263,18 @@ class CompilerSpec(object):
return (self.name, self.versions)
+ def to_dict(self):
+ d = {'name' : self.name}
+ d.update(self.versions.to_dict())
+ return { 'compiler' : d }
+
+
+ @staticmethod
+ def from_dict(d):
+ d = d['compiler']
+ return CompilerSpec(d['name'], VersionList.from_dict(d))
+
+
def __str__(self):
out = self.name
if self.versions and self.versions != _any_version:
@@ -604,18 +621,91 @@ class Spec(object):
return Prefix(spack.install_layout.path_for_spec(self))
- def dep_hash(self, length=None):
- """Return a hash representing all dependencies of this spec
- (direct and indirect).
+ def dag_hash(self, length=None):
+ """Return a hash of the entire spec DAG, including connectivity."""
+ yaml_text = yaml.dump(
+ self.to_node_dict(), default_flow_style=True, width=sys.maxint)
+ sha = hashlib.sha1(yaml_text)
+ return base64.b32encode(sha.digest()).lower()[:length]
+
+
+ def to_node_dict(self):
+ d = {
+ 'variants' : dict(
+ (name,v.enabled) for name, v in self.variants.items()),
+ 'arch' : self.architecture,
+ 'dependencies' : dict((d, self.dependencies[d].dag_hash())
+ for d in sorted(self.dependencies))
+ }
+ if self.compiler:
+ d.update(self.compiler.to_dict())
+ else:
+ d['compiler'] = None
+ d.update(self.versions.to_dict())
+ return { self.name : d }
+
+
+ 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 },
+ stream=stream, default_flow_style=False)
+
+
+ @staticmethod
+ def from_node_dict(node):
+ name = next(iter(node))
+ node = node[name]
+
+ spec = Spec(name)
+ spec.versions = VersionList.from_dict(node)
+ spec.architecture = node['arch']
+
+ if node['compiler'] is None:
+ spec.compiler = None
+ else:
+ spec.compiler = CompilerSpec.from_dict(node)
+
+ for name, enabled in node['variants'].items():
+ spec.variants[name] = VariantSpec(name, enabled)
+
+ return spec
+
+
+ @staticmethod
+ 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.
- If you want this hash to be consistent, you should
- concretize the spec first so that it is not ambiguous.
"""
- sha = hashlib.sha1()
- sha.update(self.dep_string())
- full_hash = sha.hexdigest()
+ deps = {}
+ spec = None
- return full_hash[:length]
+ try:
+ yfile = yaml.load(stream)
+ except MarkedYAMLError, e:
+ raise SpackYAMLError("error parsing YMAL spec:", str(e))
+
+ for node in yfile['spec']:
+ name = next(iter(node))
+ dep = Spec.from_node_dict(node)
+ if not spec:
+ spec = dep
+ deps[dep.name] = dep
+
+ for node in yfile['spec']:
+ name = next(iter(node))
+ for dep_name in node[name]['dependencies']:
+ deps[name].dependencies[dep_name] = deps[dep_name]
+ return spec
def _concretize_helper(self, presets=None, visited=None):
@@ -1256,7 +1346,7 @@ class Spec(object):
$%@ Compiler & compiler version
$+ Options
$= Architecture
- $# Dependencies' 8-char sha1 prefix
+ $# 7-char prefix of DAG hash
$$ $
Optionally you can provide a width, e.g. $20_ for a 20-wide name.
@@ -1312,8 +1402,7 @@ class Spec(object):
if self.architecture:
write(fmt % (c + str(self.architecture)), c)
elif c == '#':
- if self.dependencies:
- out.write(fmt % ('-' + self.dep_hash(8)))
+ out.write('-' + fmt % (self.dag_hash(7)))
elif c == '$':
if fmt != '':
raise ValueError("Can't use format width with $$.")
@@ -1359,12 +1448,15 @@ class Spec(object):
cover = kwargs.pop('cover', 'nodes')
indent = kwargs.pop('indent', 0)
fmt = kwargs.pop('format', '$_$@$%@$+$=')
+ prefix = kwargs.pop('prefix', None)
check_kwargs(kwargs, self.tree)
out = ""
cur_id = 0
ids = {}
for d, node in self.traverse(order='pre', cover=cover, depth=True):
+ if prefix is not None:
+ out += prefix(node)
out += " " * indent
if depth:
out += "%-4d" % d
@@ -1740,3 +1832,7 @@ class UnsatisfiableDependencySpecError(UnsatisfiableSpecError):
def __init__(self, provided, required):
super(UnsatisfiableDependencySpecError, self).__init__(
provided, required, "dependency")
+
+class SpackYAMLError(spack.error.SpackError):
+ def __init__(self, msg, yaml_error):
+ super(SpackError, self).__init__(msg, str(yaml_error))