GCC - 多个预编译 headers 和特定路径
GCC - Multiple precompiled headers and specific paths
背景
我有一个大型 Makefile
项目正在处理,我想稍微整理一下。它构建了几十个 sub-projects,每个包含大约 100 个 .cpp
和 .h
文件。我已经对其进行了设置,以便它能够为多个操作系统(Linux、OSX/Mac、QNX 等)和多个体系结构( x86/i386
、x64/amd64
、armhf
、arm64/aarch64
) 并行。这是因为它是一个庞大的项目,并且使其快速构建的唯一方法是并行使用多个工具链。
我有一个所有项目都遵守的主要规则,即在构建时将中间 objects(即:.o
文件)存储在临时目录中。因此,为 Linux、arm64、释放模式构建 test.c
;将在当前工作目录中的以下 sub-directory 中构建 object 文件:
.tmp/Linux/arm64/release
问题
此功能在我的构建中没有问题,但使用此设置,我似乎无法在 GCC 中正确使用 pre-compiled headers(即:.GCH
文件) .通过我的设置,我有一对 stdafx.h
/stdafx.cpp
。使用 GCC,我可以很容易地创建一个 stdafx.h.gch
文件。但是,如果文件与源文件位于同一路径,该项目似乎只使用它(加速构建)。如果预编译的 header 位于中间 object 路径(即:.tmp/Linux/arm64/release
),则它不会被检测到或使用。即使我明确地将包含路径添加到包含 gch
文件的中间 objects 路径,它也会失败。包括文件名本身的完整路径会导致它被视为无效的链接描述文件,并被忽略。
所以,我的第一个解决方法是制定规则强制所有 OS/arch 构建等待初始 pre-compiled header 代,而不是构建 gch
每 OS/arch 个基础。但是,如果我使用发布模式设置构建 gch
并尝试 make
调试构建,我会收到以下警告:
warning: stdafx.h.gch: created with -gnone, but used with -gdwarf-2
首先,我不知道这是否会对我的构建造成严重后果,其次,不同的操作系统可能会通过不同的编译时间定义 gch
代的标志,所以这不是“据我所知,一刀切”用例。
问题
我该如何解决这个问题,以便预编译的 header 位于 $PWD
以外的位置并且可以被 GCC 检测到?我目前使用的是 gcc v5.3.1。
谢谢。
这是针对您的问题的 MVCE
场景:
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
生成文件
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
此项目在 tmp
中输出其中间文件。 .o
文件放在那里
PCH hw.h.gch
.
也是如此
构建并运行它:
$ 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
到目前为止一切顺利。但它真的使用了 PCH 吗?让我们看看:
$ 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
不,它没有。我们知道,因为定义了 ENFORCE_PCH
,我们有
在 hw.h
after 生成
好tmp/hw.h.gch
。因此,如果前者随后在任何地方 #include
-ed
而不是后者,构建中断。它刚刚做到了。
这是应该的。 GCC 手册 3.21 Using Precompiled Headers,
段。 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.
因此,给定包含搜索路径 .
,指令 #include <hw.h>
将导致
gcc
在使用 ./hw.h
之前检查 PCH ./hw.h.gch
,因为有
没有 ./hw.h.gch
,它将使用 ./hw.h
.
从刚刚引用的文档来看,将 tmp
添加到包含搜索路径 - CPPFLAGS += -Itmp -I.
- 可能会导致
tmp/hw.h.gch
优先于 ./hw.h
使用。但实际上并没有什么区别。
该文档省略了一个关键条件。第二句应该是:
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.
要找到并使用 PCH,PCH 必须是匹配 header 的 同级 。并考虑
这就是你想要的。否则,没有匹配的兄弟 header 的 a/foo.h.gch
可能是
多亏 -Ia
找到并使用了 b/foo.h.gch
,并匹配了 b/foo.h
,
多亏了后来的 -Ib
,才能找到并使用。显然,后者是更合理的选择。
有了这个洞察力,不难找到解决方案:如果你真的想编译和使用
不是源 header 的同级 PCH,确保给它一个匹配 header 的 phony
是一个兄弟姐妹。你可以按照你认为合适的方式安排,例如
Makefile(固定)
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
从tmp
看到PCH被使用:
$ 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
如果您强制编译器 -include tmp/pch.h
或 -include-pch tmp/pch.h.gch
,pch.h
中的保护块将阻止它再次被包含。
# 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 $@
备注:
- gcc 搜索名为 pch.h
.gch
的预编译 header
- clang 搜索名为 pch.h
.pch
或 .gch
[=32= 的预编译 header ]
- clang 需要
-include pch.h
或 -include-pch pch.h.pch
- gcc 总是搜索预编译的header,不支持
-include-pch
背景
我有一个大型 Makefile
项目正在处理,我想稍微整理一下。它构建了几十个 sub-projects,每个包含大约 100 个 .cpp
和 .h
文件。我已经对其进行了设置,以便它能够为多个操作系统(Linux、OSX/Mac、QNX 等)和多个体系结构( x86/i386
、x64/amd64
、armhf
、arm64/aarch64
) 并行。这是因为它是一个庞大的项目,并且使其快速构建的唯一方法是并行使用多个工具链。
我有一个所有项目都遵守的主要规则,即在构建时将中间 objects(即:.o
文件)存储在临时目录中。因此,为 Linux、arm64、释放模式构建 test.c
;将在当前工作目录中的以下 sub-directory 中构建 object 文件:
.tmp/Linux/arm64/release
问题
此功能在我的构建中没有问题,但使用此设置,我似乎无法在 GCC 中正确使用 pre-compiled headers(即:.GCH
文件) .通过我的设置,我有一对 stdafx.h
/stdafx.cpp
。使用 GCC,我可以很容易地创建一个 stdafx.h.gch
文件。但是,如果文件与源文件位于同一路径,该项目似乎只使用它(加速构建)。如果预编译的 header 位于中间 object 路径(即:.tmp/Linux/arm64/release
),则它不会被检测到或使用。即使我明确地将包含路径添加到包含 gch
文件的中间 objects 路径,它也会失败。包括文件名本身的完整路径会导致它被视为无效的链接描述文件,并被忽略。
所以,我的第一个解决方法是制定规则强制所有 OS/arch 构建等待初始 pre-compiled header 代,而不是构建 gch
每 OS/arch 个基础。但是,如果我使用发布模式设置构建 gch
并尝试 make
调试构建,我会收到以下警告:
warning: stdafx.h.gch: created with -gnone, but used with -gdwarf-2
首先,我不知道这是否会对我的构建造成严重后果,其次,不同的操作系统可能会通过不同的编译时间定义 gch
代的标志,所以这不是“据我所知,一刀切”用例。
问题
我该如何解决这个问题,以便预编译的 header 位于 $PWD
以外的位置并且可以被 GCC 检测到?我目前使用的是 gcc v5.3.1。
谢谢。
这是针对您的问题的 MVCE 场景:
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
生成文件
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
此项目在 tmp
中输出其中间文件。 .o
文件放在那里
PCH hw.h.gch
.
构建并运行它:
$ 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
到目前为止一切顺利。但它真的使用了 PCH 吗?让我们看看:
$ 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
不,它没有。我们知道,因为定义了 ENFORCE_PCH
,我们有
在 hw.h
after 生成
好tmp/hw.h.gch
。因此,如果前者随后在任何地方 #include
-ed
而不是后者,构建中断。它刚刚做到了。
这是应该的。 GCC 手册 3.21 Using Precompiled Headers, 段。 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.
因此,给定包含搜索路径 .
,指令 #include <hw.h>
将导致
gcc
在使用 ./hw.h
之前检查 PCH ./hw.h.gch
,因为有
没有 ./hw.h.gch
,它将使用 ./hw.h
.
从刚刚引用的文档来看,将 tmp
添加到包含搜索路径 - CPPFLAGS += -Itmp -I.
- 可能会导致
tmp/hw.h.gch
优先于 ./hw.h
使用。但实际上并没有什么区别。
该文档省略了一个关键条件。第二句应该是:
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.
要找到并使用 PCH,PCH 必须是匹配 header 的 同级 。并考虑
这就是你想要的。否则,没有匹配的兄弟 header 的 a/foo.h.gch
可能是
多亏 -Ia
找到并使用了 b/foo.h.gch
,并匹配了 b/foo.h
,
多亏了后来的 -Ib
,才能找到并使用。显然,后者是更合理的选择。
有了这个洞察力,不难找到解决方案:如果你真的想编译和使用 不是源 header 的同级 PCH,确保给它一个匹配 header 的 phony 是一个兄弟姐妹。你可以按照你认为合适的方式安排,例如
Makefile(固定)
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
从tmp
看到PCH被使用:
$ 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
如果您强制编译器 -include tmp/pch.h
或 -include-pch tmp/pch.h.gch
,pch.h
中的保护块将阻止它再次被包含。
# 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 $@
备注:
- gcc 搜索名为 pch.h
.gch
的预编译 header
- clang 搜索名为 pch.h
.pch
或.gch
[=32= 的预编译 header ] - clang 需要
-include pch.h
或-include-pch pch.h.pch
- gcc 总是搜索预编译的header,不支持
-include-pch