Fixes for virtual packages.

- Added more tests
- cleaned up spec preorder traversal
- fixed concretization
This commit is contained in:
Todd Gamblin 2013-12-08 19:26:29 -08:00
parent 90f2154a32
commit e0c029c347
14 changed files with 393 additions and 117 deletions

View File

@ -5,6 +5,9 @@
import spack import spack
def setup_parser(subparser):
subparser.add_argument('file', nargs='?', help="file to run")
description = "Launch an interpreter as spack would launch a command" description = "Launch an interpreter as spack would launch a command"
def python(parser, args): def python(parser, args):
@ -16,6 +19,10 @@ def python(parser, args):
with closing(open(startup_file)) as startup: with closing(open(startup_file)) as startup:
console.runsource(startup.read(), startup_file, 'exec') console.runsource(startup.read(), startup_file, 'exec')
if args.file:
with closing(open(args.file)) as file:
console.runsource(file.read(), args.file, 'exec')
else:
console.interact("Spack version %s\nPython %s, %s %s""" console.interact("Spack version %s\nPython %s, %s %s"""
% (spack.spack_version, platform.python_version(), % (spack.spack_version, platform.python_version(),
platform.system(), platform.machine())) platform.system(), platform.machine()))

View File

@ -12,8 +12,9 @@
import spack.arch import spack.arch
import spack.compilers import spack.compilers
import spack.packages import spack.packages
import spack.spec
from spack.version import * from spack.version import *
from spack.spec import *
class DefaultConcretizer(object): class DefaultConcretizer(object):
@ -31,12 +32,12 @@ def concretize_version(self, spec):
if spec.versions.concrete: if spec.versions.concrete:
return return
pkg = spec.package
# If there are known avaialble versions, return the most recent # If there are known avaialble versions, return the most recent
available_versions = pkg.available_versions # version that satisfies the spec
if available_versions: pkg = spec.package
spec.versions = ver([available_versions[-1]]) valid_versions = pkg.available_versions.intersection(spec.versions)
if valid_versions:
spec.versions = ver([valid_versions[-1]])
else: else:
spec.versions = ver([pkg.version]) spec.versions = ver([pkg.version])
@ -91,9 +92,11 @@ def choose_provider(self, spec, providers):
"""This is invoked for virtual specs. Given a spec with a virtual name, """This is invoked for virtual specs. Given a spec with a virtual name,
say "mpi", and a list of specs of possible providers of that spec, say "mpi", and a list of specs of possible providers of that spec,
select a provider and return it. select a provider and return it.
Default implementation just chooses the last provider in sorted order.
""" """
assert(spec.virtual) assert(spec.virtual)
assert(providers) assert(providers)
return sorted(providers)[-1]
index = spack.spec.index_specs(providers)
first_key = sorted(index.keys())[0]
latest_version = sorted(index[first_key])[-1]
return latest_version

View File

@ -39,41 +39,53 @@ class ProviderIndex(object):
Calling find_provider(spec) will find a package that provides a Calling find_provider(spec) will find a package that provides a
matching implementation of MPI. matching implementation of MPI.
""" """
def __init__(self, providers): def __init__(self, specs, **kwargs):
"""Takes a list of provider packagse and build an index of the virtual restrict = kwargs.setdefault('restrict', False)
packages they provide."""
self.providers = {} self.providers = {}
self.add(*providers)
for spec in specs:
if type(spec) != spack.spec.Spec:
spec = spack.spec.Spec(spec)
def add(self, *providers): if spec.virtual:
"""Look at the provided map on the provider packages, invert it and continue
add it to this provider index."""
for pkg in providers: pkg = spec.package
for provided_spec, provider_spec in pkg.provided.iteritems(): for provided_spec, provider_spec in pkg.provided.iteritems():
if provider_spec.satisfies(spec):
provided_name = provided_spec.name provided_name = provided_spec.name
if provided_name not in self.providers: if provided_name not in self.providers:
self.providers[provided_name] = {} self.providers[provided_name] = {}
self.providers[provided_name][provided_spec] = provider_spec
if restrict:
self.providers[provided_name][provided_spec] = spec
else:
# Before putting the spec in the map, constrain it so that
# it provides what was asked for.
constrained = spec.copy()
constrained.constrain(provider_spec)
self.providers[provided_name][provided_spec] = constrained
def providers_for(self, *vpkg_specs): def providers_for(self, *vpkg_specs):
"""Gives names of all packages that provide virtual packages """Gives names of all packages that provide virtual packages
with the supplied names.""" with the supplied names."""
packages = set() providers = set()
for vspec in vpkg_specs: for vspec in vpkg_specs:
# Allow string names to be passed as input, as well as specs # Allow string names to be passed as input, as well as specs
if type(vspec) == str: if type(vspec) == str:
vspec = spack.spec.Spec(vspec) vspec = spack.spec.Spec(vspec)
# Add all the packages that satisfy the vpkg spec. # Add all the providers that satisfy the vpkg spec.
if vspec.name in self.providers: if vspec.name in self.providers:
for provider_spec, pkg in self.providers[vspec.name].items(): for provider_spec, spec in self.providers[vspec.name].items():
if provider_spec.satisfies(vspec): if provider_spec.satisfies(vspec):
packages.add(pkg) providers.add(spec)
# Return packages in order # Return providers in order
return sorted(packages) return sorted(providers)
def get(pkg_name): def get(pkg_name):
@ -86,7 +98,7 @@ def get(pkg_name):
def providers_for(vpkg_spec): def providers_for(vpkg_spec):
if providers_for.index is None: if providers_for.index is None:
providers_for.index = ProviderIndex(all_packages()) providers_for.index = ProviderIndex(all_package_names())
providers = providers_for.index.providers_for(vpkg_spec) providers = providers_for.index.providers_for(vpkg_spec)
if not providers: if not providers:

View File

@ -5,6 +5,9 @@ class Mpich(Package):
url = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz" url = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
md5 = "9c5d5d4fe1e17dd12153f40bc5b6dbc0" md5 = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
list_url = "http://www.mpich.org/static/downloads/"
list_depth = 2
def install(self, prefix): def install(self, prefix):
configure("--prefix=%s" % prefix) configure("--prefix=%s" % prefix)
make() make()

View File

@ -44,7 +44,7 @@ class Parser(object):
"""Base class for simple recursive descent parsers.""" """Base class for simple recursive descent parsers."""
def __init__(self, lexer): def __init__(self, lexer):
self.tokens = iter([]) # iterators over tokens, handled in order. Starts empty. self.tokens = iter([]) # iterators over tokens, handled in order. Starts empty.
self.token = None # last accepted token self.token = Token(None) # last accepted token starts at beginning of file
self.next = None # next token self.next = None # next token
self.lexer = lexer self.lexer = lexer
self.text = None self.text = None

View File

@ -94,8 +94,8 @@ def _ensure_caller_is_spack_package():
def _parse_local_spec(spec_like, pkg_name): def _parse_local_spec(spec_like, pkg_name):
"""Allow the user to omit the package name part of a spec in relations. """Allow the user to omit the package name part of a spec in relations.
e.g., provides('mpi@2.1', when='@1.9:') says that this package provides e.g., provides('mpi@2', when='@1.9:') says that this package provides
MPI 2.1 when its version is higher than 1.9. MPI-3 when its version is higher than 1.9.
""" """
if type(spec_like) not in (str, Spec): if type(spec_like) not in (str, Spec):
raise TypeError('spec must be Spec or spec string. Found %s' raise TypeError('spec must be Spec or spec string. Found %s'
@ -104,7 +104,7 @@ def _parse_local_spec(spec_like, pkg_name):
if type(spec_like) == str: if type(spec_like) == str:
try: try:
local_spec = Spec(spec_like) local_spec = Spec(spec_like)
except ParseError: except spack.parse.ParseError:
local_spec = Spec(pkg_name + spec_like) local_spec = Spec(pkg_name + spec_like)
if local_spec.name != pkg_name: raise ValueError( if local_spec.name != pkg_name: raise ValueError(
"Invalid spec for package %s: %s" % (pkg_name, spec_like)) "Invalid spec for package %s: %s" % (pkg_name, spec_like))
@ -118,21 +118,17 @@ def _parse_local_spec(spec_like, pkg_name):
return local_spec return local_spec
def _make_relation(map_name):
def relation_fun(*specs):
_ensure_caller_is_spack_package()
package_map = _caller_locals().setdefault(map_name, {})
for string in specs:
for spec in spack.spec.parse(string):
package_map[spec.name] = spec
return relation_fun
"""Adds a dependencies local variable in the locals of """Adds a dependencies local variable in the locals of
the calling class, based on args. """ the calling class, based on args. """
depends_on = _make_relation("dependencies") def depends_on(*specs):
pkg = _ensure_caller_is_spack_package()
dependencies = _caller_locals().setdefault('dependencies', {})
for string in specs:
for spec in spack.spec.parse(string):
if pkg == spec.name:
raise CircularDependencyError('depends_on', pkg)
dependencies[spec.name] = spec
def provides(*specs, **kwargs): def provides(*specs, **kwargs):
@ -153,13 +149,30 @@ def provides(*specs, **kwargs):
"""Packages can declare conflicts with other packages. """Packages can declare conflicts with other packages.
This can be as specific as you like: use regular spec syntax. This can be as specific as you like: use regular spec syntax.
""" """
conflicts = _make_relation("conflicted") def conflicts(*specs):
# TODO: implement conflicts
pass
class ScopeError(spack.error.SpackError):
class RelationError(spack.error.SpackError):
"""This is raised when something is wrong with a package relation."""
def __init__(self, relation, message):
super(RelationError, self).__init__(message)
self.relation = relation
class ScopeError(RelationError):
"""This is raised when a relation is called from outside a spack package.""" """This is raised when a relation is called from outside a spack package."""
def __init__(self, relation): def __init__(self, relation):
super(ScopeError, self).__init__( super(ScopeError, self).__init__(
relation,
"Cannot inovke '%s' from outside of a Spack package!" % relation) "Cannot inovke '%s' from outside of a Spack package!" % relation)
self.relation = relation
class CircularDependencyError(RelationError):
"""This is raised when something depends on itself."""
def __init__(self, relation, package):
super(CircularDependencyError, self).__init__(
relation, "Package %s cannot depend on itself." % package)

View File

@ -93,6 +93,19 @@
separators = '[%s]' % ''.join(color_formats.keys()) separators = '[%s]' % ''.join(color_formats.keys())
def index_specs(specs):
"""Take a list of specs and return a dict of lists. Dict is
keyed by spec name and lists include all specs with the
same name.
"""
spec_dict = {}
for spec in specs:
if not spec.name in spec_dict:
spec_dict[spec.name] = []
spec_dict[spec.name].append(spec)
return spec_dict
def colorize_spec(spec): def colorize_spec(spec):
"""Returns a spec colorized according to the colors specified in """Returns a spec colorized according to the colors specified in
color_formats.""" color_formats."""
@ -360,11 +373,17 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
"""Generic preorder traversal of the DAG represented by this spec. """Generic preorder traversal of the DAG represented by this spec.
This will yield each node in the spec. Options: This will yield each node in the spec. Options:
unique [=True] cover [=nodes|edges|paths]
When True (default), every node in the DAG is yielded only once. Determines how extensively to cover the dag. Possible vlaues:
When False, the traversal will yield already visited
nodes but not their children. This lets you see that a node 'nodes': Visit each node in the dag only once. Every node
points to an already-visited subgraph without descending into it. yielded by this function will be unique.
'edges': If a node has been visited once but is reached along a
new path from the root, yield it but do not descend
into it. This traverses each 'edge' in the DAG once.
'paths': Explore every unique path reachable from the root.
This descends into visited subtrees and will yield
nodes twice if they're reachable by multiple paths.
depth [=False] depth [=False]
Defaults to False. When True, yields not just nodes in the Defaults to False. When True, yields not just nodes in the
@ -375,30 +394,37 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
Allow a custom key function to track the identity of nodes Allow a custom key function to track the identity of nodes
in the traversal. in the traversal.
noroot [=False] root [=True]
If true, this won't yield the root node, just its descendents. If false, this won't yield the root node, just its descendents.
""" """
unique = kwargs.setdefault('unique', True)
depth = kwargs.setdefault('depth', False) depth = kwargs.setdefault('depth', False)
keyfun = kwargs.setdefault('key', id) key_fun = kwargs.setdefault('key', id)
noroot = kwargs.setdefault('noroot', False) yield_root = kwargs.setdefault('root', True)
cover = kwargs.setdefault('cover', 'nodes')
cover_values = ('nodes', 'edges', 'paths')
if cover not in cover_values:
raise ValueError("Invalid value for cover: %s. Choices are %s"
% (cover, ",".join(cover_values)))
if visited is None: if visited is None:
visited = set() visited = set()
if keyfun(self) in visited: result = (d, self) if depth else self
if not unique: key = key_fun(self)
yield (d, self) if depth else self
return
visited.add(keyfun(self))
if d > 0 or not noroot: if key in visited:
yield (d, self) if depth else self if cover == 'nodes': return
if yield_root or d > 0: yield result
if cover == 'edges': return
else:
if yield_root or d > 0: yield result
for key in sorted(self.dependencies.keys()): visited.add(key)
for result in self.dependencies[key].preorder_traversal( for name in sorted(self.dependencies):
visited, d+1, **kwargs): child = self.dependencies[name]
yield result for elt in child.preorder_traversal(visited, d+1, **kwargs):
yield elt
def _concretize_helper(self, presets=None, visited=None): def _concretize_helper(self, presets=None, visited=None):
@ -436,6 +462,14 @@ def _expand_virtual_packages(self):
"""Find virtual packages in this spec, replace them with providers, """Find virtual packages in this spec, replace them with providers,
and normalize again to include the provider's (potentially virtual) and normalize again to include the provider's (potentially virtual)
dependencies. Repeat until there are no virtual deps. dependencies. Repeat until there are no virtual deps.
TODO: If a provider depends on something that conflicts with
other dependencies in the spec being expanded, this can
produce a conflicting spec. For example, if mpich depends
on hwloc@:1.3 but something in the spec needs hwloc1.4:,
then we should choose an MPI other than mpich. Cases like
this are infrequent, but should implement this before it is
a problem.
""" """
while True: while True:
virtuals =[v for v in self.preorder_traversal() if v.virtual] virtuals =[v for v in self.preorder_traversal() if v.virtual]
@ -545,8 +579,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
providers = provider_index.providers_for(pkg_dep) providers = provider_index.providers_for(pkg_dep)
# If there is a provider for the vpkg, then use that instead of # If there is a provider for the vpkg, then use that instead of
# the virtual package. If there isn't a provider, just merge # the virtual package.
# constraints on the virtual package.
if providers: if providers:
# Can't have multiple providers for the same thing in one spec. # Can't have multiple providers for the same thing in one spec.
if len(providers) > 1: if len(providers) > 1:
@ -613,7 +646,7 @@ def normalize(self):
# Remove virtual deps that are already provided by something in the spec # Remove virtual deps that are already provided by something in the spec
spec_packages = [d.package for d in spec_deps.values() if not d.virtual] spec_packages = [d.package for d in spec_deps.values() if not d.virtual]
index = packages.ProviderIndex(spec_packages) index = packages.ProviderIndex(spec_deps.values(), restrict=True)
visited = set() visited = set()
self._normalize_helper(visited, spec_deps, index) self._normalize_helper(visited, spec_deps, index)
@ -666,6 +699,9 @@ def constrain(self, other):
def satisfies(self, other): def satisfies(self, other):
if type(other) != Spec:
other = Spec(other)
def sat(attribute): def sat(attribute):
s = getattr(self, attribute) s = getattr(self, attribute)
o = getattr(other, attribute) o = getattr(other, attribute)
@ -716,6 +752,7 @@ def copy(self, **kwargs):
clone._dup(self, **kwargs) clone._dup(self, **kwargs)
return clone return clone
@property @property
def version(self): def version(self):
if not self.concrete: if not self.concrete:
@ -723,6 +760,27 @@ def version(self):
return self.versions[0] return self.versions[0]
def __getitem__(self, name):
"""TODO: does the way this is written make sense?"""
for spec in self.preorder_traversal():
if spec.name == name:
return spec
raise KeyError("No spec with name %s in %s" % (name, self))
def __contains__(self, spec):
"""True if this spec has any dependency that satisfies the supplied
spec."""
if type(spec) != Spec:
spec = Spec(spec)
for s in self.preorder_traversal():
if s.satisfies(spec):
return True
return False
def _cmp_key(self): def _cmp_key(self):
return (self.name, self.versions, self.variants, return (self.name, self.versions, self.variants,
self.architecture, self.compiler, self.dependencies) self.architecture, self.compiler, self.dependencies)
@ -757,16 +815,20 @@ def tree(self, **kwargs):
"""Prints out this spec and its dependencies, tree-formatted """Prints out this spec and its dependencies, tree-formatted
with indentation.""" with indentation."""
color = kwargs.get('color', False) color = kwargs.get('color', False)
depth = kwargs.get('depth', False)
cover = kwargs.get('cover', 'paths')
out = "" out = ""
cur_id = 0 cur_id = 0
ids = {} ids = {}
for d, node in self.preorder_traversal(unique=False, depth=True): for d, node in self.preorder_traversal(cover=cover, depth=True):
if depth:
out += "%-4d" % d
if not id(node) in ids: if not id(node) in ids:
cur_id += 1 cur_id += 1
ids[id(node)] = cur_id ids[id(node)] = cur_id
out += str(ids[id(node)]) out += "%-4d" % ids[id(node)]
out += " "+ (" " * d) out += (" " * d)
out += node.str_no_deps(color=color) + "\n" out += node.str_no_deps(color=color) + "\n"
return out return out
@ -777,7 +839,7 @@ def __repr__(self):
def __str__(self): def __str__(self):
byname = lambda d: d.name byname = lambda d: d.name
deps = self.preorder_traversal(key=byname, noroot=True) deps = self.preorder_traversal(key=byname, root=False)
sorted_deps = sorted(deps, key=byname) sorted_deps = sorted(deps, key=byname)
dep_string = ''.join("^" + dep.str_no_deps() for dep in sorted_deps) dep_string = ''.join("^" + dep.str_no_deps() for dep in sorted_deps)
return self.str_no_deps() + dep_string return self.str_no_deps() + dep_string
@ -1011,8 +1073,8 @@ class MultipleProviderError(SpecError):
""" """
def __init__(self, vpkg, providers): def __init__(self, vpkg, providers):
"""Takes the name of the vpkg""" """Takes the name of the vpkg"""
super(NoProviderError, self).__init__( super(MultipleProviderError, self).__init__(
"Multiple providers found for vpkg '%s': %s" "Multiple providers found for '%s': %s"
% (vpkg, [str(s) for s in providers])) % (vpkg, [str(s) for s in providers]))
self.vpkg = vpkg self.vpkg = vpkg
self.providers = providers self.providers = providers

View File

@ -1,4 +1,6 @@
import unittest import unittest
import spack.packages as packages
from spack.spec import Spec from spack.spec import Spec
from spack.test.mock_packages_test import * from spack.test.mock_packages_test import *
@ -35,8 +37,99 @@ def test_concretize_no_deps(self):
def test_concretize_dag(self): def test_concretize_dag(self):
spec = Spec('mpileaks')
spec.normalize()
self.check_concretize('callpath') self.check_concretize('callpath')
self.check_concretize('mpileaks') self.check_concretize('mpileaks')
self.check_concretize('libelf')
def test_concretize_with_virtual(self):
self.check_concretize('mpileaks ^mpi')
self.check_concretize('mpileaks ^mpi@:1.1')
self.check_concretize('mpileaks ^mpi@2:')
self.check_concretize('mpileaks ^mpi@2.1')
self.check_concretize('mpileaks ^mpi@2.2')
self.check_concretize('mpileaks ^mpi@2.2')
self.check_concretize('mpileaks ^mpi@:1')
self.check_concretize('mpileaks ^mpi@1.2:2')
def test_concretize_with_restricted_virtual(self):
self.check_concretize('mpileaks ^mpich2')
concrete = self.check_concretize('mpileaks ^mpich2@1.1')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1'))
concrete = self.check_concretize('mpileaks ^mpich2@1.2')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.2'))
concrete = self.check_concretize('mpileaks ^mpich2@:1.5')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.5'))
concrete = self.check_concretize('mpileaks ^mpich2@:1.3')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.3'))
concrete = self.check_concretize('mpileaks ^mpich2@:1.2')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.2'))
concrete = self.check_concretize('mpileaks ^mpich2@:1.1')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.1'))
concrete = self.check_concretize('mpileaks ^mpich2@1.1:')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1:'))
concrete = self.check_concretize('mpileaks ^mpich2@1.5:')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.5:'))
concrete = self.check_concretize('mpileaks ^mpich2@1.3.1:1.4')
self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.3.1:1.4'))
def test_concretize_with_provides_when(self):
"""Make sure insufficient versions of MPI are not in providers list when
we ask for some advanced version.
"""
self.assertTrue(not any(spec.satisfies('mpich2@:1.0')
for spec in packages.providers_for('mpi@2.1')))
self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
for spec in packages.providers_for('mpi@2.2')))
self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
for spec in packages.providers_for('mpi@2.2')))
self.assertTrue(not any(spec.satisfies('mpich@:1')
for spec in packages.providers_for('mpi@2')))
self.assertTrue(not any(spec.satisfies('mpich@:1')
for spec in packages.providers_for('mpi@3')))
self.assertTrue(not any(spec.satisfies('mpich2')
for spec in packages.providers_for('mpi@3')))
def test_virtual_is_fully_expanded_for_callpath(self):
# force dependence on fake "zmpi" by asking for MPI 10.0
spec = Spec('callpath ^mpi@10.0')
self.assertIn('mpi', spec.dependencies)
self.assertNotIn('fake', spec)
spec.concretize()
self.assertIn('zmpi', spec.dependencies)
self.assertNotIn('mpi', spec)
self.assertIn('fake', spec.dependencies['zmpi'])
def test_virtual_is_fully_expanded_for_mpileaks(self):
spec = Spec('mpileaks ^mpi@10.0')
self.assertIn('mpi', spec.dependencies)
self.assertNotIn('fake', spec)
spec.concretize()
self.assertIn('zmpi', spec.dependencies)
self.assertIn('callpath', spec.dependencies)
self.assertIn('zmpi', spec.dependencies['callpath'].dependencies)
self.assertIn('fake', spec.dependencies['callpath'].dependencies['zmpi'].dependencies)
self.assertNotIn('mpi', spec)

View File

@ -0,0 +1,13 @@
from spack import *
class Fake(Package):
homepage = "http://www.fake-spack-example.org"
url = "http://www.fake-spack-example.org/downloads/fake-1.0.tar.gz"
md5 = "foobarbaz"
versions = '1.0'
def install(self, prefix):
configure("--prefix=%s" % prefix)
make()
make("install")

View File

@ -8,9 +8,10 @@ class Mpich(Package):
list_url = "http://www.mpich.org/static/downloads/" list_url = "http://www.mpich.org/static/downloads/"
list_depth = 2 list_depth = 2
versions = '1.0.3, 1.3.2p1, 1.4.1p1, 3.0.4, 3.1b1' versions = '3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0'
provides('mpi') provides('mpi@:3', when='@3:')
provides('mpi@:1', when='@1:')
def install(self, prefix): def install(self, prefix):
configure("--prefix=%s" % prefix) configure("--prefix=%s" % prefix)

View File

@ -0,0 +1,20 @@
from spack import *
class Mpich2(Package):
homepage = "http://www.mpich.org"
url = "http://www.mpich.org/static/downloads/1.5/mpich2-1.5.tar.gz"
md5 = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
list_url = "http://www.mpich.org/static/downloads/"
list_depth = 2
versions = '1.5, 1.4, 1.3, 1.2, 1.1, 1.0'
provides('mpi@:2.0')
provides('mpi@:2.1', when='@1.1:')
provides('mpi@:2.2', when='@1.2:')
def install(self, prefix):
configure("--prefix=%s" % prefix)
make()
make("install")

View File

@ -0,0 +1,18 @@
from spack import *
class Zmpi(Package):
"""This is a fake MPI package used to demonstrate virtual package providers
with dependencies."""
homepage = "http://www.spack-fake-zmpi.org"
url = "http://www.spack-fake-zmpi.org/downloads/zmpi-1.0.tar.gz"
md5 = "foobarbaz"
versions = '1.0'
provides('mpi@10.0:')
depends_on('fake')
def install(self, prefix):
configure("--prefix=%s" % prefix)
make()
make("install")

View File

@ -28,42 +28,49 @@ def test_conflicting_package_constraints(self):
spec.package.validate_dependencies) spec.package.validate_dependencies)
def test_preorder_traversal(self): def test_unique_node_traversal(self):
dag = Spec('mpileaks', dag = Spec('mpileaks ^zmpi')
Spec('callpath',
Spec('dyninst',
Spec('libdwarf',
Spec('libelf')),
Spec('libelf')),
Spec('mpich')),
Spec('mpich'))
dag.normalize() dag.normalize()
unique_names = [ names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'mpich'] 'zmpi', 'fake']
unique_depths = [0,1,2,3,4,2] pairs = zip([0,1,2,3,4,2,3], names)
non_unique_names = [ traversal = dag.preorder_traversal()
'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'libelf', self.assertListEqual([x.name for x in traversal], names)
'mpich', 'mpich']
non_unique_depths = [0,1,2,3,4,3,2,1]
self.assertListEqual( traversal = dag.preorder_traversal(depth=True)
[x.name for x in dag.preorder_traversal()], self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
unique_names)
self.assertListEqual(
[(x, y.name) for x,y in dag.preorder_traversal(depth=True)],
zip(unique_depths, unique_names))
self.assertListEqual( def test_unique_edge_traversal(self):
[x.name for x in dag.preorder_traversal(unique=False)], dag = Spec('mpileaks ^zmpi')
non_unique_names) dag.normalize()
self.assertListEqual( names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
[(x, y.name) for x,y in dag.preorder_traversal(unique=False, depth=True)], 'libelf', 'zmpi', 'fake', 'zmpi']
zip(non_unique_depths, non_unique_names)) pairs = zip([0,1,2,3,4,3,2,3,1], names)
traversal = dag.preorder_traversal(cover='edges')
self.assertListEqual([x.name for x in traversal], names)
traversal = dag.preorder_traversal(cover='edges', depth=True)
self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
def test_unique_path_traversal(self):
dag = Spec('mpileaks ^zmpi')
dag.normalize()
names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
'libelf', 'zmpi', 'fake', 'zmpi', 'fake']
pairs = zip([0,1,2,3,4,3,2,3,1,2], names)
traversal = dag.preorder_traversal(cover='paths')
self.assertListEqual([x.name for x in traversal], names)
traversal = dag.preorder_traversal(cover='paths', depth=True)
self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
def test_conflicting_spec_constraints(self): def test_conflicting_spec_constraints(self):
@ -270,3 +277,14 @@ def test_normalize_with_virtual_package(self):
Spec('mpi')), Spec('mpi')) Spec('mpi')), Spec('mpi'))
self.assertEqual(str(spec), str(expected_normalized)) self.assertEqual(str(spec), str(expected_normalized))
def test_contains(self):
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
self.assertIn(Spec('mpi'), spec)
self.assertIn(Spec('libelf'), spec)
self.assertIn(Spec('libelf@1.8.11'), spec)
self.assertNotIn(Spec('libelf@1.8.12'), spec)
self.assertIn(Spec('libdwarf'), spec)
self.assertNotIn(Spec('libgoblin'), spec)
self.assertIn(Spec('mpileaks'), spec)

View File

@ -113,3 +113,16 @@ def copy(self):
for key in self: for key in self:
clone[key] = self[key].copy() clone[key] = self[key].copy()
return clone return clone
def in_function(function_name):
"""True if the caller was called from some function with
the supplied Name, False otherwise."""
stack = inspect.stack()
try:
for elt in stack[2:]:
if elt[3] == function_name:
return True
return False
finally:
del stack