How do I get a makefile function to stop the current target? - makefile

I have a function in a makefile that I want to stop the entire make run if a file doesn't exist, or at least the target it is executed from:
vaultfile = ./vault
$(shell test -f $(1) || exit 1)
define get_token
$(shell test -f $1 && cat $1 || exit 2)
endef
a: token = $(call get_token,$(vaultfile),tokenname)
a:
echo ==== $(token)
.PHONY=a
The above doesn't work, silently failing when the file is missing
$ rm vault
$ make
echo ====
====
$ echo f > vault
$ make
echo ==== f
==== f
I want this to be in the function, because most targets do not call the function (which obviously does more IRL).
How do I make this work?

First a few things:
.PHONY=a
doesn't do anything: the variable .PHONY is not special to make. To declare a target phony you need to list it as a prerequisite of the .PHONY pseudo target:
.PHONY: a
Second, this line:
$(shell test -f $(1) || exit 1)
doesn't do anything: the make variable $(1) is not set here so the test always fails, but it doesn't matter because the exit code is ignored, see below.
The exit code from the make shell function won't cause make to fail, it's ignored. To cause make to think that a recipe failed you have to get the command line itself to exit with a non-zero value.
A good rule of thumb is, if you find yourself using the make shell function inside a recipe, you're doing something wrong and you aren't understanding how make expands variables and functions. A recipe is already going to be passed to a shell, so you don't need to use the shell function at all.
Let's look at what your recipe will be after the first step of expansion, for the token variable:
echo ==== $(call get_token,$(vaultfile),tokenname)
Now after the call function is expanded (note that the second argument to the function, tokenname, is completely ignored) you get:
echo ==== $(shell test -f ./vault && cat ./vault || exit 2)
Now make expands the shell function which invokes a shell to run the command and replace the expansion with the output... but the exit code is ignored. Let's say that ./vault doesn't exist: then this shell command outputs nothing, and make runs this rule:
echo ====
The best way to stop an entire make run is using the error function. You can use make functions to do all the work, like this:
vaultfile = ./vault
get_token = $(if $(wildcard $1),`cat $1`,$(error File $1 does not exist))
a: token = $(call get_token,$(vaultfile),tokenname)
a:
echo ==== $(token)
Let's look at what the results of the call expansion will be now:
echo ==== $(if $(wildcard ,/vault),`cat ./vault`,$(error File ./vault does not exist))
Now make evaluates the if function and the condition is the wildcard function which will expand to ./vault if it exists, and the empty string if not. The if function treats a non-empty string as "true" and an empty string as "false", so if the file exists it will expand to:
echo ==== `cat ./vault`
If the file doesn't exist it will run the error function which stops make immediately, printing that error message.

Thank you MadScientist!
The simplified version of what I ended up with is: (formatted for easier readability)
vaultfile = ./vault
define get_token
$(shell \
$(if $(wildcard $1), \
echo "Decoded $1" \
, \
$(error File $1 does not exist)
)
)
endef
a: token = $(call get_token,$(vaultfile))
a:
echo ==== $(token)

Related

How can I have a Makefile target update dependent on the value of an environment variable?

I have a make target that will have different output depending on the value of an environment variable.
How can I:
skip the dependency and not re-make the target if the environment variable has not changed the last run
make or re-make the target if the environment variable is not set or has changed
I thought I could create or conditionally update a file with the current environment variable value and then use that file as a make dependency. I couldn't find an elegant way to do that with native tools. (sed -i always updated the file's timestamp, maybe awk is possible)
How about using a shell script to update a file that holds the variable value?
SHELL = /bin/bash
var_file := var.txt
var_name := NAME
is_var_updated = [[ ! -e $(var_file) ]] || [[ "$$(< $(var_file))" != "$($(var_name))" ]]
update_var_file = echo "$($(var_name))" > $(var_file)
$(shell $(is_var_updated) && $(update_var_file))
output.txt: $(var_file)
echo "Name is $$NAME" > $#
This works like this.
$ ls
Makefile
$ NAME=foo make
echo "Name is $NAME" > output.txt
$ NAME=foo make
make: `output.txt' is up to date.
$ NAME=bar make
echo "Name is $NAME" > output.txt
Make conditionals could be a starting point:
.PHONY: all
FILE := foobar
ifdef ENV_VAR
OLD_ENV_VAR := $(shell [ -f $(FILE) ] && cat $(FILE))
ifeq ($(ENV_VAR),$(OLD_ENV_VAR))
DONTRUN := 1
endif
endif
ifdef DONTRUN
all:
#echo 'ENV_VAR unmodified'
else
$(shell printenv ENV_VAR > $(FILE))
all:
#echo 'ENV_VAR undefined or modified'
endif

What is the difference between '$$directoryName' and '$(directoryName)' in makefile

I have two makefiles
The first :
dir = ../dir1
dis = ../dir2
test:
$(MAKE) -C $(dir)
The second one :
DIRS = dir1 dir2 dir3
test:
for dir in $(DIRS); do \
if $(MAKE) -C $$dir ; then \
true; \
else \
exit 1; \
fi; \
done
Why in the for loop I need $$dir when in a simple recipe I have to write $(dir)
Another question:
I have this other makefile, in which I have this other for loop:
all clean dep depend print:
for dir in $(DIRS); do \
if $(MAKE) $(MAKE_FLAGS) -C $$dir $#; then \
true; \
else \
exit 1; \
fi; \
done
What is the meaning of $# in the line
if $(MAKE) $(MAKE_FLAGS) -C $$dir $#; then \
I know this is an Automatic Variable that matches the file name of the target of the rule.
Here the target appears to be a command like cancel:
cancell:
rm -rf *.o
One is a makefile variable the other is a shell variable. $(directoryName) will be resolved by make, as it's reading. For $$directoryName, make converts the $$ to $, and passes that to the shell (so $$directoryName becomes$directoryName). The shell expands it to whatever it has in its environment.
A bit more detail:
If, in make, you define (outside of any recipe)
var := 1
then var is a make variable. If you then call
all:
echo $(var)
Then make expands the recipe to echo ../dir1 before passing the command to the shell. If, on the other hand, you do:
all:
var=1; echo $$var
Then make passes var=1; echo $var to the shell. The shell sets var to 1, and then prints it. Notice if you tried:
all:
var=1;
echo $$var
Then the recipe will not print anything -- that's because the first line is run in a shell, which sets var to 1, but then exits. The next line runs, which passes echo $var in a new shell, but this shell doesn't know what var was set to in the previous shell.
In addition, if you run var=1;make, then make will set a makefile variable var to be 1 when it starts. Thus a $(info $(var)) will show 1, in this case.
For syntax, in make you can do var := 1 (with spaces). In bash you cannot add spaces. In bash, $var refers to the variable var. In make $var refers to $v followed by the literal ar. (In make you have to use either {} or () to refer to multicharacter variables). In bash, you can either use {}, or no braces ($(var) has a different meaning in bash).
make also has two flavors of variables, one defined with := and one with =.

How to use eval Makefile variable from recipe to conditionally execute some commands in recipe

Goal is to apply patch ONLY if patch is not present. If patch is present don't do anything.
I used below makefile rule.
C_FILE_PATCH_SIG=###MAGIC_CODE;
C_FILE_CODE=~/code/file.c
C_PATCH_FILE=~/test.patch
.tmp/patch_c:
cp ${C_PATCH_FILE} ${SDK}
ifneq ($(PATCH_DONE), 1)
$(MAKE) applypatch || $(MAKE) helppatch
endif
#echo DONE > .tmp/patch_c
applypatch:
#echo "Patching ${C_FILE_CODE}"
if grep -Fq '${C_FILE_PATCH_SIG}' ${C_FILE_CODE} ; \
then \
echo 1 > .tmp/PATCH_PRESENT_file; \
else \
echo 0 > .tmp/PATCH_PRESENT_file;\
fi
cat .tmp/PATCH_PRESENT_file
# $(eval PATCH_PRESENT := `cat .tmp/PATCH_PRESENT_file`)
$(eval PATCH_PRESENT := $(shell cat .tmp/PATCH_PRESENT_file))
#echo "WWWWWW PATCH_PRESENT=[$(PATCH_PRESENT)] WWWWWWW"
ifeq ($(PATCH_PRESENT), 0)
#echo "Applying the patch $(PATCH_PRESENT)"
cd ~/code && git apply -v ${C_PATCH_FILE}
else
#echo "NOT Applying the patch $(PATCH_PRESENT)"
endif
helppatch:
#echo -e "\n\n\n"
#echo -e "++++++++++ Apply below patch manually then run 'make build PATCH_DONE=1' ++++++++++\n\n"
#echo -e "+++++++++++++++++++++++++++++++++++++"
#cat ${C_PATCH_FILE}
#echo -e "+++++++++++++++++++++++++++++++++++++"
#echo -e "\n\n\n"
false
But it always evaluates to the else part of ifeq.
Where am I doing wrong?
If I use the patch command of git withing the shell multiline I loose the error code returned by the git patch.
Thanks in advance...
Your ifeq will be evaluated when the makefile is first read (as opposed to when the recipe is run). The eval, on the other hand, will not be executed until the recipe is run (afterwards). Thus, PATCH_PRESENT is not equal to 0 at parse time, and make will expand the else portion of the clause. By the time the eval is run, the if statement is already evaluated and gone from memory.
BTW, a cleaner way to do this is to do everything in bash:
applypatch:
#echo "Patching ${C_FILE_CODE}"
#if grep -Fq '${C_FILE_PATCH_SIG}' ${C_FILE_CODE}; then \
echo "NOT Applying the patch"; \
else \
echo "Applying the patch"; \
cd ~/code && git apply -v ${C_PATCH_FILE}; \
fi

Makefile: redefining variable in runtime using #if,eval: variable always takes value of last eval

So I'm trying to hack one of my makefiles to be simpler (simpler, as if, not defining a lot of rules how to transform subdirectory into .deb).
build-if-need-status-vars:
#if [ ! -f debs/1.deb ]; then \
$(eval STATUS_REBUILD=1) \
echo "component: file not found: 1"; exit;\
else \
if [ $(shell find sources/ -newer debs/1.deb 2>/dev/null | wc -l) -gt 0 ]; then \
$(eval STATUS_REBUILD=1) echo "component: newer files exists: 1"; exit;\
else \
$(eval STATUS_REBUILD=0) echo "component: no newer files: 0"; \
fi;\
fi
#echo "status $(STATUS_REBUILD)"
actual-target: build-if-need-status-vars
ifeq ($(STATUS_REBUILD), 1)
#echo first status: 1
else
#echo second status: 0
#echo different action
endif
all: actual-target
.PHONY: actual-target
Test with:
mkdir -p test/{sources,debs}; touch test/debs/1.deb; sleep 2; touch test/sources/1.src; (create makefile there and run)
Result:
component: file not found: 1
status 0
second status: 0
Regardless of what conditional block is executed, STATUS_REBUILD will always be 0 (last evaluated value), try it: touch test/debs/1.deb
So it seems that last $(eval) is always used.. How to avoid this behaviour and keep the correct assigned value (from first match in build-if-need-status-var)?
$(eval) is a make-level function. It is expanded in your recipe during recipe the recipe expansion stage.
The contents of a recipe are expanded in the second phase of makefile parsing (discussed briefly in the manual here).
I believe, but cannot say for sure (without testing), that recipes are not expanded until they are about to be run (but for the purposes here that doesn't change anything either way).
So your problem here is that all the $(eval) calls are expanded by the time make goes to run your shell script so you always see the last value in effect when the last line is run.
That all being said you don't actually need a make-level variable here. Your recipe is already only two shell executions.
You can simply include the last line in the same execution as the first (split) line and use a shell variable.
build-if-need-status-vars:
#if [ ! -f debs/1.deb ]; then \
STATUS_REBUILD=1; \
echo "component: file not found"; \
else \
if [ $(shell find sources/ -newer debs/1.deb 2>/dev/null | wc -l) -gt 0 ]; then \
STATUS_REBUILD=1; echo "component: newer files exists"; \
else \
STATUS_REBUILD=0; echo "component: no newer files"; \
fi;\
fi; \
echo "status $$STATUS_REBUILD"
Note that I needed to remove the exit pieces to make this work. If those are necessary in the real makefile (because this is a stripped down sample) then you can keep them by wrapping the if in a sub-shell and/or by rewriting the recipe.

reading variable value declared in script.sh in Makefile

I run makefile to generate an image file for a target device. After I burn the image into the target device during one of the operation funtion1.sh calls script.sh where my VAR is declared.
I want during running Makefile to generate the target image access script.sh knowing the path, read the value of VAR and use it in Makefile.
example:
script.sh:
...
VAR=some_value
...
=====Now what script do I need for Makefile ???===============
-I tried this method but it did not work--------------------------
Makefile:
PLAT_SCRIPT := /path/to/script.sh
PLAT_VAR := VAR
PLAT_SCRIPT_TEXT := $(shell grep ${PLAT_VAR} ${PLAT_SCRIPT}) VAR := $(filter-out ${PLAT_VAR}, $(strip $(subst =, , $(subst ",, $(strip ${PLAT_SCRIPT_TEXT})))))
all:
#echo VAR=$(VAR)
It did not work for some reason. Maybe I should replace line 4 with:
VAR := $(shell echo $(PLAT_SCRIPT_TEXT)|cut -d, -f1|awk -F'=' '{print $2 }' )
all:
#echo VAR=$(VAR)
You must export the variable to make it visible in subprocess.
exporting variable from Makefile to bash script:
export variable := Stop
all:
/path/to/script.sh
or export it using shell style:
all:
variable=Stop /path/to/script.sh
exporting variable from shell to make:
export variable=Stop
make -C path/to/dir/with/makefile
or:
variable=Stop make -C path/to/dir/with/makefile
or:
make -C path/to/dir/with/makefile variable=Stop
If you need to read variable from script you can find it's declaration and extract the value like that:
script.sh:
...
VAR=some_value
...
Makefile:
VAR := $(shell sed -n '/VAR=/s/^.*=//p' script1.sh)
all:
#echo VAR=$(VAR)
But, think this is not a very good method.
Better is to output results of execution in the script and fetch it in Makefile.
Example:
script.sh:
#!/bin/bash
VAR=some_value
# some work here
echo "some useful output here"
# outputting result with the variable to use it in Makefile
echo "result: $VAR"
Makefile:
# start script and fetch the value
VAR := $(shell ./script.sh | sed -n '/^result: /s/^.*: //p')
all:
#echo VAR=$(VAR)

Resources