From 3444d40ae2a5fe47f209f4c234992085b87c7bb9 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Fri, 28 Mar 2025 02:18:18 -0700 Subject: [PATCH] bugfix: pure `cxx` and `fortran` deps should display with a compiler. (#49752) I noticed that `abseil-cpp` was showing in `spack find` with "no compiler", and the only difference between it and other nodes was that it *only* depends on `cxx` -- others depend on `c` as well. It turns out that the `select()` method on `EdgeMap` only takes `Sequence[str]` and doesn't check whether they're actually just one `str`. So asking for, e.g., `cxx` is like asking for `c` or `x` or `x`, as the `str` is treated like a sequence. This causes Spack to miss `cxx` and `fortran` language virtuals in `DeprecatedCompilerSpec`. Signed-off-by: Todd Gamblin --- lib/spack/spack/spec.py | 15 +++++++++++---- lib/spack/spack/test/spec_semantics.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 0858c92ff83..b8b4727ac8a 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -1022,7 +1022,7 @@ def select( parent: Optional[str] = None, child: Optional[str] = None, depflag: dt.DepFlag = dt.ALL, - virtuals: Optional[Sequence[str]] = None, + virtuals: Optional[Union[str, Sequence[str]]] = None, ) -> List[DependencySpec]: """Selects a list of edges and returns them. @@ -1062,7 +1062,10 @@ def select( # Filter by virtuals if virtuals is not None: - selected = (dep for dep in selected if any(v in dep.virtuals for v in virtuals)) + if isinstance(virtuals, str): + selected = (dep for dep in selected if virtuals in dep.virtuals) + else: + selected = (dep for dep in selected if any(v in dep.virtuals for v in virtuals)) return list(selected) @@ -1602,7 +1605,11 @@ def edges_from_dependents( ] def edges_to_dependencies( - self, name=None, depflag: dt.DepFlag = dt.ALL, *, virtuals: Optional[Sequence[str]] = None + self, + name=None, + depflag: dt.DepFlag = dt.ALL, + *, + virtuals: Optional[Union[str, Sequence[str]]] = None, ) -> List[DependencySpec]: """Returns a list of edges connecting this node in the DAG to children. @@ -1644,7 +1651,7 @@ def dependencies( name=None, deptype: Union[dt.DepTypes, dt.DepFlag] = dt.ALL, *, - virtuals: Optional[Sequence[str]] = None, + virtuals: Optional[Union[str, Sequence[str]]] = None, ) -> List["Spec"]: """Returns a list of direct dependencies (nodes in the DAG) diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py index 382c852d8e0..fd159c0977d 100644 --- a/lib/spack/spack/test/spec_semantics.py +++ b/lib/spack/spack/test/spec_semantics.py @@ -1960,6 +1960,23 @@ def test_edge_equality_does_not_depend_on_virtual_order(): assert tuple(sorted(edge2.virtuals)) == edge1.virtuals +def test_virtual_queries_work_for_strings_and_lists(): + """Ensure that ``dependencies()`` works with both virtuals=str and virtuals=[str, ...].""" + parent, child = Spec("parent"), Spec("child") + parent._add_dependency( + child, depflag=dt.BUILD, virtuals=("cxx", "fortran") # multi-char dep names + ) + + assert not parent.dependencies(virtuals="c") # not in virtuals but shares a char with cxx + + for lang in ["cxx", "fortran"]: + assert parent.dependencies(virtuals=lang) # string arg + assert parent.edges_to_dependencies(virtuals=lang) # string arg + + assert parent.dependencies(virtuals=[lang]) # list arg + assert parent.edges_to_dependencies(virtuals=[lang]) # string arg + + def test_old_format_strings_trigger_error(default_mock_concretization): s = spack.concretize.concretize_one("pkg-a") with pytest.raises(SpecFormatStringError):