Repository: spack/spack

Issue:
Environment: external and %gcc fails view update
### Steps to reproduce

Create an env like:

```
spack:
  # add package specs to the `specs` list
  specs:
  - zlib
  packages:
    zlib-api:
      buildable: false
    zlib:
      externals:
      - spec: zlib@1.2.11
        prefix: /usr/
  view: true
  concretizer:
    unify: true
```

and then run 

```
$ spack concretize -f -U
$ spack install
[+] /usr/ (external zlib-1.2.11-o5v2wczqppwk56x4kkfdu6556em2jnys)
==> Updating view at <spack-prefix>/var/spack/environments/t1/.spack-env/view
==> Warning: Skipping external package: zlib@1.2.11%gcc@10.3.1+optimize+pic+shared build_system=makefile arch=linux-rhel8-broadwell/o5v2wcz
==> Error: [Errno 2] No such file or directory: '<spack-prefix>/opt/spack/linux-rhel8-broadwell/gcc-10.3.1/gcc-runtime-10.3.1-wnbqufepp7kyfd32a3mvkxfpkgm4cerd/'
```

it looks like:

* The installation did not actually install a `gcc-runtime` package
* The view is trying to add it anyway

I assume the core issue is that gcc-runtime should actually be getting installed.

### Error message

When running with `spack -d...`:

```
Traceback (most recent call last):
  File "<spack-prefix>/bin/spack", line 52, in <module>
    sys.exit(main())
  File "<spack-prefix>/lib/spack/spack_installable/main.py", line 42, in main
    sys.exit(spack.main.main(argv))
  File "<spack-prefix>/lib/spack/spack/main.py", line 1066, in main
    return _main(argv)
  File "<spack-prefix>/lib/spack/spack/main.py", line 1019, in _main
    return finish_parse_and_run(parser, cmd_name, args, env_format_error)
  File "<spack-prefix>/lib/spack/spack/main.py", line 1049, in finish_parse_and_run
    return _invoke_command(command, parser, args, unknown)
  File "<spack-prefix>/lib/spack/spack/main.py", line 648, in _invoke_command
    return_val = command(parser, args)
  File "<spack-prefix>/lib/spack/spack/cmd/install.py", line 355, in install
    install_with_active_env(env, args, install_kwargs, reporter_factory)
  File "<spack-prefix>/lib/spack/spack/cmd/install.py", line 429, in install_with_active_env
    env.write(regenerate=True)
  File "<spack-prefix>/lib/spack/spack/environment/environment.py", line 2255, in write
    self.regenerate_views()
  File "<spack-prefix>/lib/spack/spack/environment/environment.py", line 1709, in regenerate_views
    view.regenerate(self.concrete_roots())
  File "<spack-prefix>/lib/spack/spack/environment/environment.py", line 717, in regenerate
    view.add_specs(*specs, with_dependencies=False)
  File "<spack-prefix>/lib/spack/spack/filesystem_view.py", line 686, in add_specs
    visit_directory_tree(src_prefix, visitor)
  File "<spack-prefix>/lib/spack/llnl/util/filesystem.py", line 1496, in visit_directory_tree
    dir_entries = sorted(os.scandir(dir), key=lambda d: d.name)
FileNotFoundError: [Errno 2] No such file or directory: '<spack-prefix>/opt/spack/linux-rhel8-broadwell/gcc-10.3.1/gcc-runtime-10.3.1-wnbqufepp7kyfd32a3mvkxfpkgm4cerd/'
```

### Information on your system

* **Spack:** 0.22.0.dev0 (861979a24f554130e569db26d32d580f0fa177a0)
* **Python:** 3.9.12
* **Platform:** linux-rhel8-broadwell
* **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:
> I assume the core issue is that gcc-runtime should actually be getting installed.

the issue is that externals depend on `gcc-runtime` too (and the installer probably assumes externals have no installable deps)

@alalazo is that a bug or intentional?

```
==> Concretized zlib
[e]  atsd7wc  zlib@1.2.11%gcc@13.2.0+optimize+pic+shared build_system=makefile arch=linux-ubuntu23.10-zen2
[+]  g7lop5r      ^gcc-runtime@13.2.0%gcc@13.2.0 build_system=generic arch=linux-ubuntu23.10-zen2
```

That was intentional in the encoding, but if it causes issues and we want to special case externals it should be easy to exclude them from the rule.

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
@@ -2927,6 +2927,7 @@ def depends_on(self, dependency_str: str, *, when: str, type: str, description:
         body_clauses = self._setup.spec_clauses(when_spec, body=True)
         body_str = (
             f"  {f',{os.linesep}  '.join(str(x) for x in body_clauses)},\n"
+            f"  not external({node_variable}),\n"
             f"  not runtime(Package)"
         ).replace(f'"{placeholder}"', f"{node_variable}")
         head_clauses = self._setup.spec_clauses(dependency_spec, body=False)


Test patch:
diff --git a/lib/spack/spack/test/concretize_compiler_runtimes.py b/lib/spack/spack/test/concretize_compiler_runtimes.py
--- a/lib/spack/spack/test/concretize_compiler_runtimes.py
+++ b/lib/spack/spack/test/concretize_compiler_runtimes.py
@@ -13,7 +13,10 @@
 import spack.spec
 from spack.version import Version
 
-pytestmark = [pytest.mark.only_clingo("Original concretizer does not support compiler runtimes")]
+pytestmark = [
+    pytest.mark.only_clingo("Original concretizer does not support compiler runtimes"),
+    pytest.mark.usefixtures("enable_runtimes"),
+]
 
 
 @pytest.fixture
@@ -31,7 +34,7 @@ def enable_runtimes():
     spack.solver.asp.WITH_RUNTIME = original
 
 
-def test_correct_gcc_runtime_is_injected_as_dependency(runtime_repo, enable_runtimes):
+def test_correct_gcc_runtime_is_injected_as_dependency(runtime_repo):
     s = spack.spec.Spec("a%gcc@10.2.1 ^b%gcc@4.5.0").concretized()
     a, b = s["a"], s["b"]
 
@@ -40,3 +43,20 @@ def test_correct_gcc_runtime_is_injected_as_dependency(runtime_repo, enable_runt
 
     # And the gcc-runtime version should be that of the newest gcc used in the dag.
     assert a["gcc-runtime"].version == Version("10.2.1")
+
+
+@pytest.mark.regression("41972")
+def test_external_nodes_do_not_have_runtimes(runtime_repo, mutable_config, tmp_path):
+    """Tests that external nodes don't have runtime dependencies."""
+
+    packages_yaml = {"b": {"externals": [{"spec": "b@1.0", "prefix": f"{str(tmp_path)}"}]}}
+    spack.config.set("packages", packages_yaml)
+
+    s = spack.spec.Spec("a%gcc@10.2.1").concretized()
+
+    a, b = s["a"], s["b"]
+
+    # Since b is an external, it doesn't depend on gcc-runtime
+    assert a.dependencies("gcc-runtime")
+    assert a.dependencies("b")
+    assert not b.dependencies("gcc-runtime")