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.py915
1 files changed, 596 insertions, 319 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 89a023a750..16b61236a9 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1,4 +1,4 @@
-##############################################################################
+#
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
@@ -21,7 +21,7 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
+#
"""
Spack allows very fine-grained control over how packages are installed and
over how they are built and configured. To make this easy, it has its own
@@ -72,7 +72,9 @@ Here is the EBNF grammar for a spec::
dep_list = { ^ spec }
spec = id [ options ]
options = { @version-list | +variant | -variant | ~variant |
- %compiler | =architecture }
+ %compiler | arch=architecture | [ flag ]=value}
+ flag = { cflags | cxxflags | fcflags | fflags | cppflags |
+ ldflags | ldlibs }
variant = id
architecture = id
compiler = id [ version-list ]
@@ -80,6 +82,9 @@ Here is the EBNF grammar for a spec::
version = id | id: | :id | id:id
id = [A-Za-z0-9_][A-Za-z0-9_.-]*
+Identifiers using the <name>=<value> command, such as architectures and
+compiler flags, require a space before the name.
+
There is one context-sensitive part: ids in versions may contain '.', while
other ids may not.
@@ -91,7 +96,6 @@ specs to avoid ambiguity. Both are provided because ~ can cause shell
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
@@ -117,24 +121,24 @@ from spack.virtual import ProviderIndex
identifier_re = r'\w[\w-]*'
# Convenient names for color formats so that other things can use them
-compiler_color = '@g'
-version_color = '@c'
-architecture_color = '@m'
-enabled_variant_color = '@B'
+compiler_color = '@g'
+version_color = '@c'
+architecture_color = '@m'
+enabled_variant_color = '@B'
disabled_variant_color = '@r'
-dependency_color = '@.'
-hash_color = '@K'
+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.
See spack.color for descriptions of the color codes. """
-color_formats = {'%' : compiler_color,
- '@' : version_color,
- '=' : architecture_color,
- '+' : enabled_variant_color,
- '~' : disabled_variant_color,
- '^' : dependency_color,
- '#' : hash_color }
+color_formats = {'%': compiler_color,
+ '@': version_color,
+ '=': architecture_color,
+ '+': enabled_variant_color,
+ '~': disabled_variant_color,
+ '^': dependency_color,
+ '#': hash_color}
"""Regex used for splitting by spec field separators."""
_separators = '[%s]' % ''.join(color_formats.keys())
@@ -151,7 +155,7 @@ def index_specs(specs):
"""
spec_dict = {}
for spec in specs:
- if not spec.name in spec_dict:
+ if spec.name not in spec_dict:
spec_dict[spec.name] = []
spec_dict[spec.name].append(spec)
return spec_dict
@@ -161,6 +165,7 @@ def colorize_spec(spec):
"""Returns a spec colorized according to the colors specified in
color_formats."""
class insert_color:
+
def __init__(self):
self.last = None
@@ -178,9 +183,11 @@ def colorize_spec(spec):
@key_ordering
class CompilerSpec(object):
+
"""The CompilerSpec field represents the compiler or range of compiler
versions that a package should be built with. CompilerSpecs have a
name and a version list. """
+
def __init__(self, *args):
nargs = len(args)
if nargs == 1:
@@ -198,8 +205,8 @@ class CompilerSpec(object):
else:
raise TypeError(
- "Can only build CompilerSpec from string or CompilerSpec." +
- " Found %s" % type(arg))
+ "Can only build CompilerSpec from string or " +
+ "CompilerSpec. Found %s" % type(arg))
elif nargs == 2:
name, version = args
@@ -211,23 +218,19 @@ class CompilerSpec(object):
raise TypeError(
"__init__ takes 1 or 2 arguments. (%d given)" % nargs)
-
def _add_version(self, version):
self.versions.add(version)
-
def _autospec(self, compiler_spec_like):
if isinstance(compiler_spec_like, CompilerSpec):
return compiler_spec_like
return CompilerSpec(compiler_spec_like)
-
def satisfies(self, other, strict=False):
other = self._autospec(other)
return (self.name == other.name and
self.versions.satisfies(other.versions, strict=strict))
-
def constrain(self, other):
"""Intersect self's versions with other.
@@ -241,44 +244,37 @@ class CompilerSpec(object):
return self.versions.intersect(other.versions)
-
@property
def concrete(self):
"""A CompilerSpec is concrete if its versions are concrete and there
is an available compiler with the right version."""
return self.versions.concrete
-
@property
def version(self):
if not self.concrete:
raise SpecError("Spec is not concrete: " + str(self))
return self.versions[0]
-
def copy(self):
clone = CompilerSpec.__new__(CompilerSpec)
clone.name = self.name
clone.versions = self.versions.copy()
return clone
-
def _cmp_key(self):
return (self.name, self.versions)
-
def to_dict(self):
- d = {'name' : self.name}
+ d = {'name': self.name}
d.update(self.versions.to_dict())
- return { 'compiler' : d }
-
+ 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:
@@ -292,43 +288,44 @@ class CompilerSpec(object):
@key_ordering
class VariantSpec(object):
+
"""Variants are named, build-time options for a package. Names depend
on the particular package being built, and each named variant can
be enabled or disabled.
"""
- def __init__(self, name, enabled):
- self.name = name
- self.enabled = enabled
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
def _cmp_key(self):
- return (self.name, self.enabled)
-
+ return (self.name, self.value)
def copy(self):
- return VariantSpec(self.name, self.enabled)
-
+ return VariantSpec(self.name, self.value)
def __str__(self):
- out = '+' if self.enabled else '~'
- return out + self.name
+ if self.value in [True, False]:
+ out = '+' if self.value else '~'
+ return out + self.name
+ else:
+ return ' ' + self.name + "=" + self.value
class VariantMap(HashableMap):
+
def __init__(self, spec):
super(VariantMap, self).__init__()
self.spec = spec
-
def satisfies(self, other, strict=False):
if strict or self.spec._concrete:
- return all(k in self and self[k].enabled == other[k].enabled
+ return all(k in self and self[k].value == other[k].value
for k in other)
else:
- return all(self[k].enabled == other[k].enabled
+ return all(self[k].value == other[k].value
for k in other if k in self)
-
def constrain(self, other):
"""Add all variants in other that aren't in self to self.
@@ -343,11 +340,11 @@ class VariantMap(HashableMap):
changed = False
for k in other:
if k in self:
- if self[k].enabled != other[k].enabled:
+ if self[k].value != other[k].value:
raise UnsatisfiableVariantSpecError(self[k], other[k])
else:
self[k] = other[k].copy()
- changed =True
+ changed = True
return changed
@property
@@ -355,27 +352,92 @@ class VariantMap(HashableMap):
return self.spec._concrete or all(
v in self for v in self.spec.package_class.variants)
-
def copy(self):
clone = VariantMap(None)
for name, variant in self.items():
clone[name] = variant.copy()
return clone
-
def __str__(self):
sorted_keys = sorted(self.keys())
return ''.join(str(self[key]) for key in sorted_keys)
+_valid_compiler_flags = [
+ 'cflags', 'cxxflags', 'fflags', 'ldflags', 'ldlibs', 'cppflags']
+
+
+class FlagMap(HashableMap):
+
+ def __init__(self, spec):
+ super(FlagMap, self).__init__()
+ self.spec = spec
+
+ def satisfies(self, other, strict=False):
+ if strict or (self.spec and self.spec._concrete):
+ return all(f in self and set(self[f]) <= set(other[f])
+ for f in other)
+ else:
+ return all(set(self[f]) <= set(other[f])
+ for f in other if (other[f] != [] and f in self))
+
+ def constrain(self, other):
+ """Add all flags in other that aren't in self to self.
+
+ Return whether the spec changed.
+ """
+ if other.spec and other.spec._concrete:
+ for k in self:
+ if k not in other:
+ raise UnsatisfiableCompilerFlagSpecError(
+ self[k], '<absent>')
+
+ changed = False
+ for k in other:
+ if k in self and not set(self[k]) <= set(other[k]):
+ raise UnsatisfiableCompilerFlagSpecError(
+ ' '.join(f for f in self[k]),
+ ' '.join(f for f in other[k]))
+ elif k not in self:
+ self[k] = other[k]
+ changed = True
+ return changed
+
+ @staticmethod
+ def valid_compiler_flags():
+ return _valid_compiler_flags
+
+ @property
+ def concrete(self):
+ return all(flag in self for flag in _valid_compiler_flags)
+
+ def copy(self):
+ clone = FlagMap(None)
+ for name, value in self.items():
+ clone[name] = value
+ return clone
+
+ def _cmp_key(self):
+ return ''.join(str(key) + ' '.join(str(v) for v in value)
+ for key, value in sorted(self.items()))
+
+ def __str__(self):
+ sorted_keys = filter(
+ lambda flag: self[flag] != [], sorted(self.keys()))
+ cond_symbol = ' ' if len(sorted_keys) > 0 else ''
+ return cond_symbol + ' '.join(str(key) + '=\"' + ' '.join(str(f)
+ for f in self[key]) + '\"'
+ for key in sorted_keys)
+
+
class DependencyMap(HashableMap):
+
"""Each spec has a DependencyMap containing specs for its dependencies.
The DependencyMap is keyed by name. """
@property
def concrete(self):
return all(d.concrete for d in self.values())
-
def __str__(self):
return ''.join(
["^" + str(self[name]) for name in sorted(self.keys())])
@@ -383,6 +445,7 @@ class DependencyMap(HashableMap):
@key_ordering
class Spec(object):
+
def __init__(self, spec_like, *dep_like, **kwargs):
# Copy if spec_like is a Spec.
if isinstance(spec_like, Spec):
@@ -409,20 +472,23 @@ class Spec(object):
self.versions = other.versions
self.architecture = other.architecture
self.compiler = other.compiler
+ self.compiler_flags = other.compiler_flags
+ self.compiler_flags.spec = self
self.dependencies = other.dependencies
self.variants = other.variants
self.variants.spec = self
self.namespace = other.namespace
+ self._hash = other._hash
# Specs are by default not assumed to be normal, but in some
# cases we've read them from a file want to assume normal.
# This allows us to manipulate specs that Spack doesn't have
# package.py files for.
- self._normal = kwargs.get('normal', False)
+ self._normal = kwargs.get('normal', False)
self._concrete = kwargs.get('concrete', False)
# Allow a spec to be constructed with an external path.
- self.external = kwargs.get('external', None)
+ self.external = kwargs.get('external', None)
# This allows users to construct a spec DAG with literals.
# Note that given two specs a and b, Spec(a) copies a, but
@@ -431,7 +497,6 @@ class Spec(object):
spec = dep if isinstance(dep, Spec) else Spec(dep)
self._add_dependency(spec)
-
#
# Private routines here are called by the parser when building a spec.
#
@@ -439,32 +504,49 @@ class Spec(object):
"""Called by the parser to add an allowable version."""
self.versions.add(version)
-
- def _add_variant(self, name, enabled):
+ def _add_variant(self, name, value):
"""Called by the parser to add a variant."""
- if name in self.variants: raise DuplicateVariantError(
+ if name in self.variants:
+ raise DuplicateVariantError(
"Cannot specify variant '%s' twice" % name)
- self.variants[name] = VariantSpec(name, enabled)
-
+ if isinstance(value, basestring) and value.upper() == 'TRUE':
+ value = True
+ elif isinstance(value, basestring) and value.upper() == 'FALSE':
+ value = False
+ self.variants[name] = VariantSpec(name, value)
+
+ def _add_flag(self, name, value):
+ """Called by the parser to add a known flag.
+ Known flags currently include "arch"
+ """
+ valid_flags = FlagMap.valid_compiler_flags()
+ if name == 'arch':
+ self._set_architecture(value)
+ elif name in valid_flags:
+ assert(self.compiler_flags is not None)
+ self.compiler_flags[name] = value.split()
+ else:
+ self._add_variant(name, value)
def _set_compiler(self, compiler):
"""Called by the parser to set the compiler."""
- if self.compiler: raise DuplicateCompilerSpecError(
+ if self.compiler:
+ raise DuplicateCompilerSpecError(
"Spec for '%s' cannot have two compilers." % self.name)
self.compiler = compiler
-
def _set_architecture(self, architecture):
"""Called by the parser to set the architecture."""
- if self.architecture: raise DuplicateArchitectureError(
+ if self.architecture:
+ raise DuplicateArchitectureError(
"Spec for '%s' cannot have two architectures." % self.name)
self.architecture = architecture
-
def _add_dependency(self, spec):
"""Called by the parser to add another spec as a dependency."""
if spec.name in self.dependencies:
- raise DuplicateDependencyError("Cannot depend on '%s' twice" % spec)
+ raise DuplicateDependencyError(
+ "Cannot depend on '%s' twice" % spec)
self.dependencies[spec.name] = spec
spec.dependents[self.name] = self
@@ -473,8 +555,8 @@ class Spec(object):
#
@property
def fullname(self):
- return '%s.%s' % (self.namespace, self.name) if self.namespace else self.name
-
+ return (('%s.%s' % (self.namespace, self.name)) if self.namespace else
+ (self.name if self.name else ''))
@property
def root(self):
@@ -494,12 +576,10 @@ class Spec(object):
assert(all(first_root is d.root for d in depiter))
return first_root
-
@property
def package(self):
return spack.repo.get(self)
-
@property
def package_class(self):
"""Internal package call gets only the class object for a package.
@@ -507,7 +587,6 @@ class Spec(object):
"""
return spack.repo.get_pkg_class(self.name)
-
@property
def virtual(self):
"""Right now, a spec is virtual if no package exists with its name.
@@ -519,12 +598,10 @@ class Spec(object):
"""
return Spec.is_virtual(self.name)
-
@staticmethod
def is_virtual(name):
"""Test if a name is virtual without requiring a Spec."""
- return not spack.repo.exists(name)
-
+ return (name is not None) and (not spack.repo.exists(name))
@property
def concrete(self):
@@ -535,17 +612,17 @@ class Spec(object):
if self._concrete:
return True
- self._concrete = bool(not self.virtual
- and self.namespace is not None
- and self.versions.concrete
- and self.variants.concrete
- and self.architecture
- and self.compiler and self.compiler.concrete
- and self.dependencies.concrete)
-
+ self._concrete = bool(not self.virtual and
+ self.namespace is not None and
+ self.versions.concrete and
+ self.variants.concrete and
+ self.architecture and
+ self.compiler and
+ self.compiler.concrete and
+ self.compiler_flags.concrete and
+ self.dependencies.concrete)
return self._concrete
-
def traverse(self, visited=None, d=0, **kwargs):
"""Generic traversal of the DAG represented by this spec.
This will yield each node in the spec. Options:
@@ -589,14 +666,14 @@ class Spec(object):
"""
# get initial values for kwargs
- depth = kwargs.get('depth', False)
- key_fun = kwargs.get('key', id)
+ depth = kwargs.get('depth', False)
+ key_fun = kwargs.get('key', id)
if isinstance(key_fun, basestring):
key_fun = attrgetter(key_fun)
yield_root = kwargs.get('root', True)
- cover = kwargs.get('cover', 'nodes')
- direction = kwargs.get('direction', 'children')
- order = kwargs.get('order', 'pre')
+ cover = kwargs.get('cover', 'nodes')
+ direction = kwargs.get('direction', 'children')
+ order = kwargs.get('order', 'pre')
# Make sure kwargs have legal values; raise ValueError if not.
def validate(name, val, allowed_values):
@@ -633,50 +710,53 @@ class Spec(object):
visited.add(key)
for name in sorted(successors):
child = successors[name]
- for elt in child.traverse(visited, d+1, **kwargs):
+ for elt in child.traverse(visited, d + 1, **kwargs):
yield elt
# Postorder traversal yields after successors
if yield_me and order == 'post':
yield result
-
@property
def short_spec(self):
"""Returns a version of the spec with the dependencies hashed
instead of completely enumerated."""
return self.format('$_$@$%@$+$=$#')
-
@property
def cshort_spec(self):
"""Returns a version of the spec with the dependencies hashed
instead of completely enumerated."""
return self.format('$_$@$%@$+$=$#', color=True)
-
@property
def prefix(self):
return Prefix(spack.install_layout.path_for_spec(self))
-
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]
-
+ if self._hash:
+ return self._hash[:length]
+ else:
+ yaml_text = yaml.dump(
+ self.to_node_dict(), default_flow_style=True, width=sys.maxint)
+ sha = hashlib.sha1(yaml_text)
+ b32_hash = base64.b32encode(sha.digest()).lower()[:length]
+ if self.concrete:
+ self._hash = b32_hash
+ return b32_hash
def to_node_dict(self):
+ params = dict((name, v.value) for name, v in self.variants.items())
+ params.update(dict((name, value)
+ for name, value in self.compiler_flags.items()))
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))
+ 'parameters': params,
+ 'arch': self.architecture,
+ 'dependencies': dict((d, self.dependencies[d].dag_hash())
+ for d in sorted(self.dependencies)),
}
# Older concrete specs do not have a namespace. Omit for
@@ -689,8 +769,8 @@ class Spec(object):
else:
d['compiler'] = None
d.update(self.versions.to_dict())
- return { self.name : d }
+ return {self.name: d}
def to_yaml(self, stream=None):
node_list = []
@@ -698,10 +778,9 @@ class Spec(object):
node = s.to_node_dict()
node[s.name]['hash'] = s.dag_hash()
node_list.append(node)
- return yaml.dump({ 'spec' : node_list },
+ return yaml.dump({'spec': node_list},
stream=stream, default_flow_style=False)
-
@staticmethod
def from_node_dict(node):
name = next(iter(node))
@@ -712,17 +791,31 @@ class Spec(object):
spec.versions = VersionList.from_dict(node)
spec.architecture = node['arch']
+ if 'hash' in node:
+ spec._hash = node['hash']
+
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)
+ if 'parameters' in node:
+ for name, value in node['parameters'].items():
+ if name in _valid_compiler_flags:
+ spec.compiler_flags[name] = value
+ else:
+ spec.variants[name] = VariantSpec(name, value)
+ elif 'variants' in node:
+ for name, value in node['variants'].items():
+ spec.variants[name] = VariantSpec(name, value)
+ for name in FlagMap.valid_compiler_flags():
+ spec.compiler_flags[name] = []
+ else:
+ raise SpackRecordError(
+ "Did not find a valid format for variants in YAML file")
return spec
-
@staticmethod
def from_yaml(stream):
"""Construct a spec from YAML.
@@ -755,15 +848,16 @@ class Spec(object):
deps[name].dependencies[dep_name] = deps[dep_name]
return spec
-
def _concretize_helper(self, presets=None, visited=None):
"""Recursive helper function for concretize().
This concretizes everything bottom-up. As things are
concretized, they're added to the presets, and ancestors
will prefer the settings of their children.
"""
- if presets is None: presets = {}
- if visited is None: visited = set()
+ if presets is None:
+ presets = {}
+ if visited is None:
+ visited = set()
if self.name in visited:
return False
@@ -772,7 +866,8 @@ class Spec(object):
# Concretize deps first -- this is a bottom-up process.
for name in sorted(self.dependencies.keys()):
- changed |= self.dependencies[name]._concretize_helper(presets, visited)
+ changed |= self.dependencies[
+ name]._concretize_helper(presets, visited)
if self.name in presets:
changed |= self.constrain(presets[self.name])
@@ -781,17 +876,19 @@ class Spec(object):
# Concretize virtual dependencies last. Because they're added
# to presets below, their constraints will all be merged, but we'll
# still need to select a concrete package later.
- changed |= any(
- (spack.concretizer.concretize_architecture(self),
- spack.concretizer.concretize_compiler(self),
- spack.concretizer.concretize_version(self),
- spack.concretizer.concretize_variants(self)))
+ if not self.virtual:
+ changed |= any(
+ (spack.concretizer.concretize_architecture(self),
+ spack.concretizer.concretize_compiler(self),
+ spack.concretizer.concretize_compiler_flags(
+ self), # has to be concretized after compiler
+ spack.concretizer.concretize_version(self),
+ spack.concretizer.concretize_variants(self)))
presets[self.name] = self
visited.add(self.name)
return changed
-
def _replace_with(self, concrete):
"""Replace this virtual spec with a concrete spec."""
assert(self.virtual)
@@ -803,7 +900,6 @@ class Spec(object):
if concrete.name not in dependent.dependencies:
dependent._add_dependency(concrete)
-
def _replace_node(self, replacement):
"""Replace this spec with another.
@@ -821,7 +917,6 @@ class Spec(object):
del dep.dependents[self.name]
del self.dependencies[dep.name]
-
def _expand_virtual_packages(self):
"""Find virtual packages in this spec, replace them with providers,
and normalize again to include the provider's (potentially virtual)
@@ -854,12 +949,14 @@ class Spec(object):
# TODO: may break if in-place on self but
# shouldn't happen if root is traversed first.
spec._replace_with(replacement)
- done=False
+ done = False
break
if not replacement:
- # Get a list of possible replacements in order of preference.
- candidates = spack.concretizer.choose_virtual_or_external(spec)
+ # Get a list of possible replacements in order of
+ # preference.
+ candidates = spack.concretizer.choose_virtual_or_external(
+ spec)
# Try the replacements in order, skipping any that cause
# satisfiability problems.
@@ -872,11 +969,12 @@ class Spec(object):
copy[spec.name]._dup(replacement.copy(deps=False))
try:
- # If there are duplicate providers or duplicate provider
- # deps, consolidate them and merge constraints.
+ # If there are duplicate providers or duplicate
+ # provider deps, consolidate them and merge
+ # constraints.
copy.normalize(force=True)
break
- except SpecError as e:
+ except SpecError:
# On error, we'll try the next replacement.
continue
@@ -891,15 +989,15 @@ class Spec(object):
def feq(cfield, sfield):
return (not cfield) or (cfield == sfield)
- if replacement is spec or (feq(replacement.name, spec.name) and
- feq(replacement.versions, spec.versions) and
- feq(replacement.compiler, spec.compiler) and
- feq(replacement.architecture, spec.architecture) and
- feq(replacement.dependencies, spec.dependencies) and
- feq(replacement.variants, spec.variants) and
- feq(replacement.external, spec.external)):
+ if replacement is spec or (
+ feq(replacement.name, spec.name) and
+ feq(replacement.versions, spec.versions) and
+ feq(replacement.compiler, spec.compiler) and
+ feq(replacement.architecture, spec.architecture) and
+ feq(replacement.dependencies, spec.dependencies) and
+ feq(replacement.variants, spec.variants) and
+ feq(replacement.external, spec.external)):
continue
-
# Refine this spec to the candidate. This uses
# replace_with AND dup so that it can work in
# place. TODO: make this more efficient.
@@ -910,12 +1008,11 @@ class Spec(object):
changed = True
self_index.update(spec)
- done=False
+ done = False
break
return changed
-
def concretize(self):
"""A spec is concrete if it describes one build of a package uniquely.
This will ensure that this spec is concrete.
@@ -924,10 +1021,12 @@ class Spec(object):
of a package, this will add constraints to make it concrete.
Some rigorous validation and checks are also performed on the spec.
- Concretizing ensures that it is self-consistent and that it's consistent
- with requirements of its pacakges. See flatten() and normalize() for
- more details on this.
+ Concretizing ensures that it is self-consistent and that it's
+ consistent with requirements of its pacakges. See flatten() and
+ normalize() for more details on this.
"""
+ if not self.name:
+ raise SpecError("Attempting to concretize anonymous spec")
if self._concrete:
return
@@ -940,7 +1039,7 @@ class Spec(object):
self._expand_virtual_packages(),
self._concretize_helper())
changed = any(changes)
- force=True
+ force = True
for s in self.traverse():
# After concretizing, assign namespaces to anything left.
@@ -957,7 +1056,6 @@ class Spec(object):
# Mark everything in the spec as concrete, as well.
self._mark_concrete()
-
def _mark_concrete(self):
"""Mark this spec and its dependencies as concrete.
@@ -968,7 +1066,6 @@ class Spec(object):
s._normal = True
s._concrete = True
-
def concretized(self):
"""This is a non-destructive version of concretize(). First clones,
then returns a concrete version of this package without modifying
@@ -977,7 +1074,6 @@ class Spec(object):
clone.concretize()
return clone
-
def flat_dependencies(self, **kwargs):
"""Return a DependencyMap containing all of this spec's
dependencies with their constraints merged.
@@ -1016,7 +1112,6 @@ class Spec(object):
# parser doesn't allow it. Spack must be broken!
raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message)
-
def index(self):
"""Return DependencyMap that points to all the dependencies in this
spec."""
@@ -1025,7 +1120,6 @@ class Spec(object):
dm[spec.name] = spec
return dm
-
def flatten(self):
"""Pull all dependencies up to the root (this spec).
Merge constraints for dependencies with the same name, and if they
@@ -1033,7 +1127,6 @@ class Spec(object):
for dep in self.flat_dependencies(copy=False):
self._add_dependency(dep)
-
def _evaluate_dependency_conditions(self, name):
"""Evaluate all the conditions on a dependency with this name.
@@ -1054,12 +1147,11 @@ class Spec(object):
try:
dep.constrain(dep_spec)
except UnsatisfiableSpecError, e:
- e.message = ("Conflicting conditional dependencies on package "
- "%s for spec %s" % (self.name, self))
+ e.message = ("Conflicting conditional dependencies on"
+ "package %s for spec %s" % (self.name, self))
raise e
return dep
-
def _find_provider(self, vdep, provider_index):
"""Find provider for a virtual spec in the provider index.
Raise an exception if there is a conflicting virtual
@@ -1071,6 +1163,12 @@ class Spec(object):
# If there is a provider for the vpkg, then use that instead of
# the virtual package.
if providers:
+ # Remove duplicate providers that can concretize to the same
+ # result.
+ for provider in providers:
+ for spec in providers:
+ if spec is not provider and provider.satisfies(spec):
+ providers.remove(spec)
# Can't have multiple providers for the same thing in one spec.
if len(providers) > 1:
raise MultipleProviderError(vdep, providers)
@@ -1085,11 +1183,10 @@ class Spec(object):
elif required:
raise UnsatisfiableProviderSpecError(required[0], vdep)
-
def _merge_dependency(self, dep, visited, spec_deps, provider_index):
"""Merge the dependency into this spec.
- This is the core of the normalize() method. There are a few basic steps:
+ This is the core of normalize(). There are some basic steps:
* If dep is virtual, evaluate whether it corresponds to an
existing concrete dependency, and merge if so.
@@ -1123,19 +1220,17 @@ class Spec(object):
if required:
raise UnsatisfiableProviderSpecError(required[0], dep)
provider_index.update(dep)
-
# If the spec isn't already in the set of dependencies, clone
# it from the package description.
if dep.name not in spec_deps:
spec_deps[dep.name] = dep.copy()
changed = True
-
# Constrain package information with spec info
try:
changed |= spec_deps[dep.name].constrain(dep)
except UnsatisfiableSpecError, e:
- e.message = "Invalid spec: '%s'. "
+ e.message = "Invalid spec: '%s'. "
e.message += "Package %s requires %s %s, but spec asked for %s"
e.message %= (spec_deps[dep.name], dep.name, e.constraint_type,
e.required, e.provided)
@@ -1146,10 +1241,10 @@ class Spec(object):
if dep.name not in self.dependencies:
self._add_dependency(dependency)
- changed |= dependency._normalize_helper(visited, spec_deps, provider_index)
+ changed |= dependency._normalize_helper(
+ visited, spec_deps, provider_index)
return changed
-
def _normalize_helper(self, visited, spec_deps, provider_index):
"""Recursive helper function for _normalize."""
if self.name in visited:
@@ -1172,7 +1267,6 @@ class Spec(object):
for dep_name in pkg.dependencies:
# Do we depend on dep_name? If so pkg_dep is not None.
pkg_dep = self._evaluate_dependency_conditions(dep_name)
-
# If pkg_dep is a dependency, merge it.
if pkg_dep:
changed |= self._merge_dependency(
@@ -1181,24 +1275,26 @@ class Spec(object):
return any_change
-
def normalize(self, force=False):
"""When specs are parsed, any dependencies specified are hanging off
the root, and ONLY the ones that were explicitly provided are there.
Normalization turns a partial flat spec into a DAG, where:
1. Known dependencies of the root package are in the DAG.
- 2. Each node's dependencies dict only contains its known direct deps.
+ 2. Each node's dependencies dict only contains its known direct
+ deps.
3. There is only ONE unique spec for each package in the DAG.
* This includes virtual packages. If there a non-virtual
package that provides a virtual package that is in the spec,
then we replace the virtual package with the non-virtual one.
- TODO: normalize should probably implement some form of cycle detection,
- to ensure that the spec is actually a DAG.
-
+ TODO: normalize should probably implement some form of cycle
+ detection, to ensure that the spec is actually a DAG.
"""
+ if not self.name:
+ raise SpecError("Attempting to normalize anonymous spec")
+
if self._normal and not force:
return False
@@ -1228,14 +1324,14 @@ class Spec(object):
self._normal = True
return any_change
-
def normalized(self):
- """Return a normalized copy of this spec without modifying this spec."""
+ """
+ Return a normalized copy of this spec without modifying this spec.
+ """
clone = self.copy()
clone.normalize()
return clone
-
def validate_names(self):
"""This checks that names of packages and compilers in this spec are real.
If they're not, it will raise either UnknownPackageError or
@@ -1243,7 +1339,7 @@ class Spec(object):
"""
for spec in self.traverse():
# Don't get a package for a virtual name.
- if not spec.virtual:
+ if (not spec.virtual) and spec.name:
spack.repo.get(spec.fullname)
# validate compiler in addition to the package name.
@@ -1256,7 +1352,6 @@ class Spec(object):
if vname not in spec.package_class.variants:
raise UnknownVariantError(spec.name, vname)
-
def constrain(self, other, deps=True):
"""Merge the constraints of other with self.
@@ -1264,19 +1359,22 @@ class Spec(object):
"""
other = self._autospec(other)
- if not self.name == other.name:
+ if not (self.name == other.name or
+ (not self.name) or
+ (not other.name)):
raise UnsatisfiableSpecNameError(self.name, other.name)
- if other.namespace is not None:
- if self.namespace is not None and other.namespace != self.namespace:
- raise UnsatisfiableSpecNameError(self.fullname, other.fullname)
+ if (other.namespace is not None and
+ self.namespace is not None and
+ other.namespace != self.namespace):
+ raise UnsatisfiableSpecNameError(self.fullname, other.fullname)
if not self.versions.overlaps(other.versions):
raise UnsatisfiableVersionSpecError(self.versions, other.versions)
for v in other.variants:
if (v in self.variants and
- self.variants[v].enabled != other.variants[v].enabled):
+ self.variants[v].value != other.variants[v].value):
raise UnsatisfiableVariantSpecError(self.variants[v],
other.variants[v])
@@ -1295,6 +1393,8 @@ class Spec(object):
changed |= self.versions.intersect(other.versions)
changed |= self.variants.constrain(other.variants)
+ changed |= self.compiler_flags.constrain(other.compiler_flags)
+
old = self.architecture
self.architecture = self.architecture or other.architecture
changed |= (self.architecture != old)
@@ -1304,7 +1404,6 @@ class Spec(object):
return changed
-
def _constrain_dependencies(self, other):
"""Apply constraints of other spec's dependencies to this spec."""
other = self._autospec(other)
@@ -1323,7 +1422,6 @@ class Spec(object):
for name in self.common_dependencies(other):
changed |= self[name].constrain(other[name], deps=False)
-
# Update with additional constraints from other spec
for name in other.dep_difference(self):
self._add_dependency(other[name].copy())
@@ -1331,7 +1429,6 @@ class Spec(object):
return changed
-
def common_dependencies(self, other):
"""Return names of dependencies that self an other have in common."""
common = set(
@@ -1340,14 +1437,12 @@ class Spec(object):
s.name for s in other.traverse(root=False))
return common
-
def constrained(self, other, deps=True):
"""Return a constrained copy without modifying this spec."""
clone = self.copy(deps=deps)
clone.constrain(other, deps)
return clone
-
def dep_difference(self, other):
"""Returns dependencies in self that are not in other."""
mine = set(s.name for s in self.traverse(root=False))
@@ -1355,21 +1450,24 @@ class Spec(object):
s.name for s in other.traverse(root=False))
return mine
-
def _autospec(self, spec_like):
- """Used to convert arguments to specs. If spec_like is a spec, returns it.
- If it's a string, tries to parse a string. If that fails, tries to parse
- a local spec from it (i.e. name is assumed to be self's name).
+ """
+ Used to convert arguments to specs. If spec_like is a spec, returns
+ it. If it's a string, tries to parse a string. If that fails, tries
+ to parse a local spec from it (i.e. name is assumed to be self's name).
"""
if isinstance(spec_like, spack.spec.Spec):
return spec_like
try:
- return spack.spec.Spec(spec_like)
+ spec = spack.spec.Spec(spec_like)
+ if not spec.name:
+ raise SpecError(
+ "anonymous package -- this will always be handled")
+ return spec
except SpecError:
return parse_anonymous_spec(spec_like, self.name)
-
def satisfies(self, other, deps=True, strict=False):
"""Determine if this spec satisfies all constraints of another.
@@ -1396,14 +1494,14 @@ class Spec(object):
return False
# Otherwise, first thing we care about is whether the name matches
- if self.name != other.name:
+ if self.name != other.name and self.name and other.name:
return False
# namespaces either match, or other doesn't require one.
- if other.namespace is not None:
- if self.namespace is not None and self.namespace != other.namespace:
- return False
-
+ if (other.namespace is not None and
+ self.namespace is not None and
+ self.namespace != other.namespace):
+ return False
if self.versions and other.versions:
if not self.versions.satisfies(other.versions, strict=strict):
return False
@@ -1417,7 +1515,10 @@ class Spec(object):
elif strict and (other.compiler and not self.compiler):
return False
- if not self.variants.satisfies(other.variants, strict=strict):
+ var_strict = strict
+ if (not self.name) or (not other.name):
+ var_strict = True
+ if not self.variants.satisfies(other.variants, strict=var_strict):
return False
# Architecture satisfaction is currently just string equality.
@@ -1428,15 +1529,24 @@ class Spec(object):
elif strict and (other.architecture and not self.architecture):
return False
+ if not self.compiler_flags.satisfies(
+ other.compiler_flags,
+ strict=strict):
+ return False
+
# If we need to descend into dependencies, do it, otherwise we're done.
if deps:
- return self.satisfies_dependencies(other, strict=strict)
+ deps_strict = strict
+ if not (self.name and other.name):
+ deps_strict = True
+ return self.satisfies_dependencies(other, strict=deps_strict)
else:
return True
-
def satisfies_dependencies(self, other, strict=False):
- """This checks constraints on common dependencies against each other."""
+ """
+ This checks constraints on common dependencies against each other.
+ """
other = self._autospec(other)
if strict:
@@ -1447,7 +1557,8 @@ class Spec(object):
return False
elif not self.dependencies or not other.dependencies:
- # if either spec doesn't restrict dependencies then both are compatible.
+ # if either spec doesn't restrict dependencies then both are
+ # compatible.
return True
# Handle first-order constraints directly
@@ -1463,11 +1574,12 @@ class Spec(object):
if not self_index.satisfies(other_index):
return False
- # These two loops handle cases where there is an overly restrictive vpkg
- # in one spec for a provider in the other (e.g., mpi@3: is not compatible
- # with mpich2)
+ # These two loops handle cases where there is an overly restrictive
+ # vpkg in one spec for a provider in the other (e.g., mpi@3: is not
+ # compatible with mpich2)
for spec in self.virtual_dependencies():
- if spec.name in other_index and not other_index.providers_for(spec):
+ if (spec.name in other_index and
+ not other_index.providers_for(spec)):
return False
for spec in other.virtual_dependencies():
@@ -1476,12 +1588,10 @@ class Spec(object):
return True
-
def virtual_dependencies(self):
"""Return list of any virtual deps in this spec."""
return [spec for spec in self.traverse() if spec.virtual]
-
def _dup(self, other, **kwargs):
"""Copy the spec other into self. This is an overwriting
copy. It does not copy any dependents (parents), but by default
@@ -1497,10 +1607,14 @@ class Spec(object):
# We don't count dependencies as changes here
changed = True
if hasattr(self, 'name'):
- changed = (self.name != other.name and self.versions != other.versions and
- self.architecture != other.architecture and self.compiler != other.compiler and
- self.variants != other.variants and self._normal != other._normal and
- self.concrete != other.concrete and self.external != other.external)
+ changed = (self.name != other.name and
+ self.versions != other.versions and
+ self.architecture != other.architecture and
+ self.compiler != other.compiler and
+ self.variants != other.variants and
+ self._normal != other._normal and
+ self.concrete != other.concrete and
+ self.external != other.external)
# Local node attributes get copied first.
self.name = other.name
@@ -1510,10 +1624,12 @@ class Spec(object):
if kwargs.get('cleardeps', True):
self.dependents = DependencyMap()
self.dependencies = DependencyMap()
+ self.compiler_flags = other.compiler_flags.copy()
self.variants = other.variants.copy()
self.variants.spec = self
self.external = other.external
self.namespace = other.namespace
+ self._hash = other._hash
# If we copy dependencies, preserve DAG structure in the new spec
if kwargs.get('deps', True):
@@ -1534,7 +1650,6 @@ class Spec(object):
self.external = other.external
return changed
-
def copy(self, **kwargs):
"""Return a copy of this spec.
By default, returns a deep copy. Supply dependencies=False
@@ -1544,14 +1659,12 @@ class Spec(object):
clone._dup(self, **kwargs)
return clone
-
@property
def version(self):
if not self.versions.concrete:
raise SpecError("Spec version is not concrete: " + str(self))
return self.versions[0]
-
def __getitem__(self, name):
"""Get a dependency from the spec by its name."""
for spec in self.traverse():
@@ -1570,7 +1683,6 @@ class Spec(object):
raise KeyError("No spec with name %s in %s" % (name, self))
-
def __contains__(self, spec):
"""True if this spec satisfis the provided spec, or if any dependency
does. If the spec has no name, then we parse this one first.
@@ -1582,13 +1694,11 @@ class Spec(object):
return False
-
def sorted_deps(self):
"""Return a list of all dependencies sorted by name."""
deps = self.flat_dependencies()
return tuple(deps[name] for name in sorted(deps))
-
def _eq_dag(self, other, vs, vo):
"""Recursive helper for eq_dag and ne_dag. Does the actual DAG
traversal."""
@@ -1601,18 +1711,22 @@ class Spec(object):
if len(self.dependencies) != len(other.dependencies):
return False
- ssorted = [self.dependencies[name] for name in sorted(self.dependencies)]
- osorted = [other.dependencies[name] for name in sorted(other.dependencies)]
+ ssorted = [self.dependencies[name]
+ for name in sorted(self.dependencies)]
+ osorted = [other.dependencies[name]
+ for name in sorted(other.dependencies)]
for s, o in zip(ssorted, osorted):
visited_s = id(s) in vs
visited_o = id(o) in vo
# Check for duplicate or non-equal dependencies
- if visited_s != visited_o: return False
+ if visited_s != visited_o:
+ return False
# Skip visited nodes
- if visited_s or visited_o: continue
+ if visited_s or visited_o:
+ continue
# Recursive check for equality
if not s._eq_dag(o, vs, vo):
@@ -1620,17 +1734,14 @@ class Spec(object):
return True
-
def eq_dag(self, other):
"""True if the full dependency DAGs of specs are equal"""
return self._eq_dag(other, set(), set())
-
def ne_dag(self, other):
"""True if the full dependency DAGs of specs are not equal"""
return not self.eq_dag(other)
-
def _cmp_node(self):
"""Comparison key for just *this node* and not its deps."""
return (self.name,
@@ -1638,19 +1749,17 @@ class Spec(object):
self.versions,
self.variants,
self.architecture,
- self.compiler)
-
+ self.compiler,
+ self.compiler_flags)
def eq_node(self, other):
"""Equality with another spec, not including dependencies."""
return self._cmp_node() == other._cmp_node()
-
def ne_node(self, other):
"""Inequality with another spec, not including dependencies."""
return self._cmp_node() != other._cmp_node()
-
def _cmp_key(self):
"""This returns a key for the spec *including* DAG structure.
@@ -1662,52 +1771,56 @@ class Spec(object):
tuple(hash(self.dependencies[name])
for name in sorted(self.dependencies)),)
-
def colorized(self):
return colorize_spec(self)
-
- def format(self, format_string='$_$@$%@$+$=', **kwargs):
- """Prints out particular pieces of a spec, depending on what is
- in the format string. The format strings you can provide are::
-
- $_ Package name
- $. Full package name (with namespace)
- $@ Version with '@' prefix
- $% Compiler with '%' prefix
- $%@ Compiler with '%' prefix & compiler version with '@' prefix
- $+ Options
- $= Architecture with '=' prefix
- $# 7-char prefix of DAG hash with '-' prefix
- $$ $
-
- You can also use full-string versions, which leave off the prefixes:
-
- ${PACKAGE} Package name
- ${VERSION} Version
- ${COMPILER} Full compiler string
- ${COMPILERNAME} Compiler name
- ${COMPILERVER} Compiler version
- ${OPTIONS} Options
- ${ARCHITECTURE} Architecture
- ${SHA1} Dependencies 8-char sha1 prefix
-
- ${SPACK_ROOT} The spack root directory
- ${SPACK_INSTALL} The default spack install directory, ${SPACK_PREFIX}/opt
-
- Optionally you can provide a width, e.g. $20_ for a 20-wide name.
- Like printf, you can provide '-' for left justification, e.g.
- $-20_ for a left-justified name.
-
- Anything else is copied verbatim into the output stream.
-
- *Example:* ``$_$@$+`` translates to the name, version, and options
- of the package, but no dependencies, arch, or compiler.
-
- TODO: allow, e.g., $6# to customize short hash length
- TODO: allow, e.g., $## for full hash.
- """
- color = kwargs.get('color', False)
+ def format(self, format_string='$_$@$%@+$+$=', **kwargs):
+ """
+ Prints out particular pieces of a spec, depending on what is
+ in the format string. The format strings you can provide are::
+
+ $_ Package name
+ $. Full package name (with namespace)
+ $@ Version with '@' prefix
+ $% Compiler with '%' prefix
+ $%@ Compiler with '%' prefix & compiler version with '@' prefix
+ $%+ Compiler with '%' prefix & compiler flags prefixed by name
+ $%@+ Compiler, compiler version, and compiler flags with same
+ prefixes as above
+ $+ Options
+ $= Architecture prefixed by 'arch='
+ $# 7-char prefix of DAG hash with '-' prefix
+ $$ $
+
+ You can also use full-string versions, which elide the prefixes:
+
+ ${PACKAGE} Package name
+ ${VERSION} Version
+ ${COMPILER} Full compiler string
+ ${COMPILERNAME} Compiler name
+ ${COMPILERVER} Compiler version
+ ${COMPILERFLAGS} Compiler flags
+ ${OPTIONS} Options
+ ${ARCHITECTURE} Architecture
+ ${SHA1} Dependencies 8-char sha1 prefix
+
+ ${SPACK_ROOT} The spack root directory
+ ${SPACK_INSTALL} The default spack install directory,
+ ${SPACK_PREFIX}/opt
+
+ Optionally you can provide a width, e.g. $20_ for a 20-wide name.
+ Like printf, you can provide '-' for left justification, e.g.
+ $-20_ for a left-justified name.
+
+ Anything else is copied verbatim into the output stream.
+
+ *Example:* ``$_$@$+`` translates to the name, version, and options
+ of the package, but no dependencies, arch, or compiler.
+
+ TODO: allow, e.g., $6# to customize short hash length
+ TODO: allow, e.g., $## for full hash.
+ """
+ color = kwargs.get('color', False)
length = len(format_string)
out = StringIO()
named = escape = compiler = False
@@ -1734,7 +1847,8 @@ class Spec(object):
fmt += 's'
if c == '_':
- out.write(fmt % self.name)
+ name = self.name if self.name else ''
+ out.write(fmt % name)
elif c == '.':
out.write(fmt % self.fullname)
elif c == '@':
@@ -1749,7 +1863,7 @@ class Spec(object):
write(fmt % str(self.variants), c)
elif c == '=':
if self.architecture:
- write(fmt % (c + str(self.architecture)), c)
+ write(fmt % (' arch' + c + str(self.architecture)), c)
elif c == '#':
out.write('-' + fmt % (self.dag_hash(7)))
elif c == '$':
@@ -1764,22 +1878,28 @@ class Spec(object):
elif compiler:
if c == '@':
if (self.compiler and self.compiler.versions and
- self.compiler.versions != _any_version):
+ self.compiler.versions != _any_version):
write(c + str(self.compiler.versions), '%')
+ elif c == '+':
+ if self.compiler_flags:
+ write(fmt % str(self.compiler_flags), '%')
+ compiler = False
elif c == '$':
escape = True
+ compiler = False
else:
out.write(c)
- compiler = False
+ compiler = False
elif named:
if not c == '}':
if i == length - 1:
- raise ValueError("Error: unterminated ${ in format: '%s'"
- % format_string)
+ raise ValueError("Error: unterminated ${ in format:"
+ "'%s'" % format_string)
named_str += c
- continue;
+ continue
if named_str == 'PACKAGE':
+ name = self.name if self.name else ''
write(fmt % self.name, '@')
if named_str == 'VERSION':
if self.versions and self.versions != _any_version:
@@ -1793,6 +1913,9 @@ class Spec(object):
elif named_str == 'COMPILERVER':
if self.compiler:
write(fmt % self.compiler.versions, '%')
+ elif named_str == 'COMPILERFLAGS':
+ if self.compiler:
+ write(fmt % str(self.compiler_flags), '%')
elif named_str == 'OPTIONS':
if self.variants:
write(fmt % str(self.variants), '+')
@@ -1820,24 +1943,21 @@ class Spec(object):
result = out.getvalue()
return result
-
def dep_string(self):
return ''.join("^" + dep.format() for dep in self.sorted_deps())
-
def __str__(self):
return self.format() + self.dep_string()
-
def tree(self, **kwargs):
"""Prints out this spec and its dependencies, tree-formatted
with indentation."""
- color = kwargs.pop('color', False)
- depth = kwargs.pop('depth', False)
+ color = kwargs.pop('color', False)
+ depth = kwargs.pop('depth', False)
showid = kwargs.pop('ids', False)
- cover = kwargs.pop('cover', 'nodes')
+ cover = kwargs.pop('cover', 'nodes')
indent = kwargs.pop('indent', 0)
- fmt = kwargs.pop('format', '$_$@$%@$+$=')
+ fmt = kwargs.pop('format', '$_$@$%@+$+$=')
prefix = kwargs.pop('prefix', None)
check_kwargs(kwargs, self.tree)
@@ -1861,7 +1981,6 @@ class Spec(object):
out += node.format(fmt, color=color) + "\n"
return out
-
def __repr__(self):
return str(self)
@@ -1869,68 +1988,123 @@ class Spec(object):
#
# These are possible token types in the spec grammar.
#
-DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, ID = range(9)
+HASH, DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, QT, ID = range(11)
+
class SpecLexer(spack.parse.Lexer):
+
"""Parses tokens that make up spack specs."""
+
def __init__(self):
super(SpecLexer, self).__init__([
- (r'\^', lambda scanner, val: self.token(DEP, val)),
- (r'\@', lambda scanner, val: self.token(AT, val)),
- (r'\:', lambda scanner, val: self.token(COLON, val)),
- (r'\,', lambda scanner, val: self.token(COMMA, val)),
- (r'\+', lambda scanner, val: self.token(ON, val)),
- (r'\-', lambda scanner, val: self.token(OFF, val)),
- (r'\~', lambda scanner, val: self.token(OFF, val)),
- (r'\%', lambda scanner, val: self.token(PCT, val)),
- (r'\=', lambda scanner, val: self.token(EQ, val)),
+ (r'/', lambda scanner, val: self.token(HASH, val)),
+ (r'\^', lambda scanner, val: self.token(DEP, val)),
+ (r'\@', lambda scanner, val: self.token(AT, val)),
+ (r'\:', lambda scanner, val: self.token(COLON, val)),
+ (r'\,', lambda scanner, val: self.token(COMMA, val)),
+ (r'\+', lambda scanner, val: self.token(ON, val)),
+ (r'\-', lambda scanner, val: self.token(OFF, val)),
+ (r'\~', lambda scanner, val: self.token(OFF, val)),
+ (r'\%', lambda scanner, val: self.token(PCT, val)),
+ (r'\=', lambda scanner, val: self.token(EQ, val)),
# This is more liberal than identifier_re (see above).
# Checked by check_identifier() for better error messages.
+ (r'([\"\'])(?:(?=(\\?))\2.)*?\1',
+ lambda scanner, val: self.token(QT, val)),
(r'\w[\w.-]*', lambda scanner, val: self.token(ID, val)),
- (r'\s+', lambda scanner, val: None)])
+ (r'\s+', lambda scanner, val: None)])
+
+# Lexer is always the same for every parser.
+_lexer = SpecLexer()
class SpecParser(spack.parse.Parser):
- def __init__(self):
- super(SpecParser, self).__init__(SpecLexer())
+ def __init__(self):
+ super(SpecParser, self).__init__(_lexer)
+ self.previous = None
def do_parse(self):
specs = []
try:
while self.next:
+ # TODO: clean this parsing up a bit
+ if self.previous:
+ specs.append(self.spec(self.previous.value))
if self.accept(ID):
- specs.append(self.spec())
+ self.previous = self.token
+ if self.accept(EQ):
+ if not specs:
+ specs.append(self.spec(None))
+ if self.accept(QT):
+ self.token.value = self.token.value[1:-1]
+ else:
+ self.expect(ID)
+ specs[-1]._add_flag(
+ self.previous.value, self.token.value)
+ else:
+ specs.append(self.spec(self.previous.value))
+ self.previous = None
+ elif self.accept(HASH):
+ specs.append(self.spec_by_hash())
elif self.accept(DEP):
if not specs:
- self.last_token_error("Dependency has no package")
- self.expect(ID)
- specs[-1]._add_dependency(self.spec())
+ self.previous = self.token
+ specs.append(self.spec(None))
+ self.previous = None
+ if self.accept(HASH):
+ specs[-1]._add_dependency(self.spec_by_hash())
+ else:
+ self.expect(ID)
+ specs[-1]._add_dependency(self.spec(self.token.value))
else:
- self.unexpected_token()
+ # Attempt to construct an anonymous spec, but check that
+ # the first token is valid
+ # TODO: Is this check even necessary, or will it all be Lex
+ # errors now?
+ specs.append(self.spec(None, True))
+
except spack.parse.ParseError, e:
raise SpecParseError(e)
return specs
-
def parse_compiler(self, text):
self.setup(text)
return self.compiler()
+ def spec_by_hash(self):
+ self.expect(ID)
+
+ specs = spack.installed_db.query()
+ matches = [spec for spec in specs if
+ spec.dag_hash()[:len(self.token.value)] == self.token.value]
+
+ if not matches:
+ tty.die("%s does not match any installed packages." %
+ self.token.value)
- def spec(self):
+ if len(matches) != 1:
+ raise AmbiguousHashError(
+ "Multiple packages specify hash %s." % self.token.value,
+ *matches)
+
+ return matches[0]
+
+ def spec(self, name, check_valid_token=False):
"""Parse a spec out of the input. If a spec is supplied, then initialize
and return it instead of creating a new one."""
-
- spec_namespace, dot, spec_name = self.token.value.rpartition('.')
- if not spec_namespace:
+ if name:
+ spec_namespace, dot, spec_name = name.rpartition('.')
+ if not spec_namespace:
+ spec_namespace = None
+ self.check_identifier(spec_name)
+ else:
spec_namespace = None
-
- self.check_identifier(spec_name)
+ spec_name = None
# This will init the spec without calling __init__.
spec = Spec.__new__(Spec)
@@ -1940,9 +2114,11 @@ class SpecParser(spack.parse.Parser):
spec.architecture = None
spec.compiler = None
spec.external = None
- spec.dependents = DependencyMap()
+ spec.compiler_flags = FlagMap(spec)
+ spec.dependents = DependencyMap()
spec.dependencies = DependencyMap()
spec.namespace = spec_namespace
+ spec._hash = None
spec._normal = False
spec._concrete = False
@@ -1951,26 +2127,51 @@ class SpecParser(spack.parse.Parser):
# unspecified or not.
added_version = False
+ if self.previous and self.previous.value == DEP:
+ if self.accept(HASH):
+ spec.add_dependency(self.spec_by_hash())
+ else:
+ self.expect(ID)
+ if self.accept(EQ):
+ raise SpecParseError(spack.parse.ParseError(
+ "", "", "Expected dependency received anonymous spec"))
+ spec.add_dependency(self.spec(self.token.value))
+
while self.next:
if self.accept(AT):
vlist = self.version_list()
for version in vlist:
spec._add_version(version)
added_version = True
+ check_valid_token = False
elif self.accept(ON):
spec._add_variant(self.variant(), True)
+ check_valid_token = False
elif self.accept(OFF):
spec._add_variant(self.variant(), False)
+ check_valid_token = False
elif self.accept(PCT):
spec._set_compiler(self.compiler())
+ check_valid_token = False
- elif self.accept(EQ):
- spec._set_architecture(self.architecture())
+ elif self.accept(ID):
+ self.previous = self.token
+ if self.accept(EQ):
+ if self.accept(QT):
+ self.token.value = self.token.value[1:-1]
+ else:
+ self.expect(ID)
+ spec._add_flag(self.previous.value, self.token.value)
+ self.previous = None
+ else:
+ return spec
else:
+ if check_valid_token:
+ self.unexpected_token()
break
# If there was no version in the spec, consier it an open range
@@ -1979,18 +2180,21 @@ class SpecParser(spack.parse.Parser):
return spec
-
- def variant(self):
- self.expect(ID)
- self.check_identifier()
- return self.token.value
-
+ def variant(self, name=None):
+ # TODO: Make generalized variants possible
+ if name:
+ return name
+ else:
+ self.expect(ID)
+ self.check_identifier()
+ return self.token.value
def architecture(self):
+ # TODO: Make this work properly as a subcase of variant (includes
+ # adding names to grammar)
self.expect(ID)
return self.token.value
-
def version(self):
start = None
end = None
@@ -2007,11 +2211,12 @@ class SpecParser(spack.parse.Parser):
# No colon and no id: invalid version.
self.next_token_error("Invalid version specifier")
- if start: start = Version(start)
- if end: end = Version(end)
+ if start:
+ start = Version(start)
+ if end:
+ end = Version(end)
return VersionRange(start, end)
-
def version_list(self):
vlist = []
vlist.append(self.version())
@@ -2019,7 +2224,6 @@ class SpecParser(spack.parse.Parser):
vlist.append(self.version())
return vlist
-
def compiler(self):
self.expect(ID)
self.check_identifier()
@@ -2035,7 +2239,6 @@ class SpecParser(spack.parse.Parser):
compiler.versions = VersionList(':')
return compiler
-
def check_identifier(self, id=None):
"""The only identifiers that can contain '.' are versions, but version
ids are context-sensitive so we have to check on a case-by-case
@@ -2068,9 +2271,16 @@ def parse_anonymous_spec(spec_like, pkg_name):
if isinstance(spec_like, str):
try:
anon_spec = Spec(spec_like)
+ if anon_spec.name != pkg_name:
+ raise SpecParseError(spack.parse.ParseError(
+ "",
+ "",
+ "Expected anonymous spec for package %s but found spec for"
+ "package %s" % (pkg_name, anon_spec.name)))
except SpecParseError:
- anon_spec = Spec(pkg_name + spec_like)
- if anon_spec.name != pkg_name: raise ValueError(
+ anon_spec = Spec(pkg_name + ' ' + spec_like)
+ if anon_spec.name != pkg_name:
+ raise ValueError(
"Invalid spec for package %s: %s" % (pkg_name, spec_like))
else:
anon_spec = spec_like.copy()
@@ -2083,13 +2293,17 @@ def parse_anonymous_spec(spec_like, pkg_name):
class SpecError(spack.error.SpackError):
+
"""Superclass for all errors that occur while constructing specs."""
+
def __init__(self, message):
super(SpecError, self).__init__(message)
class SpecParseError(SpecError):
+
"""Wrapper for ParseError for when we're parsing specs."""
+
def __init__(self, parse_error):
super(SpecParseError, self).__init__(parse_error.message)
self.string = parse_error.string
@@ -2097,61 +2311,79 @@ class SpecParseError(SpecError):
class DuplicateDependencyError(SpecError):
+
"""Raised when the same dependency occurs in a spec twice."""
+
def __init__(self, message):
super(DuplicateDependencyError, self).__init__(message)
class DuplicateVariantError(SpecError):
+
"""Raised when the same variant occurs in a spec twice."""
+
def __init__(self, message):
super(DuplicateVariantError, self).__init__(message)
class DuplicateCompilerSpecError(SpecError):
+
"""Raised when the same compiler occurs in a spec twice."""
+
def __init__(self, message):
super(DuplicateCompilerSpecError, self).__init__(message)
class UnsupportedCompilerError(SpecError):
+
"""Raised when the user asks for a compiler spack doesn't know about."""
+
def __init__(self, compiler_name):
super(UnsupportedCompilerError, self).__init__(
"The '%s' compiler is not yet supported." % compiler_name)
class UnknownVariantError(SpecError):
+
"""Raised when the same variant occurs in a spec twice."""
+
def __init__(self, pkg, variant):
super(UnknownVariantError, self).__init__(
"Package %s has no variant %s!" % (pkg, variant))
class DuplicateArchitectureError(SpecError):
+
"""Raised when the same architecture occurs in a spec twice."""
+
def __init__(self, message):
super(DuplicateArchitectureError, self).__init__(message)
class InconsistentSpecError(SpecError):
+
"""Raised when two nodes in the same spec DAG have inconsistent
constraints."""
+
def __init__(self, message):
super(InconsistentSpecError, self).__init__(message)
class InvalidDependencyException(SpecError):
+
"""Raised when a dependency in a spec is not actually a dependency
of the package."""
+
def __init__(self, message):
super(InvalidDependencyException, self).__init__(message)
class NoProviderError(SpecError):
+
"""Raised when there is no package that provides a particular
virtual dependency.
"""
+
def __init__(self, vpkg):
super(NoProviderError, self).__init__(
"No providers found for virtual package: '%s'" % vpkg)
@@ -2159,9 +2391,11 @@ class NoProviderError(SpecError):
class MultipleProviderError(SpecError):
+
"""Raised when there is no package that provides a particular
virtual dependency.
"""
+
def __init__(self, vpkg, providers):
"""Takes the name of the vpkg"""
super(MultipleProviderError, self).__init__(
@@ -2172,8 +2406,10 @@ class MultipleProviderError(SpecError):
class UnsatisfiableSpecError(SpecError):
+
"""Raised when a spec conflicts with package constraints.
Provide the requirement that was violated when raising."""
+
def __init__(self, provided, required, constraint_type):
super(UnsatisfiableSpecError, self).__init__(
"%s does not satisfy %s" % (provided, required))
@@ -2183,55 +2419,96 @@ class UnsatisfiableSpecError(SpecError):
class UnsatisfiableSpecNameError(UnsatisfiableSpecError):
+
"""Raised when two specs aren't even for the same package."""
+
def __init__(self, provided, required):
super(UnsatisfiableSpecNameError, self).__init__(
provided, required, "name")
class UnsatisfiableVersionSpecError(UnsatisfiableSpecError):
+
"""Raised when a spec version conflicts with package constraints."""
+
def __init__(self, provided, required):
super(UnsatisfiableVersionSpecError, self).__init__(
provided, required, "version")
class UnsatisfiableCompilerSpecError(UnsatisfiableSpecError):
+
"""Raised when a spec comiler conflicts with package constraints."""
+
def __init__(self, provided, required):
super(UnsatisfiableCompilerSpecError, self).__init__(
provided, required, "compiler")
class UnsatisfiableVariantSpecError(UnsatisfiableSpecError):
+
"""Raised when a spec variant conflicts with package constraints."""
+
def __init__(self, provided, required):
super(UnsatisfiableVariantSpecError, self).__init__(
provided, required, "variant")
+class UnsatisfiableCompilerFlagSpecError(UnsatisfiableSpecError):
+
+ """Raised when a spec variant conflicts with package constraints."""
+
+ def __init__(self, provided, required):
+ super(UnsatisfiableCompilerFlagSpecError, self).__init__(
+ provided, required, "compiler_flags")
+
+
class UnsatisfiableArchitectureSpecError(UnsatisfiableSpecError):
+
"""Raised when a spec architecture conflicts with package constraints."""
+
def __init__(self, provided, required):
super(UnsatisfiableArchitectureSpecError, self).__init__(
provided, required, "architecture")
class UnsatisfiableProviderSpecError(UnsatisfiableSpecError):
+
"""Raised when a provider is supplied but constraints don't match
a vpkg requirement"""
+
def __init__(self, provided, required):
super(UnsatisfiableProviderSpecError, self).__init__(
provided, required, "provider")
# TODO: get rid of this and be more specific about particular incompatible
# dep constraints
+
+
class UnsatisfiableDependencySpecError(UnsatisfiableSpecError):
+
"""Raised when some dependency of constrained specs are incompatible"""
+
def __init__(self, provided, required):
super(UnsatisfiableDependencySpecError, self).__init__(
provided, required, "dependency")
+
class SpackYAMLError(spack.error.SpackError):
+
def __init__(self, msg, yaml_error):
super(SpackYAMLError, self).__init__(msg, str(yaml_error))
+
+
+class SpackRecordError(spack.error.SpackError):
+
+ def __init__(self, msg):
+ super(SpackRecordError, self).__init__(msg)
+
+
+class AmbiguousHashError(SpecError):
+
+ def __init__(self, msg, *specs):
+ super(AmbiguousHashError, self).__init__(msg)
+ for spec in specs:
+ print ' ', spec.format('$.$@$%@+$+$=$#')