Key value pair in MAKEFILE - bash

I am new in makefle, I had need array in makefile then I found that I can achieve that having variable that their items separated with spaces, and then iterating over it.
Now I want something like map(key,value) pair to store values with keys.
Question: can I have that in makefile ?
Thanks in advance..

You can use token pasting for that:
VAR_FOO_KEY := FOO_VAL
VAR_BAR_KEY := BAR_VAL
#example lookup:
KEY := FOO_KEY
LOOKUP_VAL := $(VAR_$(KEY))

Make a file named KV.mk with the following function definitions:
# KV(3 args): in a dictionary directory record a key value pair in a file
# KV(2 args): in a dictionary directory recover value associated with a key
# KV(1 arg ): remove a dictionary directory
# KV(0 arg ): remove all dictionary directories
define KV
$(if $4,$(error K0123.mk: args>3 forbidden), \
$(if $3,mkdir -p $1.key; echo "$3" > $1.key/$2, \
$(if $2,`cat $1.key/$2`, \
$(if $1,rm -fr $1.key/*, \
rm -fr *.key \
))))
endef
Then make a client Makefile with the following contents:
include KV.mk
all: hello.cpp hello
.PHONY: hello.cpp
hello.cpp:
#echo '#include <iostream>' > $#
#echo 'int main(int argc, char** argv)' >> $#
#echo '{ std::cout << "hello" << std::endl; return 0; }' >> $#
hello: hello.cpp
#$(call KV,$#)
#$(call KV,$#,compiler,g++)
#$(call KV,$#,compiler) -o hello hello.cpp
#./$#
clean:
#rm -f hello.cpp hello
#$(call KV)
Copy these two files into an empty directory and run "make".
This method offers much liberty in having multiple dictionaries
with multiple key/value pairs per dictionary.
One dictionary may be used for the entire Makefile.
Another dictionary may be used for all rules having common needs.
Individual dictionaries may be used for each rule.
Simplifying one more step, here is a shell script encapsulating the KV idea.
#!/bin/bash
usage() {
cat <<USAGE
KV(1) -- syre(tm) user command
NAME
KV - key value store management for Makefile
SYNOPSIS
(1) KV [--help]
(2) KV [--clear]
(3) KV [OPTION]... {key}
(4) KV [OPTION]... {key}={value}
1: display usage (this text)
2: clear all dictionaries (make clean)
3: print value associated with key
4: store key value pair in the dictionary and key file
USAGE
if [ "" != "$1" ] ; then echo "KV error: $1"; fi
exit 0
}
if [ $1 = "--help" ] ; then usage;
elif [ $1 = "--clear" ] ; then rm -fr *.key;
else
case $# in
3) mkdir -p $1.key; echo "$3" > $1.key/$2;;
2) cat $1.key/$2;;
1) rm -fr $1.key;;
*) usage "KV error: 1, 2, or 3 args required";;
esac
fi
and here is the same Makefile showing that simplification.
all: hello.cpp hello
.PHONY: hello.cpp
hello.cpp:
#echo '#include <iostream>' > $#
#echo 'int main(int argc, char** argv)' >> $#
#echo '{ std::cout << "hello" << std::endl; return 0; }' >> $#
hello: hello.cpp
#./KV $#
#./KV $# compiler g++
#`./KV $# compiler` -o hello hello.cpp
#./$#
clean:
#rm -f hello.cpp hello
#./KV --clear

Related

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

preventing variable expansion in makefile

I want to write this into a file:
-MF"$(#:%.o=%.d)" -MT"$(#)" -o "$#" "$<"
but when I echo this into a file, with "", or '', the variables will expand, how can I prevent the expansion and write it as is?
P.S. echo '-MF"$(#:%.o=%.d)" -MT"$(#)" -o "$#" "$<"' is called in a makefile.
in my makefile, I let's say I have:
all:
echo '-MF"$(#:%.o=%.d)" -MT"$(#)" -o "$#" "$<"' > $file
what I see in the file is
-MF"all" -MT"all" -o "all" ""
Use $$ to put a dollar sign into a Makefile recipe
all:
echo '-MF"$$(#:%.o=%.d)" -MT"$$(#)" -o "$$#" "$$<"' > $file

Makefile that rebuilds on an external condition such as an ENV change

I would like to rebuild a program if an external condition has changed.
From this example, the condition is the date of the day and here is my program:
#include <stdio.h>
#define STRINGIZE(x) #x
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
int main(int argc, char *argv[])
{
printf("%s", STRINGIZE_VALUE_OF(condition));
}
And the Makefile:
condition != date +"%m-%d-%y"
all: foo
foo: foo.c
gcc -Dcondition="$(condition)" $< -o $#
I tried to modify the Makefile as follow:
condition != date +"%m-%d-%y" | perl -pe chomp
$(shell \
if [ ! -f CONDITION] || [ "$(condition)" != "$$(cat CONDITION | perl -pe chomp)" ]; \
then \
echo "$(condition)" > CONDITION; \
fi \
)
all: foo
foo: foo.c | CONDITION
gcc -Dcondition="$(condition)" $< -o $#
Unfortunately, it does not work. I guess because make evaluates if the files have changes right before executing my $(shell) routine.
How can I implement such feature with make?
EDIT
The trick is to not call a sub-makefile
The thing to do here is not to fight make but to use it.
condition != date +"%m-%d-%y"
all: foo
foo: foo.c CONDITION
gcc -Dcondition="$(condition)" $< -o $#
CONDITION: FORCE
[ -f $# ] && [ "$(condition)" = "$$(cat $#)" ] || echo '$(condition)' > $#
FORCE: ;
Another approach is to turn you condition into a file timestamp and then make your target depend on that condition file.
Whenever the condition evaluates to true the condition file gets assigned a new timestamp causing a rebuild of all targets that depend on it. Changing the timestamp of that file must be made by $(shell) command.
E.g.
$ cat Makefile
# Example: update the condition every minute.
$(shell date +%Y%m%d%H%M > condition.txt.tmp; cmp --quiet condition.txt.tmp condition.txt 2>/dev/null || mv -f condition.txt.tmp condition.txt)
all : foo
foo : condition.txt
#echo "rebuild $# because $? changed."
touch $#
$ date; make
Mon 18 Apr 15:17:25 BST 2016
rebuild foo because condition.txt changed.
touch foo
$ date; make
Mon 18 Apr 15:17:27 BST 2016
make: Nothing to be done for 'all'.
$ date; make
Mon 18 Apr 15:18:01 BST 2016
rebuild foo because condition.txt changed.
touch foo
$ date; make
Mon 18 Apr 15:18:03 BST 2016
make: Nothing to be done for 'all'.
I thing I found something. The trick is to mark the target as .PHONY if the condition is true
condition != date +"%m-%d-%y" | perl -pe chomp
need_rebuild != \
if [ ! -f CONDITION ] || [ "$(condition)" != "$$(cat CONDITION | perl -pe chomp)" ]; \
then \
echo "$(condition)" > CONDITION; \
echo YES; \
fi
ifeq ($(need_rebuild),YES)
$(info Rebuild Needed)
.PHONY: foo
endif
all: foo
foo: foo.c | CONDITION
gcc -Dcondition="$(condition)" $< -o $#

Makefile improvement when intermediate files do not really change

I have a Makefile like this
all: *.foo
./finalstep *.foo > $#
%.foo: %.bar
./secondstep < $< > $#
%.bar: %.baz
./firststep < $< > $#
The thing is that often changes in a .baz file are minor in the sense that the .bar file produced after the change is the same (content-wise, or as would be detected by diff) as before the change.
Since secondstep and finalstep (and possible some more intermediate steps) are expensive it would be preferable if the lack of change in the .bar files could be detected and thus the invocation of secondstep (and maybe even finalstep) spared. Is there any way to achieve this?
My attempt to do something like this is as follows:
%.bar: %.baz
touch $#; cp $# $#.backup; ./firststep < $< > $#
%.foo: %.bar
diff -q $< $<.backup || ./secondstep < $< > $#
But this has a lot of drawbacks (and does not work correctly if one invokes make with arguments).
Is there any better method? Basically, make should consider two different filetimes for .bar files: One that gets updated each time firststep is run and that is used to determine whether .bar itself needs to be remade. Another that is only updated when a run of firststep results in a net change of content of the file and that is used to determine whethr .foo needs to be remade ...
I think you should experiment with something like this (not tested):
all: %.foo
./finalstep %.foo > $#
%.foo: %.bar
#if [[ -f $< && -f $<.backup ]]; then \
if diff -q $< $<.backup; then \
echo "IDENTICAL => do nothing"; \
else \
echo "DIFFERENT => proceed"; \
./secondstep < $< > $#; \
fi; \
else \
echo "NOT FOUND => proceed"; \
./secondstep < $< > $#; \
fi; \
cp $< $<.backup
%.bar: %.baz
./firststep < $< > $#;
Basically, the intention of the big recipe is to handle all the relevant combinations of $< and $<.backup (in the same subshell!):
both files present and equal (diff returns 0) => do nothing
both files present and differ (diff returns 1) => execute
one of the files not present => execute
Whatever is the above choice, make a backup at the end.

What's the difference between $# and $1 when there is only one parameter?

There are some C code:
apple.c
#include<stdio.h>
int main(void)
{
printf("apple\n");
return 0;
}
Makefile
apple:
gcc -c $#.c
gcc $#.o -o $#
$ make apple
and it works perfectly. But if I modify Makefile as:
apple:
gcc -c $1.c
gcc $1.o -o $1
$ make apple
It does not work. What is the difference between $# and $1 when there is only one parameter?
In a shell script, there'd be no difference. But this is a Makefile, so these references are to make variables. $# is the name of the rule target (apple here), while $1 is a variable named 1—nothing special. Bash does not see these variable references; they're handled by make.
$ cat Makefile
1 = one
target:
#echo '# = $#'
#echo '1 = $1'
$ make
# = target
1 = one

Resources