我的Makefile的目标是最终从Fortran77文件中创建一个静态库* .a和一些* .c + * .h,而标头的特定部分必须使用特殊的公司内部预编译器进行预编译,通过可执行文件提供,您只需交出路径名+文件名。
我们叫预编译器CPreComp。
需要预编译* _l.h的文件。
因此,我想先收集所有需要进行预编译的标头,然后将其移交给一个脚本,该脚本具有一些魔术作用(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 "$1")"
dir="$(dirname "$1")"
cprecomptool="$2"
echo ${dir} ${filename} ${cprecomptool} >> scriptTellMeWhatYouKnow.txt
"${cprecomptool}" "precomp_path/${filename}.1" >&cprecomp.err
cp "precomp_path/${filename}.1" "precomp_path/${filename}"
因此,根据makefileTellMeWhatYouHaveSoFar.txt
,我收集了所有标头,显然还有未用_l.h
指定的标头。这有改进的空间,但是预编译器足够聪明,可以跳过不适合的文件。因此makefileTellMeWhatYouHaveSoFar.txt
看起来像这样:
header1.h header2.h header2_l.h headerx_l.h headery_l.h headerz.h
错误告诉我:
path_to_here/do_cprec : line xy: $2: unbound variable
make[2]: *** [precomp_path/%_l.h] Error 1
make[1]: *** [lib.a] Error 2
scriptTellMeWhatYouKnow.txt
向我展示了脚本一无所知,甚至没有创建。如果我修改cprecomptool
并将其直接添加到经过硬编码的脚本中,则scriptTellMeWhatYouKnow.txt
会给我显示参数$(CPRECOMP)
两次,分别是文件名和路径名以及硬编码的预编译器。并且Ofc最终以Segmentation Fault结束,因此标头名称从未移交。
另外:
如果我没有在第二个foreach
中调用脚本,而是让$(i)
在另一个文件中以回显的方式打印出来,则该文件为空。
也许我太盲目了。并且,如果您能帮助我,请为愚蠢的人向我解释,以便下次我迷失于一个问题时,我会变得更聪明,因为我知道我在做什么。 :)
答案 0 :(得分:1)
好的,既然解决了主要问题,让我们看一下make编码风格。实现所需目标的方法并不完全是在食谱中使用foreach
。这种方法有很多缺点,例如,make不能运行并行作业,尽管这样做非常好。在现代多核架构上,它确实可以有所作为。或事实可能总是最新时总是重做。
假设foo_l.h
文件的预编译结果是foo.h
(我们将在以后讨论其他选项),其制作方法类似于:
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。使用这种编码样式,仅需要重新编译的标头(因为它们早于其先决条件)。而且,如果您在并行模式下运行make(make -j4
并行执行4个作业),您应该会在多核处理器上看到不错的加速因子。
但是,如果预编译修改了foo_l.h
文件本身怎么办?在这种情况下,您需要另一个虚拟(空)文件来跟踪文件何时被预编译:
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
的最后编译时间。如果此后foo_l.h
或DatabaseForPreComp.txt
发生了更改,则foo_l.h.done
已过时并进行重新构建,即预编译foo_l.h
然后触摸{{1 }}以更新其上次修改时间。当然,如果使用此选项,则必须告诉make其他一些目标取决于foo_l.h.done
。
答案 1 :(得分:0)
在@Renaud Pacalet的帮助下,我找到了解决方案。 在评论中,您可以阅读进一步的尝试与错误。
我正在使用为x86_64-redhat-linux-gnu构建的GNU Make 3.82。似乎foreach
不喜欢i后面的空格,或者将空格作为变量的一部分。
# ... 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);)
这样做的好处是,我只预编译了_l.h
-结尾的标头。是否将brackets $(i)
放在$i
周围不会改变。真正改变了一切的是第一个i
后面的空间。
祝你好运!