Use shell arguments inside GNU Make function - makefile

Is is possible to use shell arguments inside a makefile function?
e.g.
mywords:=hello there neighbour
myrecipe:
for ((i = 0 ; i < 3 ; i++)); do \
this_word=$(word $${i}, $(mywords)); \
done
Except myrecipe gives the error non-numeric first argument to 'word' function: '${i}'. Stop

Remember that make first expands the entire recipe (yes, that's all of the recipe lines) before ever doing anything with the shell.
Only after this full expansion does make then look for individual recipe lines, running each one in turn.
Thus make behaves sensibly when you have a macro that expands to more than one line.
It also hints that you should use make functionality wherever possible.
mywords := hello there neighbour
define mkcommand
echo $1
something $1
endef
myrecipe:
$(foreach _,${mywords},$(call mkcommand,$_))
Now, when you ask make to build myrecipe,
make sees something like:
myrecipe:
echo hello
something hello
echo there
something there
⋮
IMHO it's nearly always a mistake if you find yourself writing shell loops in a makefile.

Related

How do I change a makefile target into a function?

I am looking for a clean way to change a few target declarations I created in a makefile into a more functional type declaration in which I am able to pass variables and the output will remain consistent.
For example:
default: clean run1 run2
run1:
for entity in $(FLIST_01); do \
echo $entity ; \
done
run2:
for entity in $(FLIST_02); do \
echo $entity ; \
done
Ideally, I would like to remove the repetitive run target declarations and only have 1.
FLIST_01 = my_flist.txt
FLIST_02 = other.txt
default: clean run
run:
$(run_func $(FLIST_01))
$(run_func $(FLIST_02))
How do I create a custom function in make to do what run_func is supposed to be doing (the for loop reading of the file list variable passed to it?
UPDATE:
My attempt so far as been this:
run:
runfunc() { \
echo "test" \
for entity in $1; do \
echo $(entity); \
done \
}
runfunc $(FLIST_01)
runfunc $(FLIST_02)
But I get a syntax error on the do line: syntax error near unexpected token `do'
First off, unless you specify .ONESHELL, the commands in a target will be run in separate subshells. So your runfunc() declaration will run in a shell which declares the function, then exits. The next statement will run in a new shell which will know nothing about the function which was declared and then basically forgotten.
Secondly,
echo "test" \
for entity in $1;
will be expanded by make into
echo "test" for entity in ;
which obviously contains multiple errors. You mean
echo "test"; \
for entity in $$1;
to properly pass the dollar sign through to the shell. But on the whole, I would say your approach is flawed. You can refactor this to a make function like you originally hoped;
define run
for entity in $(1); do \
echo $$entity; \
done
endef
Now you can call this like
run:
$(call run,$(FLIST_01))
$(call run,$(FLIST_02))
But this particular loop can quite easily be replaced with a single shell statement.
run:
printf '%s\n' $(FLIST_01)
printf '%s\n' $(FLIST_02)
Another twopenn'orth.
First off, we want to expand $FLIST_01 for target run1, and FLIST_02 for target run2.
A clean way might be:
FLIST_run1 := a b c
FLIST_run2 := 1 2 3
.PHONY: run1 run2
run1 run2:
for entity in ${FLIST_$#}; do echo $entity; done
For a slightly more obscure solution we notice that what you are trying to do can be described with make's noddy pattern matching:
.PHONY: run1 run2
run%: run1 run2:
for entity in ${FLIST_$*}; do echo $entity; done
Here we use a static pattern rule.
In the recipe, $* expands to whatever matched the % in the pattern.
This solution looks quite clean to my jaundiced eye.
I urge you to seek out idiomatic make rather than idiomatic shell.
Your makefiles will nearly always thank you for it.
In the first instance, keep your two run targets rather than coalesce them into the same recipe.
.PHONY: run1 run2
run%: run1 run2:
for entity in ${FLIST_$*}; do echo $entity; done
.PHONY: run
run: run1
run: run2
run: ; echo $# Success
Here, when you make run for instance, make first carries out the recipe for run1, then the recipe for run2, and finally the recipe for run.
Is there any advantage to this? Sure. If you run make -j2 run then the commands for run1 and run2 will be run in parallel. The whole point of make really.
The final issue is to get rid of the shell loop, and to replace it with separate commands. That way make will check the exit code of the echo for you, and even run them in parallel if that is suitable. Sure, not helpful in this noddy example, but they could be compile commands for instance.
Usually, it is not easier to maintain shell functions inside Makefile. Main reasons:
Each shell '$' need to be escaped to '$$'
The $(...) construct has different meaning in shell vs Make.
Each line in the Makefile is executing by new shell instance, meaning that variables assignment, functions definitions, etc, are not shared between lines.
Consider few options: write shell helper script (recommended), use make user-defined functions or inline the function into the Make. For the specific case of the "echo", it might be possible to use one of the other approaches, see "Alternative Solution"
From personal experience, for any nontrivial functions, better to use shell helper script. They are much easier to develop and test.
Using Shell Helper Script
Write a small helper script "print-list.sh"
#! /bin/bash
echo "test" \
for entity ; do
echo $entity
done
And then invoke it from the target
FLIST_01 = a.txt b.txt c.txt
t1:
print-list.sh ${FLIST_01)
Inlining the shell function
As indicated above, embedding the shell function into the Makefile requires matching make rules about quoting, escapes, etc. It also require lot of effort debugging the script, as error messages are very cryptic in this setup.
Note that RUNFUNC must be included before every action lines that uses the command.
RUNFUNC = runfunc() { \
echo "test" ; \
for entity in $$*; do \
echo $$entity; \
done \
} ;
FLIST_01 = a.txt b.txt c.txt
t2:
$(RUNFUNC) runfunc ${FLIST_01)
Note that the function can be written as one liner, or using the 'define'/'endef' to simplify end-of-line escapes.
Using make user-defined functions
It is possible to create simple functions using make. This option require experience and time.
UFUNC = \
echo "test" ; \
for entity in $1; do \
echo $$entity ; \
done
FLIST_01 = a.txt b.txt c.txt
t3:
$(call UFUNC, ${FLIST_01))
Alternative Solution
Last option is to use existing construct (assuming that the only goal of the function is to convert the space-seperate list of files to new-line separated
t4:
echo ${FLIST_01} | tr " " "\n"

/bin/sh: -c: line 1: syntax error: unexpected end of file in bash [duplicate]

Considering that every command is run in its own shell, what is the best way to run a multi-line bash command in a makefile? For example, like this:
for i in `find`
do
all="$all $i"
done
gcc $all
You can use backslash for line continuation. However note that the shell receives the whole command concatenated into a single line, so you also need to terminate some of the lines with a semicolon:
foo:
for i in `find`; \
do \
all="$$all $$i"; \
done; \
gcc $$all
But if you just want to take the whole list returned by the find invocation and pass it to gcc, you actually don't necessarily need a multiline command:
foo:
gcc `find`
Or, using a more shell-conventional $(command) approach (notice the $ escaping though):
foo:
gcc $$(find)
As indicated in the question, every sub-command is run in its own shell. This makes writing non-trivial shell scripts a little bit messy -- but it is possible! The solution is to consolidate your script into what make will consider a single sub-command (a single line).
Tips for writing shell scripts within makefiles:
Escape the script's use of $ by replacing with $$
Convert the script to work as a single line by inserting ; between commands
If you want to write the script on multiple lines, escape end-of-line with \
Optionally start with set -e to match make's provision to abort on sub-command failure
This is totally optional, but you could bracket the script with () or {} to emphasize the cohesiveness of a multiple line sequence -- that this is not a typical makefile command sequence
Here's an example inspired by the OP:
mytarget:
{ \
set -e ;\
msg="header:" ;\
for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
msg="$$msg :footer" ;\
echo msg=$$msg ;\
}
The ONESHELL directive allows to write multiple line recipes to be executed in the same shell invocation.
all: foo
SOURCE_FILES = $(shell find . -name '*.c')
.ONESHELL:
foo: ${SOURCE_FILES}
FILES=()
for F in $^; do
FILES+=($${F})
done
gcc "$${FILES[#]}" -o $#
There is a drawback though : special prefix characters (‘#’, ‘-’, and ‘+’) are interpreted differently.
https://www.gnu.org/software/make/manual/html_node/One-Shell.html
Of course, the proper way to write a Makefile is to actually document which targets depend on which sources. In the trivial case, the proposed solution will make foo depend on itself, but of course, make is smart enough to drop a circular dependency. But if you add a temporary file to your directory, it will "magically" become part of the dependency chain. Better to create an explicit list of dependencies once and for all, perhaps via a script.
GNU make knows how to run gcc to produce an executable out of a set of .c and .h files, so maybe all you really need amounts to
foo: $(wildcard *.h) $(wildcard *.c)
What's wrong with just invoking the commands?
foo:
echo line1
echo line2
....
And for your second question, you need to escape the $ by using $$ instead, i.e. bash -c '... echo $$a ...'.
EDIT: Your example could be rewritten to a single line script like this:
gcc $(for i in `find`; do echo $i; done)

Makefile produces different results when run second time

I have a rule like this in a makefile:
target : dependencies
rm -rd somedirectory
runcodecoverage.exe # this generates somefile
$(eval COVERAGE=$(shell grep "blah" somefile))
#echo $(COVERAGE)
When I run this file the first time (after make cleaning) the echo doesn't print anything. But the second and times after that, it prints the correct result. If I replace the $(eval ..) line with just grep "blah" somefile I get the result I want, so the problem must be in the use of $(eval) and $(shell). Why is this happening?
Edit: I solved this by adding a new dependency, so it now looks like this:
generatesomefile :
runcodecoverage.exe # this generates somefile
target : dependencies generatesomefile
rm -rd somedirectory
$(eval COVERAGE=$(shell grep "blah" somefile))
#echo $(COVERAGE)
It seems like $(eval) was being substituted with the result of grep as soon as the "target" target was entered, even though I wanted it to be run after runcodecoverage.exe had run. In this sense the answer I accepted as correct wasn't quite right - the docs say this on variable expansion:
Rule Definition
A rule is always expanded the same way, regardless of the form:
immediate : immediate ; deferred
deferred
The eval function doesn't work the way you think it does.
The first time you run Make, it expands the eval function and carries out the variable assignment before executing any rule. And since there is no somefile, the grep returns nothing and COVERAGE stays empty. When Make executes the rule, it passes the empty variable to the echo which duly reports nothing.
The second time you run Make, once again it expands the eval function, executes the grep on somefile (which was built in the first run), and stores the result in COVERAGE. Then it executes the rule and passes COVERAGE to the echo, which puts it up on your screen.
The $(eval ...) runs when your Makefile is parsed, not when the recipe is executed.
It's not clear to me what you expect to happen; setting a Make variable from within a recipe does not seem like a sustainable approach, but it depends a lot on where and how you need to use this variable. If all you need is to output the coverage from the same recipe, replace the last couple of lines with just
grep "blah" somefile

Make commands with variables

I've been learning make and am struggling to figure something out. I have some rules with this general structure.
FILE = "myfile.txt"
test :
YOUR = $(subst my,your,$(FILE));\
cat $(FILE) $(YOUR)
I would expect the end result to be running the command:
cat myfile.txt yourfile.txt
Instead I get the following...
YOUR = "yourfile.txt";\
cat "myfile.txt"
/bin/sh: YOUR: command not found
make: *** [test] Error 1
If instead of using the subst function, I just do YOUR="yourfile" in the makefile, everything looks fine. Any suggestions or have I missed something pretty fundamental? I should add that I'm using tabs and not spaces to start the lines for the commands within the rule.
FILE = "myfile.txt"
test :
$(eval YOUR = $(subst my,your,$(FILE)))
cp $(FILE) $(YOUR)
You have to use the eval function in the recipe (Define make variable at rule execution time)
You need to distinguish between what make executes and what the shell executes. Your line with YOUR = starts with a tab and is part of the actions of a rule, so it is executed by the shell, which can't find a program YOUR to execute with some arguments.
Place the expansion outside the rule:
YOUR = $(subst my,your,$(FILE))
test:
cat $(FILE) $(YOUR)
Note that shell assignments require no space around the equals sign, and use ${} rather than $() to reference variables: YOUR=${FILE/my/your} in Bash (and if written in a make rule, you'd need $$ in place of $ so that the shell sees a single dollar sign and make does not try the variable expansion that it doesn't understand). The shell uses $() to execute the command contained within, and the result is often captured in a variable: YOUR=$(echo "${FILE}" | sed 's/my/your/').
If you only need the variable in the shell recipe and not in the make context then you don't need to bother playing with eval (which are hoisted) and can just assign to shell variables instead.
For example:
FILE = "myfile.txt"
test :
YOUR='$(subst my,your,$(FILE))';\
cat $(FILE) "$${YOUR}"

Multi-line bash commands in makefile

Considering that every command is run in its own shell, what is the best way to run a multi-line bash command in a makefile? For example, like this:
for i in `find`
do
all="$all $i"
done
gcc $all
You can use backslash for line continuation. However note that the shell receives the whole command concatenated into a single line, so you also need to terminate some of the lines with a semicolon:
foo:
for i in `find`; \
do \
all="$$all $$i"; \
done; \
gcc $$all
But if you just want to take the whole list returned by the find invocation and pass it to gcc, you actually don't necessarily need a multiline command:
foo:
gcc `find`
Or, using a more shell-conventional $(command) approach (notice the $ escaping though):
foo:
gcc $$(find)
As indicated in the question, every sub-command is run in its own shell. This makes writing non-trivial shell scripts a little bit messy -- but it is possible! The solution is to consolidate your script into what make will consider a single sub-command (a single line).
Tips for writing shell scripts within makefiles:
Escape the script's use of $ by replacing with $$
Convert the script to work as a single line by inserting ; between commands
If you want to write the script on multiple lines, escape end-of-line with \
Optionally start with set -e to match make's provision to abort on sub-command failure
This is totally optional, but you could bracket the script with () or {} to emphasize the cohesiveness of a multiple line sequence -- that this is not a typical makefile command sequence
Here's an example inspired by the OP:
mytarget:
{ \
set -e ;\
msg="header:" ;\
for i in $$(seq 1 3) ; do msg="$$msg pre_$${i}_post" ; done ;\
msg="$$msg :footer" ;\
echo msg=$$msg ;\
}
The ONESHELL directive allows to write multiple line recipes to be executed in the same shell invocation.
all: foo
SOURCE_FILES = $(shell find . -name '*.c')
.ONESHELL:
foo: ${SOURCE_FILES}
FILES=()
for F in $^; do
FILES+=($${F})
done
gcc "$${FILES[#]}" -o $#
There is a drawback though : special prefix characters (‘#’, ‘-’, and ‘+’) are interpreted differently.
https://www.gnu.org/software/make/manual/html_node/One-Shell.html
Of course, the proper way to write a Makefile is to actually document which targets depend on which sources. In the trivial case, the proposed solution will make foo depend on itself, but of course, make is smart enough to drop a circular dependency. But if you add a temporary file to your directory, it will "magically" become part of the dependency chain. Better to create an explicit list of dependencies once and for all, perhaps via a script.
GNU make knows how to run gcc to produce an executable out of a set of .c and .h files, so maybe all you really need amounts to
foo: $(wildcard *.h) $(wildcard *.c)
What's wrong with just invoking the commands?
foo:
echo line1
echo line2
....
And for your second question, you need to escape the $ by using $$ instead, i.e. bash -c '... echo $$a ...'.
EDIT: Your example could be rewritten to a single line script like this:
gcc $(for i in `find`; do echo $i; done)

Resources