refactor: spack.package.possible_dependencies() handles virtuals

- [x] move some logic for handling virtual packages from the `spack
  dependencies` command into `spack.package.possible_dependencies()`

- [x] rework possible dependencies tests so that expected and actual
  output are on the left/right respectively
This commit is contained in:
Todd Gamblin 2020-01-05 19:28:23 -08:00
parent 7cfa497912
commit 3255eaba0d
4 changed files with 75 additions and 44 deletions

View File

@ -9,6 +9,7 @@
import spack.cmd import spack.cmd
import spack.cmd.common.arguments as arguments import spack.cmd.common.arguments as arguments
import spack.environment as ev import spack.environment as ev
import spack.package
import spack.repo import spack.repo
import spack.store import spack.store
@ -52,22 +53,15 @@ def dependencies(parser, args):
else: else:
spec = specs[0] spec = specs[0]
dependencies = spack.package.possible_dependencies(
if not spec.virtual: spec,
packages = [spec.package] transitive=args.transitive,
else: expand_virtuals=args.expand_virtuals,
packages = [ deptype=args.deptype
spack.repo.get(s.name) )
for s in spack.repo.path.providers_for(spec)]
dependencies = set()
for pkg in packages:
possible = pkg.possible_dependencies(
args.transitive, args.expand_virtuals, deptype=args.deptype)
dependencies.update(possible)
if spec.name in dependencies: if spec.name in dependencies:
dependencies.remove(spec.name) del dependencies[spec.name]
if dependencies: if dependencies:
colify(sorted(dependencies)) colify(sorted(dependencies))

View File

@ -605,11 +605,10 @@ def possible_dependencies(
""" """
deptype = spack.dependency.canonical_deptype(deptype) deptype = spack.dependency.canonical_deptype(deptype)
if visited is None: visited = {} if visited is None else visited
visited = {cls.name: set()} missing = {} if missing is None else missing
if missing is None: visited.setdefault(cls.name, set())
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
@ -624,6 +623,7 @@ def possible_dependencies(
providers = spack.repo.path.providers_for(name) providers = spack.repo.path.providers_for(name)
dep_names = [spec.name for spec in providers] dep_names = [spec.name for spec in providers]
else: else:
visited.setdefault(cls.name, set()).add(name)
visited.setdefault(name, set()) visited.setdefault(name, set())
continue continue
else: else:
@ -2157,13 +2157,20 @@ def possible_dependencies(*pkg_or_spec, **kwargs):
packages = [] packages = []
for pos in pkg_or_spec: for pos in pkg_or_spec:
if isinstance(pos, PackageMeta): if isinstance(pos, PackageMeta):
pkg = pos packages.append(pos)
elif isinstance(pos, spack.spec.Spec): continue
pkg = pos.package
else:
pkg = spack.spec.Spec(pos).package
packages.append(pkg) if not isinstance(pos, spack.spec.Spec):
pos = spack.spec.Spec(pos)
if spack.repo.path.is_virtual(pos.name):
packages.extend(
p.package_class
for p in spack.repo.path.providers_for(pos.name)
)
continue
else:
packages.append(pos.package_class)
visited = {} visited = {}
for pkg in packages: for pkg in packages:

View File

@ -17,7 +17,7 @@
mpi_deps = ['fake'] mpi_deps = ['fake']
def test_immediate_dependencies(mock_packages): def test_direct_dependencies(mock_packages):
out = dependencies('mpileaks') out = dependencies('mpileaks')
actual = set(re.split(r'\s+', out.strip())) actual = set(re.split(r'\s+', out.strip()))
expected = set(['callpath'] + mpis) expected = set(['callpath'] + mpis)
@ -47,7 +47,7 @@ def test_transitive_dependencies_with_deptypes(mock_packages):
@pytest.mark.db @pytest.mark.db
def test_immediate_installed_dependencies(mock_packages, database): def test_direct_installed_dependencies(mock_packages, database):
with color_when(False): with color_when(False):
out = dependencies('--installed', 'mpileaks^mpich') out = dependencies('--installed', 'mpileaks^mpich')

View File

@ -11,12 +11,17 @@
""" """
import pytest import pytest
import spack.package
import spack.repo import spack.repo
@pytest.fixture @pytest.fixture(scope="module")
def mpileaks_possible_deps(mock_packages): def mpi_names(mock_repo_path):
mpi_names = [spec.name for spec in spack.repo.path.providers_for('mpi')] return [spec.name for spec in mock_repo_path.providers_for('mpi')]
@pytest.fixture()
def mpileaks_possible_deps(mock_packages, mpi_names):
possible = { possible = {
'callpath': set(['dyninst'] + mpi_names), 'callpath': set(['dyninst'] + mpi_names),
'dyninst': set(['libdwarf', 'libelf']), 'dyninst': set(['libdwarf', 'libelf']),
@ -34,47 +39,72 @@ def mpileaks_possible_deps(mock_packages):
def test_possible_dependencies(mock_packages, mpileaks_possible_deps): def test_possible_dependencies(mock_packages, mpileaks_possible_deps):
mpileaks = spack.repo.get('mpileaks') mpileaks = spack.repo.get('mpileaks')
assert (mpileaks.possible_dependencies(expand_virtuals=True) == assert mpileaks_possible_deps == (
mpileaks_possible_deps) mpileaks.possible_dependencies(expand_virtuals=True))
assert mpileaks.possible_dependencies(expand_virtuals=False) == { assert {
'callpath': set(['dyninst']), 'callpath': set(['dyninst', 'mpi']),
'dyninst': set(['libdwarf', 'libelf']), 'dyninst': set(['libdwarf', 'libelf']),
'libdwarf': set(['libelf']), 'libdwarf': set(['libelf']),
'libelf': set(), 'libelf': set(),
'mpi': set(), 'mpi': set(),
'mpileaks': set(['callpath']), 'mpileaks': set(['callpath', 'mpi']),
} } == mpileaks.possible_dependencies(expand_virtuals=False)
def test_possible_direct_dependencies(mock_packages, mpileaks_possible_deps):
mpileaks = spack.repo.get('mpileaks')
deps = mpileaks.possible_dependencies(transitive=False,
expand_virtuals=False)
assert {
'callpath': set(),
'mpi': set(),
'mpileaks': set(['callpath', 'mpi']),
} == deps
def test_possible_dependencies_virtual(mock_packages, mpi_names):
expected = dict(
(name, set(spack.repo.get(name).dependencies))
for name in mpi_names
)
# only one mock MPI has a dependency
expected['fake'] = set()
assert expected == spack.package.possible_dependencies(
"mpi", transitive=False)
def test_possible_dependencies_missing(mock_packages): def test_possible_dependencies_missing(mock_packages):
md = spack.repo.get("missing-dependency") md = spack.repo.get("missing-dependency")
missing = {} missing = {}
md.possible_dependencies(transitive=True, missing=missing) md.possible_dependencies(transitive=True, missing=missing)
assert missing["missing-dependency"] == set([ assert set([
"this-is-a-missing-dependency" "this-is-a-missing-dependency"
]) ]) == missing["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')
assert dtbuild1.possible_dependencies(deptype=('link', 'run')) == { assert {
'dtbuild1': set(['dtrun2', 'dtlink2']), 'dtbuild1': set(['dtrun2', 'dtlink2']),
'dtlink2': set(), 'dtlink2': set(),
'dtrun2': set(), 'dtrun2': set(),
} } == dtbuild1.possible_dependencies(deptype=('link', 'run'))
assert dtbuild1.possible_dependencies(deptype=('build')) == { assert {
'dtbuild1': set(['dtbuild2', 'dtlink2']), 'dtbuild1': set(['dtbuild2', 'dtlink2']),
'dtbuild2': set(), 'dtbuild2': set(),
'dtlink2': set(), 'dtlink2': set(),
} } == dtbuild1.possible_dependencies(deptype=('build'))
assert dtbuild1.possible_dependencies(deptype=('link')) == { assert {
'dtbuild1': set(['dtlink2']), 'dtbuild1': set(['dtlink2']),
'dtlink2': set(), 'dtlink2': set(),
} } == dtbuild1.possible_dependencies(deptype=('link'))
def test_possible_dependencies_with_multiple_classes( def test_possible_dependencies_with_multiple_classes(
@ -88,4 +118,4 @@ def test_possible_dependencies_with_multiple_classes(
'dt-diamond-bottom': set(), 'dt-diamond-bottom': set(),
}) })
assert spack.package.possible_dependencies(*pkgs) == expected assert expected == spack.package.possible_dependencies(*pkgs)