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.py183
1 files changed, 166 insertions, 17 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index a58e1ceb2f..aa22978217 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -971,7 +971,159 @@ class SpecBuildInterface(ObjectWrapper):
@key_ordering
class Spec(object):
- def __init__(self, spec_like, *dep_like, **kwargs):
+ @staticmethod
+ def from_literal(spec_dict, normal=True):
+ """Builds a Spec from a dictionary containing the spec literal.
+
+ The dictionary must have a single top level key, representing the root,
+ and as many secondary level keys as needed in the spec.
+
+ The keys can be either a string or a Spec or a tuple containing the
+ Spec and the dependency types.
+
+ Args:
+ spec_dict (dict): the dictionary containing the spec literal
+ normal (bool): if True the same key appearing at different levels
+ of the ``spec_dict`` will map to the same object in memory.
+
+ Examples:
+ A simple spec ``foo`` with no dependencies:
+
+ .. code-block:: python
+
+ {'foo': None}
+
+ A spec ``foo`` with a ``(build, link)`` dependency ``bar``:
+
+ .. code-block:: python
+
+ {'foo':
+ {'bar:build,link': None}}
+
+ A spec with a diamond dependency and various build types:
+
+ .. code-block:: python
+
+ {'dt-diamond': {
+ 'dt-diamond-left:build,link': {
+ 'dt-diamond-bottom:build': None
+ },
+ 'dt-diamond-right:build,link': {
+ 'dt-diamond-bottom:build,link,run': None
+ }
+ }}
+
+ The same spec with a double copy of ``dt-diamond-bottom`` and
+ no diamond structure:
+
+ .. code-block:: python
+
+ {'dt-diamond': {
+ 'dt-diamond-left:build,link': {
+ 'dt-diamond-bottom:build': None
+ },
+ 'dt-diamond-right:build,link': {
+ 'dt-diamond-bottom:build,link,run': None
+ }
+ }, normal=False}
+
+ Constructing a spec using a Spec object as key:
+
+ .. code-block:: python
+
+ mpich = Spec('mpich')
+ libelf = Spec('libelf@1.8.11')
+ expected_normalized = Spec.from_literal({
+ 'mpileaks': {
+ 'callpath': {
+ 'dyninst': {
+ 'libdwarf': {libelf: None},
+ libelf: None
+ },
+ mpich: None
+ },
+ mpich: None
+ },
+ })
+
+ """
+
+ # Maps a literal to a Spec, to be sure we are reusing the same object
+ spec_cache = LazySpecCache()
+
+ def spec_builder(d):
+ # The invariant is that the top level dictionary must have
+ # only one key
+ assert len(d) == 1
+
+ # Construct the top-level spec
+ spec_like, dep_like = next(iter(d.items()))
+
+ # If the requirements was for unique nodes (default)
+ # then re-use keys from the local cache. Otherwise build
+ # a new node every time.
+ if not isinstance(spec_like, Spec):
+ spec = spec_cache[spec_like] if normal else Spec(spec_like)
+ else:
+ spec = spec_like
+
+ if dep_like is None:
+ return spec
+
+ def name_and_dependency_types(s):
+ """Given a key in the dictionary containing the literal,
+ extracts the name of the spec and its dependency types.
+
+ Args:
+ s (str): key in the dictionary containing the literal
+
+ """
+ t = s.split(':')
+
+ if len(t) > 2:
+ msg = 'more than one ":" separator in key "{0}"'
+ raise KeyError(msg.format(s))
+
+ n = t[0]
+ if len(t) == 2:
+ dtypes = tuple(dt.strip() for dt in t[1].split(','))
+ else:
+ dtypes = ()
+
+ return n, dtypes
+
+ def spec_and_dependency_types(s):
+ """Given a non-string key in the literal, extracts the spec
+ and its dependency types.
+
+ Args:
+ s (spec or tuple): either a Spec object or a tuple
+ composed of a Spec object and a string with the
+ dependency types
+
+ """
+ if isinstance(s, Spec):
+ return s, ()
+
+ spec_obj, dtypes = s
+ return spec_obj, tuple(dt.strip() for dt in dtypes.split(','))
+
+ # Recurse on dependencies
+ for s, s_dependencies in dep_like.items():
+
+ if isinstance(s, string_types):
+ dag_node, dependency_types = name_and_dependency_types(s)
+ else:
+ dag_node, dependency_types = spec_and_dependency_types(s)
+
+ dependency_spec = spec_builder({dag_node: s_dependencies})
+ spec._add_dependency(dependency_spec, dependency_types)
+
+ return spec
+
+ return spec_builder(spec_dict)
+
+ def __init__(self, spec_like, **kwargs):
# Copy if spec_like is a Spec.
if isinstance(spec_like, Spec):
self._dup(spec_like)
@@ -1014,22 +1166,6 @@ class Spec(object):
self.external_path = kwargs.get('external_path', None)
self.external_module = kwargs.get('external_module', None)
- # This allows users to construct a spec DAG with literals.
- # Note that given two specs a and b, Spec(a) copies a, but
- # Spec(a, b) will copy a but just add b as a dep.
- deptypes = ()
- for dep in dep_like:
-
- if isinstance(dep, (list, tuple)):
- # Literals can be deptypes -- if there are tuples in the
- # list, they will be used as deptypes for the following Spec.
- deptypes = tuple(dep)
- continue
-
- spec = dep if isinstance(dep, Spec) else Spec(dep)
- self._add_dependency(spec, deptypes)
- deptypes = ()
-
@property
def external(self):
return bool(self.external_path) or bool(self.external_module)
@@ -2944,6 +3080,19 @@ class Spec(object):
return str(self)
+class LazySpecCache(collections.defaultdict):
+ """Cache for Specs that uses a spec_like as key, and computes lazily
+ the corresponding value ``Spec(spec_like``.
+ """
+ def __init__(self):
+ super(LazySpecCache, self).__init__(Spec)
+
+ def __missing__(self, key):
+ value = self.default_factory(key)
+ self[key] = value
+ return value
+
+
#
# These are possible token types in the spec grammar.
#