using `date` in Makefile variable - makefile

Using GNU Make 4.1, in my Makefile I can create a timestamp log file with this::
flush_log:
#echo "==> flush logs"
cat file > file_`date +%FT%T%Z`.log
But who to put:
file_`date +%FT%T%Z`.log
in a var inside the Makefile e.g to make a wc on it ?
I've try (with no success):
flush_log:
#echo "==> flush logs"
logfile:=file_$(shell date +%FT%T%Z).log
cat my_log > $(logfile)
echo `wc -l $(logfile)`
I get this error:
$ make flush_log
==> flush logs
logfile:=file_2016-12-24T20:09:52CET.log
/bin/sh: 1: logfile:=file_2016-12-24T20:09:52CET.log: not found
Makefile:7: recipe for target 'flush_log' failed
make: *** [flush_log] Error 127
$
I was following recommendation from https://stackoverflow.com/a/14939434/3313834 and Simply expanded variables https://www.gnu.org/software/make/manual/html_node/Flavors.html#Flavors

Every line in a makefile (that appears after a rule statement) that is indented with a TAB character is part of the rule's recipe. Lines in the rule's recipe are passed to the shell for handling, they're not parsed by make (except to expand variable/function references).
So your line logfile:=file... is being passed to the shell and being interpreted by the shell... and there is no valid := operator in the shell, so the shell thinks that entire line is a single word and tries to run a program with that name, which obviously doesn't exist.
You probably want to create a make variable, which must be done outside of the recipe, like this:
logfile := file_$(shell date +%FT%T%Z).log
flush_log:
#echo "==> flush logs"
cat my_log > $(logfile)
echo `wc -l $(logfile)`

Related

How to compare two string variables in Makefile

I have the following code:
LOCAL_VERSION := $(shell some_binary -v | head -n 1)
REMOTE_VERSION := $(shell curl -s https://example.com/key)
all:
ifeq($(REMOTE_VERSION), $(LOCAL_VERSION))
#echo yes
endfi
But I am getting this:
user:tmp user$ make
ifeq(v0.11.1, v0.11.1)
/bin/sh: -c: line 0: syntax error near unexpected token `v0.11.1,'
/bin/sh: -c: line 0: `ifeq(v0.11.1, v0.11.1)'
make: *** [all] Error
I am on Mac OSX, but it's using GNU Make anyway.
ifeq should not be indented, e.g.
LOCAL_VERSION := $(shell some_binary -v | head -n 1)
REMOTE_VERSION := $(shell curl -s https://example.com/key)
all:
ifeq ($(REMOTE_VERSION), $(LOCAL_VERSION))
#echo yes
else
#echo NO
endif
The issue is not that ifeq is indented in the recipe, the problem is that it was indented using a tab. If you indent using space, the code runs as expected.
From Make manual:
5.1 Recipe Syntax
Each line in the recipe must start with a tab (or the first character in the value of the .RECIPEPREFIX variable; see Special Variables), except that the first recipe line may be attached to the target-and-prerequisites line with a semicolon in between. Any line in the makefile that begins with a tab and appears in a “rule context” (that is, after a rule has been started until another rule or variable definition) will be considered part of a recipe for that rule. Blank lines and lines of just comments may appear among the recipe lines; they are ignored.

Running expressions (without compilation of files) in Makefile without targets?

I wanted to test some expressions of the ifeq kind that run a shell command that I read somewhere, so I wrote this tiny mymakefile (all lines being indented with a tab):
ifeq ($(shell echo test 2>/dev/null; echo $$?),0)
$(info I am inside)
endif
... and I tried to run it:
$ make -f mymakefile
make: *** No targets. Stop.
How could I test expressions like this inside their own makefile? Do I need to define a default target, or not? And how should the commands be formatted (indented with a tab, or space, or not indented at all?)
Well, I got somewhere - apparently, one must specify a target; but since I'm a make noob, I would love to see a more qualified answer.
I found this link https://www.gnu.org/software/make/manual/html_node/Conditional-Example.html that gave me a hint.. Anyways, this is mymakefile now:
.PHONY: default
default: mytarget;
ifeq ($(shell echo test 2>/dev/null; echo $$?),0)
$(info I am inside)
else
$(info I am outside)
endif
mytarget:
\t (TAB) echo A
So, the mytarget here is just a dummy, which simply does an echo A; running this prints:
$ make -f mymakefile
I am outside
echo A
A
If you don't want the echo A printed, suppress it with at sign: #echo A.
The echo A line has to be indented with a TAB - else error "mymakefile:11: *** missing separator. Stop.".
Strangely, if I indent the two $(info... lines with a TAB, then "I am outside" is printed last (?!), but when they are not indented (or indented with spaces), then it is printed first (as per the order in the file).

Using Makefile for editing files rather than creating them

I was thinking about using Make for small checks for my dev setup. One thing I want is to check that a particular string exists in a file before doing some action. If I wanted to create the entire file it would be trivial
action: filename
...
filename:
echo 'blah' >> filename
But how can this logic be applied to actions, like grep? My dependency isn't that a file exists, it's that the file has correct content.
I'm asking specifically about Make and not other solutions like chef/puppet
You can run any shell commands you want in a make recipe. As many of them as you want also.
So if you need to run grep before doing something else just do that.
Just remember that every line in a recipe is run in its own shell session so they don't share state.
So this:
action: filename
...
filename:
grep -qw blah $# || echo 'blah' > $#
runs grep on filename (via the automatic variable for the current target $#) looking for whole words and quitting on the first match (-q).
If grep finds blah then it will return success and the || will short-circuit and the recipe is done. If grep fails then the || will trigger and the echo will run.
You might be tempted to do things that require the inverse logic do X only if Y is true:
filename:
grep -qw blah $# && echo blah2 > $#
but that doesn't work correctly. When grep fails the && short-circuits and make sees a recipe failure and bails the make process out with an error.
You need this instead.
filename:
! grep -qw blah $# || echo blah2 > $#
to invert the logic and ensure that the "failure" from grep is seen as success as far as make is concerned.
That all being said in this specific example if filename exists at all then that recipe won't ever run as it has no prerequisites so make will always consider it up to date. To work around that you need to give the file a prerequisite that will force it to be considered out of date. Specifically a force target.
Don't follow the advice about .PHONY for this case though. .PHONY targets should never be prerequisites of non-.PHONY targets.
Expanding on what #john wrote I got the following to work:
TEST_FILE=filename
.PHONY: ${TEST_FILE}
string=testing
filecheck=$(shell grep -qw ${string} ${TEST_FILE} || echo ${TEST_FILE})
all: ${filecheck}
${TEST_FILE}:
echo 'changing the file'
echo ${string} >> ${TEST_FILE}
Here the file on which I'm operating is a .PHONY target. I think that's ok because I'm actually not creating the file, just modifying it. This will work if the file does not exist, or exists without the needed string.
You could add a test in the target's recipe (As Etan posted before I could complete this answer...). If you do want to do this using just make logic, you could do something along the lines of:
actions: $(if $(shell grep -q $$string filename && echo y),filename,)
filename:
echo blah >> $#
If filename contains the string, then there will be an actions: filename dependency, and filename will be built when you build actions. Notice, though that this will check whether the string exists in filename at the time the makefile is parsed -- if filename is generated, or modified in this makefile, then it would not effect whether the action is run. If you want to test right before overwriting the file, then you would use a bash if statement in the recipe itself.

Calling a shell function (in an external file) with arguments from a makefile

I have a file sedstr.sh containing a function sedstr:
#!/bin/bash
function sedstr {
# From stackoverflow.com/a/29626460/633251 (Thanks Ed!)
old="$1"
new="$2"
file="${3:--}"
escOld=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< "$old")
escNew=$(sed 's/[&/\]/\\&/g' <<< "$new")
sed -i.tmp "s/\<$escOld\>/$escNew/g" "$file" # added -i.tmp
echo "sedstr done"
}
I have an external file "test" to be edited in place with these contents:
My last name is Han.son and I need help.
If the makefile works, I'll have a new last name.
I want to call the sedstr function with its arguments from a makefile. Nothing should be returned, but the external file should be edited. Here is a small makefile that doesn't work:
all: doEdit
doEdit:
$(shell ./sedstr.sh) # I was hoping this would bring the function into the scope, but nay
$(shell sedstr 'Han.son', 'Dufus', test)
How can I call this function using variables in the makefile? The error is:
make: sedstr: Command not found
make: Nothing to be done for `all'.
Each line in a make recipe is executed in its own shell.
Similarly, so is each call to $(shell).
They don't share state.
To do what you want ould would need a recipe line of
$(shell . ./sedstr.sh; sedstr 'Han.son' 'Dufus' test)
That being said there's no reason to use $(shell) here at all as you are already in a shell context and as you can just as easily (and more correctly) use a recipe line of
. ./sedstr.sh; sedstr 'Hans.son' 'Dufus' test
And yes, the commas in the original are just incorrect.
You can call the function from inside sedstr.sh. At the end
sedstr "$1" "$2" "$3"
EDIT
Or see other answer

Make commands with variables

I've been learning make and am struggling to figure something out. I have some rules with this general structure.
FILE = "myfile.txt"
test :
YOUR = $(subst my,your,$(FILE));\
cat $(FILE) $(YOUR)
I would expect the end result to be running the command:
cat myfile.txt yourfile.txt
Instead I get the following...
YOUR = "yourfile.txt";\
cat "myfile.txt"
/bin/sh: YOUR: command not found
make: *** [test] Error 1
If instead of using the subst function, I just do YOUR="yourfile" in the makefile, everything looks fine. Any suggestions or have I missed something pretty fundamental? I should add that I'm using tabs and not spaces to start the lines for the commands within the rule.
FILE = "myfile.txt"
test :
$(eval YOUR = $(subst my,your,$(FILE)))
cp $(FILE) $(YOUR)
You have to use the eval function in the recipe (Define make variable at rule execution time)
You need to distinguish between what make executes and what the shell executes. Your line with YOUR = starts with a tab and is part of the actions of a rule, so it is executed by the shell, which can't find a program YOUR to execute with some arguments.
Place the expansion outside the rule:
YOUR = $(subst my,your,$(FILE))
test:
cat $(FILE) $(YOUR)
Note that shell assignments require no space around the equals sign, and use ${} rather than $() to reference variables: YOUR=${FILE/my/your} in Bash (and if written in a make rule, you'd need $$ in place of $ so that the shell sees a single dollar sign and make does not try the variable expansion that it doesn't understand). The shell uses $() to execute the command contained within, and the result is often captured in a variable: YOUR=$(echo "${FILE}" | sed 's/my/your/').
If you only need the variable in the shell recipe and not in the make context then you don't need to bother playing with eval (which are hoisted) and can just assign to shell variables instead.
For example:
FILE = "myfile.txt"
test :
YOUR='$(subst my,your,$(FILE))';\
cat $(FILE) "$${YOUR}"

Resources