Repository: spack/spack

Issue:
Module generation error when computing Lmod path hierarchy
### Steps to reproduce the issue

I was trying to `spack install gcc`.

```console
$ spack spec -I gcc
Input spec
--------------------------------
 -   gcc

Concretized
--------------------------------
 -   gcc@12.2.0%apple-clang@14.0.0~binutils+bootstrap~graphite~nvptx~piclibs~strip build_system=autotools build_type=RelWithDebInfo languages=c,c++,fortran arch=darwin-ventura-skylake
[+]      ^diffutils@3.8%apple-clang@14.0.0 build_system=autotools arch=darwin-ventura-skylake
[+]      ^gawk@5.2.1%apple-clang@14.0.0~nls build_system=autotools arch=darwin-ventura-skylake
[+]      ^gmp@6.2.1%apple-clang@14.0.0 build_system=autotools libs=shared,static arch=darwin-ventura-skylake
[+]          ^autoconf@2.69%apple-clang@14.0.0 build_system=autotools patches=35c4492,7793209,a49dd5b arch=darwin-ventura-skylake
[+]              ^perl@5.36.0%apple-clang@14.0.0+cpanm+shared+threads build_system=generic arch=darwin-ventura-skylake
[+]                  ^berkeley-db@18.1.40%apple-clang@14.0.0+cxx~docs+stl build_system=autotools patches=26090f4,b231fcc arch=darwin-ventura-skylake
[+]                  ^bzip2@1.0.8%apple-clang@14.0.0~debug~pic+shared build_system=generic arch=darwin-ventura-skylake
[+]                  ^gdbm@1.23%apple-clang@14.0.0 build_system=autotools arch=darwin-ventura-skylake
[+]                      ^readline@8.2%apple-clang@14.0.0 build_system=autotools patches=bbf97f1 arch=darwin-ventura-skylake
[+]                          ^ncurses@6.3%apple-clang@14.0.0~symlinks+termlib abi=none build_system=autotools arch=darwin-ventura-skylake
[+]                              ^pkg-config@0.29.2%apple-clang@14.0.0+internal_glib build_system=autotools arch=darwin-ventura-skylake
[+]          ^automake@1.16.5%apple-clang@14.0.0 build_system=autotools arch=darwin-ventura-skylake
[+]          ^m4@1.4.6%apple-clang@14.0.0+sigsegv build_system=autotools arch=darwin-ventura-skylake
[+]      ^libiconv@1.16%apple-clang@14.0.0 build_system=autotools libs=shared,static arch=darwin-ventura-skylake
[+]      ^libtool@2.4.7%apple-clang@14.0.0 build_system=autotools arch=darwin-ventura-skylake
[+]      ^mpc@1.2.1%apple-clang@14.0.0 build_system=autotools libs=shared,static arch=darwin-ventura-skylake
[+]      ^mpfr@4.1.0%apple-clang@14.0.0 build_system=autotools libs=shared,static arch=darwin-ventura-skylake
[+]          ^autoconf-archive@2022.02.11%apple-clang@14.0.0 build_system=autotools patches=139214f arch=darwin-ventura-skylake
[+]      ^texinfo@5.2%apple-clang@14.0.0 build_system=autotools patches=8554f52 arch=darwin-ventura-skylake
[+]      ^zlib@1.2.13%apple-clang@14.0.0+optimize+pic+shared build_system=makefile arch=darwin-ventura-skylake
[+]      ^zstd@1.5.2%apple-clang@14.0.0~programs build_system=makefile libs=shared,static arch=darwin-ventura-skylake
```


### Error message

<details><summary>Error message</summary><pre>
==> Error: DuplicateCompilerSpecError: %gcc@12.2.0 cannot have multiple compilers
See build log for details:
  /var/folders/0p/4sfhr4b10yd891rdgj757cnc0000gn/T/fortran/spack-stage/spack-stage-gcc-12.2.0-o7cycranmikhyholodnohru7omojszp2/spack-build-out.txt
</pre></details>


### Information on your system

```console
$ spack debug report
* **Spack:** 0.20.0.dev0 (a98c400447c551ba92389b3ac8f591af8884dab0)
* **Python:** 3.10.9
* **Platform:** darwin-ventura-skylake
* **Concretizer:** clingo
```

My `packages.yaml`:
```
packages:
  curl:
    externals:
    - spec: curl@7.85.0+gssapi+ldap+nghttp2
      prefix: /usr
  git:
    externals:
    - spec: git@2.37.1~tcltk
      prefix: /usr
    - spec: git@2.39.0~tcltk
      prefix: /usr/local
  pkg-config:
    externals:
    - spec: pkg-config@0.29.2
      prefix: /usr/local
  groff:
    externals:
    - spec: groff@1.22.4
      prefix: /usr/local
  openssh:
    externals:
    - spec: openssh@9.0p1
      prefix: /usr
  m4:
    externals:
    - spec: m4@1.4.6
      prefix: /usr
  coreutils:
    externals:
    - spec: coreutils@9.1
      prefix: /usr/local
  gmake:
    externals:
    - spec: gmake@4.4
      prefix: /Users/fortran
    - spec: gmake@3.81
      prefix: /usr
  ninja:
    externals:
    - spec: ninja@1.11.1
      prefix: /usr/local
  cvs:
    externals:
    - spec: cvs@1.12.13
      prefix: /usr/local
  gawk:
    externals:
    - spec: gawk@5.2.1
      prefix: /usr/local
  subversion:
    externals:
    - spec: subversion@1.14.2
      prefix: /usr/local
  cmake:
    externals:
    - spec: cmake@3.25.1
      prefix: /usr/local
  git-lfs:
    externals:
    - spec: git-lfs@3.3.0
      prefix: /usr/local
  diffutils:
    externals:
    - spec: diffutils@3.8
      prefix: /usr/local
  texinfo:
    externals:
    - spec: texinfo@5.2
      prefix: /usr/local
  bison:
    externals:
    - spec: bison@2.3
      prefix: /usr
  flex:
    externals:
    - spec: flex@2.6.4+lex
      prefix: /usr
```

My `modules.yaml`:
```
modules:
  default:
    enable::
      - lmod
    lmod:
      core_compilers:
      - 'apple-clang@14.0.0'
      hierarchy:
      - mpi
      hash_length: 0
      include:
      - gcc
      exclude:
      - 'apple-clang@14.0.0'
      all:
        filter:
          exclude_env_vars:
          - "C_INCLUDE_PATH"
          - "CPLUS_INCLUDE_PATH"
          - "LIBRARY_PATH"
        environment:
          set:
            '{name}_ROOT': '{prefix}'
      openmpi:
        environment:
          set:
            SLURM_MPI_TYPE: pmi2
            OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
      projections:
        all:          '{name}/{version}'
        ^lapack:      '{name}/{version}-{^lapack.name}'
```

### Additional information

I had to `gzip` up my build-out because, well, it's a `gcc` build. 

[spack-build-out.txt.gz](https://github.com/spack/spack/files/10322532/spack-build-out.txt.gz)
[spack-build-env.txt](https://github.com/spack/spack/files/10322533/spack-build-env.txt)

Mentions:
@alalazo
@michaelkuhn

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have run `spack maintainers <name-of-the-package>` and **@mentioned** any maintainers
- [X] I have uploaded the build log and environment files
- [X] I have searched the issues of this repo and believe this is not a duplicate


Discussion:
I ran into the same problem. I tracked the cause to line 183 in `modules/lmod.py`:

```
# If it is in the list of supported compilers family -> compiler
if self.spec.name in spack.compilers.supported_compilers():
    provides["compiler"] = spack.spec.CompilerSpec(str(self.spec))  # line modules.lmod.py:183
```

The exception is triggered when:

```
str(self.spec) = 'gcc@12.2.0%gcc@12.1.0~binutils+bootstrap+graphite~nvptx~piclibs~profiled~strip ...'
```

The same exception can be triggered in a `spack python` shell:

```
 ▶ spack python
Spack version 0.20.0.dev0
Python 3.10.6, Linux x86_64
>>> spack.spec.CompilerSpec('gcc@12.2.0%gcc@12.1.0')
Traceback (most recent call last):
  File "/usr/lib/python3.10/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/spack/spack/lib/spack/spack/spec.py", line 553, in __init__
    spec = spack.parser.parse_one_or_raise(f"%{arg}")
  File "/spack/spack/lib/spack/spack/parser.py", line 482, in parse_one_or_raise
    result = parser.next_spec(initial_spec)
  File "/spack/spack/lib/spack/spack/parser.py", line 263, in next_spec
    root_spec = SpecNodeParser(self.ctx).parse(initial_spec)
  File "/spack/spack/lib/spack/spack/parser.py", line 341, in parse
    raise spack.spec.DuplicateCompilerSpecError(
spack.spec.DuplicateCompilerSpecError: %gcc@12.2.0 cannot have multiple compilers

```

Well, glad I'm not alone at least! Not sure what is causing it!
same happens when trying to regenerate module files for compilers (have multiple compilers installed using spack, only a single compiler configured for spack builds):

==> You are about to regenerate lmod module files for:

-- linux-ubuntu20.04-x86_64 / gcc@9.3.0 -------------------------
md2kq5o nvhpc@22.7

==> Do you want to proceed? [y/n] y
==> Regenerating lmod module files
==> Warning: Could not write module file [/share/spack/lmod/linux-ubuntu20.04-x86_64/Core/nvhpc/22.7-md2kq5o.lua]
==> Warning: 	--> %nvhpc@22.7 cannot have multiple compilers <--
Huh. Okay. So apparently it's not really a GCC issue but a module generation issue?  I suppose it's not really on @alalazo & @michaelkuhn then...but I'm not sure who to target.
@mathomp4 - I've seen this problem too and attempted to diagnose it.  I think it was introduced with @alalazo's recent parser rewrite.  I do not think it is specific to gcc - I see the same exception triggered if I use a spec for any compiler package, compiled with itself.  (E.g., using @hbar-x-2pi's spack python test approach, it is triggered if you use 'oneapi%oneapi' as a spec, or 'clang%clang'.)

From what I can tell - the CompilerSpec.__init__ method calls the new parser's parse_one_or_raise() method, prefixing the given spec string with a '%'.  This causes a CompilerSpec for a compiler, e.g., 'gcc@12.2.0%gcc@7.5.0',  to be given to the new parser as '%gcc@12.2.0%gcc@7.5.0' which the parser correctly recognizes as a spec with a fatal problem.

Sadly, I don't know enough about Spack internals to suggest a fix.
I'm making an assumption here, but the culprit seems to be in parser.py:
325: if self.ctx.accept(TokenType.COMPILER):
or 335:  elif self.ctx.accept(TokenType.COMPILER_AND_VERSION):
this is a very broad clause that treats compilers as "spack compilers" and nothing else, even though they might be 'regular packages' 

#34151

As @witlox suggested a few days ago, this does seem to be related to module file generation.  From what I can tell, it is triggered only if I'm using Lmod module files but not Tcl-based module files.  So one workaround to allow building a compiler  package with one of the same family is to use Tcl-based module files.

It's just a guess at this point, but I suspect another workaround would be to build a compiler package using a compiler from a different family.  E.g., use a system's /usr/bin/gcc to build llvm, then that llvm's clang to build a newer/different gcc.
I'll try to have a look at this asap. Not sure at the moment if it is an error in the parser, but I suspect it might be something nasty we do when generating module files rather than something in the parser. For instance I can:
```console
$ spack spec gcc@9.4.0%gcc@9.4.0
Input spec
--------------------------------
gcc@9.4.0%gcc@9.4.0

Concretized
--------------------------------
gcc@9.4.0%gcc@9.4.0~binutils+bootstrap~graphite~nvptx~piclibs~profiled~strip build_system=autotools build_type=RelWithDebInfo languages=c,c++,fortran arch=linux-ubuntu20.04-icelake
    ^diffutils@3.8%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
        ^libiconv@1.16%gcc@9.4.0 build_system=autotools libs=shared,static arch=linux-ubuntu20.04-icelake
    ^gawk@5.1.1%gcc@9.4.0~nls build_system=autotools arch=linux-ubuntu20.04-icelake
        ^libsigsegv@2.13%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
        ^readline@8.2%gcc@9.4.0 build_system=autotools patches=bbf97f1 arch=linux-ubuntu20.04-icelake
            ^ncurses@6.3%gcc@9.4.0~symlinks+termlib abi=none build_system=autotools arch=linux-ubuntu20.04-icelake
                ^pkgconf@1.8.0%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
    ^gmp@6.2.1%gcc@9.4.0 build_system=autotools libs=shared,static arch=linux-ubuntu20.04-icelake
        ^autoconf@2.69%gcc@9.4.0 build_system=autotools patches=35c4492,7793209,a49dd5b arch=linux-ubuntu20.04-icelake
        ^automake@1.16.5%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
        ^m4@1.4.19%gcc@9.4.0+sigsegv build_system=autotools patches=9dc5fbd,bfdffa7 arch=linux-ubuntu20.04-icelake
    ^libtool@2.4.7%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
    ^mpc@1.1.0%gcc@9.4.0 build_system=autotools libs=shared,static arch=linux-ubuntu20.04-icelake
    ^mpfr@3.1.6%gcc@9.4.0 build_system=autotools libs=shared,static patches=7a6dd71 arch=linux-ubuntu20.04-icelake
    ^texinfo@7.0%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
        ^gettext@0.21.1%gcc@9.4.0+bzip2+curses+git~libunistring+libxml2+tar+xz build_system=autotools arch=linux-ubuntu20.04-icelake
            ^bzip2@1.0.8%gcc@9.4.0~debug~pic+shared build_system=generic arch=linux-ubuntu20.04-icelake
            ^libxml2@2.10.3%gcc@9.4.0~python build_system=autotools arch=linux-ubuntu20.04-icelake
            ^tar@1.34%gcc@9.4.0 build_system=autotools zip=pigz arch=linux-ubuntu20.04-icelake
                ^pigz@2.7%gcc@9.4.0 build_system=makefile arch=linux-ubuntu20.04-icelake
                ^zstd@1.5.2%gcc@9.4.0+programs build_system=makefile compression=none libs=shared,static arch=linux-ubuntu20.04-icelake
            ^xz@5.2.7%gcc@9.4.0~pic build_system=autotools libs=shared,static arch=linux-ubuntu20.04-icelake
        ^perl@5.36.0%gcc@9.4.0+cpanm+shared+threads build_system=generic arch=linux-ubuntu20.04-icelake
            ^berkeley-db@18.1.40%gcc@9.4.0+cxx~docs+stl build_system=autotools patches=26090f4,b231fcc arch=linux-ubuntu20.04-icelake
            ^gdbm@1.23%gcc@9.4.0 build_system=autotools arch=linux-ubuntu20.04-icelake
    ^zlib@1.2.13%gcc@9.4.0+optimize+pic+shared build_system=makefile arch=linux-ubuntu20.04-icelake
```
and that parses the specs correctly.
So, it seems the correct diagnosis was already done by @hbar-x-2pi in https://github.com/spack/spack/issues/34728#issuecomment-1369295681
This diff goes past the error reported at line 183 in `lmod.py`:
```diff
diff --git a/lib/spack/spack/modules/lmod.py b/lib/spack/spack/modules/lmod.py
index 38d89f085e..2878054ec2 100644
--- a/lib/spack/spack/modules/lmod.py
+++ b/lib/spack/spack/modules/lmod.py
@@ -180,7 +180,7 @@ def provides(self):
 
         # If it is in the list of supported compilers family -> compiler
         if self.spec.name in spack.compilers.supported_compilers():
-            provides["compiler"] = spack.spec.CompilerSpec(str(self.spec))
+            provides["compiler"] = spack.spec.CompilerSpec(self.spec.format("{name}{@version}"))
         elif self.spec.name in spack.compilers.package_name_to_compiler_name:
             # If it is the package for a supported compiler, but of a different name
             cname = spack.compilers.package_name_to_compiler_name[self.spec.name]
```
I'm still trying to reproduce the full error, to check if there's more, but I think this is it.
> I think it was introduced with @alalazo's recent parser rewrite. 

The funny thing is that the bug was there since ever, the new parser is just stricter (i.e. more correct) in what it parses. With `releases/v0.19` I can do for instance:
```python
Spack version 0.19.0
Python 3.8.10, Linux x86_64
>>> from spack.spec import CompilerSpec
>>> CompilerSpec("gcc@9.4.0 adbgf%%%%foooo")
gcc@9.4.0
```
with `develop`:
```python
Spack version 0.20.0.dev0
Python 3.8.10, Linux x86_64
>>> from spack.spec import CompilerSpec
>>> CompilerSpec("gcc@9.4.0 adbgf%%%%foooo")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/culpo/PycharmProjects/spack/lib/spack/spack/spec.py", line 553, in __init__
    spec = spack.parser.parse_one_or_raise(f"%{arg}")
  File "/home/culpo/PycharmProjects/spack/lib/spack/spack/parser.py", line 488, in parse_one_or_raise
    raise ValueError(message)
ValueError: a single spec was requested, but parsed more than one:
%gcc@9.4.0 adbgf%%%%foooo
          ^^^^^^^^^^^^^^^
```
I think the constructor of `CompilerSpec` might need some polishing too.

Resolving patch:
diff --git a/lib/spack/spack/modules/lmod.py b/lib/spack/spack/modules/lmod.py
--- a/lib/spack/spack/modules/lmod.py
+++ b/lib/spack/spack/modules/lmod.py
@@ -180,7 +180,7 @@ def provides(self):
 
         # If it is in the list of supported compilers family -> compiler
         if self.spec.name in spack.compilers.supported_compilers():
-            provides["compiler"] = spack.spec.CompilerSpec(str(self.spec))
+            provides["compiler"] = spack.spec.CompilerSpec(self.spec.format("{name}{@version}"))
         elif self.spec.name in spack.compilers.package_name_to_compiler_name:
             # If it is the package for a supported compiler, but of a different name
             cname = spack.compilers.package_name_to_compiler_name[self.spec.name]


Test patch: