-isystem on a system include directory causes errors
What's up with the following code?
#include <cmath>
int
main(int argc, char *argv[])
{
}
When compiled on a recent Arch Linux installation with GCC 6.1.1 and the flag -isystem /usr/include
it produces:
$ g++ -isystem /usr/include math.cc
In file included from math.cc:1:0:
/usr/include/c++/6.1.1/cmath:45:23: fatal error: math.h: No such file or directory
#include_next <math.h>
^
compilation terminated.
That is a very simplified example; the original command line was:
$ g++ ... -isystem `llvm-config -includedir` ...
for a part of a program using LLVM. On Arch Linux, the LLVM package is installed with its header directory in /usr/include
, which is the directory reported by llvm-config
. The ...'s included -Wextra
and -Wconversion
, which cause warnings in the LLVM headers. The -isystem
flag, as opposed to -I
, prevents the warnings by considering the LLVM directory to be "system headers". See the GNU C preprocessor documentation for more information.
But with an upgrade to GCC 6.1.1, the error above appears in the build.
In addition to considering the directory to contain "system headers", -isystem
alters the header search list, putting the directory argument at the top of the system header directories. If the directory already exists in the search list, it is removed from its current location.
As of (at least) GCC 6.1.1, some C++ headers such as cmath
use #include_next
to monkey-patch C++ support for the standard C headers. See Why < cstdlib > is more complicated than you might think for more information. For example, cmath
has the line:
#include_next <math.h>
#include_next
, unlike the normal #include
statement, starts the search for the file at the next entry in the include directory search path, rather than at the top of the search path. Since -isystem /usr/include
moves /usr/include
in the search path before the directory containing cmath
, math.h
cannot be found.
In detail, the search path for the command g++ -I /usr/include
is
/usr/include/c++/6.1.1
/usr/include/c++/6.1.1/x86_64-pc-linux-gnu
/usr/include/c++/6.1.1/backward
/usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include-fixed
/usr/include
(/usr/include
is a system directory; the -I
argument does nothing.)
cmath
is at the path /usr/include/c++/6.1.1/cmath
, which is the first element of the search path. math.h
can be found in
/usr/include/math.h
/usr/include/c++/6.1.1/math.h
The use of #include_next <math.h>
in cmath
ensures that the copy of math.h
in /usr/include/c++/6.1.1
is skipped and that the copy used is /usr/include/math.h
.
With g++ -isystem /usr/include
, the search path is
/usr/include
/usr/include/c++/6.1.1
/usr/include/c++/6.1.1/x86_64-pc-linux-gnu
/usr/include/c++/6.1.1/backward
/usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include
/usr/local/include
/usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/include-fixed
The use of #include_next <math.h>
now skips /usr/include/c++/6.1.1
but also /usr/include
, which is above it in the search path. As a result, the compiler cannot find any copy of math.h
.
To summarize, be cautious about using -isystem
for its error-silencing side-effects; if the directory being included is already on the search path, the order of the path may be modified and GCC may report errors.
Something like the following Makefile
work-around should suffice:
llvm.include.dir := $(shell $(LLVM_CONFIG) --includedir)
include.paths := $(shell echo | cc -v -E - 2>&1)
ifeq (,$(findstring $(llvm.include.dir),$(include.paths)))
# LLVM include directory is not in the existing paths;
# put it at the top of the system list
llvm.include := -isystem $(llvm.include.dir)
else
# LLVM include directory is already on the existing paths;
# do nothing
llvm.include :=
endif
This sets the make
variable llvm.include
to be either -isystem <dir>
or nothing, depending on if it is actually needed or not.