package: make possible_dependencies consider deptypes
- `PackageBase.possible_dependencies` now: - accepts a deptype param that controls dependency types traversed - returns a dict mapping possible depnames to their immediate possible dependencies (this lets you build a graph easily) - Add tests for PackageBaes
This commit is contained in:
parent
087a511da7
commit
3dac78fc19
3 changed files with 95 additions and 18 deletions
|
@ -34,17 +34,14 @@ def canonical_deptype(deptype):
|
||||||
raise ValueError('Invalid dependency type: %s' % deptype)
|
raise ValueError('Invalid dependency type: %s' % deptype)
|
||||||
return (deptype,)
|
return (deptype,)
|
||||||
|
|
||||||
elif isinstance(deptype, (tuple, list)):
|
elif isinstance(deptype, (tuple, list, set)):
|
||||||
bad = [d for d in deptype if d not in all_deptypes]
|
bad = [d for d in deptype if d not in all_deptypes]
|
||||||
if bad:
|
if bad:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Invalid dependency types: %s' % ','.join(str(t) for t in bad))
|
'Invalid dependency types: %s' % ','.join(str(t) for t in bad))
|
||||||
return tuple(sorted(deptype))
|
return tuple(sorted(deptype))
|
||||||
|
|
||||||
elif deptype is None:
|
raise ValueError('Invalid dependency type: %s' % repr(deptype))
|
||||||
raise ValueError('Invalid dependency type: None')
|
|
||||||
|
|
||||||
return deptype
|
|
||||||
|
|
||||||
|
|
||||||
class Dependency(object):
|
class Dependency(object):
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
import spack.directives
|
import spack.directives
|
||||||
|
import spack.dependency
|
||||||
import spack.directory_layout
|
import spack.directory_layout
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.fetch_strategy as fs
|
import spack.fetch_strategy as fs
|
||||||
|
@ -530,39 +531,66 @@ def installed_upstream(self):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def possible_dependencies(
|
def possible_dependencies(
|
||||||
cls, transitive=True, expand_virtuals=True, visited=None):
|
cls, transitive=True, expand_virtuals=True, deptype='all',
|
||||||
"""Return set of possible transitive dependencies of this package.
|
visited=None):
|
||||||
|
"""Return dict of possible dependencies of this package.
|
||||||
Note: the set returned *includes* the package itself.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
transitive (bool): return all transitive dependencies if True,
|
transitive (bool): return all transitive dependencies if True,
|
||||||
only direct dependencies if False.
|
only direct dependencies if False.
|
||||||
expand_virtuals (bool): expand virtual dependencies into all
|
expand_virtuals (bool): expand virtual dependencies into all
|
||||||
possible implementations.
|
possible implementations.
|
||||||
|
deptype (str or tuple): dependency types to consider
|
||||||
visited (set): set of names of dependencies visited so far.
|
visited (set): set of names of dependencies visited so far.
|
||||||
"""
|
|
||||||
if visited is None:
|
|
||||||
visited = set([cls.name])
|
|
||||||
|
|
||||||
for i, name in enumerate(cls.dependencies):
|
Returns:
|
||||||
|
(dict): dictionary mapping dependency names to *their*
|
||||||
|
immediate dependencies
|
||||||
|
|
||||||
|
Each item in the returned dictionary maps a (potentially
|
||||||
|
transitive) dependency of this package to its possible
|
||||||
|
*immediate* dependencies. If ``expand_virtuals`` is ``False``,
|
||||||
|
virtual package names wil be inserted as keys mapped to empty
|
||||||
|
sets of dependencies. Virtuals, if not expanded, are treated as
|
||||||
|
though they have no immediate dependencies
|
||||||
|
|
||||||
|
Note: the returned dict *includes* the package itself.
|
||||||
|
|
||||||
|
"""
|
||||||
|
deptype = spack.dependency.canonical_deptype(deptype)
|
||||||
|
|
||||||
|
if visited is None:
|
||||||
|
visited = {cls.name: set()}
|
||||||
|
|
||||||
|
for name, conditions in cls.dependencies.items():
|
||||||
|
# check whether this dependency could be of the type asked for
|
||||||
|
types = [dep.type for cond, dep in conditions.items()]
|
||||||
|
types = set.union(*types)
|
||||||
|
if not any(d in types for d in deptype):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# expand virtuals if enabled, otherwise just stop at virtuals
|
||||||
if spack.repo.path.is_virtual(name):
|
if spack.repo.path.is_virtual(name):
|
||||||
if expand_virtuals:
|
if expand_virtuals:
|
||||||
providers = spack.repo.path.providers_for(name)
|
providers = spack.repo.path.providers_for(name)
|
||||||
dep_names = [spec.name for spec in providers]
|
dep_names = [spec.name for spec in providers]
|
||||||
else:
|
else:
|
||||||
visited.add(name)
|
visited.setdefault(name, set())
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
dep_names = [name]
|
dep_names = [name]
|
||||||
|
|
||||||
|
# add the dependency names to the visited dict
|
||||||
|
visited.setdefault(cls.name, set()).update(set(dep_names))
|
||||||
|
|
||||||
|
# recursively traverse dependencies
|
||||||
for dep_name in dep_names:
|
for dep_name in dep_names:
|
||||||
if dep_name not in visited:
|
if dep_name not in visited:
|
||||||
visited.add(dep_name)
|
visited.setdefault(dep_name, set())
|
||||||
if transitive:
|
if transitive:
|
||||||
pkg = spack.repo.get(dep_name)
|
dep_cls = spack.repo.path.get_pkg_class(dep_name)
|
||||||
pkg.possible_dependencies(
|
dep_cls.possible_dependencies(
|
||||||
transitive, expand_virtuals, visited)
|
transitive, expand_virtuals, deptype, visited)
|
||||||
|
|
||||||
return visited
|
return visited
|
||||||
|
|
||||||
|
|
52
lib/spack/spack/test/package_class.py
Normal file
52
lib/spack/spack/test/package_class.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
"""Test class methods on Package objects.
|
||||||
|
|
||||||
|
This doesn't include methods on package *instances* (like do_install(),
|
||||||
|
etc.). Only methods like ``possible_dependencies()`` that deal with the
|
||||||
|
static DSL metadata for packages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import spack.repo
|
||||||
|
|
||||||
|
|
||||||
|
def test_possible_dependencies(mock_packages):
|
||||||
|
mpileaks = spack.repo.get('mpileaks')
|
||||||
|
mpi_names = [spec.name for spec in spack.repo.path.providers_for('mpi')]
|
||||||
|
|
||||||
|
assert mpileaks.possible_dependencies() == {
|
||||||
|
'callpath': set(['dyninst'] + mpi_names),
|
||||||
|
'dyninst': set(['libdwarf', 'libelf']),
|
||||||
|
'fake': set(),
|
||||||
|
'libdwarf': set(['libelf']),
|
||||||
|
'libelf': set(),
|
||||||
|
'mpich': set(),
|
||||||
|
'mpich2': set(),
|
||||||
|
'mpileaks': set(['callpath'] + mpi_names),
|
||||||
|
'multi-provider-mpi': set(),
|
||||||
|
'zmpi': set(['fake']),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_possible_dependencies_with_deptypes(mock_packages):
|
||||||
|
dtbuild1 = spack.repo.get('dtbuild1')
|
||||||
|
|
||||||
|
assert dtbuild1.possible_dependencies(deptype=('link', 'run')) == {
|
||||||
|
'dtbuild1': set(['dtrun2', 'dtlink2']),
|
||||||
|
'dtlink2': set(),
|
||||||
|
'dtrun2': set(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert dtbuild1.possible_dependencies(deptype=('build')) == {
|
||||||
|
'dtbuild1': set(['dtbuild2', 'dtlink2']),
|
||||||
|
'dtbuild2': set(),
|
||||||
|
'dtlink2': set(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert dtbuild1.possible_dependencies(deptype=('link')) == {
|
||||||
|
'dtbuild1': set(['dtlink2']),
|
||||||
|
'dtlink2': set(),
|
||||||
|
}
|
Loading…
Reference in a new issue