在 makefile 中自动预编译 headers

Precompile headers in a makefile automatically

我的 Makefile 的目标是最终创建一个静态库 *.a,其中包含 Fortran77 文件和一些 *.c 的 + *.h,而 header 的特定部分必须预编译使用一个特殊的公司内部预编译器,它通过可执行文件提供,您只需交出路径名+文件名。

我们将预编译器称为 CPreComp。

需要预编译的文件 *_l.h .

所以我想首先收集我需要预编译的所有 headers,然后将它交给一个脚本,它会做一些魔术(env 变量 blubb blubb)并调用预编译器。

这是我的 Makefile:

SHELL=/usr/bin/bash
.SHELLFLAGS:= -ec

SOURCE_PATH = ./src
CPRECOMP = ./tools/cprecomp.exe
DO_CPreComp = $(SOURCE_PATH)/do_cprec
HDREXT = .h
PREC_HEADERS = $(foreach d, $(SOURCE_PATH), $(wildcard $(addprefix $(d)/*, $(HDREXT))))

.PHONY: all prereq

all:: \
      prereq \
      lib.a

prereq: chmod 777 $(DO_CPreComp)
        echo $(PREC_HEADERS) >> makefileTellMeWhatYouHaveSoFar.txt

lib.a: \
     obj/file1.o \
     obj/file2.o
     ar -r lib.a $?

obj/file1.o:

# do some fortran precompiling stuff here for a specific file

obj/file2.o: $(SOURCE_PATH)/*.h precomp_path/*.h $(SOURCE_PATH)/file2.c precomp_path/%_l.h
         cc -c -g file2.c

precomp_path/%_l.h : DatabaseForPreComp.txt

precomp_path/%_l.h : 
       $(foreach i , $(PREC_HEADERS) , $(DO_CPreComp) $(i) $(CPRECOMP);)

这就是我的 Makefile,DO_CPreComp 的脚本如下所示:

#!/bin/bash
filename="(basename "")"
dir="$(dirname "")"
cprecomptool=""

echo ${dir} ${filename} ${cprecomptool} >> scriptTellMeWhatYouKnow.txt     

"${cprecomptool}" "precomp_path/${filename}.1" >&cprecomp.err
cp "precomp_path/${filename}.1" "precomp_path/${filename}"

所以根据 makefileTellMeWhatYouHaveSoFar.txt 我收集了所有 headers,显然还有 _l.h 未指定的那些。这有 space 需要改进,但预编译器足够聪明,可以跳过不合适的文件。所以 makefileTellMeWhatYouHaveSoFar.txt 看起来像这样:

header1.h header2.h header2_l.h headerx_l.h headery_l.h headerz.h

错误告诉我:

path_to_here/do_cprec : line xy: : unbound variable
make[2]: *** [precomp_path/%_l.h] Error 1
make[1]: *** [lib.a] Error 2

scriptTellMeWhatYouKnow.txt 告诉我脚本什么都不知道,甚至都没有创建。如果我修改 cprecomptool 并直接将其添加到硬编码的脚本中,scriptTellMeWhatYouKnow.txt 会向我显示参数 $(CPRECOMP) 两次作为文件名和路径名以及硬编码的预编译器。而且它最终以分段错误结束,因此 header 名称从未被移交。

另外: 如果我不在第二个 foreach 中调用脚本,但让 $(i) 在另一个文件中用 echo 打印出来,它是空的。

也许是我太盲目了。如果你能帮助我,请为愚蠢的人向我解释,这样下次我遇到问题时我会更聪明,因为我知道我在做什么。 :)

在@Renaud Pacalet 的帮助下,我找到了解决方案。 在评论中,您可以进一步阅读尝试和错误。

我正在使用 GNU Make 3.82 Built for x86_64-redhat-linux-gnu。似乎 foreach 不喜欢 i 后面的 space 或者进一步将 space 作为变量的一部分。

# ... like beforehand check out in the question
PREC_HEADERS=$(shell find $(SOURCE_PATH) -iname '*_l.h')
# nothing changed here in between...
$(foreach i,$(PREC_HEADERS),$(DO_CPC) $i $(CPC);)

这样做的好处是我只预编译了 headers ,它有 _l.h - 结尾。 brackets $(i) 是否位于 $i 附近,不会产生任何变化。真正改变一切的是第一个 i 背后的 space .

祝你好运!

OK,主要问题解决了,我们来看看make coding styles。完成你想要的东西的方法并不完全是在食谱中使用 foreach 。这种方法有几个缺点,例如,make 不能 运行 并行作业,虽然它在这方面非常擅长。在现代 multi-core 架构上,它确实可以发挥作用。或者事实上,事情总是在可能是最新的时候重做。

假设 foo_l.h 文件的 pre-compilation 的结果是 foo.h (我们稍后会看其他选项),make 方式更像是:

SOURCE_PATH         := ./src
CPRECOMP            := ./tools/cprecomp.exe
DO_CPreComp         := $(SOURCE_PATH)/do_cprec
HDREXT              := .h
PREC_HEADERS        := $(wildcard $(addsuffix /*_l.$(HDREXT),$(SOURCE_PATH)))
PRECOMPILED_HEADERS := $(patsubst %_l.h,%.h,$(PREC_HEADERS))

$(PRECOMPILED_HEADERS): %_l.h: %.h DatabaseForPreComp.txt
    $(DO_CPreComp) $@ $(CPRECOMP)

$@ 作为目标展开)。这是一个static pattern rule。使用这种编码风格,只有 headers 需要 pre-compiled(因为它们比它们的先决条件旧)是 re-built。如果您 运行 在并行模式下制作(make -j4 并行处理 4 个作业),您应该会在 multi-core 处理器上看到一个不错的 speed-up 因子。

但是如果 pre-compilation 修改了 foo_l.h 文件本身呢?在这种情况下,您需要另一个虚拟(空)文件来跟踪文件何时 pre-compiled:

SOURCE_PATH         := ./src
CPRECOMP            := ./tools/cprecomp.exe
DO_CPreComp         := $(SOURCE_PATH)/do_cprec
HDREXT              := .h
PREC_HEADERS        := $(wildcard $(addsuffix /*_l.$(HDREXT),$(SOURCE_PATH)))
PREC_TAGS           := $(patsubst %,%.done,$(PREC_HEADERS))

$(PREC_TAGS): %.done: % DatabaseForPreComp.txt
    $(DO_CPreComp) $< $(CPRECOMP) && \
    touch $@

$< 作为第一个先决条件展开)。这里的技巧是 foo_l.h.done 空文件是一个标记。它的最后修改时间记录了上次foo_l.h已经pre-compiled。如果 foo_l.hDatabaseForPreComp.txt 从那以后发生了变化,那么 foo_l.h.done 已经过时并使 re-builds 它,即 pre-compiles foo_l.h 然后触摸 foo_l.h.done 以更新其最后修改时间。当然,如果你使用这个,你必须告诉make,其他一些目标依赖于$(PREC_TAGS)