Makefile ifeq always true - makefile

I have the following Makefile target:
target1:
$(eval count_abc := $(shell grep -c "ABC" myFileA))
$(eval count_def := $(shell grep -c "DEF" myFileB))
echo $(count_abc)
echo $(count_def)
ifeq ($(count_abc),$(count_def))
echo "TRUE"
else
echo "FALSE"
endif
But the output is always TRUE, e.g.:
echo 22
22
echo 21
21
echo TRUE
TRUE
What am I doing wrong here? What I want is INSIDE the target do 2 greps and compare their outputs and do something or something else based on the result. Please note that the greps must be done within the target since myFileA and myFileB get created on the target before and don't exist at the beginning when running make.
Thanks,
Amir

The rule file for "make" is declarative in nature - the makefile defines rules and targets, and then the make program evaluate the rules, and decide which action to take based on the target. As a result, execution is not always in the order the lines are entered into the file.
More specifically, the "ifeq" is evaluated at the rule definition stage, but the actions for building the target (eval count_abc ...) are executed when the target is built. As a result, when the ifeq is processed, both count_abc and count_def are still uninitialized, expanded to empty strings.
For the specific case you described - building a target that will compare the grep -c output from the two files, you can try something like below, effectively using shell variables (evaluated when target is evaluated), and not make variables (which are mostly declarative, evaluated when makefile is read)
target1:
count_abc=$(grep -c "ABC" myFileA) ; \
count_def=$(grep -c "DEF" myFileB) ; \
echo $(count_abc) ; \
echo $(count_def) ; \
if [ "$count_abc" -eq "$count_def" ] ; then echo TRUE ; else echo FALSE ; fi
Disclaimer: I did not run the revised makefile, not having access to desktop at this time.

Related

Makefile compare string input

To understand better string variables in a Makefile, I have tried to do this example :
KEYWORD=Nothing
test:
$(call myFunc)
define myFunc
ifeq ($(KEYWORD), "Apple")
echo "You have found the key"
else
echo "Try again"
endif
endef
But when I'm typing
make test KEYWORD="Fork"
It prints errors
syntax error near unexpected token `Fork,'
`ifeq (Fork, "Apple")'
I have also tried :
Put Apple or 'Apple' in ifeq
Put a space or not after "," : ifeq ($(KEYWORD), "Apple")
Run command with KEYWORD=Fork
Did it using shell (if [ ${KEYWORD} -eq "Apple" ])
I'm running out of ideas because I don't understand how Makefille / Shell interpret the assignment KEYWORD="Fork"
Thanks
MadScientist identifies the problem. Perhaps the solution you're looking for is simply evaluating the conditional earlier. eg:
KEYWORD ?= NOTHING
...
ifeq ($(KEYWORD), Apple)
define myFunc
echo "You have found the key"
endef
else
define myFunc
echo "Try again"
endef
endif
First, $(call myfunc) is 100% identical to writing $(myfunc). The call function in make merely expands a variable with some other local values (the arguments) bound first. If you don't provide any local values, then you're just expanding the variable.
Expanding a variable just replaces the variable reference with what it expands to. So writing:
FOO = bar
foo:
echo $(FOO)
is 100% identical to writing:
foo:
echo bar
So in your situation,
test:
$(call myFunc)
is the same as:
test:
$(myFunc)
which is the same as:
test:
ifeq ($(KEYWORD), "Apple")
echo "You have found the key"
else
echo "Try again"
endif
which is why you get the output you did: these are not valid shell commands, but since you've expanded the variable as part of a recipe, they are sent to the shell as part of the recipe.

How to declare a deferred variable that is computed only once for all?

I have a shell program that takes ages to complete. As written, executing make build takes 4 x 2 seconds to complete because $(value) is computed for each file.
A solution is to declare value a deferred variable by using := instead of =.
Unfortunately this is not a solution either because it slows down the execution of make clean and any other targets by 2 seconds because value is computed for nothing.
value = $(shell sleep 2 && echo 42)
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $# -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
How can I set a variable what is assigned only if used, but only computed once ?
Said differently, I would like build to take 2 seconds to complete and clean to be immediate.
I am not interested to a solution that involves conditionals in order to bypass the assignment of value if the target is not build.
An alternative solution would be this. Unfortunately in this case I need to check whether or not the shelve file needs to be regenerated.
value = $(cat shelve)
shelve:
sleep 2 && echo 42 > $# || [ rm $# -a true ]
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $# -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
Here's a trick you can play:
value = $(eval value := $(shell cat shelve))$(value)
How this works: value is first assigned using recursive assignment so the value on the RHS is not expanded.
The first time value is expanded the make parser will first run the $(eval ...) which starts up a "new parser" for makefiles. In that parser, the content value := $(cat shelve) is evaluated. Here, value is a simple variable assignment so the RHS is expanded immediately and the $(shell ...) is run and assigned to value.
Remember make doesn't really have a concept of variable scope, so this value is just the same global value variable that we are setting in the outer parser.
Then the eval completes and expands to the empty string, and make continues parsing things. Here it finds the value $(value) and expands that... value now has the result from the eval, not the eval text itself, so that's what will be expanded.
Maybe this will help:
value = $(eval value := $(shell cat shelve))$(value)
Here value contains the string $(eval value := $(shell cat shelve))$(value)
Now you expand it:
%.out: %
echo $(value) > $< ...
Make starts to expand this recipe. It gets to $(value) and sees it needs to expand the variable value: since it's recursive it expands the value:
$(eval value := $(shell cat shelve))$(value)
First it expands the eval, which parses this:
value := $(shell cat shelve)
That sets the value variable as a simply-expanded variable, so the RHS is expanded immediately. Say the results of cat shelve are "foo", so value is now set to foo (and it's marked simply expanded).
That's the end of the eval, so then make starts the next part which is $(value), so it looks up the variable value and discovers it's a simply-expanded variable with the value foo.
One solution would be to turn that value into a regular file target that gets updated only when its prerequisites change. If you insist on rebuilding that target for every build, mark it as phony.
When clean target does not depend on that file, then it won't be rebuilt when you invoke make clean.
In
%.out: %
echo $(value) > $< || [ rm $# -a true ]
echo $(value) > $< updates the prerequisite, whereas make expects it to update the target only. Updating a prerequisite must be done by a separate rule with that prerequisite being the target.
You can make the assignment depend on the target name in $(MAKECMDGOALS):
ifneq ($(MAKECMDGOALS),clean)
value := $(shell sleep 2 && echo 42)
endif
See also the docs for details.

Makefile - run time decisions using ifeq

I am trying to solve a particular problem where variables are assigned in one recipe and then interpreted in other recipes, all during run time. As I understand it, ifeq conditions are evaluated during parsing which doesn't work for me as some of them are always false. Is there a way to implement what I'm trying to do (expected output is below)? I'll provide more info if needed.
I'm using make version 3.81 on Linux Mint 17.1.
Here is what I have so far:
fourth =
all: check valueOfFourth definitionOfFourth
.PHONY: all
check:
#echo "TEST"$(cnt)
ifeq ($(first),$(second))
#echo "1. First condition"
$(eval fourth = "first")
else ifeq ($(first),$(third))
#echo "1. Second condition"
$(eval fourth = "second")
else
#echo "1. Conditions weren't met"
endif
valueOfFourth:
ifeq ($(fourth),"first")
#echo "2. First"
else ifeq ($(fourth),"second")
#echo "2. Second"
else
#echo "2."
endif
definitionOfFourth:
ifeq ($(fourth),)
#echo "3. Variable is not defined"
else
#echo "3. Variable is defined"
endif
It is invoked like this:
make cnt="1" first="x" second="x" third="y" && printf "\n" && \
make cnt="2" first="x" second="y" third="x" && printf "\n" && \
make cnt="3" first="x" second="y" third="z"
Expected output:
TEST1
1. First condition
2. First
3. Variable is defined
TEST2
1. Second condition
2. Second
3. Variable is defined
TEST3
1. Conditions weren't met
2.
3. Variable is not defined
Actual output:
TEST1
1. First condition
2.
3. Variable is not defined
TEST2
1. Second condition
2.
3. Variable is not defined
TEST3
1. Conditions weren't met
2.
3. Variable is not defined
It is evident that only the "check" target does what it is supposed to do, the other two simply do not work.
I'm still not entirely clear how these targets are supposed to interact (any interaction between them is generally a bad idea as parallel make execution means that without being explicitly sequenced via prerequisites between them execution order is not guaranteed). But assuming non-parallel make and that each target is supposed to output one of the lines of output I believe this does what you want.
.PHONY: all
all: check valueOfFourth definitionOfFourth
ifeq ($(first),$(second))
fourth = First
condmsg = $(fourth) condition
else ifeq ($(first),$(third))
fourth = Second
condmsg = $(fourth) condition
else
condmsg = Conditions weren'\''t met
endif
check:
#echo 'TEST$(cnt)'
#echo '1. $(condmsg)'
valueOfFourth:
#echo '2. $(fourth)'
definitionOfFourth:
ifeq ($(fourth),)
#echo "3. Variable is not defined"
else
#echo "3. Variable is defined"
endif

Do calculation in the Makefile

I got confused with Makefile. I am trying to run a simple command in the Makefile but it gives me the error "/bin/bash: line 3: :=: command not found". I am using shell to run this makefile
This is my part of my Makefile:
all:
vlog Benchmarks/$(NAME)/Syn/*.v
$(eval tux_number := 1)
$(eval range := 1)
$(eval ssh_log := 255)
echo "Start Range: ${range}"
echo "tux-number: ${tux_number}"
while [[ $$range -le 50 ]] ; do \
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit ; \
echo "range: ${range}" ; \
eval $$range := $$((${range}+1)) ; \
done
Thanks
all:
#range=1; \
while [ $$range -le 10 ] ; \
do echo Range: $$range; \
let range=range+1 ; \
done;
Note that the whitespace in front of #range... is the only TAB.
Just to fix your obvious problems with Makefile syntax, here is an attempt at refactoring your attempt into valid code.
tux_number := 1
ssh_log := 255 # not used anywhere
all:
vlog Benchmarks/$(NAME)/Syn/*.v
echo "Start Range: 1" # This is probably no longer very useful output
echo "tux-number: ${tux_number}"
range=1; while [ $$range -le 50 ] ; do \
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit ; \
echo "range: $$range" ; \
range=$$(expr "$$range + 1); \
done
Notice how tux_number and ssh_log are Makefile variables, while range only exists in the shell which executes the while loop. I have avoided the Bashisms in order to make this portable. (If portability is not important, you might want to refactor it back to Bash syntax and use for ((range=1; range<=50; range++)); do... instead.)
Your use of eval is misguided. As you can see, I simply lifted out the Makefile variables outside the recipe where they don't belong. What you were doing was (1) have Make evaluate the expression range := 1 (which evaluates to itself) and (2) use the output as a shell command in a recipe. Since it's not a valid shell command, you got the syntax error from Bash. Without further ado, I'll just take the easy way out here and say that eval is a complex subject, and until you get more experience with Make, it's probably just best to forget that it exists.
In order to properly make use of Make's facilities, I would make this parallelizable, i.e. split it up into 50 individual targets. This is a bit clumsy (there's probably a better way to define range here), but at least it should illustrate a number of differences to your approach. (If you don't insist on having range count up from 1, making it zero-based would make this a little less clumsy. This exploits the fact that the empty string is harmless in a shell snippet, so we can use it instead of a zero prefix. Again, this could be simplifed if you don't care about the human readability of the range index.)
digits := 0 1 2 3 4 5 6 7 8 9
deca := "" 1 2 3 4
range := $(filter-out ""0,$(foreach d,$(deca),$(foreach i,$(digits),$d$i))) 50
# Or, at the expense of an external process,
# range := $(shell perl -le 'print $$_ for 1..50')
.PHONY: all
all: $(patsubst %,ssh-%,$(range))
.PHONY: ssh-%
ssh-%:
ssh -l yazdanbakhsh tux-$(tux_number).cae.wisc.edu exit
echo "range: $*"
This can be run with something like make -j 5 to execute these in parallel batches of five, for example.
Incidentally, the commented-out $(shell ...) call might be the actual answer to your question, if what you really wanted to do was to use Make to drive an external program to calculate something for you.

GNU override target?

I'm wondering if it's possible to override a target in a makefile! The environment I'm working in does not allow me to do this due to auto generation! I was wondering if I coded the same rule above or below the static target would this achieve an override?
%_emul.flist: $(if ${GEN_FLIST},%_synth.flist,) ${rdlh_file_deps}
${QUIET}if test ${SYN_DEBUG} -eq 1 ; then set -xv ; fi; \
$(if ${TOOL_VERILOG},rm -f $#; touch $#,$(if ${TOOL_BBOX_LIBS},echo ${TOOL_BBOX_LIBS} > $#,rm -f $#; touch $#))
/bin/sed -e '/\/libs\//d' -e '/\/place\//d' $(foreach mod,$(filter %.vhd,$^),-e 's%^\(.*\/\)\{0,1\}$(basename $(notdir ${mod}))\.v$$%${mod}%') $*_synth.flist >> $#
Yes , i think that would work .... but you need to be a bit more careful in the way you code things. You don't want to override something that might be useful!
GNU make would take the most recent of the target it encounters. So, the following works (but not as i would have liked it to work :( )
Output: I think you are looking for something like this --
Kaizen ~/make_prac $ make -nf mk.name
mk.name:20: warning: overriding recipe for target `name'
mk.name:17: warning: ignoring old recipe for target `name'
arg1="Kaizen" ;
echo "hello "" ;" ;
hello ;
Code: Here the target "name" appears twice and is overridden.
Kaizen ~/make_prac $ cat mk.name
##
## make to accept name and display hello name
##
arg1="" ;
.PHONY : name \
hello
#.DEFAULT :
# hello
hello : name
+ echo "hello $(arg1)" ;
name :
echo "name given is : $(arg1)" ;
name :
arg1="Kaizen" ;
PS: Take note of the use of : -- if you use :: then both rules get executed.
Explanation for the arg1 .... not showing in the output: The variable arg1, even though it gets assigned in the first parsing, it gets ignored, since its assignment is target dependent. If you would have had a variable declaration elsewhere -- e.g. like arg1 is defined at the start -- there would not be any dereferencing issues.

Resources