Repository: spack/spack

Issue:
Sticky variants seem to not be working with externals
### Steps to reproduce

Create environment file with an external cuda and clang that are unsupported together (clang@16.0.6 and cuda@12.2.2).

Partial `spack.yaml`:
```
  'compilers:':
  - compiler:
      environment: {}
      extra_rpaths: []
      flags:
        # Required as spack cannot recognize compiler-specific linker
        # flags in mixed toolchains
        ldlibs: -lgfortran
      modules: []
      operating_system: rhel7
      paths:
        cc: /usr/tce/packages/clang/clang-ibm-16.0.6-gcc-8.3.1/bin/clang
        cxx: /usr/tce/packages/clang/clang-ibm-16.0.6-gcc-8.3.1/bin/clang++
        f77: /usr/tce/packages/gcc/gcc-8.3.1/bin/gfortran
        fc: /usr/tce/packages/gcc/gcc-8.3.1/bin/gfortran
      spec: clang@16.0.6
      target: ppc64le

  packages:
    cuda:
      externals:
      - spec: cuda@12.2.2+allow-unsupported-compilers
        prefix: /usr/tce/packages/cuda/cuda-12.2.2
      buildable: false
```

```
spack -D <path to environment that has cuda+allow-unsupported-compilers> spec cuda
```


### Error message

Partial error:

```
==> Error: concretization failed for the following reasons:

   1. Attempted to use external for 'cuda' which does not satisfy any configured external spec
   2. umpire: '%clang@16:' conflicts with '+cuda ^cuda@:12.3~allow-unsupported-compilers'
   3. serac: '%clang@16:' conflicts with '+cuda ^cuda@:12.3~allow-unsupported-compilers'
   4. raja: '%clang@16:' conflicts with '+cuda ^cuda@:12.3~allow-unsupported-compilers'
   5. mfem: '%clang@16:' conflicts with '+cuda ^cuda@:12.3~allow-unsupported-compilers'
   6. camp: '%clang@16:' conflicts with '+cuda ^cuda@:12.3~allow-unsupported-compilers'
```

### Information on your system

Machine name: Lassen
OS: blueos

### 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:
I was able to narrow this down to being specific to externals. `spack spec cuda+allow-unsupported-compilers` works.
Exact same issue was reported by @finkandreas. Users expect the `buildable: false` +  `spec: x +sticky_variant` to be enough.
I have a candidate patch for this that I've given to @white238 to ensure it works for him, and I'll push that as a PR soon
I tested @becker33 's fix and it got around my concretization error.

Resolving patch:
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1863,15 +1863,12 @@ def external_packages(self):
             for local_idx, spec in enumerate(external_specs):
                 msg = "%s available as external when satisfying %s" % (spec.name, spec)
 
-                def external_imposition(input_spec, _):
-                    return [fn.attr("external_conditions_hold", input_spec.name, local_idx)]
+                def external_imposition(input_spec, requirements):
+                    return requirements + [
+                        fn.attr("external_conditions_hold", input_spec.name, local_idx)
+                    ]
 
-                self.condition(
-                    spec,
-                    spack.spec.Spec(spec.name),
-                    msg=msg,
-                    transform_imposed=external_imposition,
-                )
+                self.condition(spec, spec, msg=msg, transform_imposed=external_imposition)
                 self.possible_versions[spec.name].add(spec.version)
                 self.gen.newline()
 
diff --git a/var/spack/repos/builtin.mock/packages/sticky-variant-dependent/package.py b/var/spack/repos/builtin.mock/packages/sticky-variant-dependent/package.py
new file mode 100644
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/sticky-variant-dependent/package.py
@@ -0,0 +1,17 @@
+# Copyright 2013-2024 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.package import *
+
+
+class StickyVariantDependent(AutotoolsPackage):
+    """Package with a sticky variant and a conflict"""
+
+    homepage = "http://www.example.com"
+    url = "http://www.example.com/a-1.0.tar.gz"
+
+    version("1.0", md5="0123456789abcdef0123456789abcdef")
+
+    depends_on("sticky-variant")
+    conflicts("%gcc", when="^sticky-variant~allow-gcc")


Test patch:
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -1522,6 +1522,30 @@ def test_sticky_variant_in_package(self):
         s = Spec("sticky-variant %clang").concretized()
         assert s.satisfies("%clang") and s.satisfies("~allow-gcc")
 
+    @pytest.mark.regression("42172")
+    @pytest.mark.only_clingo("Original concretizer cannot use sticky variants")
+    @pytest.mark.parametrize(
+        "spec,allow_gcc",
+        [
+            ("sticky-variant@1.0+allow-gcc", True),
+            ("sticky-variant@1.0~allow-gcc", False),
+            ("sticky-variant@1.0", False),
+        ],
+    )
+    def test_sticky_variant_in_external(self, spec, allow_gcc):
+        # setup external for sticky-variant+allow-gcc
+        config = {"externals": [{"spec": spec, "prefix": "/fake/path"}], "buildable": False}
+        spack.config.set("packages:sticky-variant", config)
+
+        maybe = llnl.util.lang.nullcontext if allow_gcc else pytest.raises
+        with maybe(spack.error.SpackError):
+            s = Spec("sticky-variant-dependent%gcc").concretized()
+
+        if allow_gcc:
+            assert s.satisfies("%gcc")
+            assert s["sticky-variant"].satisfies("+allow-gcc")
+            assert s["sticky-variant"].external
+
     @pytest.mark.only_clingo("Use case not supported by the original concretizer")
     def test_do_not_invent_new_concrete_versions_unless_necessary(self):
         # ensure we select a known satisfying version rather than creating