reading variable value declared in script.sh in Makefile - bash

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)

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

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 get "at most once" semantics in variable assignments?

Shell commands sometimes take a long time to run, so you may not want to do VAR = $(shell slow-cmd) (with =, the slow-cmd will be run every time the variable is referenced). Using VAR := $(shell slow-cmd) can be useful, but if you are building a target that does not ever need the variable expanded, you will get one more invocation of the slow-cmd than is needed. In the following makefile (with gnu-make), you can get the desired behavior: the shell command to define a value for V2 is never invoked more than once, and for the target foo it is not invoked at all. But this is a heinous kludge. Is there a more reasonable way to ensure that a variable is only defined when needed, but never evaluated more than once?
V1 = $(shell echo evaluating V1 > /dev/tty; echo V1 VALUE)
all: foo bar V2
#echo $(V1) $#
#echo $(V2) $#
foo:
#echo $(V1) $#
bar: V2
#echo $(V1) $#
#echo $(V2) $#
V2:
$(eval V2 := $(shell echo evaluating V2 > /dev/tty; echo V2 VALUE))
.PHONY: all foo bar
There's no way to do it without tricks, but there's a cleaner way (maybe) than you're using. You can use:
V1 = $(eval V1 := $$(shell some-comand))$(V1)
For more details and explanation of exactly how this works see this page.
Target-specific deferred variables are an option:
host> cat Makefile
foo: VFOO = $(shell echo "VFOO" >> log.txt; echo "VFOO")
foo:
#echo '$(VFOO)' > $#
bar: VBAR = $(shell echo "VBAR" >> log.txt; echo "VBAR")
bar:
#echo '$(VBAR)' > $#
host> make foo
host> cat log.txt
VFOO
host> make foo
make: 'foo' is up to date.
host> cat log.txt
VFOO
host> make bar
host> cat log.txt
VFOO
VBAR
host> make bar
make: 'bar' is up to date.
host> cat log.txt
VFOO
VBAR

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)

Resources