What is the difference between '$$directoryName' and '$(directoryName)' in makefile - 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 =.

Related

How do I get a variable into a $(shell) command in a makefile?

❯ make --version
GNU Make 3.81
❯ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
How can I pass a variable from inside a for loop to $(shell)? I can access the var outside of $(shell) but I can't figure out how to pass it in:
A_LIST:= one two
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo inside recipe loop with sh command: $$iii ; \
export SAVED_OUTPUT=$(shell echo $$iii) ; \
echo $$SAVED_OUTPUT ; \
done
This is the output I get:
inside recipe loop with sh command: one
<blank line here>
inside recipe loop with sh command: two
<blank line here>
The last line in the loop echo $$SAVED_OUTPUT should output one and two because it is echoing the var and storing it in another var. But it is a blank line. I suspect it's because it's looking for an env var $iii but that doesn't exist- so how to I pass the value of iii into the shell?
Here is a bad way of doing this I don't like. I don't want to have to write a local file just to access a variable like this:
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo inside recipe loop with sh command: $$iii ; \
echo $$iii > scratch ; \
export SAVED_OUTPUT=$(shell echo $$(cat scratch)) ; \
echo $$SAVED_OUTPUT ; \
done
The for loop is already being executed by a shell - in this case, there's no reason to bring $(shell ...) into it too. Just use normal $() shell command substitution syntax (Doubling up the $ to make make happy, like with the variable names):
A_LIST:= one two
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo "inside recipe loop with sh command: $$iii" ; \
SAVED_OUTPUT="$$(somecommand "$$iii")" ; \
echo "$$SAVED_OUTPUT" ; \
done

$$ in shell , Linux

I have the following code from a make file, I know this creates bin folder in Home if that doesn't exist... but I couldn't understand what $$HOME/bin mean...
I googled and found $$ is to get the processid of the bash... but couldn't understand what $$HOME/bin mean... can someone please explain ?
.PHONY: home_bin
home_bin: ## Create home bin if not created
# if [[ ! -d "$$HOME/bin" ]]; then \
echo "Creating $$HOME/bin"; \
mkdir $$HOME/bin; \
echo "✔︎ $$HOME/bin created"; \
else \
echo "✔︎ $$HOME/bin already created"; \
fi
Thank you.
make itself performs expansion of $-prefixed characters; the $$ is expanded to a single literal $ to pass to the shell.
Consider a simple Makefile:
x=f
all:
xoo=3 && echo $xoo
which will output foo, because
make expands $x to the single character f.
make passes the string xoo=3 && echo foo to the shell for execution
Compare with
x=f
all:
xoo=3 && echo $$xoo
which outputs 3, because
make expands $$ to $
make passes the string xoo=3 && echo $xoo to the shell for execution

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

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)

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