Reputation: 16361
I have a makefile that runs a rule when it fails. I have written out a small example below:
temp = "start"
run:
make run_all || make run_failed
run_all: step1
run_all: step2
run_all: step3
run_all: ;
step1:
echo temp = "step1"
step2:
echo temp = "step2"
$(error fail test)
step3:
echo temp = "step3"
run_failed:
@echo "failed on step ${temp}"
I force this little example to fail in step 2 with the $(error ...) command. The problem I have is that I want to set my variable temp at each step. But I don't know how to do that.
If it remove the "echo" before temp =
in each step I just get a shell error (can't find temp).
How can I set my variable so that I can print out the step that the makefile failed on?
Upvotes: 2
Views: 1118
Reputation: 29240
Note: you shouldn't use make
in your recipes. Use $(MAKE)
instead. There are several very good reasons for this that you will find in the GNU make manual.
Make variables and shell variables are different. When you write:
temp = "start"
it is make syntax and temp
is a make variable. When you write:
step1:
temp = "step1"
temp
is a shell variable and you get a syntax error because shell variables assignment is temp="step1"
(no spaces around =
).
But this is not the whole story. Each line of a recipe is run by a separate shell (unless the special target .ONESHELL
appears in the makefile) and different recipes can't be run by the same shell instance (regardless of .ONESHELL
). So, you cannot expect the recipe of your run_failed
rule to get access to the same shell variable temp
as the one that has been assigned by one line of your step2
recipe.
Anyway, when make fails on a recipe it tells you what went wrong, including what rule failed. If it is not enough the best option would be to equip each recipe with an error message:
$ cat fail
#!/usr/bin/env bash
exit 1
$ cat Makefile
run: step1 step2 step3
step1 step3:
@echo "doing $@" || echo "$@ failed"
step2:
@./fail || echo "$@ failed"
$ make step1
doing step1
$ make run
doing step1
step2 failed
doing step3
If, for any reason, this is not possible, you will have to:
step2
) to another (run
),run-all
) to another (run_failed
).All this will be extremely difficult, unless you use a log file and tell make not to run in parallel mode:
$ cat Makefile
.NOTPARALLEL:
FAILED := failed.txt
run:
@{ rm -f $(FAILED) && $(MAKE) run_all; } || $(MAKE) run_failed
run_all: step1 step2 step3
step1 step3:
@echo "doing $@" || { echo "$@ failed" >> $(FAILED) && exit 1; }
step2:
@./fail || { echo "$@ failed" >> $(FAILED) && exit 1; }
run_failed:
@cat $(FAILED)
$ make --no-print-directory
doing step1
Makefile:14: recipe for target 'step2' failed
make[1]: *** [step2] Error 1
step2 failed
Upvotes: 2