Reputation: 19333
I have a large Makefile
project I'm working on which I'd like to tidy up a bit. It builds several dozen sub-projects, each of which contains roughly 100 .cpp
and .h
files. I've set it up so that it's able to build debug
and release
builds for multiple operating systems (Linux, OSX/Mac, QNX, etc) and for multiple architectures (x86/i386
, x64/amd64
, armhf
, arm64/aarch64
) in parallel. This is because it's a massive project, and the only way to make it build quickly is in parallel with multiple tool chains.
I have a master rule that all projects obey that stores the intermediate objects (ie: .o
files) in temporary directories when building. So, building test.c
for Linux, arm64, release mode; would build the object file in the following sub-directory in the present working directory:
.tmp/Linux/arm64/release
This feature works without issue in my builds, but with this setup, I can't seem to properly use pre-compiled headers (ie: .GCH
files) with GCC. With my setup, I have a stdafx.h
/stdafx.cpp
pair. Using GCC, I can create a stdafx.h.gch
file easily enough. However, the project only seems to use it (which accelerates the build) if the file is in the same path as the source files. If the precompiled header is inn the intermediate object path (ie: .tmp/Linux/arm64/release
) it doesn't get detected or used. Even if I explicitly add the include path to the intermediate objects path which would contain the gch
file, it fails. Including the full path to the file name itself results in it being treated as an invalid linker script, and is ignored.
So, my first workaround was to make a rule to force all OS/arch builds to wait on initial pre-compiled header generation, rather than build a gch
on a per-OS/arch basis. However, if I build the gch
with release mode settings and attempt to make
a debug build, I get the following warning:
warning: stdafx.h.gch: created with -gnone, but used with -gdwarf-2
First, I don't know if that has severe consequences for my build, and second, different operating systems might pass different compile time define flags for the gch
generation, so this isn't a "one size fits all" use case, as far as I can see.
How can I work around this so that the precompiled header is in a location other than the $PWD
and it can be detected by GCC? I'm currently using gcc v5.3.1.
Thank you.
Upvotes: 3
Views: 2564
Reputation: 1459
If you force the compiler to -include tmp/pch.h
or -include-pch tmp/pch.h.gch
, the guard block in pch.h
will prevent it from being included again.
# Makefile
SOURCES := main.cpp
OBJECTS := $(SOURCES:%.cpp=tmp/%.o)
PCH_H := tmp/pch.h
PCH := $(PCH_H).gch
$(PCH) : *.h
$(COMPILE.cpp) -x c++-header src/pch.h -o $@
$(OBJECTS) : tmp/%.o : src/%.cpp $(PCH)
$(COMPILE.cpp) -include $(PCH_H) $< -o $@
Notes:
.gch
.pch
or .gch
-include pch.h
or -include-pch pch.h.pch
-include-pch
Upvotes: 0
Reputation: 61327
Here is an MVCE for your problem scenario:
main.c
#include <hw.h>
#include <stdio.h>
int main(void)
{
puts(HW);
return 0;
}
hw.h
#ifndef HW_H
#define HW_H
#define HW "Hello World"
#endif
Makefile
srcs := main.c
objs := $(addprefix tmp/,$(srcs:.c=.o))
pch := tmp/hw.h.gch
CPPFLAGS += -I.
.PHONY: all clean
all: hw
tmp:
mkdir -p tmp
tmp/%.o: %.c | $(pch)
gcc -c $(CPPFLAGS) -o $@ $<
$(pch): hw.h | tmp
gcc -c $(CPPFLAGS) -o $@ $<
ifdef ENFORCE_PCH
echo "#error Debug." >> $^
endif
hw: $(objs)
gcc -o $@ $^
clean:
sed -i '/^#error/d' hw.h
rm -fr hw tmp
This project outputs its intermediate files in tmp
. The .o
files go there
and so does the PCH hw.h.gch
.
Build and run it:
$ make && ./hw
mkdir -p tmp
gcc -c -I. -o tmp/hw.h.gch hw.h
gcc -c -I. -o tmp/main.o main.c
gcc -o hw tmp/main.o
Hello World
So far so good. But did it actually use the PCH? Let's see:
$ make clean
sed -i '/^#error/d' hw.h
rm -fr hw tmp
$ make ENFORCE_PCH=true
mkdir -p tmp
gcc -c -I. -o tmp/hw.h.gch hw.h
echo "#error Debug." >> hw.h
gcc -c -I. -o tmp/main.o main.c
In file included from main.c:1:0:
./hw.h:5:2: error: #error Debug.
#error Debug.
^
Makefile:15: recipe for target 'tmp/main.o' failed
make: *** [tmp/main.o] Error 1
No it didn't. We know that because, with ENFORCE_PCH
defined, we have
tacked an #error
directive to the end of hw.h
after generating the
good tmp/hw.h.gch
. So if the former is subsequently #include
-ed anywhere
instead of the latter, the build breaks. Which it just did.
And that is just as it should be. GCC manual 3.21 Using Precompiled Headers, para. 3:
A precompiled header file is searched for when #include is seen in the compilation. As it searches for the included file (see Search Path) the compiler looks for a precompiled header in each directory just before it looks for the include file in that directory. The name searched for is the name specified in the #include with ‘.gch’ appended. If the precompiled header file can't be used, it is ignored.
So, given include search path .
, the directive #include <hw.h>
will cause
gcc
to check for the PCH ./hw.h.gch
before using ./hw.h
, and as there is
no ./hw.h.gch
, it will use ./hw.h
.
It might appear, from the documentation just quoted, that adding tmp
to the include search path - CPPFLAGS += -Itmp -I.
- should cause
tmp/hw.h.gch
to be used in preference to./hw.h
. But in fact it makes no difference.
The documentation omits a crucial qualification. The second sentence ought to read:
As it searches for the included file (see Search Path) the compiler looks for a precompiled header in each directory just before it looks for the include file in that directory and will use the precompiled header for preference if the include file is found.
To be found and used, a PCH has to be a sibling of the matching header. And on consideration
this is what you'd want. Otherwise, a a/foo.h.gch
without a matching sibling header might be
found and used thanks to -Ia
when there is a b/foo.h.gch
, with a matching b/foo.h
, that
could be found and used thanks to a later -Ib
. Clearly, the latter is the sounder choice.
With this insight it's not hard to see a solution: if you really want to compile and use a PCH that's not a sibling of its source header, make sure to give it a phony matching header that is a sibling. You can arrange this as you see fit, e.g.
Makefile (fixed)
srcs := main.c
objs := $(addprefix tmp/,$(srcs:.c=.o))
pch := tmp/hw.h.gch
# Seek headers in tmp first...
CPPFLAGS += -Itmp -I.
.PHONY: all clean
all: hw
tmp:
mkdir -p tmp
tmp/%.o: %.c | $(pch)
gcc -c $(CPPFLAGS) -o $@ $<
$(pch): hw.h | tmp
# Make phony header in tmp...
echo "#error You should not be here" > $(basename $@)
gcc -c $(CPPFLAGS) -o $@ $<
ifdef ENFORCE_PCH
echo "#error Debug." >> $^
endif
hw: $(objs)
gcc -o $@ $^
clean:
sed -i '/^#error/d' hw.h
rm -fr hw tmp
See that the PCH is used from tmp
:
$ make clean
sed -i '/^#error/d' hw.h
rm -fr hw tmp
$ make ENFORCE_PCH=true && ./hw
mkdir -p tmp
# Make phony header in tmp...
echo "#error You should not be here" > tmp/hw.h
gcc -c -Itmp -I. -o tmp/hw.h.gch hw.h
echo "#error Debug." >> hw.h
gcc -c -Itmp -I. -o tmp/main.o main.c
gcc -o hw tmp/main.o
Hello World
Upvotes: 4