Make $* check for string - makefile

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.

Related

Specifying wild card patterns when invoking make targets

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.

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.

How do computed variables passed to included make files' functions get expanded?

To reduce the amount of repeated boiler plate in a top level makefile, I created an included make file that uses computed variable names. Where I'm having difficulty is in the excerpt from the included makefile: ftp-files.mk:
...
$($(FNMPFX)_FTP_CFG): $(CFG_MAKE_FILE) | $($(FNMPFX)_FTP_CFG_DIR)
$(call ftp_helper, $#, $($(FNMPFX)_FTP_DIR), $($(FNMPFX)_CACHE_DIR), $($(FNMPFX)_FTP_NAME))
...
In the main makefile, I was hoping to do something akin to:
CFG_MAKE_FILE := Makefile
define ftp_helper
echo "quote USER anonymous" > $(1)
echo "quote PASS" >> $(1)
echo "cd $(2)" >> $(1)
echo "lcd $(3)" >> $(1)
echo "binary" >> $(1)
echo "get $(4)" >> $(1)
echo "quit" >> $(1)
endef
FNMPFX := FILE_A
include ftp-files.mk
...
FNMPFX := FILE_Z
include ftp-files.mk
...
The trouble is that the order only prerequisite (also tried it as a normal prerequisite) expands to the last ... instanciation (?) of the included file.
What appears to be happening is a first expansion of the two targets that behave as though they were written:
# Point of confusion V
# |
$(FILE_A_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_A_FTP_DIR), $(FILE_A_CACHE_DIR), $(FILE_A_FTP_NAME))
$(FILE_Z_FTP_CFG): $(CFG_MAKE_FILE) | $(FILE_Z_FTP_CFG_DIR)
$(call ftp_helper, $#, $(FILE_Z_FTP_DIR), $(FILE_Z_CACHE_DIR), $(FILE_Z_FTP_NAME))
Is this possible?
My work around was to include that simple target rule in the main Makefile (ftp-file.mk is reasonably wordy at 100 lines), so having those two lines (repeated) throughout the main Makefile isn't too burdensome.
Can somebody suggest a working alternative?
In general you can't read a whole Makefile like a shell script. It's read in multiple phases, and certain things are done with the entire contents of the Makefile before proceeding to the next step. The rules are rather complex, but suffice it to say that it looks like the last assigned value is used:
$ cat Makefile
variable := original
first:
echo $(variable)
variable := other
second:
echo $(variable)
$ make first
echo other
other
$ make second
echo other
other

If Else syntax evaluating wrong clause in GNU Makefile

I am trying to get if else syntax working in Makefile
TYPE=src
RTL=src
program_%:
ifeq ($(TYPE),$(RTL))
echo "RTL"
else
echo "Test"
endif
Here is the command line
$make -f test.make prog_src
and I get the following in the output
echo "RTL"
RTL
However, when i change the if statement such that instead of hard-coded variables I do something like
program_%:
ifeq ($(TYPE),$*)
echo "RTL"
else
echo "Test"
endif
and run as follows
$make -f test.make prog_src
I get the wrong clause evaluated
echo "Test"
Test
remove the whitespaces in the condition
ifeq ($(TYPE),$(RTL))
Regarding the second part of the question:
No; this is not possible ifeq is evaluated in the context of the makefile and $* is not expanded there. You can try $(if ...) but checking equality is not directly possible:
program_%:
#echo $(if $(filter ${TYPE},$*),"RTL","Test")
"Context of makefile" means that if* sees only global make variables. Special variables like $* are available in the rule context only.
Complex rules
complex rules are possible by
define complex_rule_A
#echo "complex_rule_A"
#echo "done"
endef
define complex_rule_B
#echo "complex_rule_B"
#echo "failed"
endef
program_%:
$(if $(filter ${TYPE},$*),$(call complex_rule_A),$(call complex_rule_B))
Alternative solutions
When ${TYPE} can be enumerated, a more clean solution might be
program_%: .program_${TYPE}_%
:
.program_RTL_%:
echo else
.program_RTL_RTL:
echo RTL

stop on error when target of makefile rule is a foreach function

I have a makefile that defines several rules where the target is a foreach function.
$(foreach var,$(list), $($(var)_stuff) $($(var)_more_stuff)):
#echo Building $# from $^...
$(CC) $(FLAGS) ...
Is there any way to get make to quit when encountering an error without going through the entire list.
One workaround is to "manually" invoke exit on failure.
For example, assume we have a directory called scripts with a number of shell scripts (with filenames that end with .sh) that we want to execute.
Then a variable declaration like this:
LIST_OF_SCRIPTS ?= $(wildcard scripts/*.sh)
will give us a list of those scripts, and a target like this:
run-all-scripts
#$(foreach scriptfile,$(LIST_OF_SCRIPTS),$(scriptfile);)
will run all of those scripts, but as you note, the foreach loop will keep going whether or not one of the scripts returns an error code. Adding a || exit to the command will force the subcommand to exit on error, which Make will then treat as a failure.
E.g.,
run-all-scripts
#$(foreach scriptfile,$(LIST_OF_SCRIPTS),$(scriptfile) || exit;)
will do what you want (I believe).
Specifically, using your pseudo-code example, I think you want something like this:
$(foreach var,$(list), $($(var)_stuff) $($(var)_more_stuff)):
#echo Building $# from $^...
($(CC) $(FLAGS) ...) || exit
(where all I've changed is wrapping the (CC) $(FLAGS) ... bit in parens and appending || exit to make it fail on error).
The foreach is completely evaluated and substituted before any of the rules are executed. So the behaviour of this should be identical to as if you had hardcoded the rule without using the foreach. In other words, it's not directly relevant to the problem.
There are only a few possible explanations for what you're seeing, mostly described in the manual here:
You are running Make with -k or --keep-going
You are running Make with -i or --ignore-errors
Your targets is defined as prerequisites of the special .IGNORE target
Your recipe starts with a -
Your recipe isn't actually returning a non-zero exit status
Not sure about your example, but maybe problem is in ; - look at Makefile : show and execute:
dirs = $(shell ls)
clean:
$(foreach dir,$(dirs),echo $(dir);)
produce:
$ make clean
echo bin; echo install.sh; echo Makefile; echo README.md; echo utils;
So make check exit code only for last command: echo utils.

Resources