Linuxbrew/brew

Cannot Compile Formulae using c++ with gcc-8 (probably gcc >= 6)

kalaxy opened this issue · 21 comments

I'm trying to compile formulae using the gcc@8 package. When I try to compile formulae which use the c++ stdlib, I get build failures. Steps to reproduce:

  1. Create a fresh prefix:
    $ git clone https://github.com/Linuxbrew/brew.git ~/.linuxbrew
    $ PATH="$HOME/.linuxbrew/bin:$PATH"
  2. Install gcc@8:
    $ brew install gcc@8
  3. Install a formula, compiling from source, which uses c++ stdlib using gcc-8 as the compiler. For example, berkeley-db:
    $ brew install berkeley-db --cc=gcc-8

This fails for me:

==> Downloading https://download.oracle.com/berkeley-db/db-6.2.32.tar.gz
Already downloaded: /home/kmills/.cache/Homebrew/berkeley-db-6.2.32.tar.gz
==> ../dist/configure --disable-debug --prefix=/home/kmills/.linuxbrew/Cellar/berkeley-db/6.2.32 --mandir=/home/kmills/.linuxbrew/Cellar/berkeley-db/6.2.32/share/man --enable-cxx --enable-compat185
Last 15 lines from /home/kmills/.cache/Homebrew/Logs/berkeley-db/01.configure:
checking if the linker (/home/kmills/.linuxbrew/Library/Homebrew/shims/linux/super/ld -m elf_x86_64) is GNU ld... yes
checking whether the g++-8 linker (/home/kmills/.linuxbrew/Library/Homebrew/shims/linux/super/ld -m elf_x86_64) supports shared libraries... yes
checking for g++-8 option to produce PIC... -fPIC -DPIC
checking if g++-8 PIC flag -fPIC -DPIC works... yes
checking if g++-8 static flag -static works... yes
checking if g++-8 supports -c -o file.o... yes
checking if g++-8 supports -c -o file.o... (cached) yes
checking whether the g++-8 linker (/home/kmills/.linuxbrew/Library/Homebrew/shims/linux/super/ld -m elf_x86_64) supports shared libraries... yes
checking dynamic linker characteristics... (cached) GNU/Linux ld.so
checking how to hardcode library paths into programs... immediate
checking SOSUFFIX from libtool... .so
checking MODSUFFIX from libtool... .so
checking JMODSUFFIX from libtool... .so
checking for main in -ldl... yes
checking whether the C++ compiler supports templates for STL... configure: error: no

After digging in the config.log I found this failure:

configure:19261: g++-8 -c -O3   -D_GNU_SOURCE -D_REENTRANT conftest.cpp >&5
In file included from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ext/string_conversions.h:41,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/basic_string.h:6361,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/string:52,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/locale_classes.h:40,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/ios_base.h:41,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ios:42,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ostream:38,
                 from /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/iostream:39,
                 from conftest.cpp:23:
/home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/cstdlib:75:15: fatal error: stdlib.h: No such file or directory
 #include_next <stdlib.h>
               ^~~~~~~~~~
compilation terminated.

I tracked this down to a behavior change in gcc's -isystem handling. See gcc bug 70129. It appears that since gcc 6, but I haven't tried 6 or 7. Essentially, adding an include path which is already part of the default include paths causes issues when resolving c++ stdlib includes.

brew appears to automatically add -isystem=$(brew --prefix)/include even though it is already encoded as a default search path for gcc.

Note the default search paths in gcc-8 already include $(brew --prefix)/include.

$ echo | gcc-8 -E -Wp,-v -
ignoring nonexistent directory "/home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/../../../../../../x86_64-pc-linux-gnu/include"
ignoring duplicate directory "/home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/../../../../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/include"
ignoring nonexistent directory "/home/kmills/.linuxbrew/nonexistent/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/home/kmills/.linuxbrew/nonexistent/usr/local/include"
ignoring duplicate directory "/home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/../../../../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/include-fixed"
ignoring nonexistent directory "/home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/../../../../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/../../../../../../x86_64-pc-linux-gnu/include"
ignoring nonexistent directory "/home/kmills/.linuxbrew/nonexistent/usr/include/x86_64-linux-gnu"
ignoring nonexistent directory "/home/kmills/.linuxbrew/nonexistent/usr/include"
#include "..." search starts here:
#include <...> search starts here:
 /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/include
 /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/include-fixed
 /home/kmills/.linuxbrew/Cellar/gcc@8/8.1.0/bin/../lib/gcc/8/gcc/../../../../include
 /home/kmills/.linuxbrew/include
End of search list.
...

I tried reproducing this on homebrew for mac and couldn't. That said, I was using the default install location rather than a custom location, and it looks like there might be hooks in place detecting the default location and acting differently in that case.

Anyway, I believe to make this work brew needs to forgo adding -isystem$(brew --prefix)/include for gcc 6 and later when it is already in the include path. But I'm not sure how to do that. Is this something easily fixable?

For info:

$ brew config
HOMEBREW_VERSION: >=1.4.0 (shallow or no git repository)
ORIGIN: https://github.com/Linuxbrew/brew.git
HEAD: b9917814b14a54406a1e8588f4157e97cdc88c32
Last commit: 3 days ago
Core tap ORIGIN: https://github.com/Linuxbrew/homebrew-core
Core tap HEAD: 39bae62cca3c05ae051f11111b2e303f0261f89a
Core tap last commit: 89 minutes ago
HOMEBREW_PREFIX: /home/kmills/.linuxbrew
HOMEBREW_REPOSITORY: /home/kmills/.linuxbrew
HOMEBREW_CELLAR: /home/kmills/.linuxbrew/Cellar
HOMEBREW_CACHE: /home/kmills/.cache/Homebrew
HOMEBREW_NO_ANALYTICS_THIS_RUN: 1
CPU: octa-core 64-bit haswell
Homebrew Ruby: 2.3.3 => /home/kmills/.linuxbrew/Library/Homebrew/vendor/portable-ruby/2.3.3_2/bin/ruby
Clang: N/A
Git: 1.8.3.1 => /bin/git
Curl: 7.29.0 => /usr/bin/curl
Java: N/A
Kernel: Linux 3.10.0-693.21.1.el7.x86_64 x86_64 GNU/Linux
OS: CentOS Linux release 7.4.1708 (Core) 
Host glibc: 2.17
/usr/bin/gcc: 4.8.5
glibc: 2.23
gcc: 5.5.0_4
xorg: N/A

Unrelated, but since the output was asked for...

$ brew doctor
Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: An outdated version (1.8.3.1) of Git was detected in your PATH.
Git 1.8.5 or newer is required to perform checkouts over HTTPS from GitHub and
to support the 'git -C <path>' option.
Please upgrade:
  brew install git

Warning: Homebrew's sbin was not found in your PATH but you have installed
formulae that put executables in /home/kmills/.linuxbrew/sbin.
Consider setting the PATH for example like so
  echo 'export PATH="/home/kmills/.linuxbrew/sbin:$PATH"' >> ~/.bash_profile

@kalaxy Thanks for the bug report, Kalon. Linuxbrew modified the default GCC specs file to add $HOMEBREW_PREFIX/include to the default search path; it otherwise would not be part of the default search path. See these two files:

/home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/specs.orig
/home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/specs

I wasn't able to reproduce this issue using…

docker run -it --rm linuxbrew/linuxbrew:1.6.7
brew install gcc@8
brew install hello --cc=gcc-8
…
🍺  /home/linuxbrew/.linuxbrew/Cellar/hello/2.10: 53 files, 312.8KB, built in 1 minute 9 seconds

Are you able to reproduce this issue in a Docker image?

Hi @sjackman, thanks for starting to look at this. In order to see the failure you need to install a formula that uses the c++ std library. It looks like hello is only C. Installing hello won't reproduce it on my machine either. Sorry that was unclear; I mentioned it in step 3 but didn't make it stick out enough.

Yeah, adding $HOMEBREW_PREFIX/include to the default search path when building GCC seems like the right thing to do. It probably just shouldn't be later added as an -isystem path when using the compiler to build formulae.

Ah, my mistake. Thanks for clearing that up, Kalon. I tried installing berkeley-db, but I wasn't able to reproduce the issue.

docker run -it --rm linuxbrew/linuxbrew:1.6.8
brew install gcc@8
brew install berkeley-db --cc=gcc-8
…
🍺  /home/linuxbrew/.linuxbrew/Cellar/berkeley-db/6.2.32: 5,632 files, 126.4MB, built in 8 minutes 37 seconds

Are you able to reproduce this issue in a Docker image?

Gotcha, sorry about that. I'm not that familiar with Docker so was avoiding it. (Yeah I know I'm behind on the times.)

No, as you found, I am not able to replicate it from a the linuxbrew/linuxbrew docker container.

I am, however, able to get this to reproduce with a CentOS docker container. That is what matches my system. Does that help?

docker run -it --rm centos:7
yum install -y bzip2 ca-certificates curl file fonts-dejavu-core g++ git locales make openssh-client patch sudo uuid-runtime
localedef -i en_US -f UTF-8 en_US.UTF-8 && useradd -m -s /bin/bash linuxbrew && echo 'linuxbrew ALL=(ALL) NOPASSWD:ALL' >>/etc/sudoers
sudo su - linuxbrew
git clone https://github.com/Linuxbrew/brew.git ~/.linuxbrew
PATH="$HOME/.linuxbrew/bin:$PATH"
brew install gcc@8
brew install berkeley-db --cc=gcc-8
...
checking whether the C++ compiler supports templates for STL... configure: error: no

Debugging a bit, It appears that the packages installed are a little different. For instance on the CentOS container I after installing gcc@8 I have

$ brew list
binutils  gcc  gcc@8  glibc  gmp  isl  isl@0.18  libmpc  linux-headers  m4  mpfr  patchelf  zlib

But in the linuxbrew docker container I only have

$ brew list
gcc@8  gmp  isl  libmpc  mpfr  patchelf  zlib

So something about the env is changing how things are installed. Maybe it is also affecting the -isystem flags? But I'm just grasping at straws now. HTH

I just noticed that you already have a Dockerfile for CentOS7 which includes some of the setup that I did above. It also reproduces the problem.

docker build -t linuxbrew-centos7 https://raw.githubusercontent.com/Linuxbrew/docker/master/centos7/Dockerfile
docker run -it --rm linuxbrew-centos7
brew install gcc@8
brew install berkeley-db --cc=gcc-8
...
checking whether the C++ compiler supports templates for STL... configure: error: no

@xu-cheng @sjackman thoughts

Sorry, I am in travel this week. I can only look into this issue next week.

@kalaxy It's installing glibc that's breaking gcc@8. I can reproduce this issue in the linuxbrew/linuxbrew Docker image.

docker run -it --rm linuxbrew/linuxbrew:1.6.8
brew install glibc
brew install gcc@8
brew install berkeley-db --cc=gcc-8
…
checking whether the C++ compiler supports templates for STL... configure: error: no

Removing the specs file created by Linuxbrew works around this issue.

docker run -it --rm linuxbrew/linuxbrew:1.6.8
brew install glibc
brew install gcc@8
rm /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/lib/gcc/8/gcc/x86_64-pc-linux-gnu/8.1.0/specs
brew install berkeley-db --cc=gcc-8
…
🍺  /home/linuxbrew/.linuxbrew/Cellar/berkeley-db/6.2.32: 5,632 files, 126.4MB, built in 8 minutes 18 seconds

I am still in the travelling. @sjackman Could you help to test whether it can be workaround using std env instead of super env?

I suspect that this is caused by passing -isystem=HOMEBREW_PREFIX/include by homebrew superenv. As a result, it will invalid the -idirafter=HOMEBREW_PREFIX/include from the gcc spec file, which supports to tell gcc to search HOMEBREW_PREFIX/include with the lowest priority. The invalidation happening is because that gcc treats the second HOMEBREW_PREFIX/include (from -idirafter in spec file) as duplication and ignores it.

After the -idirafter=HOMEBREW_PREFIX/include is invalided, it means include_next <stdlib.h> wouldn't be able to find header files from glibc. Please note the special meaning of include_next instead of the standard include. Ref: https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html

Removing the specs file created by Linuxbrew works around this issue.

I think this is because without the spec file, gcc will search glibc header file in /usr/include. However, this is not a desirable behaviour. As in the system with old glibc, we want brew to ignore the system glibc and use brew glibc. That is the gcc should use the glibc header files from brew glibc.

As for solutions, one way is to add -idirafter HOMEBREW_PREFIX/opt/glibc/include in the end of gcc spec file for the users who use brew glibc. Another way is using -idirafter HOMEBREW_PREFIX/include instead of -isystem=HOMEBREW_PREFIX/include in the superenv wrapper.

The -isystem and -idirafter options also mark the directory as a system directory, so that it gets the same special treatment that is applied to the standard system directories.

https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html

Sounds like using -idirafter consistently should resolve this issue. I'll test it out this week.

When I change Brew's -isystem to -idirafter, I get this error:

configure:19128: checking whether the C++ compiler supports templates for STL
configure:19419: g++-8 -c -O3   -D_GNU_SOURCE -D_REENTRANT conftest.cpp >&5
In file included from /home/linuxbrew/.linuxbrew/include/errno.h:35,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/cerrno:42,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ext/string_conversions.h:44,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/basic_string.h:6361,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/string:52,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/locale_classes.h:40,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/bits/ios_base.h:41,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ios:42,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/ostream:38,
                 from /home/linuxbrew/.linuxbrew/Cellar/gcc@8/8.1.0/include/c++/8.1.0/iostream:39,
                 from conftest.cpp:24:
/home/linuxbrew/.linuxbrew/include/bits/errno.h:24:11: fatal error: linux/errno.h: No such file or directory
 # include <linux/errno.h>
           ^~~~~~~~~~~~~~~
compilation terminated.
configure:19419: $? = 1

linux-headers was not installed. Currently the dependency of glibc on linux-headers is :build. Perhaps it should not be :build. I'm trying again with linux-headers installed.

Success!

diff --git a/Library/Homebrew/shims/super/cc b/Library/Homebrew/shims/super/cc
index d6085a3..55e9e27 100755
--- a/Library/Homebrew/shims/super/cc
+++ b/Library/Homebrew/shims/super/cc
@@ -270,7 +270,7 @@ class Cmd
   end
 
   def cppflags
-    path_flags("-isystem", isystem_paths) + path_flags("-I", include_paths)
+    path_flags("-idirafter", isystem_paths) + path_flags("-I", include_paths)
   end
 
   def ldflags_mac(args)

Here's a PR to hopefully address this issue: #733

@sjackman Personally, I would prefer to fix this in gcc@6 gcc@7 and gcc@8 formula.

Further, I suspect the root issue is a bug from gcc >= 6 rather than Linuxbrew. For example in Ubuntu Bionic docker image, I can reproduce this bug as following:

root@11e517c8810c:/# cat /tmp/test.cpp
#include <cstdlib>

int main()
{
	return 0;
}
root@11e517c8810c:/# g++-8 /tmp/test.cpp -o /tmp/test
root@11e517c8810c:/# g++-8 /tmp/test.cpp -isystem=/usr/include/ -o /tmp/test
In file included from /tmp/test.cpp:1:
/usr/include/c++/8/cstdlib:75:15: fatal error: stdlib.h: No such file or directory
 #include_next <stdlib.h>
               ^~~~~~~~~~
compilation terminated.

Another reason to fix it in formula is that this bug can be trigger by users who use gcc formula as shown in the above.

@xu-cheng, fyi see gcc bug 70129. It appears that they have decided it is working as designed. It sounds like their suggestion is to just not add the -isystem path when it is already included in other ways, for example, via the specs file.

Though others, such as comment 5, on that thread seem to have good reasons it should be allowed. So it may be worth pressing the gcc team again.

I commented on the closed GCC bug to mention that it affects Linuxbrew as well. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129#c7

This fix PR https://github.com/Linuxbrew/homebrew-core/pull/8108 looks good to me. Thanks again, @xu-cheng! Shall I close this PR in favour of PR https://github.com/Linuxbrew/homebrew-core/pull/8108 ?

I can confirm that the changes you made work for me. Thanks so much for your help diagnosing and coming to a solution!

Closing, as the desired workflow works now.

Thanks for following up, Kalon. Thanks again, @xu-cheng !