Using variables in Makefile - makefile

Here is a simple Makefile.
FILENAME=test.`date +"%Y.%m.%d %H:%M:%S"`.txt
test:
#echo ${FILENAME}
#sleep 2s
#echo ${FILENAME}
The output of make test is
test.2013.02.18 15:30:23.txt
test.2013.02.18 15:30:25.txt
The problem is that FILENAME is being calculated each time it is used. I want it to be calculated only once and be the same while script is running. Am I doing it wrong?

GNU Make has two flavours of variables. You have used a recursively-expanded variable, but you want a simply-expanded variable. See http://www.gnu.org/software/make/manual/html_node/Flavors.html#Flavors
With your current Makefile, the variable contains the exact text test.date +"%Y.%m.%d %H:%M:%S".txt and every time you reference it that text gets substituted verbatim, and so your Makefile is equivalent to:
test:
#echo test.`date +"%Y.%m.%d %H:%M:%S"`.txt
#sleep 2s
#echo test.`date +"%Y.%m.%d %H:%M:%S"`.txt
Seen like this it should be obvious that the shell runs the date command twice.
To get the behaviour you want, you need to set a simply-expanded variable and you need to run the shell command at the point where you define it, which can't use backticks because they are shell metacharacters but Make ignores them, so you use Make's shell function instead:
FILENAME := test.$(shell date +"%Y.%m.%d %H:%M:%S").txt

Related

In makefile, how to store multi-line shell output in variable

I have a shell command where it outputs multiple lines. I want to store it in a variable in makefile for later processing in the target.
A simplified example:
I have this file called zfile1
#zfile1
some text
$echo 123
more text
$$$#&^$
more text
The makefile:
a:
#$(eval v1 = $(shell cat zfile1))
# need to process the variable here, example:
#echo "$(v1)"
# I want to prevent expansion of values in the file and print in multi-line
If you have GNU make 4.2 or above you can use the $(file <zfile1) function. See https://www.gnu.org/software/make/manual/html_node/File-Function.html
If you don't have a new-enough version of GNU make, you can't do it. Of course in your example there's no real need to put the contents of the file into a make variable at all: you can just have your recipe use the file itself. But maybe your real use-case isn't so simple.
ETA
You should never use either the make function eval or the make function shell in a recipe [1].
You can just write:
v1 := $(file <zfile1)
.ONESHELL:
a:
#echo "$(v1)"
You must have the .ONESHELL because otherwise each line of the recipe (after it expands into multiple lines) is considered a separate recipe line. Note that .ONESHELL is in effect for the entire make process so could cause other recipes to break if they rely on each line being invoked in a different shell.
Another option is to export the result into the environment, and use a shell variable like this:
export v1 := $(file <zfile1)
a:
#echo "$$v1"
There are probably better ways to do it but since you've only given us this example and not what you really want to do, that's about all we can say.
[1] There are times where it can be useful but if you have a sufficiently sophisticated requirement to need this you'll probably know how to use it.
I think you're making things too complicated.
Start by writing your recipes as proper self-contained shell scripts.
You can then either store the whole script in a file and run it from make, or you can include it directly in your makefile as a single logical line, as in the following:
a:
#v1=$$(< zfile1); \
echo $$v1
Note the need to "escape" the dollar sign by repeating it.
You could also use global make variables, depending on the actual logic of your real-world use.

How to perform a string replacement on a path in a Makefile?

I am trying to remove the path prefix. Here is a small example showing just the issue.
Makefile
dist_directory = ./dist
default: build
build: $(patsubst %.md, $(dist_directory)/%.html, $(wildcard *.md))
$(dist_directory)/%.html: %.md
#echo start
#echo $#
#echo ${$#//$(dist_directory)/}
#echo end
Create a file: touch stuff.md
Then build: make
The output is:
start
dist/stuff.html
end
The expected output is:
start
dist/stuff.html
/stuff.html
end
There are similar posts on Stack Exchange. However, they have not worked for me in a Makefile for some reason. I'm probably doing something wrong.
https://unix.stackexchange.com/questions/311758/remove-specific-word-in-variable
Remove a fixed prefix/suffix from a string in Bash
Remove substring matching pattern both in the beginning and the end of the variable
You have many issues here. The most fundamental one is that if you want to use shell variables you have to escape the dollar sign so that make doesn't interpret it. And, you can only use shell variable substitutions on shell variables, while $# is a make variable, so you need:
#foo='$#' ; echo $${foo//$(dist_directory)/}
The more subtle one is that make always uses /bin/sh (POSIX standard shell) when it invokes recipes, and the above syntax is specific to bash. One way around that would be to explicitly set SHELL := /bin/bash in your makefile to force make to use bash. Luckily that is not necessary because POSIX sh can also do this, as mentioned by Reda in another answer:
#foo='$#' ; echo $${###*/}
But even more, you don't need any of this because make sets the automatic variable $* to the part of the target that matches the stem (the %):
#echo $*.html
It also sets $(#F) to the filename part of the $# variable:
#echo $(#F)
ETA
If you want to do something very similar to your shell variable expansion using GNU make you can use:
#echo $(patsubst $(dist_directory)/%,%,$#)

Makefile: how to set a LOGFILE=`date +'test_%m.%d_%H.%M.%S.log'` variable only once?

I like to log output of test program to a log file with time stamp.
I created following Makefile, but it doesn't work. The "make" seems to calculate LOGFILE at the last moment as needed.
Makefile
LOGFILE=`date +'test_%m.%d_%H.%M.%S.log'`
export DLOG=$(LOGFILE)
test2:
echo DLOG=$$DLOG
echo DLOG=${DLOG}
sleep 2
echo DLOG=${DLOG}
make test2
echo DLOG=$DLOG
DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
echo DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
DLOG=test_10.22_10.28.04.log
sleep 2
echo DLOG=`date +'test_%m.%d_%H.%M.%S.log'`
DLOG=test_10.22_10.28.06.log
I like to find someway to have the "make" calculate the LOGFILE or DLOG variable only once and I can use the same value everywhere in the makefile. Is it possible?
This is because of the flavor of your variable.
The manual section in question is The Two Flavors of Variables.
Specifically
The first flavor of variable is a recursively expanded variable. Variables of this sort are defined by lines using ‘=’ (see Setting Variables) or by the define directive (see Defining Multi-Line Variables). The value you specify is installed verbatim; if it contains references to other variables, these references are expanded whenever this variable is substituted (in the course of expanding some other string). When this happens, it is called recursive expansion.
and
To avoid all the problems and inconveniences of recursively expanded variables, there is another flavor: simply expanded variables.
Simply expanded variables are defined by lines using ‘:=’ or ‘::=’ (see Setting Variables). Both forms are equivalent in GNU make; however only the ‘::=’ form is described by the POSIX standard (support for ‘::=’ was added to the POSIX standard in 2012, so older versions of make won’t accept this form either).
So you want to use := on either the LOGFILE assignment or on the DLOG assignment (or both).
You also need to use the make $(shell) function to have make execute the command instead of using backticks to make the shell (run for the recipe line) to do it.
LOGFILE=$(shell date +'test_%m.%d_%H.%M.%S.log')
export DLOG:=$(LOGFILE)
test2:
echo DLOG=$$DLOG
echo DLOG=${DLOG}
sleep 2
echo DLOG=${DLOG}
An important thing to note here is that this will cause make to run the command at make parse time and not at recipe execution time.
If you don't want that (because you may not run that target) or you want it to count recipe execution time then you need to do the command execution in the recipe and either use a single shell (via line-continuation or .ONESHELL) or you need to use $(eval) in the recipe to force make to expand the simply-expanded make-level variable only at recipe execution time.

Bash - String manipulation in Makefile

I've learned about string manipulation with bash, and more especially about substring replacement:
#! /bin/bash
VAR1="aaaa.bbbb.cccc"
VAR2="bbbb*"
echo ${VAR1%${VAR2}}
This bash script prints "aaaa.". I tried to include it in my makefile, but I can't make it work..
SHELL:=/bin/bash
VAR1="aaaa.bbbb.cccc"
VAR2="bbbb*"
all:
#echo $${VAR1%$${VAR2}}
This Makefile only prints a blank line.
I think I've misunderstood something, but can't figure out what. Any help would be really appreciated.
No need to put double quotes around VAR1 and VAR2. And you need to use export if you want to put VAR1 and VAR2 above all:
SHELL:=/bin/bash
export VAR1=aaaa.bbbb.cccc
export VAR2=bbbb*
all:
#echo $${VAR1%$${VAR2}}
The problems is that VAR1 and VAR2 are not shell variables, but Makefile variables.
To complicate matters further, each line in the Makefile recipe is executed in a separate shell process, which means the following naive attempt will fail:
all:
VAR1="aaaa.bbbb.cccc"
VAR2="bbbb*"
#echo $${VAR1%$${VAR2}}
since VAR1 is defined in one shell, VAR2 in another, and the echo in a third where neither variable is defined. You could use the following:
all:
#VAR1="aaaa.bbbb.cccc"; \
VAR2="bbbb*"; \
echo $${VAR1%$${VAR2}};
to have a single bash statement (all executed in one shell) split into multiple lines in the Makefile.
B̶a̶s̶h̶ ̶a̶n̶d̶ ̶M̶a̶k̶e̶ ̶d̶o̶ ̶n̶o̶t̶ ̶h̶a̶v̶e̶ ̶t̶h̶e̶ ̶s̶a̶m̶e̶ ̶s̶y̶n̶t̶a̶x̶.̶ ̶ ̶Y̶o̶u̶ ̶n̶e̶e̶d̶ ̶t̶o̶ You can use built-in make functions like (in this case) $(substr a,b,c)
See this

How to assign the output of a command to a Makefile variable

I need to execute some make rules conditionally, only if the Python installed is greater than a certain version (say 2.5).
I thought I could do something like executing:
python -c 'import sys; print int(sys.version_info >= (2,5))'
and then using the output ('1' if ok, '0' otherwise) in a ifeq make statement.
In a simple bash shell script it's just:
MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`
but that doesn't work in a Makefile.
Any suggestions? I could use any other sensible workaround to achieve this.
Use the Make shell builtin like in MY_VAR=$(shell echo whatever)
me#Zack:~$make
MY_VAR IS whatever
me#Zack:~$ cat Makefile
MY_VAR := $(shell echo whatever)
all:
#echo MY_VAR IS $(MY_VAR)
Beware of recipes like this
target:
MY_ID=$(GENERATE_ID);
echo $MY_ID;
It does two things wrong. The first line in the recipe is executed in a separate shell instance from the second line. The variable is lost in the meantime. Second thing wrong is that the $ is not escaped.
target:
MY_ID=$(GENERATE_ID); \
echo $$MY_ID;
Both problems have been fixed and the variable is useable. The backslash combines both lines to run in one single shell, hence the setting of the variable and the reading of the variable afterwords, works.
I realize the original post said how to get the results of a shell command into a MAKE variable, and this answer shows how to get it into a shell variable. But other readers may benefit.
One final improvement, if the consumer expects an "environment variable" to be set, then you have to export it.
my_shell_script
echo $MY_ID
would need this in the makefile
target:
export MY_ID=$(GENERATE_ID); \
./my_shell_script;
Hope that helps someone. In general, one should avoid doing any real work outside of recipes, because if someone use the makefile with '--dry-run' option, to only SEE what it will do, it won't have any undesirable side effects. Every $(shell) call is evaluated at compile time and some real work could accidentally be done. Better to leave the real work, like generating ids, to the inside of the recipes when possible.
Wrapping the assignment in an eval is working for me.
# dependency on .PHONY prevents Make from
# thinking there's `nothing to be done`
set_opts: .PHONY
$(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)
With GNU Make, you can use shell and eval to store, run, and assign output from arbitrary command line invocations. The difference between the example below and those which use := is the := assignment happens once (when it is encountered) and for all. Recursively expanded variables set with = are a bit more "lazy"; references to other variables remain until the variable itself is referenced, and the subsequent recursive expansion takes place each time the variable is referenced, which is desirable for making "consistent, callable, snippets". See the manual on setting variables for more info.
# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)
# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))
# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
#echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
$(SET_ID)
# This will now be a random number
#echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
$(SET_ID)
# This will now be a different random number
#echo $(MY_ID)
Here's a bit more complicated example with piping and variable assignment inside recipe:
getpodname:
# Getting pod name
#eval $$(minikube docker-env) ;\
$(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
echo $(PODNAME)
I'm writing an answer to increase visibility to the actual syntax that solves the problem. Unfortunately, what someone might see as trivial can become a very significant headache to someone looking for a simple answer to a reasonable question.
Put the following into the file "Makefile".
MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')
all:
#echo MY_VAR IS $(MY_VAR)
The behavior you would like to see is the following (assuming you have recent python installed).
make
MY_VAR IS 1
If you copy and paste the above text into the Makefile, will you get this? Probably not. You will probably get an error like what is reported here:
makefile:4: *** missing separator. Stop
Why: Because although I personally used a genuine tab, Stack Overflow (attempting to be helpful) converts my tab into a number of spaces. You, frustrated internet citizen, now copy this, thinking that you now have the same text that I used. The make command, now reads the spaces and finds that the "all" command is incorrectly formatted. So copy the above text, paste it, and then convert the whitespace before "#echo" to a tab, and this example should, at last, hopefully, work for you.
In the below example, I have stored the Makefile folder path to LOCAL_PKG_DIR and then use LOCAL_PKG_DIR variable in targets.
Makefile:
LOCAL_PKG_DIR := $(shell eval pwd)
.PHONY: print
print:
#echo $(LOCAL_PKG_DIR)
Terminal output:
$ make print
/home/amrit/folder
From the make manual
The shell assignment operator ‘!=’ can be used to execute a shell script and set a >variable to its output. This operator first evaluates the right-hand side, then passes >that result to the shell for execution. If the result of the execution ends in a >newline, that one newline is removed; all other newlines are replaced by spaces. The >resulting string is then placed into the named recursively-expanded variable. For >example:
hash != printf '\043'
file_list != find . -name '*.c'
source

Resources