From 531f370e0d3256202e1eb40dce669e8ae2ebb15a Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 2 Dec 2019 01:01:11 -0800 Subject: 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. --- lib/spack/spack/package.py | 54 ++++++++++++++++------ lib/spack/spack/test/package_class.py | 9 ++++ .../packages/missing-dependency/package.py | 21 +++++++++ 3 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 var/spack/repos/builtin.mock/packages/missing-dependency/package.py diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index c6dc39954c..5128a1e17f 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -556,16 +556,19 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)): @classmethod def possible_dependencies( cls, transitive=True, expand_virtuals=True, deptype='all', - visited=None): + visited=None, missing=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. + transitive (bool, optional): return all transitive dependencies if + True, only direct dependencies if False (default True).. + expand_virtuals (bool, optional): expand virtual dependencies into + all possible implementations (default True) + deptype (str or tuple, optional): dependency types to consider + 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: (dict): dictionary mapping dependency names to *their* @@ -576,7 +579,12 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)): *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 + 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. @@ -586,6 +594,9 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)): if visited is None: visited = {cls.name: set()} + if missing is None: + missing = {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()] @@ -609,12 +620,24 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)): # recursively traverse dependencies for dep_name in dep_names: - if dep_name not in visited: - visited.setdefault(dep_name, set()) - if transitive: - dep_cls = spack.repo.path.get_pkg_class(dep_name) - dep_cls.possible_dependencies( - transitive, expand_virtuals, deptype, visited) + if dep_name in visited: + continue + + visited.setdefault(dep_name, set()) + + # 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 @@ -2671,6 +2694,7 @@ def possible_dependencies(*pkg_or_spec, **kwargs): transitive = kwargs.get('transitive', True) expand_virtuals = kwargs.get('expand_virtuals', True) deptype = kwargs.get('deptype', 'all') + missing = kwargs.get('missing') packages = [] for pos in pkg_or_spec: @@ -2686,7 +2710,7 @@ def possible_dependencies(*pkg_or_spec, **kwargs): visited = {} for pkg in packages: pkg.possible_dependencies( - transitive, expand_virtuals, deptype, visited) + transitive, expand_virtuals, deptype, visited, missing) return visited diff --git a/lib/spack/spack/test/package_class.py b/lib/spack/spack/test/package_class.py index 521bd9d54a..25c9258759 100644 --- a/lib/spack/spack/test/package_class.py +++ b/lib/spack/spack/test/package_class.py @@ -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): dtbuild1 = spack.repo.get('dtbuild1') diff --git a/var/spack/repos/builtin.mock/packages/missing-dependency/package.py b/var/spack/repos/builtin.mock/packages/missing-dependency/package.py new file mode 100644 index 0000000000..e40e1b2a8b --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/missing-dependency/package.py @@ -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") -- cgit v1.2.3-60-g2f50