Specifying wild card patterns when invoking make targets - makefile

Suppose I define:
# makefile
.FORCE:
foo-bar: .FORCE
#echo "$#"
foo-biz: .FORCE
#echo "$#"
.PHONY: foo-bar foo-biz
And I write on the command line:
$: make foo-bar foo-biz
Then is there a way for me to, somehow, write:
$: make foo-*
for example?

This works but it only supports the one single % wildcard of native make pattern matching. Moreover I didn't analyze its impact on pattern rules etc.
PATTERN_GOALS := $(foreach g,$(MAKECMDGOALS),$(if $(findstring %,$(g)),$(g)))
force-on-pattern = $$(if $$(filter $(PATTERN_GOALS),$$#),$$(eval FORCE+=$$#))
# make FORCE a simple expanded variable
FORCE :=
.SECONDEXPANSION:
foo: $(force-on-pattern)
echo $#
fooa: $(force-on-pattern)
echo $#
fooab: $(force-on-pattern)
echo $#
foob: $(force-on-pattern)
echo $#
$(PATTERN_GOALS): $$(FORCE) ;
Test:
$ make foo%
fooab
fooa
foob
foo
$ make fooa%
fooab
fooa
$ make fooab%
fooab
$ make fooab
echo fooab
fooab
$ make foo%b
fooab
foob
You can have a look at the GNUmake table toolkit and specifically the glob-match function for a make-native glob matcher but this is, as #Beta noticed, the realm of 'fearsome beast' make programming.

Here's an idea. Since globbing happens in the shell before the make program even starts, you could have files in the repository named like the rules you want globbable:
touch foo-bar foo-biz
make foo-*
# executes 'make foo-bar foo-biz'
This works with real files too (not just .FORCE or PHONY), but only if the files already exist.

Related

Make $* check for string

I am trying to check if $* matches hello . But the following is not working
build: build-hello
build-%:
ifeq ($*, hello)
echo Hello
else
echo World
endif
The conditions in the ifeq's are processed at makefile read time -- when $* is still blank. There's a couple of workarounds to this: First, you could do a build-hello: rule, which would override the build-% rule for build-hello. If, on the other hand you wanted to minimize rules, you could use the $(if) function as so:
build-%:
#echo $(if $(filter $*,hello),Hello,World)
Or, you could just use shell logic to accomplish this as well.

gmake: How to assign global variable from shell command?

I have a makefile that executes some shell command and I want to store the output to a global variable:
GLOBVAR = a
all:
GLOBVAR=$(shell echo 'X')
$(info $(GLOBVAR))
GLOBVAR is empty. What am I doing wrong?
You are mixing up make and shell variables. In GLOBVAR=$(shell echo 'X') it is a shell variable that you assign, while in $(info $(GLOBVAR)) it is a make variable that you expand.
Try this, instead:
GLOBVAR = $(shell echo 'X')
all:
$(info $(GLOBVAR))
But there are several other issues with your Makefile that you should probably consider.
Using $(shell...) in recipes is not recommended because recipes are already shell scripts. So, if you want to assign a shell variable in a recipe, just:
all:
GLOBVAR="$$(echo 'X')"
Note the $$ to escape the expansion that make performs before passing the recipes to the shell.
The different lines of the recipe are executed in different shells. So, if you want to use in a line a shell variable that was assigned in a previous line you must join them:
all:
GLOBVAR="$$(echo 'X')"; echo $$GLOBVAR
(same remark as before about $$). You can use line continuation if you prefer:
all:
GLOBVAR="$$(echo 'X')"; \
echo $$GLOBVAR
And finally, if you want to assign make variables in recipes you can, with the eval make function, but I strongly discourage you to do so until you perfectly understand when make does what:
$ cat Makefile
.PHONY: all lla
all:
$(eval GLOBVAR = $(shell echo 'X'))
#echo all: $(GLOBVAR)
lla:
#echo lla: $(GLOBVAR)
$ make all
all: X
$ make lla
lla:
$ make all lla
all: X
lla: X
$ make lla all
lla:
all: X
And I let you imagine what the results could be with parallel make... In summary, if you start using make functions in recipes you are probably wandering into dangerous areas.

Makefile - Generate same file differently depending on the target

I know makefile won't allow using a target specific variable as a target prerequisite.
My question is slightly different : is there a way to generate the same file differently depending on what target was called ?
For instance, let's say I want to be able to generate file_to_generate using two different methods that I call using make example_target_1 or make example_target_2
As an example, the following code gives 2 different recipes for the same file :
example_target_1 : file_to_generate-receipe1
example_target_2 : file_to_generate-receipe2
file_to_generate-receipe1:
/* some shell code here that end up generating file_to_generate */
file_to_generate-receipe2:
/* some different shell code here that also generates file_to_generate*/
issuing make example_target_1 will generate the file using one recipe while issuing make example_target_2 will do the same using the other recipe.
The issue using this is both example_target_1 and example_target_2 are done without checking if file_to_generate is up-to-date as the name of the target isn't really a file.
Is their a way to get the same behavior and still check if the file is up-to-date ?
One way that it can be achieved may be with use of target-specific variables, like so:
$ cat Makefile
target1 target2: file_to_generate
cat $<
target1: RECIPE=recipe1
target2: RECIPE=recipe2
file_to_generate:
$(if $(filter recipe1,$(RECIPE)),$(recipe1))
$(if $(filter recipe2,$(RECIPE)),$(recipe2))
define recipe1
echo recipe1
echo foo > $#
endef
define recipe2
echo recipe2
echo bar > $#
endef
Even though it works, I would strongly advise against such design. Generating a file in a non-deterministic way may easily lead to non-trivial errors. For example, using this approach you will generate a file and it will be checked if it's up to date, but there is no way for make to guess whether it was generated with recipe1 or recipe2. Therefore the next time you call a different target, the file will not be regenerated (since it already exists), even though the recipe has changed:
$ make target1
echo recipe1
recipe1
echo foo > file_to_generate
cat file_to_generate
foo
$ make target2
cat file_to_generate
foo
When called with target2 first, the file will have different contents, which will be reused in target1 as well:
$ rm file_to_generate
$ make target2
echo recipe2
recipe2
echo bar > file_to_generate
cat file_to_generate
bar
$ make target1
cat file_to_generate
bar
This may or may not be desirable, you need to be aware of such behavior.

Define a target that depends on a value/variable that need to be resolved

Updated my question as it seemed to be not clear enough!
I was listing when to use make over bash. One thing I like about make is its declarative way of describing necessary steps; we can write a rule by relying on other rules knowing how to provide necessary files (or other external states).
I'm wondering how I can get the same benefit for a value not a file, without changing outer world (like leaving a temporary file).
hello.txt: (here, tell that it needs to resolve person's name)
# Here, person's name is available.
echo Hello $(var_name) > $#
We can imperatively prepare a necessary value with $(call prepare_name, ...) at the beginning of a command in a rule, but that's not what I'm after here.
I posted my attempts as an answer when I opened this question. Hopefully that adds more info on what I'm trying to achieve.
It's not overly clear what you're after, however to clarify a few concepts:
A target must be dependent on other targets. It cannot be dependent on a variable name. It can be dependent on the value of a variable, if that variable resolves to a target name.
So you could do:
VAR=some_target
hello.txt: $(VAR)
echo "hello $^" > $#
some_target:
touch $#
You CANNOT do:
VAR=some_target
hello.txt: VAR
and expect it to work (make would try to build VAR which likely doesn't exist and it would fail).
I'm assuming from the question that you want make to request the variable name of a person, and put that into hello.txt. In that case you would likely want to store the name in a temporary file and use that for the output:
.getname.txt:
#read -p "enter name" name > $#
hello.txt: .getname.txt
#echo "hello $$(cat $$<)" > $#
This will update .getname.txt if it didn't previously exist (so it will not necessarily ask on every invokation of make...). You could make .getname.txt be a .PHONY target, and it will run every time.
If you do want to run every time, then you can simply do:
hello.txt:
#read -p "enter name: " name && echo "hello $$name" > $#
.PHONY: hello.txt
Which will invoke the hello.txt rule regardless of whether hello.txt already exists, and will always prompt the user for a name and rebuild hello.txt.
I can think of a way using eval function. Below suppose foo is a value obtained by a complex calculation.
hello.txt: var_name
echo Hello $($<) > $#
.PHONY: var_name
var_name:
$(eval $# = foo)
Or with .INTERMEDIATE target, this also works, but I feel it's more complicated.
var_name = var-name.txt
hello.txt: $(var_name)
echo Hello $$(< $<) > $#
.PHONY: $(var_name)
.INTERMEDIATE: $(var_name)
$(var_name):
rm -f $# # In case the var file already exists
echo bar > $#
Another way could be to use a target-specific variable. It's not listing a variable as a prerequisite, but I still don't need to think about how to get var_name when writing echo Hello ....
define get_name
echo foo
endef
hello.txt: var_name = $(call get_name)
hello.txt:
echo Hello $(var_name) > $#
As noted in other answers, make track dependencies between files, using timestamps. The regular solution for handling a value will be to store it in a file (or to generate it into a file). Assuming that there is significant work to do whenever the data is changing, you can follow one of the patterns below to implement dependency check on the file value.
The following makefile snapshot will trigger rebuild of complex-result, only when the content of var-value is modified. This is useful when the content of var-value is continuously regenerated, but does not change very frequently.
all: complex-result
last-value.txt: var-value.txt
cmp -s $< $# || cat <$^ > $#
complex-result: last-value.txt
echo Buildig for "$$(cat var-value.txt)"
touch $#
Or more realistic example: trigger a build if the value (content) of any file was modified, using md5 checksum,
all: complex-result
last-value.txt: $((wildcard *.data)
md5sum $^ > $#
last-value.txt: var-value.txt
cmp -s $< $# || cat <$^ > $#
complex-result: last-value.txt
echo Building for "$$(cat var-value.txt)"
touch $#

Why $(shell ls) is expanded at the start of the makefile task?

I have the following code:
foo:
touch foo
$(foreach f, $(shell ls | grep foo), \
echo $f; \
)
it will not list the file foo created by the touch foo above, will list if the foo file already exists before the task starts, like this:
$ make foo # first time call, file 'foo' doesn't exists yet
$ make foo # second time call, file 'foo' already exists
foo
Is there a way to evaluate the ls after all the commands above are executed?
That's how Make works. The Makefile is parsed and any Makefile functions are called, then one or more recipes are evaluated.
Is there a reason you're not simply using a shell loop?
foo:
touch foo
for f in *foo*; do \
echo "$$f"; \
done
Notice how the dollar sign needs to be doubled to escape it from being evaluated by make, and also how shell variables should generally be double-quoted unless you specifically require the shell to perform whitespace tokenization and wildcard expansion on the value.
On the other hand, a more "make-ish" approach is to explicitly document any dependencies.
.PHONY: all
all: foo
printf '%s\n' $^
foo:
touch $#
Now all depends on foo, so Make knows it must create foo before it can perform the all recipe if foo doesn't exist, or is out of date in relation to its own dependencies (of which of course there are currently none).
The make variable $^ refers to the dependencies of the current target, and $# expands to the current recipe target. The printf shell script is just a more economical way to print one thing per line without a loop.

Resources