Line by line this works in terminal, but not in makefile?
test:
FOO="MACOS" ; \
FOO=$${FOO//OS/} ; \
#echo $FOO
Expected:
make test
MAC
The last shell expansion of shell variable FOO must be escaped ($$). And if you want to suppress the echo of the recipe, put the # at the beginning, not in the middle of the recipe.
test:
#FOO="MACOS" ; \
FOO=$${FOO//OS/} ; \
echo $$FOO
Note that you could also use make variables and functions:
FOO := MACOS
FOO := $(patsubst %OS,%,$(FOO))
test:
#echo '$(FOO)'
Related
I have a makefile with the below content :
mytarget:
#$(call my_func, echo \"hello world\")
define my_func
$(eval var := $(1))
if [[ "$(var)" != "" ]]; then \
$(var); \
fi;
endef
while executing with make command , this prints "hello world" (enclosed with the double quotes)
All I want is to get rid of the " from the echo "hello world" before the conditional if check.
I tried with below statement but of no use :
var2 = $(echo $var | tr '"' ''
then use $(var2) in the if condition instead of $(var)
It is not working.
Any help will be very much appreciated.
Thanks
First, you should never add # to your makefile recipes until they are working 100%. Otherwise you're trying to debug your makefiles blindfolded.
Your entire makefile script seems like it could just be written as:
mytarget:
$(call my_func, echo "hello world")
my_func = $1
and it would work exactly the same way: if my_func is called with an empty argument then $1 expands to the empty string, which is what you're trying to test anyway. Presumably you have some reason for wanting a more complicated configuration but, as I mentioned in my comment, it seems like an XY problem.
I'll give you a short answer: make doesn't care about quoting or about backslashes. Any quotes or backslashes you use are passed verbatim to the shell. So if you pass echo \"foo\" to the shell, it will print the quotes. That's exactly as expected.
If you remove the #, as I mention above, things will be a lot more clear to you.
So, why are you adding backslashes? It appears you're adding them because otherwise the condition if [ "$1" != "" ] will expand to if [ "echo "hello world"" != "" ] which is a syntax error.
One simple solution is to use single quotes instead of double quotes:
mytarget:
$(call my_func, echo "hello world")
define my_func
if [ '$1' != '' ]; then \
$1; \
fi;
endef
Of course, this will fail if you call my_func with a value containing singe-quotes.
You have two solutions:
First is to use a make conditional, not a shell conditional, since make doesn't care about quotes etc. That gives you:
mytarget:
$(call my_func, echo "hello world")
my_func = $(if $1,$1)
but that's the same as my first suggestion, just using $1 by itself.
If you really, really have to do this in the shell then you have to quote things for the shell. Make's subst function can do that for you:
mytarget:
$(call my_func, echo "hello world")
define my_func
if [ '$(subst ','\'',$1)' != '' ]; then \
$1; \
fi;
endef
which replaces all instances of ' with '\''.
Apparently there is no boolean type in GNU Make conditionals so this seemed like the best solution:
$(DEF_TARGET):
if [ "$(CHECK)" != "y" ]; then \
var=foo; \
$(if $(filter foo,$(var)),result=true,result=false); \
fi
The problem is that no matter if var=foo or var=bar, result will always be false. Replacing $(var) with foo or bar will yeld correct result.
Why will this not work? Are there any better solutions to the problem?
Following makefile is run with the command make -f make.txt
.PHONY: all
all:
X=aaa; \
Y=aaa; \
if [[ '$(filter $(X),$(Y))' ]]; then echo "matched!"; else echo "not matched!"; fi
output:
X=aaa; \
Y=aaa; \
if [[ '' ]]; then echo "matched!"; else echo "not matched!"; fi
not matched!
Why does it fail when X and Y are assigned values in the target recipe?
Apparently there is no boolean type in GNU Make conditionals
Clearly there is, else the presence of an $(if ...) macro would not make sense!
The way to read recipes
(i.e., the block of shell commands that build the target)
is to understand that make stores a recipe as a single recursively expanded variable.
The recipe is only expanded when make needs to pass some commands to the shell.
The recipe is expanded once in its entirety.
Each line of the resulting expansion is then executed one-by-one.
Each execution is run in a new instance of the shell.
So,
taking your original makefile,
let's assume that you have asked make to build ${DEF_TARGET}.
Make expands the recipe:
if [ "$(CHECK)" != "y" ]; then \
var=foo; \
$(if $(filter foo,$(var)),result=true,result=false); \
fi
${CHECK} becomes nothing (say)
$(if $(filter foo,$(var)),result=true,result=false)
First, ${var} is expanded and also becomes empty (say).
Note that the line var=foo in the recipe is not interpreted by make!
It is a shell command.
Next, $(filter foo,) is expanded and also is empty.
Next make expands $(if ,result=true,result=false), which produces result=false of course.
if [ "" != "y" ]; then \
var=foo; \
result=false; \
fi
Make sees this as a just one line due to the back-slashes.
So,
do not confuse shell variables and make variables.
All the make variables are gone by the time the shell gets its hands on the recipe.
Make does not know anything about shell variables.
Your original shell snippet could be something like:
result=$(if $(filter y,${CHECK}),true,false)
(TIMTOWTDI applies).
The shell gets result=true or result=false depending on the value of the make variable CHECK.
Makefile
.PHONY: all
all:
#echo "$(filter $(X),$(Y))"
Tests
$ make -f make.txt X='xxx yyy' Y='aaa bbb'
$ make -f make.txt X='xxx yyy' Y='aaa xxx'
xxx
$ make -f make.txt X='bbb yyy' Y='aaa bbb'
bbb
GNU Bash treats non empty strings as true in a boolean context. So a recipe with shell level condition might be:
all:
if [[ '$(filter $(X),$(Y))' ]]; then echo "matched!"; else echo "not matched!"; fi
In a makefile, I am trying to iterate through c files and use the path as well as the filename.
For example for /dir/dir2/file.c
I want to execute "cc /dir/dir2/file.c -o file"
I do not understand why basename and patsubst do not work. It just shows me the path as is.
Can anyone please help?
test_files := Test/src/test_*.c
compile_tests:
#for f in $(test_filenames); do \
echo ">>> $(basename $(patsubst %.c, %, $$f ))";\
done
You cannot mix and match make functions with shell operations. Make will fully expand all the variables and functions first, then it passes the result of the expansion to the shell and the shell runs it as a script.
You are trying to use a make function inside a shell loop, but the make function is expanded first, then the loop will run on the results. The basename and patsubst run on the literal string $f, which doesn't have any pathname and does not match the %.c pattern, so those functions have no effect.
If you want to do it this way you must use 100% shell operations, or else modify the variable before the shell gets it like this:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(basename $(patsubst %.c,%,$(test_filenames))); do \
echo ">>> $$f";\
done
ETA: if you want to do it all in the shell, you can use:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(test_filenames); do \
echo ">>> $$(basename $$f .c)";\
done
Or, perhaps more clearly:
test_filenames := $(wildcard Test/src/test_*.c)
compile_tests:
#for f in $(test_filenames); do \
echo ">>> `basename $$f .c`";\
done
I have next situation:
source of test.mk:
test_var := test_me
source of test.sh:
$test_var = some method that get test_var from .mk
if [ "$test_var" = "test_me" ] ; then
do something
fi
How can I get variable from .mk file to my .sh file, without grep + sed and other parsing techniques.
EDIT
I can't change .mk file
Create a makefile on the fly to load the test.mk file and print the variable:
value=$(make -f - 2>/dev/null <<\EOF
include test.mk
all:
#echo $(test_var)
EOF
)
echo "The value is $value"
Well if you can't use sed or grep, then you'll have to read the makefile database after parsing using something like:
make -pn -f test.mk > /tmp/make.db.txt 2>/dev/null
while read var assign value; do
if [[ ${var} = 'test_var' ]] && [[ ${assign} = ':=' ]]; then
test_var="$value"
break
fi
done </tmp/make.db.txt
rm -f /tmp/make.db.txt
this makes sure that something like:
value := 12345
test_var := ${value}
will output 12345, instead of ${value}
If you wanted to create variables representing all those from the makefile, you can change the inner if to:
if [[ ${assign} = ':=' ]]; then
# any variables containing . are replaced with _
eval ${var//./_}=\"$value\"
fi
so you will get variables like test_var set to the appropriate value. There are some make variables that start with ., which would need to be replaced with a shell-variable safe value like _, which is what the search-replace is doing.
Create a rule print_var in your makefile with the following code:
print_var:
echo $(test_var)
And in your test.sh, do:
$test_var = $(make print_var)
You also have to consider to put print_var rule in .PHONY section
A variation of #Idelic answer I came up some time ago on my own:
function get_make_var()
{
echo 'unique123:;#echo ${'"$1"'}' |
make -f - -f "$2" --no-print-directory unique123
}
test_var=`get_make_var test_var test.mk`
It uses the lesser known feature of the GNU make - ability to read multiple Makefiles from the command line using multiple -f options.
I have pairs of input/output files. I generate the name of the output file from a script: output=$(generate input). For example the pairs could be:
in1.c out1.o
in2.txt data.txt
in3 config.sh
sub/in1.c sub/out1.o
All those pairs obey the same set of rules in the makefile:
$(out): $(in) $(common)
$(run) $< > $#
What is a concise and efficient way to write such a Makefile?
I would rather avoid generating the Makefile from another script.
I wouldn't generate Makefile fragments from a script, but you could use an include:
INS := in1.c in2.txt in3 sub/in1.c
include rules.mk
rules.mk: Makefile
rm -f $#
for f in $(INS); do \
out=`generate "$$f"`; \
echo -e "$$out: $$f\n\t\$$(run) \$$<> > \$$#\n\n" >> $#; \
done
If you include a file gmake will try to generate and include it before any other targets. Combining this with a default rule should get you close to what you want
# makefile
gen=./generate.sh
source=a b c
run=echo
# Phony so the default rule doesn't match all
.PHONY:all
all:
# Update targets when makefile changes
targets.mk:makefile
rm -f $#
# Generate rules like $(target):$(source)
for s in $(source); do echo "$$($(gen) $$s):$$s" >> $#; done
# Generate rules like all:$(target)
for s in $(source); do echo "all:$$($(gen) $$s)" >> $#; done
-include targets.mk
# Default pattern match rule
%:
$(run) $< > $#
Testing with generate.sh like
#!/bin/bash
echo $1 | md5sum | awk '{print $1}'
give me
$ make
rm -f targets.mk
for s in a b c; do echo "$(./generate.sh $s):$s" >> targets.mk; done
for s in a b c; do echo "all:$(./generate.sh $s)" >> targets.mk; done
echo a > 60b725f10c9c85c70d97880dfe8191b3
echo b > 3b5d5c3712955042212316173ccf37be
echo c > 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
What is a concise and efficient way to write such a Makefile?
It is possible given a list of inputs and a shell script that generates output file name to generate targets, dependencies and rules using GNU make features:
all :
inputs := in1.c in2.txt in3 sub/in1.c
outputs :=
define make_dependency
${1} : ${2}
outputs += ${1}
endef
# replace $(shell echo ${in}.out) with your $(shell generate ${in})
$(foreach in,${inputs},$(eval $(call make_dependency,$(shell echo ${in}.out),${in})))
# generic rule for all outputs, and the common dependency
# replace "echo ..." with a real rule
${outputs} : % : ${common}
#echo "making $# from $<"
all : ${outputs}
.PHONY : all
Output:
$ make
making in1.c.out from in1.c
making in2.txt.out from in2.txt
making in3.out from in3
making sub/in1.c.out from sub/in1.c
In the above makefile one little used by powerful GNU make construct is used: $(eval $(call ...)). It requests make to expand the macro to produce a piece of text and then evaluate that piece of text as a piece of makefile, i.e. make generates makefile of the fly.