How to separate different parts with exit control in Makefile - makefile

I am writing a complicated Makefile which have multiple parts like:
step1:
if [[ ${input} == "delete" ]]; then \
echo "this is a test to delete files"; \
else \
echo "error stop"; \
fi
step2:
rm -f *.txt
test:
make step1
make step2
So if I enter "make test input=delete", it will delete all the .txt files. And if I enter "make test input=none", it would not do anything. I know the simplest way is to combine step1 and step2 as:
test:
if [[ ${input} == "delete" ]]; then \
echo "this is a test to delete files"; \
rm -f *.txt; \
else \
echo "error stop"; \
fi
But my Makefile is so complicated that I have to separate into several parts. Does Makefile support similar features? If yes, what should I look for? Thanks.

You should always use $(MAKE) and never use make directly when invoking a sub-make.
If you do that, then all command-line overrides you provide will be correctly passed to the sub-makes.
BTW, you should not use the bash syntax [[ x == y ]]. If you run this makefile on a system where the default shell is limited to POSIX standard syntax this won't work. You should use POSIX syntax: [ x = y ].

As you use GNU Make you can use its conditionals, e.g.
ifeq:
test:
ifeq ($(input),delete)
echo "this is a test to delete files"
rm -f *.txt
else
echo "error stop"
endif
Or, if you want to control the prerequisites of a target:
step1:
echo "this is a test to delete files"
step2:
rm -f *.txt
# test always depends on step1
test: step1
# test also depends on step2, if input = delete
ifeq ($(input),delete)
test: step2
endif

Related

RAII-like action in makefile rule

I'm trying to find a good way to add some RAII-like actions into a makefile I'm maintaining. Currently, I have something similar to this:
out: in
lockfile in.lock
echo in // Some action which can fail
rm -f in.lock
This code works fine when using multiple jobs, as it is mainly meant sanity instead of performance. At least, if my action is not failing. So if I like to add a fallback to this. So in short, it'll look something like:
out: in
lockfile in.lock
(echo in) || (rm -f in.lock; false)
rm -f in.lock
Yet again looking good, though I don't like having to write twice rm -f in.lock, nor does the (echo in) looks elegant if the actual content is several lines of bash-script.
This would look similar to:
out: in
lockfile in.lock
trap "rm -f in.lock" EXIT; \
(echo in)
However, this would make the actual rules look more complex if you have different rules which are really separate.
out: in
lockfile in.lock
trap "rm -f in.lock" EXIT; \
$(SHOW_DEPENDENCY_ON_DEBUG) && \
(echo in)
Where SHOW_DEPENDENCY_ON_DEBUG can be defined as echo $# <=== $^ in certain circumstances and # in others. So I'm not that sure if I can nicely chain all commands. Therefore I hope any of you know of some tricks that I've missed.
In short, I like to transform:
out: in
lockfile in.lock
echo in // Some action(s) which can fail
rm -f in.lock
In a way that always executes rm -f in.lock, without having to chain bash-commands or duplicating the action(s) that have to be executed to finalize the actions in the rules.
For the problem of ensuring the your lockfile (or any file that make makes) is deleted come what may,
make has a stock solution: make it an .INTERMEDIATE target.
Then, if make creates the file, it will auto-delete it at the end, come what may, e.g.
Makefile
.PHONY: all clean
all: out
in:
touch $# # Whatever
.INTERMEDIATE: in.lock
%.lock: %
touch $# # Whatever
out: in.lock
if [ "`shuf -i 0-1 -n 1`" = "0" ]; then echo Fail; false ; else echo Succeed; touch $#; fi
rm -f $<
clean:
rm -f in out
Here the command:
if [ "`shuf -i 0-1 -n 1`" = "0" ]; then echo Fail; false ; else echo Succeed; touch $#; fi
will fail or succeed on a pseudo-random coin-toss.
Some runs:
$ make
touch in # Whatever
touch in.lock # Whatever
if [ "`shuf -i 0-1 -n 1`" = "0" ]; then echo Fail; false ; else echo Succeed; touch out; fi
Succeed
rm -f in.lock
$ make clean
rm -f in out
$ make
touch in # Whatever
touch in.lock # Whatever
if [ "`shuf -i 0-1 -n 1`" = "0" ]; then echo Fail; false ; else echo Succeed; touch out; fi
Fail
Makefile:14: recipe for target 'out' failed
make: *** [out] Error 1
rm in.lock
But don't push this feature so far as removing the
rm -f $<
from the recipe. make will delete the intermediates at exit, which is fine if the recipe fails.
But if the recipe succeeds you presumably want the lockfile deleted right away rather than when
make finishes, which might be arbitrarily later.
Later
Any chance the .INTERMEDIATE can refer to wildcard, like %.lock?
No. You'd have to mean:
.INTERMEDIATE: %.lock
and there is no "wildcard" there. With no % in lefthand side,
it's not a pattern-rule and % in the righthand side only just means %.
But you don't need this. You must know the names of the prerequisites you
want to lock or at least be able to compute them with makefunctions.
Otherwise you can't write the makefile at all. So say they are ina inb inc.
Then you make all the locks intermediate like:
inputs := ina inb inc
locks := $(patsubst %,%.lock,$(inputs))
.INTERMEDIATE: $(locks)

How to compare two shell command output in Makefile?

My Makefile is:
.PHONY: check
check:
ifneq $(shell echo 123), $(shell echo 123)
$(error Not equal)
endif
When I run, I've got the error:
$ make
Makefile:3: *** Not equal. Stop.
But this should happen only when they're different, but they're not. Why?
ifneq cannot be indented. the way you've written it, it's being run via a shell command which means the $(error) is being evaluated first by the make command.
i'm guessing you want the make check to actually run two commands only when make check is invoked, and compare their output. you can do:
.PHONY: check
check:
if [ "`echo 123`" != "`echo 123`" ]; then \
echo "Not equal"; \
exit 1; \
fi
According to GNU Make docs, Conditional Parts cannot be used to control shell commands at the time of execution, since conditionals control what make actually "sees" in the makefile.
So to perform condition during compilation process, shell syntax is preferred, e.g.
SHELL := /bin/bash -e
.PHONY: check
check:
#test "$(shell echo 123)" = "$(shell echo 123)" \
|| { echo Not equal; exit 2; } \
&& { echo Equal; }

make with directories as pattern drops intermediate files?

I have the following Makefile:
cells.csv:
echo cellA > cells.csv
echo cellB >> cells.csv
echo cellC >> cells.csv
echo cellD >> cells.csv
mkdir -p cellA
mkdir -p cellB
mkdir -p cellC
mkdir -p cellD
%/cell_gen: cells.csv
echo '$# generated' > $#
%/cell_gds: %/cell_gen
cat $(#D)/cell_gen > $#
echo $#_GDS >> $#
The idea is to generate 'cells' in two step (called [cell]_gen and [cell]_gds) while the cells list is
not known at the beginning of make.
Here: the target 'cells.csv' is human readable (just echo) , but in
the general case, I expect something complexe, itselft resulting of previous steps ...etc..: not readable.
Each step of 'cell' should be stored in the directory named [cell] .
I don't understand why in this case, if I ask for "make cellA/cell_gds" then it looks like
the steps are all executed: I get the csv file and I get cellA/cell_gds.
...but I can't explain why I don't get cellA/cell_gen ??
... Despite I can see "echo 'cellA/cell_gen generated' > cellA/cell_gen" during make execution , and i really get "cellA/cell_gen generated" instide the cellA/cell_gds
Does anybody knows why there is no file cellA/cell_gen ??
thanks !
The file cellA/cell_gen is an intermediate file; you didn't explicitly ask for it, Make deduced that it was necessary as part of a chain of pattern rules. So by default, Make will delete it once the "real" target, cellA/cell_gds, is complete.
To prevent this, just add the line
.PRECIOUS: %/cell_gen

Use $RANDOM in a makefile

I am making a makefile to rename files with a random number in it (I am a newbie in shell script). I don't understand why, but when I run the file $rand is given the value 'ANDOM'. When I run this outside of the makefile it works.
I run this in the Mac os terminal, in case it's helpful.
all: renamefiles
renamefiles:
rand=$RANDOM && mv myfile.css $rand-myfile.css && mv myotherfile.css $rand-myotherfile.css
Wouldn't it be easier/better to use a date/time stamp so that the renamed files are listed in date order?
You need to use two $ signs in the makefile for each $ that you want the shell to see.
Thus:
all: renamefiles
renamefiles:
rand=$$RANDOM && \
mv myfile.css $$rand-myfile.css && \
mv myotherfile.css $$rand-myotherfile.css
Or, with date/time stamps:
all: renamefiles
renamefiles:
time=$$(date +'%Y%m%d-%H%M%S') && \
mv myfile.css $$time-myfile.css && \
mv myotherfile.css $$time-myotherfile.css
To use a random number within one or multiple make variables, the following works fine for me:
FOO="some string with \"$$rand\" in it"
BAR=" you may use it $$rand times."
foobar:
rand=$$$$ && \
echo $(FOO) $(BAR)
You might need to surround a multi-letter macro name with braces (or parentheses), for example
${RANDOM}
$(RANDOM)
ref

Makefile (counting)

I'm completely stumped on how to do this in a Makefile
Let's say I have a target. Inside the target I have a loop. How do i change a variable to keep track of the iterations?
For example:
COUNTER = 0
target:
(loop){
COUNTER++
echo COUNTER
}
I know that variables in Makefiles are only expanded, and I'm not sure if they can be permanently changed, but there has to be a way to do this, right? :(
Here are some sources that are asking similar questions. It seems like those examples only change the variable temporarily:
How do I perform arithmetic in a makefile?
How to do arithmetic operation in makefile?
Doing simple math in Makefile
Maybe I have to use the eval function somehow?
Maybe I have to append onto a Makefile string a character each time and then use something in the shell to count the characters?
If the variable doesn't have to survive the rule, this should do (I'm assuming bash):
clean:
#n=0 ; \
for x in $(THINGS_TO_BE_DELETED); do \
if [ -f $$x ] ; then \
rm $$x; \
let "n+=1" ; \
fi ; \
done ; \
echo deleted $$n files;
Here is one solution: Write a simple script like this:
#!/bin/bash
count=`cat count.txt`
count=$((count + 1))
echo $count
cat $count > count.txt
Initialize the file by doing
$ echo "0" > count.txt
Then include it as a .PHONY requirement to build whatever you'd like.
This is similar to the accepted answer, but the syntax below should work with a POSIX compliant shell. Quotes should also be used inside of the test.
clean:
#n=0; \
for x in *.a *.b *.c ; do \
if [ -f "$$x" ]; then \
rm "$$x"; \
n=$$((n+1)); \
fi; \
done; \
echo deleted $$n files;
Note: tabs must be used for indentation

Resources