Makefile: defining rules and prerequisites in recipes - makefile

I have a setup where the files I want to process with make are dependent on the output of another program. Building the program and all its prerequisites
is also a complicated task so I would like to use make for this as well. Now my problem is, that it doesn't seem that one can generate rules and prerequisites in Makefile recipes. Consider the following code:
bar:
echo target1 target2 target3 > bar
foo: bar
$(eval BAR := $(shell cat bar))
define FUN
$(1):
touch a$(1)
endef
ifdef BAR
$(foreach i,$BAR,$(eval $(call FUN,$(i))))
endif
blub: foo $(BAR)
I replaced a big set of complicated recipes that lead to the generation of the list of files I want to have in the end by the bar recipe. In reality, producing the content of bar is very complicated and should be done by a set of Makefile recipes and cannot just be done by (as the above suggests):
BAR:=$(shell echo target1 target2 target3)
I would like to put the foreach loop into the recipe for foo but that fails with prerequisites cannot be defined in recipes which makes sense and is also explained in function define in makefile
But it seems that when I do make blub that at the time when foo eval's BAR to a different value, the prerequisites for blub are not re-evaluated.
So I think ultimately I'm looking for two things:
how do I generate recipes dynamically at runtime, based on (and dependent on) what another recipe (bar in this case) outputs?
how do I update the prerequisites of a target (blub in this case) dynamically at runtime, based on (and dependent on) what another recipe (bar in this case) outputs?
Thank you!
EDIT: SOLUTION
With the help of #user657267 the following seems to solve my problem:
.PHONY: all
all: blub
-include bar.make
.PHONY: blub
blub: $(BAR)
echo $^
bar.make: Makefile
printf 'BAR=target1 target2 target3\n' > $#
printf 'target1 target2 target3:\n' >>$#
printf '\ttouch $$#' >> $#
.PHONY: clean
clean:
rm -f target1 target2 target3 bar.make

Sounds like you should be using make's self-remaking features
-include bar.make
blub: $(BAR)
#echo $^
bar.make:
#echo BAR := target1 target2 target3 > $#
#echo target1 target2 target3: ; touch $$# >> $#
Obviously the recipes for bar.make are contrived, in the real world they'd probably invoke some kind of script that outputs a valid makefile.

Related

How to define prerequisite for automatic variable in Makefile?

How to modify my Makefile to generate 1.bar, 2.bar and 3.bar by typing make all?
The problem is that all depends on $(bars) and it is empty unless I first run make foo.
foo:
touch 1.foo 2.foo 3.foo
bars = $(patsubst %.foo,%.bar,$(wildcard *.foo))
%.bar: %.foo
cp $< $#
all: $(bars)
You'll have to list the files *.foo in your makefile:
foos = 1.foo 2.foo 3.foo
foo:
touch $(foos)
bars = $(foos:.foo=.bar)
...
You have to have some starting point in your makefile. Make cannot infer the list of things to build starting from absolutely no information at all.
If you don't know what the output will be from a command the simplest way do it is with recursive make. Something like this:
all: geotiff.file
gdal_retile.py $<
$(MAKE) output
output: $(patsubst %.foo,%.bar,$(wildcard *.foo))
%.bar: %.foo
cp $< $#

Makefile: target-specific variables in target dependencies declaration

I'm trying to write a makefile that uses target-specific variables, but while the variables are set correctly in each target and prerequisite body, the prerequisites list itself isn't updated with the variable, thus causing the wrong prerequisite to be checked and called.
How can I update target-specific variables in the prerequisites too?
In the example below, both make foo and make bar should print "world", but make foo prints "hello".
X=hello
hello:
echo "hello"
world:
echo "world"
foo:X=world
foo:$(X)
bar:X=world
bar:
make $(X)
The goal I'm trying to achieve is that different targets will build similar prerequisites - the same files, in different folders - by passing the folders as a target-specific variable. The issue is that as in the example below, if one target is called first (foo, in the example), calling the second will not do anything.
DIR=fooDir
FILE=$(DIR)/filename
$(FILE):
touch $(FILE)
echo $(FILE)
foo: $(FILE)
bar:DIR=barDir
bar: $(FILE)
6.11 Target-specific Variable Values:
As with automatic variables, these values are only available within the context of a target’s recipe (and in other target-specific assignments).
But not in the prerequisite list. In other words, in foo:$(X) X is not target specific.
One way to achieve the desired results is:
same_files := filename another_filename
# ${1} is the target name.
# ${2} is the directory.
define similar_prerequisites
${1} : $(addprefix ${2}/,${same_files})
$(addprefix ${2}/,${same_files}) : | ${2}
touch $$#
${2} :
mkdir $$#
endef
all : foo bar
foo bar :
#echo "$# prerequisites are $^"
$(eval $(call similar_prerequisites,foo,fooDir))
$(eval $(call similar_prerequisites,bar,barDir))
Output:
$ make
mkdir fooDir
touch fooDir/filename
touch fooDir/another_filename
foo prerequisites are fooDir/filename fooDir/another_filename
mkdir barDir
touch barDir/filename
touch barDir/another_filename
bar prerequisites are barDir/filename barDir/another_filename
I needed something similar. Does this exemple suit your needs ?
# Basic rules can be ignored
# Use % to not take much space
.PHONY: foo bar
foo bar:
#printf "%s depends on %s\n" "$#" "$^"
fooDir/ barDir/:
#printf "%s\n" "Building $#"
#mkdir "$#"
fooDir/%: fooDir
barDir/%: barDir
%/filename: %/
#printf "%s\n" "Building $#"
#touch "$#"
%/filename2: %/
#printf "%s\n" "Building $#"
#touch "$#"
%/filename3: %/
#printf "%s\n" "Building $#"
#touch "$#"
fooFile = fooDir/filename2
barFile = barDir/filename2
DIR = $(*)Dir
FILE = $(DIR)/filename3
.SECONDEXPANSION:
# Here the fun begin
# See full details on https://stackoverflow.com/a/73679964/7227940
foo bar: %: $$(*)Dir/filename $$($$(*)File) $$(FILE)
output:
$ make foo; make bar
Building fooDir/
Building fooDir/filename
Building fooDir/filename2
Building fooDir/filename3
foo depends on fooDir/filename fooDir/filename2 fooDir/filename3
Building barDir/
Building barDir/filename
Building barDir/filename2
Building barDir/filename3
bar depends on barDir/filename barDir/filename2 barDir/filename3
The tricks is in .SECONDEXPANSION: all target defined after (that's why I put it at the end) will get a second expansion. The $(*) aka the stem of matched rules will only become available in the second expansion. This allow us to build name that is linked to the target.
The pattern targets: %: deps allow to match only on listed target and not catch all target.
I prefer the version 1 or 2 because the third version you might be tempted to use in place before .SECONDEXPANSION: and thus you will get weird error messages. If you really want to use 3 then put this special target at the top. Also 3 might have wrong value (you might want MAKECMDGOALS or something like that instead of *) if used with multiple wildcard rule. Just remember if you want to use 3 then put $$ either in DIR in FILE definition or in the use site.

How to prevent the prerequisite of a target from expanding when the target is not used?

In a Makefile with multiple targets, how can one prevent the prerequisites of targets that are not being used from expanding? See the following example:
thisexpands = $(warning Expanded!)
.PHONY: target1
target1: $(thisexpands)
#echo Target 1
.PHONY: target2
target2:
#echo Target 2
Calling target2 forces thisexpands to expand, even though it is lazily evaluated and it and target1 are never used.
In my real world case expanding thisexpands when calling target1 is an issue, because it is a shell command that prints errors when called out of context of target1 and it's parent targets(not shown here).
Makefiles are fully parsed before the first rule is run. As part of parsing, all the targets and prerequisites must be expanded. You can find details of when expansion happens for different parts of a makefile in How make Reads a Makefile in the GNU make manual.
One way is to use recursion:
thisexpands = $(warning Expanded!)
target1: ; $(MAKE) target1-recurse T1_DEPS='$(value thisexpands)'
T1_DEPS =
target1-recurse: $(T1_DEPS)
#echo Target 1
This doesn't work:
You can probably defer expansion by using secondary expansion, something like this:
.SECONDEXPANSION:
target1: $$(thisexpands)
Be very careful that you escape the prerequisite list appropriately.
There's no way to cancel the expansion completely. However, you can use the conditional assignment based on the value of $(MAKECMDGOALS):
thisexpands = $(if $(filter target1,$(MAKECMDGOALS)),$(warning Expanded!))
.PHONY: target1
target1: $(thisexpands)
#echo Target 1
.PHONY: target2
target2:
#echo Target 2
Note that it works if target1 is only built explicitly (make target1) and not by default, or as a part of building another target.

How to dynamically generate Makefile targets

Say I have a few files named foo.c, bar.c, baz.c, ... I want to create a Makefile target for each one that runs a build task.
foo:
make foo.c
.PHONY: foo
foo.c:
run build foo
bar:
make bar.c
.PHONY: bar
bar.c:
run build bar
...
I essentially just want to do make foo and it builds make foo.c. But I have x number of files and want to have make tasks for each. How to accomplish this. Something like:
FILES = $(foo bar baz ...)
$(FILES): $(FILES)
make $(FILE)
.PHONY: $(FILE)
$(FILES).c: $(FILES)
run build $(FILE)
But all the files are independent from each other.
You can use pattern rules for the .c targets and static pattern rules for the ones that have to be phony targets (i.e., foo and bar in your example):
targets := foo bar
.PHONY: $(targets)
# static pattern rule
$(targets): %:
make $*.c
# pattern rule
%.c:
run build $*

Specify all Makefile targets as .PHONY with wildcard

All targets in my Makefile aren't real files, is it valid to specify just .PHONY: %. Or should I list all targets?
You need to list all the targets that are meant to be phony targets as prerequisites of the .PHONY target instead of just writing .PHONY: %.
.PHONY: % doesn't do what you think it does (i.e., turning every target into a phony target).
As an example, consider the following makefile:
.PHONY: %
foo:
#echo creating $#
#touch $#
For this makefile above:
$ make
creating foo
$ make
make: 'foo' is up to date.
Therefore, the target foo is not turned into a phony target by having .PHONY: % in your makefile. Otherwise, foo's recipe would have been executed, since phony targets are always outdated.

Resources