Repository: spack/spack

Issue:
Compilers with similar versions may be mixed up without warning
[IMPORTANT EDIT]: it turns out I was not using "@=" _in the compilers.yaml_ for that specific machine.
I think my comment about spack not using the string of the compiler actually being used still holds.

### Steps to reproduce

**Reproduce**
I defined two compilers: `clang@14.0.5` and **then** `clang@14.0.5.gcc.8.3.1`. Binary path used to be the same, flags are different.
Later on, to make investigation easier, I used a different path for both compilers.

Then I asked for `spack install blt%clang@=14.0.5.gcc.8.3.1`.

I am getting no warning, the clang binary detected is the good one (same in both compilers...), and the concretization, logs and path all use the string "clang@14.0.5.gcc.8.3.1" to describe the compiler. However **Spack is in fact using clang@14.0.5**.

There is one location where a warning is printed, and that’s the .spack/spack-build-out.txt.gz in the install directory.

To investigate the behavior, I added "print("self.compiler.cc is {0}".format(self.compiler.cc))" to the BLT packages install phase.

**Analysis**
The problem is two-fold:
- Spack does considers that `clang@14.0.5` is a match for `clang@14.0.5.gcc.8.3.1`, _even when using @=_.
- Spack uses the compiler spec from the command line everywhere instead of using the `spec` field of the compiler actually being used.

**Workaround:**
We can work around the issue by defining `clang@14.0.5.gcc.8.3.1` first in the compilers.yaml. Indeed, `clang@14.0.5.gcc.8.3.1` is not considered a match for `clang@14.0.5`.

@becker33 @scheibelp @trws @white238 

### Error message

The only location where a message can be found is in the `.spack/spack-build-out.txt`. I had to use a different path for both compilers for the issue to be clearly visible:

```
==> blt: Executing phase: 'install'
==> [2024-02-14-06:41:25.597029] Installing . to /usr/WS2/bernede1/Umpire/1685064-1707749560/spack-and-build-root/spack/opt/spack/linux-rhel7-power9le/**clang-12.0.1.gcc.8.3.1**/blt-0.6.1-iqs4sbjgw6utkcstv2pgelkpk6j7zopk
==> [2024-02-14-06:41:31.032484] Multiple definitions of compiler clang@=12.0.1.gcc.8.3.1 for architecture linux-rhel7-power9le:
 [clang(**/usr/tce/packages/clang/clang-12.0.1/bin/clang**
     /usr/tce/packages/clang/clang-12.0.1/bin/clang++
     /usr/tce/packages/xl/xl-2021.09.22/bin/xlf_r
     /usr/tce/packages/xl/xl-2021.09.22/bin/xlf2003_r
     []
     rhel7), clang(/usr/tce/packages/clang/clang-12.0.1-gcc-8.3.1/bin/clang
     /usr/tce/packages/clang/clang-12.0.1-gcc-8.3.1/bin/clang++
     /usr/tce/packages/xl/xl-2021.09.22/bin/xlf_r
     /usr/tce/packages/xl/xl-2021.09.22/bin/xlf2003_r
     []
     rhel7)]
self.compiler.cc is **/usr/tce/packages/clang/clang-12.0.1/bin/clang**
```

We can clearly see here that although I asked for the second compiler, I got the first one, but the install path still suggests that I got what I asked for (so does the concretized spec).

This is printed out if I run spec in debug mode, but no reason for me to do so when there is no warning or a clue about what is really happening.

### Information on your system

* **Spack:** 0.22.0.dev0 (7d4dcd52d96b593fb5e1557794ad57a7c5eb2304)
* **Python:** 3.7.2
* **Platform:** linux-rhel7-power9le
* **Concretizer:** clingo

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -514,9 +514,10 @@ def get_compilers(config, cspec=None, arch_spec=None):
     for items in config:
         items = items["compiler"]
 
-        # NOTE: in principle this should be equality not satisfies, but config can still
-        # be written in old format gcc@10.1.0 instead of gcc@=10.1.0.
-        if cspec and not cspec.satisfies(items["spec"]):
+        # We might use equality here.
+        if cspec and not spack.spec.parse_with_version_concrete(
+            items["spec"], compiler=True
+        ).satisfies(cspec):
             continue
 
         # If an arch spec is given, confirm that this compiler


Test patch:
diff --git a/lib/spack/spack/test/compilers/basics.py b/lib/spack/spack/test/compilers/basics.py
--- a/lib/spack/spack/test/compilers/basics.py
+++ b/lib/spack/spack/test/compilers/basics.py
@@ -11,7 +11,7 @@
 import llnl.util.filesystem as fs
 
 import spack.compiler
-import spack.compilers as compilers
+import spack.compilers
 import spack.spec
 import spack.util.environment
 from spack.compiler import Compiler
@@ -25,12 +25,14 @@ class MockOs:
             pass
 
         compiler_name = "gcc"
-        compiler_cls = compilers.class_for_compiler_name(compiler_name)
+        compiler_cls = spack.compilers.class_for_compiler_name(compiler_name)
         monkeypatch.setattr(compiler_cls, "cc_version", lambda x: version)
 
-        compiler_id = compilers.CompilerID(os=MockOs, compiler_name=compiler_name, version=None)
-        variation = compilers.NameVariation(prefix="", suffix="")
-        return compilers.DetectVersionArgs(
+        compiler_id = spack.compilers.CompilerID(
+            os=MockOs, compiler_name=compiler_name, version=None
+        )
+        variation = spack.compilers.NameVariation(prefix="", suffix="")
+        return spack.compilers.DetectVersionArgs(
             id=compiler_id, variation=variation, language="cc", path=path
         )
 
@@ -56,7 +58,7 @@ def test_multiple_conflicting_compiler_definitions(mutable_config):
     mutable_config.update_config("compilers", compiler_config)
 
     arch_spec = spack.spec.ArchSpec(("test", "test", "test"))
-    cmp = compilers.compiler_for_spec("clang@=0.0.0", arch_spec)
+    cmp = spack.compilers.compiler_for_spec("clang@=0.0.0", arch_spec)
     assert cmp.f77 == "f77"
 
 
@@ -64,7 +66,7 @@ def test_get_compiler_duplicates(config):
     # In this case there is only one instance of the specified compiler in
     # the test configuration (so it is not actually a duplicate), but the
     # method behaves the same.
-    cfg_file_to_duplicates = compilers.get_compiler_duplicates(
+    cfg_file_to_duplicates = spack.compilers.get_compiler_duplicates(
         "gcc@4.5.0", spack.spec.ArchSpec("cray-CNL-xeon")
     )
 
@@ -74,7 +76,7 @@ def test_get_compiler_duplicates(config):
 
 
 def test_all_compilers(config):
-    all_compilers = compilers.all_compilers()
+    all_compilers = spack.compilers.all_compilers()
     filtered = [x for x in all_compilers if str(x.spec) == "clang@=3.3"]
     filtered = [x for x in filtered if x.operating_system == "SuSE11"]
     assert len(filtered) == 1
@@ -88,7 +90,7 @@ def test_version_detection_is_empty(
     make_args_for_version, input_version, expected_version, expected_error
 ):
     args = make_args_for_version(version=input_version)
-    result, error = compilers.detect_version(args)
+    result, error = spack.compilers.detect_version(args)
     if not error:
         assert result.id.version == expected_version
 
@@ -104,7 +106,7 @@ def test_compiler_flags_from_config_are_grouped():
         "modules": None,
     }
 
-    compiler = compilers.compiler_from_dict(compiler_entry)
+    compiler = spack.compilers.compiler_from_dict(compiler_entry)
     assert any(x == "-foo-flag foo-val" for x in compiler.flags["cflags"])
 
 
@@ -288,7 +290,7 @@ def flag_value(flag, spec):
     else:
         compiler_entry = copy(default_compiler_entry)
         compiler_entry["spec"] = spec
-        compiler = compilers.compiler_from_dict(compiler_entry)
+        compiler = spack.compilers.compiler_from_dict(compiler_entry)
 
     return getattr(compiler, flag)
 
@@ -657,8 +659,8 @@ def test_xl_r_flags():
     [("gcc@4.7.2", False), ("clang@3.3", False), ("clang@8.0.0", True)],
 )
 def test_detecting_mixed_toolchains(compiler_spec, expected_result, config):
-    compiler = compilers.compilers_for_spec(compiler_spec).pop()
-    assert compilers.is_mixed_toolchain(compiler) is expected_result
+    compiler = spack.compilers.compilers_for_spec(compiler_spec).pop()
+    assert spack.compilers.is_mixed_toolchain(compiler) is expected_result
 
 
 @pytest.mark.regression("14798,13733")
@@ -737,6 +739,49 @@ def module(*args):
     assert version == test_version
 
 
+@pytest.mark.regression("42679")
+def test_get_compilers(config):
+    """Tests that we can select compilers whose versions differ only for a suffix."""
+    common = {
+        "flags": {},
+        "operating_system": "ubuntu23.10",
+        "target": "x86_64",
+        "modules": [],
+        "environment": {},
+        "extra_rpaths": [],
+    }
+    with_suffix = {
+        "spec": "gcc@13.2.0-suffix",
+        "paths": {
+            "cc": "/usr/bin/gcc-13.2.0-suffix",
+            "cxx": "/usr/bin/g++-13.2.0-suffix",
+            "f77": "/usr/bin/gfortran-13.2.0-suffix",
+            "fc": "/usr/bin/gfortran-13.2.0-suffix",
+        },
+        **common,
+    }
+    without_suffix = {
+        "spec": "gcc@13.2.0",
+        "paths": {
+            "cc": "/usr/bin/gcc-13.2.0",
+            "cxx": "/usr/bin/g++-13.2.0",
+            "f77": "/usr/bin/gfortran-13.2.0",
+            "fc": "/usr/bin/gfortran-13.2.0",
+        },
+        **common,
+    }
+
+    compilers = [{"compiler": without_suffix}, {"compiler": with_suffix}]
+
+    assert spack.compilers.get_compilers(
+        compilers, cspec=spack.spec.CompilerSpec("gcc@=13.2.0-suffix")
+    ) == [spack.compilers._compiler_from_config_entry(with_suffix)]
+
+    assert spack.compilers.get_compilers(
+        compilers, cspec=spack.spec.CompilerSpec("gcc@=13.2.0")
+    ) == [spack.compilers._compiler_from_config_entry(without_suffix)]
+
+
 def test_compiler_get_real_version_fails(working_env, monkeypatch, tmpdir):
     # Test variables
     test_version = "2.2.2"