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:
		@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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(),
 | 
			
		||||
    }
 | 
			
		||||
		Reference in New Issue
	
	Block a user