Goal of my Makefile is to create in the end a static library *.a out of Fortran77 files and some *.c's + *.h's whereas a specific part of the headers have to be precompiled with a special company internal precompiler which is provided via executable and all you have to hand over is the pathname+filename.
Let's call the Precompiler CPreComp.
The files needing the precompilation *_l.h .
So I want first to collect all the headers I need to precompile and then hand it over to a script which does some magic (env variables blubb blubb) and calls the precompiler.
Here you go with my 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);)
So that is my Makefile, the script for the DO_CPreComp looks as follows:
#!/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}"
So according to the makefileTellMeWhatYouHaveSoFar.txt
I collect all the headers, obviously also the ones not specified with _l.h
. This has space for improvement but the precompiler is smart enough to skip the files which are not suitable. So makefileTellMeWhatYouHaveSoFar.txt
looks like that:
header1.h header2.h header2_l.h headerx_l.h headery_l.h headerz.h
The Error tells me:
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
shows me the script knows nothing and it is not even created. If I modify cprecomptool
and directly add it in the script hardcoded the scriptTellMeWhatYouKnow.txt
shows me the argument $(CPRECOMP)
twice as file name and path name and the hardcoded precompiler. And ofc it ends up with Segmentation fault, so the header name was never handed over.
Additionally:
If I do not call the script in the second foreach
but let $(i)
be printed out with echo in another file it is empty.
Perhaps I am just too blind. And please if you are able to help me , explain it to me for dumb people, such that for the next time I stumble over a problem I am smarter because I know what I am doing. :)
OK, now that the main issue is solved, let's have a look at make coding styles. The make way of accomplishing what you want is not exactly using foreach
in recipes. There are several drawbacks with this approach like, for instance, the fact that make cannot run parallel jobs, while it is extremely good at this. And on modern multi-core architectures, it can really make a difference. Or the fact that things are always redone while they are potentially up to date.
Assuming the result of the pre-compilation of foo_l.h
file is a foo.h
(we will look at other options later), the make way is more something like:
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)
($@
expands as the target). This is a static pattern rule. With this coding style only the headers that need to be pre-compiled (because they are older than their prerequisites) are re-built. And if you run make in parallel mode (make -j4
for 4 jobs in parallel) you should see a nice speed-up factor on a multi-core processor.
But what if the pre-compilation modifies the foo_l.h
file itself? In this case you need another dummy (empty) file to keep track of when a file has been 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 $@
($<
expands as the first prerequisite). The trick here is that the foo_l.h.done
empty file is a marker. Its last modification time records the last time foo_l.h
has been pre-compiled. If foo_l.h
or DatabaseForPreComp.txt
has changed since, then foo_l.h.done
is out of date and make re-builds it, that is, pre-compiles foo_l.h
and then touch foo_l.h.done
to update its last modification time. Of course, if you use this, you must tell make that some other targets depend on $(PREC_TAGS)
.