GCC - 多个预编译 headers 和特定路径

GCC - Multiple precompiled headers and specific paths

背景


我有一个大型 Makefile 项目正在处理,我想稍微整理一下。它构建了几十个 sub-projects,每个包含大约 100 个 .cpp.h 文件。我已经对其进行了设置,以便它能够为多个操作系统(Linux、OSX/Mac、QNX 等)和多个体系结构( x86/i386x64/amd64armhfarm64/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.gchpch.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