GNU make pattern rules with different file base names - gnu-make

I have a data processing job that I would like to automate with Make. Hundreds of files need to be processed, in several steps.
Unfortunately, the base name will change for at least one of the steps, but it would be easy to write these dependencies into a separate file that then is included.
However, I'd like to avoid also writing the build instructions (which are quite complicated) for all these files separately.
I envisage something along these lines:
# automatically generated rules, included into make file
dir1/test.bb: dir2/test_other_name.aa
# (many more rules like the above, linking xxx.bb to yyy.aa)
# pattern rule
%.bb: %.aa
# build step using $# $>
What I would like is the pattern rule to provide the rules, and the explicit rule defining the dependencies. Can something like this be achieved?

When make's noddy patterns don't cut the mustard,
just write out the rules explicitly.
(This has the happy side effect of not using pattern rules.)
Let's say you have a function src-to-target which will generate the target filename (i.e., $(call src-to-target,dir2/test_other_name.aa) expands to dir1/test.bb.
Also, you have a list of sources in ${srcs}, and ${recipe} is a list of shell commands using $#, $< etc.
define src-to-target = ... # $1:source
define recipe =
echo Building $# from $<
⋮
endef
define generate-rule = # $1:source
target := $(call src-to-taget,$1)
targets += $${target}
$${target}: $1 ; $${recipe}
endef
$(foreach _,${srcs},$(eval $(call generate-rule,$_)))
.PHONY: all
all: ${targets} ; : $# Success
The $(foreach ...) does all the work here.
So, looking at that in painful detail,
First expand ${srcs}
Set $_ to the first in the list (dir2/test_other_name.aa say)
Expand $(call generate-rule,$_)
Expand $(call generate-rule,dir2/test_other_name.aa)
$1 is set to dir2/test_other_name.aa, and the expansion of $(generate-rule) follows, leading to this block of text
target := dir1/test.bb
targets += ${target}
${target}: dir2/test_other_name.aa ; ${recipe}
As a side effect, $(eval) swallows the above text. The expansion of the $(eval) though is empty.
$_ is set to the next source file.
Wash, lather, rinse, repeat
Once the $(foreach) is complete,
${targets} contains the complete list of targets.
Parallel safe too.
What's not to like?

Related

How can a Makefile replacement pattern produce more than one output per input?

In our code base we have a code generator which takes foo.xyz and produces two source files foo-in.c and foo-out.c.
In an application's Makefile I would like to list the sources as:
SOURCES=main.c gadget.c foo.xyz
Then the corresponding OBJECTS variable should expand to:
OBJECTS=main.o gadget.o foo-in.o foo-out.o
but I'm unable to find whether it is possible to do this expansion generically using GNU Make. The common $(SOURCES:.c=.o) replacement pattern replaces a single source file with a single object file.
How can I write a substitution pattern which will produce multiple output files per input file?
Well, while writing the question I found a usable solution.
SOURCES=main.c gadget.c foo.xyz
OBJECTS=$(patsubst %.c,%.o,$(filter %.c,$(SOURCES))) \
$(patsubst %.xyz,%-in.o,$(filter %.xyz,$(SOURCES))) \
$(patsubst %.xyz,%-out.o,$(filter %.xyz,$(SOURCES)))
app: $(OBJECTS)
$(LD) -o $# $(LDFLAGS) $(OBJECTS)
%-in.c %-out.c: %.xyz
# Very special codegen rule
touch $(patsubst %.xyz,%-in.c,$<)
touch $(patsubst %.xyz,%-out.c,$<)
When converting from $(SOURCES) to $(OBJECTS) use two separate patsubst calls to the filteres out .xyz files. This way, both the %-in.o and %-out.o files ends up in the object list.
Another solution could be to create an intermediate sources list using the same trick but substituting xyz with the corresponding -in.c and -out.c patterns. Then the objects list could be created in the traditional way. An added benefit of this method would be that creating a rule which generates all source code files is trivial.

How to write a makefile executing make one directory level up

Can I write a wrapper makefile that will cd one level up and execute there make with all the command options I have given the wrapper?
In more detail:
Directory project contains a real Makefile with some different targets.
Directory project/resources contains the wrapper Makefile which should call Makefile in project.
When I am in my shell in directory project/resources, I execute
make TARGET
and the Makefile there just cds one directory up and calls
make TARGET
in the directory project.
Is this possible? And how?
You could use a very simple Makefile for all your sub-directories:
%:
$(MAKE) -C .. $#
% is a last resort match-anything pattern rule that will match any target... for which there is no implicit rule (GNU make has an incredibly large number of implicit rules). So, if none of your targets are covered by an implicit rule, this should work. Else you will have to tell make not to use the implicit rules it knows. This can be done (with GNU make) by calling make with the -r option:
cd project/resources
make -r <anything>
will call make in project for target <anything>. The main drawback is that the -r flag is passed to the sub-make and so the implicit rules will not apply neither in project, which can be a problem. If it is you can obtain the same effect by adding an empty .SUFFIXES target to theMakefile in project/resources:
.SUFFIXES:
%:
$(MAKE) -C .. $#
With my version of GNU make (3.82) it works like a charm and the sub-make has all the default implicit rules.
Yes, you can have a makefile which works for "any" target.
The GNU make manual discusses this in the Overriding Part of Another Makefile section:
Sometimes it is useful to have a makefile that is mostly just like another makefile. You can often use the ‘include’ directive to include one in the other, and add more targets or variable definitions. However, it is invalid for two makefiles to give different recipes for the same target. But there is another way.
In the containing makefile (the one that wants to include the other), you can use a match-anything pattern rule to say that to remake any target that cannot be made from the information in the containing makefile, make should look in another makefile. See Pattern Rules, for more information on pattern rules.
For example, if you have a makefile called Makefile that says how to make the target ‘foo’ (and other targets), you can write a makefile called GNUmakefile that contains:
foo:
frobnicate > foo
%: force
#$(MAKE) -f Makefile $#
force: ;
If you say ‘make foo’, make will find GNUmakefile, read it, and see that to make foo, it needs to run the recipe ‘frobnicate > foo’. If you say ‘make bar’, make will find no way to make bar in GNUmakefile, so it will use the recipe from the pattern rule: ‘make -f Makefile bar’. If Makefile provides a rule for updating bar, make will apply the rule. And likewise for any other target that GNUmakefile does not say how to make.
The way this works is that the pattern rule has a pattern of just ‘%’, so it matches any target whatever. The rule specifies a prerequisite force, to guarantee that the recipe will be run even if the target file already exists. We give the force target an empty recipe to prevent make from searching for an implicit rule to build it—otherwise it would apply the same match-anything rule to force itself and create a prerequisite loop!
One option: use a wrapper file to execute the commands to do that. Just be sure your target make files don't include the child directory that has the wrapper, or else you can create an endless loop. For example,
clean:
pushd .. && make clean && popd
Using the comment of user Renaud Pacalet and the answer to a different question the following one-liner is as close as I could get. The whole Makefile reads:
IGNORE := $(shell $(MAKE) -C .. $(MAKECMDGOALS))
This solutions comes with a few caveats:
Command line option -B does not get passed through to the subsequent make call.
The output of the subsequently called make process (in the project directory) is not printed to stdout.
The wrapper make process reports for any given target at the end :
make: *** No rule to make target TARGET. Stop.

Makefiles: Alternate between two different prerequisites in the same implicit goal

I'm writing my very first makefile and I'm stuck on a problem.
I have a bunch of prerequisites, of which the first one is a template that needs to be in a special position. I get to do this like so:
target : req1 req2 req3
command $(filter-out $<,$^) $# --template=$<
The thing is, sometimes I need to switch that template for another one while leaving the other prerequisites alone, so that
# Changing just the first prerequisite
target : req1b req2 req3
command $(filter-out $<,$^) $# --template=$<
I'm searching for a way to achieve this using the goal I have right now, without writing an ad-hoc explicit goal, maybe calling make with an argument or something similar, but I know too little about makefiles to get it done.
The general idea is you will want to use a variable, how you set that variable is up to you. One way is to pass a variable via the command line. Your Makefile would look like:
target : $(REQ_ONE) req2 req3
command $(filter-out $<,$^) $# --template=$<
and then do make target REQ_ONE=reg1 or make target REQ_ONE=reg1b
If you have a preferred default that you wish to use (say req1) and you want to use the alternative in rarer circumstances you could use the modified forms of the previous example.
# only set if the variable doesn't exist
REQ_ONE ?= req1
target : $(REQ_ONE) req2 req3
command $(filter-out $<,$^) $# --template=$<
Finally, a variant on this approach is to have your Makefile call make with a variable assignment:
# only set if the variable doesn't exist
REQ_ONE ?= req1
target2:
$(MAKE) target REQ_ONE=req1b
target : $(REQ_ONE) req2 req3
command $(filter-out $<,$^) $# --template=$<
Another solution is to use secondary expansion as demonstrated in this SO post on target specific variables as a prerequisites.

Passing rules to make recursively

It's probably trivial to do this but I can't see how.
I want to have a parent Makefile to decide which Makefile to call recursively based on the value of a variable passed in the command line.
I.e., I want to be able to call my main Makefile with:
make some_rule TARGET=a
or
make some_rule TARGET=b
and have my main Makefile decide based on the value of TARGET which makefile to invoke to run make some_rule. (For example, decide whether to call sub_directory_a/Makefile or sub_directory_b/Makefile to execute rule some_rule.)
Note: I have many different rules, so I do not want my main Makefile to list all the possible rules and for each of them call recursively the correct Makefile. I am hoping my main Makefile can only be a few lines long and not have to be updated whenever I create new rules.
You could do what you describe with
default_target:
%:
$(MAKE) -C some_directory_$(TARGET) $#
The %: rule is a pattern rule in which the pattern matches all rules (called a match-anything rule by the GNU make manual); $# is the current target. Note that the default_target: rule doesn't have a recipe, so calling make without a target will use the recipe of the match-anything rule (the only one that applies and has a recipe) to try to build default_target.
The caveat of this approach is that targets cannot be declared phony. If you want to have phony targets, you'll have to specify the recipe for those targets again, for example
PHONY_TARGETS = all clean distclean
.PHONY: $(PHONY_TARGETS)
$(PHONY_TARGETS):
$(MAKE) -C some_directory_$(TARGET) $#
%:
$(MAKE) -C some_directory_$(TARGET) $#
Unfortunately, I do not know a trick to declare all targets phony, which is what you'd really want to do.
Note that you can use ifeq etc. with the variables you set at the command line if you want to allow more fancy values for TARGET than parts of directory names, such as
%:
ifeq ($(TARGET),gibson)
echo 'Planet $# was successfully hacked.'
else
$(MAKE) -C some_directory_$(TARGET) $#
endif
Also note that a more common way to set common variables for many Makefiles is to put them into a file, often common.mk, and include it from the other Makefiles:
include ../common.mk # to include common.mk from some_directory_a/Makefile
But you'll have to decide yourself which approach is a better fit for your project.

Makefile: conversion from recursive make calls to a sequential flow

I have a Makefile that is executed by "gmake -f Makefile foo" and looks like the following.
foo:
#set var = 1
#$(MAKE) bar var=1
bar:
#hello.mk is included
#echo “success”
ifeq ($(var), 1)
include test\hello.mk
endif
I'd like to convert this to a sequential flow, like something in the following as it has less overhead and does not need to step back in this Makefile.
foo:
$(eval var=1)
#$(bar)
define bar
include test\hello.mk #this doesn’t work
#echo “success”
endef
I cannot have an include statement inside a function or target, so how should I go around this problem to make this Makefile sequential with no make calls?
Short answer: you can't do this.
You want to make 'foo' and 'bar' with what are in effect different makefiles, which you can't do without a recursive call to Make.
If you can put some restrictions on what hello.mk can do, you might be able to get this into one pass.

Resources