possible_dependencies() now reports missing dependencies

- Add an optional argument so that `possible_dependencies()` will report
  missing dependencies.
- Add a test to ensure it works.
- Ignore missing dependencies in `possible_dependencies()` by default.
This commit is contained in:
Todd Gamblin 2019-12-02 01:01:11 -08:00
parent 81b147cc0a
commit 531f370e0d
3 changed files with 69 additions and 15 deletions

View File

@ -556,16 +556,19 @@ def installed_upstream(self):
@classmethod @classmethod
def possible_dependencies( def possible_dependencies(
cls, transitive=True, expand_virtuals=True, deptype='all', cls, transitive=True, expand_virtuals=True, deptype='all',
visited=None): visited=None, missing=None):
"""Return dict of possible dependencies of this package. """Return dict of possible dependencies of this package.
Args: Args:
transitive (bool): return all transitive dependencies if True, transitive (bool, optional): return all transitive dependencies if
only direct dependencies if False. True, only direct dependencies if False (default True)..
expand_virtuals (bool): expand virtual dependencies into all expand_virtuals (bool, optional): expand virtual dependencies into
possible implementations. all possible implementations (default True)
deptype (str or tuple): dependency types to consider deptype (str or tuple, optional): dependency types to consider
visited (set): set of names of dependencies visited so far. visited (dicct, optional): dict of names of dependencies visited so
far, mapped to their immediate dependencies' names.
missing (dict, optional): dict to populate with packages and their
*missing* dependencies.
Returns: Returns:
(dict): dictionary mapping dependency names to *their* (dict): dictionary mapping dependency names to *their*
@ -576,7 +579,12 @@ def possible_dependencies(
*immediate* dependencies. If ``expand_virtuals`` is ``False``, *immediate* dependencies. If ``expand_virtuals`` is ``False``,
virtual package names wil be inserted as keys mapped to empty virtual package names wil be inserted as keys mapped to empty
sets of dependencies. Virtuals, if not expanded, are treated as sets of dependencies. Virtuals, if not expanded, are treated as
though they have no immediate dependencies though they have no immediate dependencies.
Missing dependencies by default are ignored, but if a
missing dict is provided, it will be populated with package names
mapped to any dependencies they have that are in no
repositories. This is only populated if transitive is True.
Note: the returned dict *includes* the package itself. Note: the returned dict *includes* the package itself.
@ -586,6 +594,9 @@ def possible_dependencies(
if visited is None: if visited is None:
visited = {cls.name: set()} visited = {cls.name: set()}
if missing is None:
missing = {cls.name: set()}
for name, conditions in cls.dependencies.items(): for name, conditions in cls.dependencies.items():
# check whether this dependency could be of the type asked for # check whether this dependency could be of the type asked for
types = [dep.type for cond, dep in conditions.items()] types = [dep.type for cond, dep in conditions.items()]
@ -609,12 +620,24 @@ def possible_dependencies(
# recursively traverse dependencies # recursively traverse dependencies
for dep_name in dep_names: for dep_name in dep_names:
if dep_name not in visited: if dep_name in visited:
visited.setdefault(dep_name, set()) continue
if transitive:
dep_cls = spack.repo.path.get_pkg_class(dep_name) visited.setdefault(dep_name, set())
dep_cls.possible_dependencies(
transitive, expand_virtuals, deptype, visited) # skip the rest if not transitive
if not transitive:
continue
try:
dep_cls = spack.repo.path.get_pkg_class(dep_name)
except spack.repo.UnknownPackageError:
# log unknown packages
missing.setdefault(cls.name, set()).add(dep_name)
continue
dep_cls.possible_dependencies(
transitive, expand_virtuals, deptype, visited, missing)
return visited return visited
@ -2671,6 +2694,7 @@ def possible_dependencies(*pkg_or_spec, **kwargs):
transitive = kwargs.get('transitive', True) transitive = kwargs.get('transitive', True)
expand_virtuals = kwargs.get('expand_virtuals', True) expand_virtuals = kwargs.get('expand_virtuals', True)
deptype = kwargs.get('deptype', 'all') deptype = kwargs.get('deptype', 'all')
missing = kwargs.get('missing')
packages = [] packages = []
for pos in pkg_or_spec: for pos in pkg_or_spec:
@ -2686,7 +2710,7 @@ def possible_dependencies(*pkg_or_spec, **kwargs):
visited = {} visited = {}
for pkg in packages: for pkg in packages:
pkg.possible_dependencies( pkg.possible_dependencies(
transitive, expand_virtuals, deptype, visited) transitive, expand_virtuals, deptype, visited, missing)
return visited return visited

View File

@ -47,6 +47,15 @@ def test_possible_dependencies(mock_packages, mpileaks_possible_deps):
} }
def test_possible_dependencies_missing(mock_packages):
md = spack.repo.get("missing-dependency")
missing = {}
md.possible_dependencies(transitive=True, missing=missing)
assert missing["missing-dependency"] == set([
"this-is-a-missing-dependency"
])
def test_possible_dependencies_with_deptypes(mock_packages): def test_possible_dependencies_with_deptypes(mock_packages):
dtbuild1 = spack.repo.get('dtbuild1') dtbuild1 = spack.repo.get('dtbuild1')

View File

@ -0,0 +1,21 @@
# 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)
from spack import *
class MissingDependency(Package):
"""Package with a dependency that does not exist."""
homepage = "http://www.example.com"
url = "http://www.example.com/missing-dependency-1.0.tar.gz"
version('1.0', '0123456789abcdef0123456789abcdef')
# intentionally missing to test possible_dependencies()
depends_on("this-is-a-missing-dependency")
# this one is a "real" mock dependency
depends_on("a")