Makefile: call multiline variable inside for loop - makefile

Here's my code:
la := a b c d
lb := 1 2 3 4
define test =
First line $$vara
Second line $$varb
endef
export test
#for vara in $(la); do \
for varb in $(lb); do \
echo $$vara $$varb; \
echo "$$test"; \
done \
done
Later this output will go in a file I'm creating using cat.
I'm expecting something like
a 1
First line a
Second line 1
a 2
First line a
Second line 2
...
But instead I'm getting
a 1
$vara $varb
a 2
$vara $varb
What am I doing wrong?
EDIT: SOLUTION
I used as a base #Beta's answer and changed it a bit to allow it to save the result in a file and send it over SSH to another terminal.
define test
[program:$$s-$$c]\nString 1\nString 2\n...\nThe End.
endef
export test
testmake:
#for s in $(serv); do \
for c in $(cust); do \
ssh user#host "mkdir -p /var/log/$$s/$$c"; \
echo "$(call test)" | ssh user#host "cat > /test.txt"; \
done \
done

This worked for me in GNU Make 3.81 (but there are some fussy little differences between versions, so you may have to tweak it):
define test
echo First line $$vara; \
echo Second line $$varb
endef
export test
some_rule:
#for vara in $(la); do \
for varb in $(lb); do \
echo $$vara $$varb; \
$(call test); \
done \
done
Note that this has the loops in a recipe -- which is what I inferred you were doing. If you want the loops to run outside a rule, the solution will look quite different.

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

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.

How To test the exit status, and do something in Makefile

I'm trying to do this ..... and this is how my Makefile look like
.PHONY: run
SHELL := /bin/tcsh
run:
md5sum -c md; \
if ($$?==0) then \
echo "PASS" \
else \
echo "FAIL" \
endif
But i got this error.
if: Badly formed number.
make: *** [run] Error 1
Is what I'm doing correct? Or is there a better way of doing that in a Makefile?
Basically, you should simply not, ever, use csh (or tcsh) for writing makefile rules. Write the rule using POSIX shell:
.PHONY: run
run:
md5sum -c md; \
if [ $$? -eq 0 ]; then \
echo "PASS"; \
else \
echo "FAIL"; \
fi

Split a string in gnu make with a separator

I have makefile with below code:
.PHONY: $(PROJECTDIR)/projectroot
$(PROJECTDIR)/projectroot:
if [ -f $(#)/etc/release ]; then \
#$(RELEASE_VERSION) will have value something like 00.01.02.03
IFS=. read major minor micro build <<<"${RELEASE_VERSION}" \
echo major=${major} > $(#)/etc/release \
echo major=${minor} >> $(#)/etc/release; \
echo major=${micro} >> $(#)/etc/release; \
echo major=${build} >> $(#)/etc/release; \
fi
I am expecting the $(#)/etc/release to get content like below:
major=00
minor=01
micro=02
build=03
I need to replace IFS=. with some equivalent command in gnu make, can you please help me with this? thanks.
The problem is that you're readin the values into shell variables called major, minor etc. but then you are printing Make variables with the same names. The shell cannot set the Make variables.
To fix your existing code you need to repeat the $ characters as $$ so Make outputs a $ to the shell, which will interpret them (also you're saying "major" every time, and are missing some semi-colons):
IFS=. read major minor micro build <<<"${RELEASE_VERSION}" ; \
echo major=$${major} > $(#)/etc/release ;\
echo minor=$${minor} >> $(#)/etc/release; \
echo micro=$${micro} >> $(#)/etc/release; \
echo build=$${build} >> $(#)/etc/release; \
But to do it within make you can use the subst and word functions:
REL_WORDS := $(subst ., ,${RELEASE_VERSION})
...
echo major=$(word 1,${REL_WORDS}) > $(#)/etc/release ;\
echo minor=$(word 2,${REL_WORDS}) >> $(#)/etc/release ;\
echo micro=$(word 3,${REL_WORDS}) >> $(#)/etc/release ;\
echo build=$(word 4,${REL_WORDS}) >> $(#)/etc/release ;\
.PHONY: $(PROJECTDIR)/projectroot
$(PROJECTDIR)/projectroot: $(PROJECTDIR)/projectroot/etc/release
$(PROJECTDIR)/projectroot/etc/release:
echo "${RELEASE_VERSION}" | \
sed 's/^/major=/; s/\./\nminor=/; s/\./\nmicro=/; s/\./\nbuild=/' >$#
Add dependencies to the release file.

How to set environment variables in makefile recipe?

Here is a simplified Makefile:
all:
#for (( i = 0; i < 5; ++i )); do \
var="$$var $$i"; \
echo $$var; \
done
#echo $$var
I suppose the value of "var" is "0 1 2 3 4", but the output is:
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
<--- NOTHING!!!
As you can see the last echo is "NOTHING". What is wrong?
From here:
When it is time to execute recipes to update a target, they are executed by invoking a new subshell for each line of the recipe...
Please note: this implies that setting shell variables and invoking shell commands such as cd that set a context local to each process will not affect the following lines in the recipe. If you want to use cd to affect the next statement, put both statements in a single recipe line. Then make will invoke one shell to run the entire line, and the shell will execute the statements in sequence.
Try the following:
all:
#for (( i = 0; i < 5; ++i )); do \
var="$$var $$i"; \
echo $$var; \
done; \
echo $$var

Resources