Skip to content

Commit

Permalink
package: make possible_dependencies consider deptypes
Browse files Browse the repository at this point in the history
- `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
  • Loading branch information
tgamblin committed Jun 5, 2019
1 parent 087a511 commit 3dac78f
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 16 deletions.
7 changes: 2 additions & 5 deletions lib/spack/spack/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,14 @@ def canonical_deptype(deptype):
raise ValueError('Invalid dependency type: %s' % 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]
if bad:
raise ValueError(
'Invalid dependency types: %s' % ','.join(str(t) for t in bad))
return tuple(sorted(deptype))

elif deptype is None:
raise ValueError('Invalid dependency type: None')

return deptype
raise ValueError('Invalid dependency type: %s' % repr(deptype))


class Dependency(object):
Expand Down
50 changes: 39 additions & 11 deletions lib/spack/spack/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import spack.store
import spack.compilers
import spack.directives
import spack.dependency
import spack.directory_layout
import spack.error
import spack.fetch_strategy as fs
Expand Down Expand Up @@ -530,39 +531,66 @@ def installed_upstream(self):

@classmethod
def possible_dependencies(
cls, transitive=True, expand_virtuals=True, visited=None):
"""Return set of possible transitive dependencies of this package.
Note: the set returned *includes* the package itself.
cls, transitive=True, expand_virtuals=True, deptype='all',
visited=None):
"""Return dict of possible dependencies of this package.
Args:
transitive (bool): return all transitive dependencies if True,
only direct dependencies if False.
expand_virtuals (bool): expand virtual dependencies into all
possible implementations.
deptype (str or tuple): dependency types to consider
visited (set): set of names of dependencies visited so far.
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 = set([cls.name])
visited = {cls.name: set()}

for i, name in enumerate(cls.dependencies):
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 expand_virtuals:
providers = spack.repo.path.providers_for(name)
dep_names = [spec.name for spec in providers]
else:
visited.add(name)
visited.setdefault(name, set())
continue
else:
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:
if dep_name not in visited:
visited.add(dep_name)
visited.setdefault(dep_name, set())
if transitive:
pkg = spack.repo.get(dep_name)
pkg.possible_dependencies(
transitive, expand_virtuals, visited)
dep_cls = spack.repo.path.get_pkg_class(dep_name)
dep_cls.possible_dependencies(
transitive, expand_virtuals, deptype, visited)

return visited

Expand Down
52 changes: 52 additions & 0 deletions lib/spack/spack/test/package_class.py
Original file line number Diff line number Diff line change
@@ -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(),
}

0 comments on commit 3dac78f

Please sign in to comment.