From 3dac78fc19e53a547779000ef8c27c7c96a44b91 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Sat, 20 Apr 2019 22:08:10 -0700 Subject: [PATCH] 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 --- lib/spack/spack/dependency.py | 7 +--- lib/spack/spack/package.py | 54 ++++++++++++++++++++------- lib/spack/spack/test/package_class.py | 52 ++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 lib/spack/spack/test/package_class.py diff --git a/lib/spack/spack/dependency.py b/lib/spack/spack/dependency.py index b8fb1771de..3e427196b5 100644 --- a/lib/spack/spack/dependency.py +++ b/lib/spack/spack/dependency.py @@ -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): diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 99dfa0be1c..226e0d1643 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -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 @@ -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. - """ - 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 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 diff --git a/lib/spack/spack/test/package_class.py b/lib/spack/spack/test/package_class.py new file mode 100644 index 0000000000..7ae5c64ec7 --- /dev/null +++ b/lib/spack/spack/test/package_class.py @@ -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(), + }