Makefile for input/output pairs - makefile

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.

Related

GNU make: what difference between eval funciton and expand variable into makefile directly

the Makefile define a multi-line variable, which define a rule, reference it by eval function work fine:
define a
b:
echo b
endef
$(eval $(a))
excute result:
$ make
echo b
b
but failed if reference variable directly:
define a :=
b:
echo b
endef
$(a)
execute make will fail:
make: *** No rule to make target 'echo', needed by 'b'. Stop.
but if I write rule in one line, that will work:
define a :=
b: ; echo b
endef
$(a)
make is ok:
$ make
echo b
b
why multi-line variable we need eval function, what GNU make do when expand variable into makefile directly.
I found not material about this in the whole GNU make manual.

How to remove substring in makefile

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)'

Variable in makefile conditional?

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

makefile iteration and retrieving the filename

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

Variables from makefile to bash

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.

Resources