在 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.h
或 DatabaseForPreComp.txt
从那以后发生了变化,那么 foo_l.h.done
已经过时并使 re-builds 它,即 pre-compiles foo_l.h
然后触摸 foo_l.h.done
以更新其最后修改时间。当然,如果你使用这个,你必须告诉make,其他一些目标依赖于$(PREC_TAGS)
。
我的 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.h
或 DatabaseForPreComp.txt
从那以后发生了变化,那么 foo_l.h.done
已经过时并使 re-builds 它,即 pre-compiles foo_l.h
然后触摸 foo_l.h.done
以更新其最后修改时间。当然,如果你使用这个,你必须告诉make,其他一些目标依赖于$(PREC_TAGS)
。