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.py246
1 files changed, 202 insertions, 44 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 10e246bf2e..d04135860e 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -353,7 +353,7 @@ class VariantMap(HashableMap):
@property
def concrete(self):
return self.spec._concrete or all(
- v in self for v in self.spec.package.variants)
+ v in self for v in self.spec.package_class.variants)
def copy(self):
@@ -418,9 +418,12 @@ class Spec(object):
# 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)
+
# 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.
@@ -498,6 +501,14 @@ class Spec(object):
@property
+ def package_class(self):
+ """Internal package call gets only the class object for a package.
+ Use this to just get package metadata.
+ """
+ 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.
@@ -770,12 +781,11 @@ 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.
- if not self.virtual:
- changed |= any(
- (spack.concretizer.concretize_architecture(self),
- spack.concretizer.concretize_compiler(self),
- spack.concretizer.concretize_version(self),
- spack.concretizer.concretize_variants(self)))
+ changed |= any(
+ (spack.concretizer.concretize_architecture(self),
+ spack.concretizer.concretize_compiler(self),
+ spack.concretizer.concretize_version(self),
+ spack.concretizer.concretize_variants(self)))
presets[self.name] = self
visited.add(self.name)
@@ -786,8 +796,30 @@ class Spec(object):
"""Replace this virtual spec with a concrete spec."""
assert(self.virtual)
for name, dependent in self.dependents.items():
+ # remove self from all dependents.
+ del dependent.dependencies[self.name]
+
+ # add the replacement, unless it is already a dep of dependent.
+ if concrete.name not in dependent.dependencies:
+ dependent._add_dependency(concrete)
+
+
+ def _replace_node(self, replacement):
+ """Replace this spec with another.
+
+ Connects all dependents of this spec to its replacement, and
+ disconnects this spec from any dependencies it has. New spec
+ will have any dependencies the replacement had, and may need
+ to be normalized.
+
+ """
+ for name, dependent in self.dependents.items():
del dependent.dependencies[self.name]
- dependent._add_dependency(concrete)
+ dependent._add_dependency(replacement)
+
+ for name, dep in self.dependencies.items():
+ del dep.dependents[self.name]
+ del self.dependencies[dep.name]
def _expand_virtual_packages(self):
@@ -807,22 +839,81 @@ class Spec(object):
this are infrequent, but should implement this before it is
a problem.
"""
+ # Make an index of stuff this spec already provides
+ self_index = ProviderIndex(self.traverse(), restrict=True)
+
changed = False
- while True:
- virtuals =[v for v in self.traverse() if v.virtual]
- if not virtuals:
- return changed
+ done = False
+ while not done:
+ done = True
+ for spec in list(self.traverse()):
+ replacement = None
+ if spec.virtual:
+ replacement = self._find_provider(spec, self_index)
+ if replacement:
+ # TODO: may break if in-place on self but
+ # shouldn't happen if root is traversed first.
+ spec._replace_with(replacement)
+ done=False
+ break
+
+ if not replacement:
+ # 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.
+ for replacement in candidates:
+ if replacement is spec:
+ break
+
+ # Replace spec with the candidate and normalize
+ copy = self.copy()
+ copy[spec.name]._dup(replacement.copy(deps=False))
+
+ try:
+ # If there are duplicate providers or duplicate provider
+ # deps, consolidate them and merge constraints.
+ copy.normalize(force=True)
+ break
+ except SpecError as e:
+ # On error, we'll try the next replacement.
+ continue
+
+ # If replacement is external then trim the dependencies
+ if replacement.external:
+ if (spec.dependencies):
+ changed = True
+ spec.dependencies = DependencyMap()
+ replacement.dependencies = DependencyMap()
+
+ # TODO: could this and the stuff in _dup be cleaned up?
+ 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)):
+ 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.
+ if spec.virtual:
+ spec._replace_with(replacement)
+ changed = True
+ if spec._dup(replacement, deps=False, cleardeps=False):
+ changed = True
- for spec in virtuals:
- providers = spack.repo.providers_for(spec)
- concrete = spack.concretizer.choose_provider(spec, providers)
- concrete = concrete.copy()
- spec._replace_with(concrete)
- changed = True
+ self_index.update(spec)
+ done=False
+ break
- # If there are duplicate providers or duplicate provider deps, this
- # consolidates them and merge constraints.
- changed |= self.normalize(force=True)
+ return changed
def concretize(self):
@@ -837,6 +928,7 @@ class Spec(object):
with requirements of its pacakges. See flatten() and normalize() for
more details on this.
"""
+
if self._concrete:
return
@@ -844,7 +936,7 @@ class Spec(object):
force = False
while changed:
- changes = (self.normalize(force=force),
+ changes = (self.normalize(force),
self._expand_virtual_packages(),
self._concretize_helper())
changed = any(changes)
@@ -970,8 +1062,8 @@ class Spec(object):
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
- dependency already in this spec.
+ Raise an exception if there is a conflicting virtual
+ dependency already in this spec.
"""
assert(vdep.virtual)
providers = provider_index.providers_for(vdep)
@@ -1012,17 +1104,14 @@ class Spec(object):
"""
changed = False
- # If it's a virtual dependency, try to find a provider and
- # merge that.
+ # If it's a virtual dependency, try to find an existing
+ # provider in the spec, and merge that.
if dep.virtual:
visited.add(dep.name)
provider = self._find_provider(dep, provider_index)
if provider:
dep = provider
-
else:
- # if it's a real dependency, check whether it provides
- # something already required in the spec.
index = ProviderIndex([dep], restrict=True)
for vspec in (v for v in spec_deps.values() if v.virtual):
if index.providers_for(vspec):
@@ -1069,7 +1158,7 @@ class Spec(object):
# if we descend into a virtual spec, there's nothing more
# to normalize. Concretize will finish resolving it later.
- if self.virtual:
+ if self.virtual or self.external:
return False
# Combine constraints from package deps with constraints from
@@ -1119,13 +1208,14 @@ class Spec(object):
# Get all the dependencies into one DependencyMap
spec_deps = self.flat_dependencies(copy=False)
- # Initialize index of virtual dependency providers
- index = ProviderIndex(spec_deps.values(), restrict=True)
+ # Initialize index of virtual dependency providers if
+ # concretize didn't pass us one already
+ provider_index = ProviderIndex(spec_deps.values(), restrict=True)
# traverse the package DAG and fill out dependencies according
# to package files & their 'when' specs
visited = set()
- any_change = self._normalize_helper(visited, spec_deps, index)
+ any_change = self._normalize_helper(visited, spec_deps, provider_index)
# If there are deps specified but not visited, they're not
# actually deps of this package. Raise an error.
@@ -1163,7 +1253,7 @@ class Spec(object):
# Ensure that variants all exist.
for vname, variant in spec.variants.items():
- if vname not in spec.package.variants:
+ if vname not in spec.package_class.variants:
raise UnknownVariantError(spec.name, vname)
@@ -1404,15 +1494,25 @@ class Spec(object):
Whether deps should be copied too. Set to false to copy a
spec but not its dependencies.
"""
+ # 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)
+
# Local node attributes get copied first.
self.name = other.name
self.versions = other.versions.copy()
self.architecture = other.architecture
self.compiler = other.compiler.copy() if other.compiler else None
- self.dependents = DependencyMap()
- self.dependencies = DependencyMap()
+ if kwargs.get('cleardeps', True):
+ self.dependents = DependencyMap()
+ self.dependencies = DependencyMap()
self.variants = other.variants.copy()
self.variants.spec = self
+ self.external = other.external
self.namespace = other.namespace
# If we copy dependencies, preserve DAG structure in the new spec
@@ -1431,6 +1531,8 @@ class Spec(object):
# Since we preserved structure, we can copy _normal safely.
self._normal = other._normal
self._concrete = other._concrete
+ self.external = other.external
+ return changed
def copy(self, **kwargs):
@@ -1571,14 +1673,28 @@ class Spec(object):
$_ Package name
$. Full package name (with namespace)
- $@ Version
- $% Compiler
- $%@ Compiler & compiler version
+ $@ Version with '@' prefix
+ $% Compiler with '%' prefix
+ $%@ Compiler with '%' prefix & compiler version with '@' prefix
$+ Options
- $= Architecture
- $# 7-char prefix of DAG hash
+ $= 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.
@@ -1594,7 +1710,8 @@ class Spec(object):
color = kwargs.get('color', False)
length = len(format_string)
out = StringIO()
- escape = compiler = False
+ named = escape = compiler = False
+ named_str = fmt = ''
def write(s, c):
if color:
@@ -1636,9 +1753,12 @@ class Spec(object):
elif c == '#':
out.write('-' + fmt % (self.dag_hash(7)))
elif c == '$':
- if fmt != '':
+ if fmt != '%s':
raise ValueError("Can't use format width with $$.")
out.write('$')
+ elif c == '{':
+ named = True
+ named_str = ''
escape = False
elif compiler:
@@ -1652,6 +1772,43 @@ class Spec(object):
out.write(c)
compiler = False
+ elif named:
+ if not c == '}':
+ if i == length - 1:
+ raise ValueError("Error: unterminated ${ in format: '%s'"
+ % format_string)
+ named_str += c
+ continue;
+ if named_str == 'PACKAGE':
+ write(fmt % self.name, '@')
+ if named_str == 'VERSION':
+ if self.versions and self.versions != _any_version:
+ write(fmt % str(self.versions), '@')
+ elif named_str == 'COMPILER':
+ if self.compiler:
+ write(fmt % self.compiler, '%')
+ elif named_str == 'COMPILERNAME':
+ if self.compiler:
+ write(fmt % self.compiler.name, '%')
+ elif named_str == 'COMPILERVER':
+ if self.compiler:
+ write(fmt % self.compiler.versions, '%')
+ elif named_str == 'OPTIONS':
+ if self.variants:
+ write(fmt % str(self.variants), '+')
+ elif named_str == 'ARCHITECTURE':
+ if self.architecture:
+ write(fmt % str(self.architecture), '=')
+ elif named_str == 'SHA1':
+ if self.dependencies:
+ out.write(fmt % str(self.dag_hash(7)))
+ elif named_str == 'SPACK_ROOT':
+ out.write(fmt % spack.prefix)
+ elif named_str == 'SPACK_INSTALL':
+ out.write(fmt % spack.install_path)
+
+ named = False
+
elif c == '$':
escape = True
if i == length - 1:
@@ -1782,6 +1939,7 @@ class SpecParser(spack.parse.Parser):
spec.variants = VariantMap(spec)
spec.architecture = None
spec.compiler = None
+ spec.external = None
spec.dependents = DependencyMap()
spec.dependencies = DependencyMap()
spec.namespace = spec_namespace