Automated make system with regex pattern matching - regex

I use GNU make for building reports (LaTeX for source, python for figures, etc.). For targets, I use extensively pattern matching, for example:
all : sample1_test1.png sample2_test1.png sample2_test.png
sample%_test1.png : sample%.dat
python gen_figure.py $< $# --test 1
sample%_test2.png : sample%.dat
python gen_figure.py $< $# --test 2
Now, to simplify the rules I would like to use multiple pattern groups (like regex groups) and use the matches separately in the build rule, for example:
all : sample1_test1.png sample2_test1.png sample2_test.png
sample(?P<Sample>[0-9]+)_test(?P<Test>[0-9]+).png : sample$(<Sample>).dat
python gen_figure.py $< $# --test $(<Test>)
(the syntax is arbitrary, but the point is that I can define two different match groups called Sample and Test and use them as parameters to my script).
How would I achieve this in make or another build system (waf, scons etc.)?

To do it in GNU make, you can use one of two different "metaprogramming" models supported by GNU make:
Auto-generated include files. In your main makefile, add -include generated.mk then write a makefile rule with the target generated.mk (probably listing Makefile as a prerequisite), where the recipe generates the appropriate targets based on the list of targets. You have the full power of the shell to construct your target lists however you want. Every time you modify the makefile, the included file will be rebuilt then GNU make will automatically re-exec itself so you don't have to do anything extra.
Use GNU make's $(eval ...) function, probably combined with $(call ...) and $(foreach ...), to automatically evaluate rules. To do this you define a "template" for the rule using define ... enddef, with variables installed where you want to provide arguments, then use $(call ...) to instantiate them, use $(eval ...) on the result of the call, and do it in a loop for each target. Something like: $(foreach T,$(TARGETS),$(eval $(call DEFINERULE,$(T))))
Here's an example of method 1. Suppose you have this predefined content in your makefile:
TESTS := sample1_test1.png sample2_test1.png sample2_test.png
Then you can use this makefile to get something like the above:
all: $(TESTS)
-include generated.mk
generated.mk : Makefile
#rm -f '$#'
#for t in $(TESTS); do \
eval `echo "$$t" | sed 's/^sample\([0-9]*\)_test\([0-9]*\).*/sample=\1 test=\2/'`; \
echo "$$t : sample$$sample.dat ; python gen_figure.py \$$< \$$# --test $$test" >> '$#'; \
done
Note I just wrote this off the top of my head but I think it will work.

Related

GNU Make: expand pattern before passing to Shell

I want to use an awk script to figure out the which modules have to be compiled before I can compile a FORTRAN source file. My project is structured in a way that I can obtain the filenames that provide the modules by running
awk '$1=/use/{print gensub(",","","g", $2) ".o"}' file.f90
on the file I want to compile.
However, my make command
%.o: $(shell awk '$$1=/use/{print gensub(",","","g", $$2) ".o"}' /path/to/%.f90)
fails with
awk: fatal: cannot open file `/path/to/%.f90' for reading: No such file or directory
So %.f90 does not get expanded. Why is that the case and how can I solve that issue?
Variables and functions in targets and prerequisites are expanded when the makefile is parsed, not when make is running the makefile. But, pattern rules are only expanded when make is running the makefile, trying to build a target that matches the pattern. So at the time these variables and functions are expanded, you only have the literal pattern string not its expansion into a real filename.
See How make reads a makefile in the docs.
There are a number of ways to do this. One option is using secondary expansion. However note you'll have to double-escape the $ you are escaping!!
.SECONDEXPANSION:
%.o: $$(shell awk '$$$$1=/use/{print gensub(",","","g", $$$$2) ".o"}' /path/to/$$*.f90)
ETA
You could alternatively not use .SECONDEXPANSION at all and instead use eval like this:
%.o:
...
SRCS := $(wildcard *.f90)
OBJS := $(SRCS:%.f90=%.o)
$(foreach O,$(OBJS),\
$(eval $O: $(shell awk '$$1=/use/{print gensub(",","","g", $$2) ".o"}' $(O:%.o=%.f90))))
Since you didn't give an actual example I just made up SRCS and OBJS variables. Maybe you have similar variables already.

Recursive search in VPATH?

My C++ project has source files organized in nested subdirectories of ./src. I have a pattern rule in my makefile which compiles all of the .cpp source files into objects:
$(OBJDIR)/%.o: %.cpp makefile
$(CXX) -c $< -o $#
Since I am using this pattern rather than writing a compilation rule for each source file, I need to tell make to look recursively through ./src for these prerequisites. Right now I have:
VPATH := $./src/:./src/folder1:./src/folder2:./src/folder3
This works, but it feels pretty inelegant and also causes bugs when I inevitably forget to add in a new folder.
Hoping someone has a better solution!
You can automate the building of the VPATH variable like yours by searching for subdirectories and replacing spaces with colons:
space :=
space +=
VPATH := $(subst $(space),:,$(shell find src -type d))
This assumes that you have no spaces in your directories or filenames.
With this approach, it is not clear to me what you would do if two source files in two different subdirectories have the same name -- but that seems to be more related to your overall setup than to your question about the VPATH specifically.
For the $(space) variable trick, see the nifty Escaping comma and space in GNU Make blog post.
gmake itself does not have any functions for recursive directory traversal, so you have to resort to $(shell ...):
VPATH := $(shell find src -type d -print | tr '\012' ':' | sed 's/:$$//')
Tweak the shell script to get the right semantics. You want to use the := operator, in order to evaluate this one time.

Call gnumake on all subdirs in parallel (-j) and only then run the linker-rule last (i.e. order important)

I have a c++ makefile project. It works great for non-parallel building. It works 99% for parallel building... the only problem I have is that I can't get my final executable link-line to run last (it must be the last thing that happens).
I have some constraints: I don't want to have any PHONY dependencies on my link line because this causes it to re-link every time. I.e. once my target is built, when I re-build it should not be re-linked.
Here is (slightly contrived) minimal example. Please don't try to pick holes in it, its really here just to show the problem, its not real, but the problem I am showing is. You should be able to just run this and see the same issue that I am.
# Set the default goal to build.
.DEFAULT_GOAL = build
#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3
#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so
# Top level build goal - depends on all of the subdir makes and the target.out
.PHONY: build
build: $(MAKE_SUB_DIRS) target.out
#echo build finished
# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
#if [ ! -f out$#.so ] ; then echo making $#... ; sleep 1 ; echo a > out$#.so ; fi
# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
#echo linking $#...
#ls $(OUTPUTS) > /dev/null
#cat $(OUTPUTS) > target.out
# Clean for convinience
clean:
#rm -rf *.so target.out
Now, I don't really care about make working, what I want is make -j to work. Here is me trying to run it:
admin#osboxes:~/sandbox$ make clean
admin#osboxes:~/sandbox$
admin#osboxes:~/sandbox$ make -j - 1st attempt
making 1...
making 2...
linking target.out...
making 3...
ls: cannot access 'out1.so': No such file or directory
ls: cannot access 'out2.so': No such file or directory
ls: cannot access 'out3.so': No such file or directory
makefile:24: recipe for target 'target.out' failed
make: *** [target.out] Error 2
make: *** Waiting for unfinished jobs....
admin#osboxes:~/sandbox$
admin#osboxes:~/sandbox$ make -j - 2nd attempt
linking target.out...
build finished
admin#osboxes:~/sandbox$
admin#osboxes:~/sandbox$ make -j - 3rd attempt
build finished
admin#osboxes:~/sandbox$
So I highlighted my three attempts to run it.
Attempt 1: you can see all 4 dependencies of build are started at the same time (approx). Since each of the makeing x... take 1 second and the linking is nearly instant we see my error. However all the three "libraries" are build correctly.
Attempt 2: The libraries only get created if they don't already exists (that's bash code - pretending to do what a makefile might have done). In this case they are already created. So the Linking passes now since it just requires the libraries to exist.
Attempt 3: nothing happens because nothing needs to :)
So you can see all the steps are there, its simply a matter of ordering them. I would like the the make sub dirs 1, 2, 3 to build in any order in parallel and then only once they are all completed I want target.out to run (i.e. the linker).
I don't want to call it like this though: $(MAKE) target.out because in my real makefile I have lots of variables all setup...
I have tried looking at (from othe answers) .NOT_PARALLEL and using the dep order operator | (pipe), and I have tried order a load of rules to get target.out to be last.... but the -j option just ploughs through all of these and ruins my ordering :( ... there must be some simple way to do this?
EDIT: add an example of ways to pass variables to sub-makes. Optimized a bit by adding $(SUBDIRS) to the prerequisites of build instead of making them in its recipe.
I am not sure I fully understand your organization but one solution to deal with sub-directories is as follows. I assume, a bit like in your example, that building sub-directory foo produces foo.o in the top directory. I assume also that your top Makefile defines variables (VAR1, VAR2...) that you want to pass to the sub-makes when building your sub-directories.
VAR1 := some-value
VAR2 := some-other-value
...
SUBDIRS := foo bar baz
SUBOBJS := $(patsubst %,%.o,$(SUBDIRS))
.PHONY: build clean $(SUBDIRS)
build: $(SUBDIRS)
$(MAKE) top
$(SUBDIRS):
$(MAKE) -C $# VAR1=$(VAR1) VAR2=$(VAR2) ...
top: top.o $(SUBOBJS)
$(CXX) $(LDFLAGS) -o $# $^ $(LDLIBS)
top.o: top.cc
$(CXX) $(CXXFLAGS) -c $< -o $#
clean:
rm -f top top.o $(SUBOBJS)
for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done
This is parallel safe and guarantees that the link will take place only after all sub-builds complete. Note that you can also export the variables you want to pass to sub-makes, instead of passing them on the command line:
VAR1 := some-value
VAR2 := some-other-value
...
export VAR1 VAR2 ...
Normally you would just add the lib files as prerequisites of target.out:
target.out: $(OUTPUTS)
#echo linking $#...
The thing is, this will relink target.out if any of the output lib files are newer. Normally this is what you want (if the lib has changed, you need to relink target), but you specifically say you do not.
GNU make provides an extension called "order only prerequisites", which you put after a |:
target.out: | $(OUTPUTS)
#echo linking $#...
now, target.out will only be relinked if it does not exist, but in that case, it will still wait until after $(OUTPUTS) have finished being built
If your $(OUTPUT) files are build by subsirectory makes, you may find you need a rule like:
.PHONY: $(OUTPUT)
$(OUTPUT):
$(MAKE) -C $$(dirname $#) $#
to invoke the recursive make, unless you have other rules that will invoke make in the subdirectories
Ok, so I have found "a" solution... but it goes a little bit against what I wanted and is therefore ugly (but not that that ugly):
The only way I can fathom to ensure order in parallel build (again from other answers I read) is like this:
rule: un ordered deps
rule:
#echo this will happen last
Here the three deps will be made (or maked?) in any order and then finally the echo line will be run.
However the thing that I want to do is a rule and specifically so, such that it checks if anything has changed or if the file does not exist - and then, and only then, runs the rule.
The only way I know of to run a rule from within the bode of another rule is to recursively call make on it. However I get the following issues just calling make recursively on the same makefile:
Variables are not passed in by default
Many of the same rules will be re-defined (not allowed or wanted)
So I came up with this:
makefile:
# Set the default goal to build.
.DEFAULT_GOAL = build
#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3
#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so
# Top level build goal - depends on all of the subdir makes and the target.out
export OUTPUTS
.PHONY: build
build: $(MAKE_SUB_DIRS)
#$(MAKE) -f link.mk target.out --no-print-directory
#echo build finished
# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
#if [ ! -f out$#.so ] ; then echo making $#... ; sleep 1 ; echo a > out$#.so ; fi
# Clean for convinience
clean:
#rm -rf *.so target.out
link.mk:
# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
#echo linking $#...
#ls $(OUTPUTS) > /dev/null
#cat $(OUTPUTS) > target.out
So here I put the linker rule into a separate makefile called link.mk, this avoids recursive make calling on the same file (and therefore with re-defined rules). But I have to export all the variables I need to pass through... which is ugly and adds a bit of a maintenance overhead if those variables change.
... but... it works :)
I will not mark this any time soon, because I am hopeful some genius will point out a neater/better way to do this...

How to use makedepend in a non-standard makefile name

I am trying to use makedepend in a makefile named Makefile_abc.
Normally when I have to build a target trg, I say
make -f Makefile_abc trg
and this works beautifully.
I have added following lines in this makefile.
dep:
makedepend main.c
Now, when I do,
make -f Makefile_abc dep
I get the error,
makedepend: error: [mM]akefile is not present
make: *** [depend] Error 1
If I rename my makefile as Makefile, then following command works fine,
make depend
So, I am looking for a way to use makedepend on non-standard makefile names.
This is a basic 'read the manual' question.
Looking at makedepend(1), you need -fMakefile_abc in the recipe for the target dep (optionally with a space between -f and Makefile_abc):
dep:
makedepend -fMakefile_abc main.c
To update the dependencies, you'd run:
$ make -f Makefile_abc dep
This would cause make to run:
makedepend -fMakefile_abc main.c
(Note that the 'standard' — most common — name for the target is depend rather than dep, so you'd normally run make -fMakefile_abc depend or, with a plain makefile file, make depend.)
If you're using GNU Make, you might also add another line to Makefile_abc:
.PHONY: dep # Or depend, depending…
This tells make that there won't be a file dep created by the rule.
You can often get information about how to run a command by using makedepend --help or makedepend -: — the first may (or may not) give a useful help message outlining options, and the second is very unlikely to be a valid option which should generate a 'usage' message that summarizes the options.

Makefile - replace in wildcard

In my project all .cpp files are stored in
Classes/
Classes/Something/
Classes/Something/Else
...
I want to compile all .cpp file separetly to Bin/ directory, replacing / with _, so that:
Classes/First.cpp -> Bin/Classes_First.o
Classes/Foo/Bar.cpp -> Bin/Classes_Foo_Bar.o
Now I wanted to create rules for compiling:
Bin/%.o: $(subst _,/,%.cpp)
$(COMPILER)g++ $(COMPILE_FLAGS) -c -o $# $^
I tried:
make Bin/Classes_Test.o
But compilation failed.
So I created debugging pattern:
%.cpp:
#echo CPP: $#
Now it printed:
CPP: Classes_Test.cpp
Why?!
So I changed my pattern to:
Bin/%.o: $(subst _,/,Test1_Test2.cpp)
and I saw:
CPP: Test1/Test2.cpp
I'm a little bit confused why subst does not work if I use wildcard as source...
This is an evaluation order issue.
When make parse the makefile it evaluates the $(subst) call but the argument to $(subst) at that point is the literal string %.cpp which has nothing to substitute in it and so does not do anything.
At target evaluation/execution time the % in the target pattern and prereq pattern are filled out but the $(subst) has long-since gone away.
To do this you will need to manually (in one way or another) map the output files to the input files. You can do that and keep the %.o pattern rule target for the actual recipe to run though (so you just need to generate a bunch of Bin/Test1_Test2.o: Test1/Test2.cpp lines).
Alternatively, I believe you can might be able to use secondary expansion to do this:
.SECONDEXPANSION:
Bin/%.o: $$(subst _,/,%.cpp)