Subject: [bug#75620] [PATCH core-packages-team] gnu: gcc-6: Use libstdc++ headers appropriate for each GCC.

From: Leo Nikkilä <[email-masked]>
Date: Fri, 17 Jan 2025 05:08:49 +0200

After the "hack" introduced for <https://issues.guix.gnu.org/42392>, all GCCs
are built with the current GCC's libstdc++ headers. This results in subtly
broken C++ headers in older versions, which aren't necessarily compatible with
libstdc++s from other versions.

For example, this test case works with GCC 11:

    $ guix shell --container --emulate-fhs --pure -e '(@ (gnu packages gcc) gcc)' binutils -- sh -c 'echo -e "#include <cmath>\nint main() { return std::isnan(0); }" | g++ -x c++ -; echo $?'
    0

but fails with GCC 9:

    $ guix shell --container --emulate-fhs --pure -e '(@ (gnu packages gcc) gcc-9)' binutils -- sh -c 'echo -e "#include <cmath>\nint main() { return std::isnan(0); }" | g++ -x c++ -; echo $?'
    
    In file included from /gnu/store/gkh2rljdrnj24q1q7baa6bhb119251w4-profile/include/c++/cmath:45,
                     from <stdin>:1:
    <stdin>: In function 'int main()':
    <stdin>:2:26: error: '__builtin_isnan' is not a member of 'std'; did you mean '__builtin_isnan'?
    <built-in>: note: '__builtin_isnan' declared here
    1

This specific error can be traced back to the GCC build, where GCC 10 and 11
are configured with:

    checking for ISO C99 support in <math.h> for C++11... yes

but GCC 9 is configured with:

    checking for ISO C99 support in <math.h> for C++11... no

The configure check fails due to errors like these due to the mismatched
libstdc++:

    configure:17817: checking for ISO C99 support in <math.h> for C++11
    configure:17886:  /tmp/guix-build-gcc-9.5.0.drv-0/build/./gcc/xgcc -shared-libgcc -B/tmp/guix-build-gcc-9.5.0.drv-0/build/./gcc -nostdinc++ -L/tmp/guix-build-gcc-9.5.0.drv-0/build/aarch64-unknown-linux-gnu/libstdc++-v3/src -L/tmp/guix-build-gcc-9.5.0.drv-0/build/aarch64-unknown-linux-gnu/libstdc++-v3/src/.libs -L/tmp/guix-build-gcc-9.5.0.drv-0/build/aarch64-unknown-linux-gnu/libstdc++-v3/libsupc++/.libs -B/gnu/store/l9a96zpznir4bm02lsycj35q0pakln32-gcc-9.5.0/aarch64-unknown-linux-gnu/bin/ -B/gnu/store/l9a96zpznir4bm02lsycj35q0pakln32-gcc-9.5.0/aarch64-unknown-linux-gnu/lib/ -isystem /gnu/store/l9a96zpznir4bm02lsycj35q0pakln32-gcc-9.5.0/aarch64-unknown-linux-gnu/include -isystem /gnu/store/l9a96zpznir4bm02lsycj35q0pakln32-gcc-9.5.0/aarch64-unknown-linux-gnu/sys-include   -fchecking=1 -o conftest -g -O2 -D_GNU_SOURCE -std=c++11 -fno-exceptions  -B/gnu/store/3gvs8sw95ldfypr1n688svl5brwdmdi9-glibc-2.39/lib -Wl,-dynamic-linker -Wl,/gnu/store/3gvs8sw95ldfypr1n688svl5brwdmdi9-glibc-2.39/lib/ld-linux-aarch64.so.1 conftest.cpp  -lm >&5
    In file included from /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/cmath:41,
                     from /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/math.h:36,
                     from conftest.cpp:41:
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:491:18: error: missing binary operator before token "("
      491 | #if __has_builtin(__builtin_is_constant_evaluated)
          |                  ^
    In file included from /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/cmath:41,
                     from /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/math.h:36,
                     from conftest.cpp:41:
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:732:25: error: missing binary operator before token "("
      732 | #if _GLIBCXX_HAS_BUILTIN(__has_unique_object_representations)
          |                         ^
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:736:25: error: missing binary operator before token "("
      736 | #if _GLIBCXX_HAS_BUILTIN(__is_aggregate)
          |                         ^
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:740:25: error: missing binary operator before token "("
      740 | #if _GLIBCXX_HAS_BUILTIN(__builtin_is_constant_evaluated)
          |                         ^
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:744:25: error: missing binary operator before token "("
      744 | #if _GLIBCXX_HAS_BUILTIN(__is_same)
          |                         ^
    /gnu/store/y3kk0ybf7hqwndl8xpm61r4a5b3lhwix-libstdc++-11.4.0/include/bits/c++config.h:748:25: error: missing binary operator before token "("
      748 | #if _GLIBCXX_HAS_BUILTIN(__builtin_launder)
          |                         ^
    configure:17886: $? = 1

Updating libstdc++ to reference each GCC works around this. I've tested this
with GCC 9, but I swapped the `libstdc++-headers' input directly as I didn't
have the time to do a `guix pull'. I wrote this little test however to check
that `this-package' should work here:

    (use-modules (guix build-system trivial)
                 (guix gexp)
                 (guix packages))
    
    (define (make-requirement pkg)
      (define builder
        #~(call-with-output-file #$output
            (lambda (port)
              (format port "~a~%" #$(package-name pkg)))))
    
      (computed-file "requirement" builder))
    
    (define package-1
      (package
        (name "package-1")
        (version "1")
        (source #f)
        (build-system trivial-build-system)
        (inputs
         `(("requirement" ,(make-requirement this-package))))
        (arguments
         '(#:builder
           (copy-file (assoc-ref %build-inputs "requirement")
                      %output)))
        (synopsis #f)
        (description #f)
        (home-page #f)
        (license #f)))
    
    (define package-2
      (package
        (inherit package-1)
        (name "package-2")
        (version "2")))
    
    (list package-1 package-2)

Building these packages results in the expected output, where each package
uses an input customised for it:

    $ cat $(guix build -f this-package.scm)
    package-2
    package-1

* gnu/packages/gcc.scm (libstdc++, libstdc++-headers): Remove variables.
(make-libstdc++-headers): New procedure.
(gcc-6)[native-inputs]: Use it with `this-package'.
---
 gnu/packages/gcc.scm | 38 ++++++++++++++++++--------------------
 1 file changed, 18 insertions(+), 20 deletions(-)

diff --git a/gnu/packages/gcc.scm b/gnu/packages/gcc.scm
index 6247919fec4..3735a9c9d94 100644
--- a/gnu/packages/gcc.scm
+++ b/gnu/packages/gcc.scm
@@ -17,6 +17,7 @@
 ;;; Copyright © 2023 Bruno Victal <[email-masked]>
 ;;; Copyright © 2023 Maxim Cournoyer <[email-masked]>
 ;;; Copyright © 2024 Nguyễn Gia Phong <[email-masked]>
+;;; Copyright © 2025 Leo Nikkilä <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -549,7 +550,7 @@ (define-public gcc-6
        ;; XXX: This gross hack allows us to have libstdc++'s <bits/c++config.h>
        ;; in the search path, thereby avoiding misconfiguration of libstdc++:
        ;; <https://bugs.gnu.org/42392>.
-       ("libstdc++" ,libstdc++-headers)
+       ("libstdc++" ,(make-libstdc++-headers this-package))
 
        ,@(package-inputs gcc-4.7)))))
 
@@ -1055,31 +1056,28 @@ (define-public (make-libstdc++ gcc)
     (propagated-inputs '())
     (synopsis "GNU C++ standard library")))
 
-(define libstdc++
-  ;; Libstdc++ matching the default GCC.
-  (make-libstdc++ gcc))
-
-(define libstdc++-headers
+(define (make-libstdc++-headers gcc)
   ;; XXX: This package is for internal use to work around
   ;; <https://bugs.gnu.org/42392> (see above).  The main difference compared
   ;; to the libstdc++ headers that come with 'gcc' is that <bits/c++config.h>
   ;; is right under include/c++ and not under
   ;; include/c++/x86_64-unknown-linux-gnu (aka. GPLUSPLUS_TOOL_INCLUDE_DIR).
-  (package
-    (inherit libstdc++)
-    (name "libstdc++-headers")
-    (outputs '("out"))
-    (build-system trivial-build-system)
-    (arguments
-     '(#:builder (let* ((out       (assoc-ref %outputs "out"))
-                        (libstdc++ (assoc-ref %build-inputs "libstdc++")))
-                   (mkdir out)
-                   (mkdir (string-append out "/include"))
-                   (symlink (string-append libstdc++ "/include")
-                            (string-append out "/include/c++")))))
-    (inputs `(("libstdc++" ,libstdc++)))
-    (synopsis "Headers of GNU libstdc++")))
+  (let ((libstdc++ (make-libstdc++ gcc)))
+    (package
+      (inherit libstdc++)
+      (name "libstdc++-headers")
+      (outputs '("out"))
+      (build-system trivial-build-system)
+      (arguments
+       '(#:builder (let* ((out       (assoc-ref %outputs "out"))
+                          (libstdc++ (assoc-ref %build-inputs "libstdc++")))
+                     (mkdir out)
+                     (mkdir (string-append out "/include"))
+                     (symlink (string-append libstdc++ "/include")
+                              (string-append out "/include/c++")))))
+      (inputs `(("libstdc++" ,libstdc++)))
+      (synopsis "Headers of GNU libstdc++"))))
 
 (define-public libstdc++-4.9
   (make-libstdc++ gcc-4.9))

-- 
2.47.1