GCC - 多个预编译头文件和指定路径

3

背景


我正在处理一个庞大的Makefile项目,希望能够稍微整理一下。它构建了几十个子项目,每个子项目包含大约100个.cpp.h文件。我已经设置好了它可以在多个操作系统(Linux、OSX/Mac、QNX等)和多个体系结构(x86/i386x64/amd64armhfarm64/aarch64)上并行地构建debugrelease版本。这是因为它是一个巨大的项目,唯一能够快速构建的方法就是使用多个工具链并行构建。

我有一个所有项目都遵循的主规则,它将中间对象(即.o文件)存储在构建时的临时目录中。因此,对于在Linux、arm64、release模式下构建test.c,会在当前工作目录下的以下子目录中构建对象文件:

.tmp/Linux/arm64/release

问题


在我的构建中,这个功能没有问题,但是在这个设置下,我似乎无法正确地使用GCC的预编译头文件(即.GCH文件)。在我的设置中,我有一个stdafx.h/stdafx.cpp对。使用GCC,我可以很容易地创建一个stdafx.h.gch文件。然而,如果预编译头文件在中间对象路径(即.tmp/Linux/arm64/release)中,项目似乎只使用它(加速构建),如果文件与源文件位于同一路径中。即使我明确将包含路径添加到包含gch文件的中间对象路径中,也会失败。包括文件名本身的完整路径会导致它被视为无效的链接器脚本,并被忽略。

因此,我的第一个解决方法是制定规则,强制所有OS/Arch构建等待初始预编译头文件生成,而不是基于每个OS/Arch构建一个gch。然而,如果我使用发布模式设置构建gch并尝试构建调试版本,则会收到以下警告:

warning: stdafx.h.gch: created with -gnone, but used with -gdwarf-2

首先,我不知道这是否会严重影响我的构建,其次,不同的操作系统可能会为生成传递不同的编译时定义标志,因此在我看来,这不是“一刀切”的用例。

问题


我该如何解决这个问题,以便预编译头文件位于除$PWD之外的其他位置,并且可以被GCC检测到?我目前正在使用gcc v5.3.1。

谢谢。


你能发一行编译代码,让GCC看到不在同一文件夹中的gch文件吗? - user657267
@user657267 当然可以:INC += .tmp/${OS_TYPE}/${CPU_TYPE}/${REL_TYPE}gcc ${INC} -c ${INPUT} -o ${OUTPUT}。虽然不是最有帮助的,但我的 Makefile 非常冗长,并在构建过程中回显所使用的 include 路径,我可以确认头文件通常从该路径解析,但 .gch 文件除外。 - Cloud
抱歉,我的意思是执行时它实际扩展成什么。 - user657267
2个回答

4

以下是您问题场景的 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

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

这个项目会在 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后,我们已经在生成好的tmp/hw.h.gch之后,在hw.h的末尾添加了一个#error指令。因此,如果以后任何地方使用前者而不是后者进行#include,则会导致构建失败。事实就是如此。
这正是应该的。GCC手册 3.21 使用预编译头文件,第3段:
预编译头文件在编译时搜索 #include 时被查找。随着它搜索包含的文件(请参见搜索路径),编译器在每个目录中寻找可执行文件。按顺序查找预编译头文件。搜索的名称是附加了“.gch”的#include中指定的名称。如果不能使用预编译头文件,则忽略它。
因此,给定包含搜索路径“.”,指令“#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。但实际上这没有任何影响。文档忽略了一个重要的限定条件。第二个句子应该写成:
随着它搜索包含的文件(请参见搜索路径),编译器在每个目录中寻找可执行文件。按顺序查找预编译头文件。如果找到include文件,则首选使用预编译头文件。
要找到并使用PCH,必须使PCH成为相应头文件的同级目录。考虑到这一点,这正是你想要的。否则,在使用-Ia时,可能会找到并使用没有匹配的兄弟头文件的a/foo.h.gch,而当有一个匹配的b/foo.h然后可以通过稍后的-Ib找到和使用b/foo.h.gch。显然,后者是更好的选择。

有了这个洞察力,解决方案就不难想到:如果你真的想编译并使用一个不是源头文件的 PCH,一定要给它一个虚假的匹配头文件,而这个头文件是与源头文件在同一级别的。你可以根据自己的需要进行安排,例如:

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

请注意 PCH 从 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

0

如果你强制编译器使用 -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 的预编译头文件。
  • clang 搜索名为 pch.h.pch.gch 的预编译头文件。
  • clang 需要使用 -include pch.h-include-pch pch.h.pch 命令。
  • gcc 总是搜索预编译头文件,不支持 -include-pch 命令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接