In the manual:
The eval function is very special: it allows you to define new
makefile constructs that are not constant; which are the result of
evaluating other variables and functions. The argument to the eval
function is expanded, then the results of that expansion are parsed as
makefile syntax.
It’s important to realize that the eval argument is expanded twice;
first by the eval function, then the results of that expansion are
expanded again when they are parsed as makefile syntax. This means you
may need to provide extra levels of escaping for “$” characters when
using eval.
the "expanded twice" confuses me.
for example, i create a makefile :
define func
tmp = $(OBJPATH)/$(strip $1)
objs += $$(tmp)
$$(tmp) : $2
gcc $$^ -o $$#
endef
all : foo
$(eval $(call func, foo, 1.c))
how will the eval function be expanded ?
The easiest way to understand it is to replace the eval with info:
$(info $(call func, foo, 1.c))
That will display as output the result of the first expansion, so you can see what make will actually be parsing. You didn't provide the values for the OBJPATH variable, but if it was obj for example then in your case the first expansion (of the call function) results in:
tmp = obj/foo
objs += $(tmp)
$(tmp) : 1.c
gcc $^ -o $#
Then the make parser will evaluate this, and in the process it will expand it again, so things like $(tmp) are expanded.
This has been issue for me, but I found a nice workaround. In my case it was related to AWS docker login. I had in my shell script previously:
eval $(aws ecr get-login --region eu-west-1 --no-include-email --profile someprofile)
but when putting that into Makefile it didn't work. The workaround for this is to change the line into:
$$(aws ecr get-login --region eu-west-1 --no-include-email --profile someprofile)
Related
I'm often creating png files out of dot (graphviz format) files. The command to do so is the following:
$ dot my_graph.dot -o my_graph.png -Tpng
However, I would like to be able to have a shorter command format like $ make my_graph.dot to automatically generate my png file.
For the moment, I'm using a Makefile in which I've defined the following rule, but the recipe is only available in the directory containing the Makefile
%.eps: %.dot
dot $< -o $# -Teps
Is it possible to define custom implicit GNU Make recipes ? Which would allow the above recipe to be available system-wide
If not, what solution do you use to solve those kind of problem ?
Setup:
Fedora Linux with ZSH/Bash
You could define shell functions in your shell's startup files, e.g.
dotpng()
{
echo dot ${1%.dot}.dot -o ${1%.dot}.png -Tpng;
}
This function can be called like
dotpng my_graph.dot
or
dotpng my_graph
The code ${1%.dot}.dot strips .dot from the file name if present and appends it (again) to allow both my_graph.dot and my_graph as function argument.
Is it possible to define custom implicit GNU Make recipes ?
Not without modifying the source code of GNU Make.
If not, what solution do you use to solve those kind of problem ?
I wouldn't be a fan o modyfying the system globally, but you could do:
Create a file /usr/local/lib/make/myimplicitrules.make with the content
%.eps: %.dot
dot $< -o $# -Teps
Use include /usr/local/lib/make/myimplicitrules.make in your Makefile.
I would rather use a git submodule or similar to share common configuration between projects, rather than depending on global configuration. Depending on global environment will make your program hard to test and non-portable.
I would rather go with a shell function, something along:
mymake() {
make -f <(cat <<'EOF'
%.eps: %.dot
dot $< -o $# -Teps
EOF
) "$#"
}
mymake my_graph.dot
GNU Make lets you specify extra makefiles to read using the MAKEFILES
environment variable. Quoting from info '(make)MAKEFILES Variable':
the default goal is never taken from one of these makefiles (or any
makefile included by them) and it is not an error if the files listed
in 'MAKEFILES' are not found
if you are running 'make' without a specific makefile, a makefile
in 'MAKEFILES' can do useful things to help the built-in implicit
rules work better
As an example, with no makefile in the current directory and the
following .mk files in make's include path (e.g. via
MAKEFLAGS=--include-dir="$HOME"/.local/lib/make/) you can create
subdir gen/ and convert my_graph.dot or dot/my_graph.dot by
running:
MAKEFILES=dot.mk make gen/my_graph.png
To further save some typing it's tempting to add MAKEFILES=dot.mk
to a session environment but defining MAKEFILES in startup files
can make things completely nontransparent. For that reason I prefer
seeing MAKEFILES=… on the command line.
File: dot.mk
include common.mk
genDir ?= gen/
dotDir ?= dot/
dotFlags ?= $(if $(DEBUG),-v)
Tvariant ?= :cairo:cairo
vpath %.dot $(dotDir)
$(genDir)%.png $(genDir)%.svg $(genDir)%.eps : %.dot | $(genDir).
dot $(dotFlags) $< -o $# -T'$(patsubst .%,%,$(suffix $#))$(Tvariant)'
The included common.mk is where you'd store general definitions to
manage directory creation, diagnostics etc., e.g.
.PRECIOUS: %/. ## preempt 'unlink: ...: Is a directory'
%/. : ; $(if $(wildcard $#),,mkdir -p -- $(#D))
References:
?= = := … - info '(make)Reading Makefiles'
vpath - info '(make)Selective Search'
order-only prerequisites (e.g. | $(genDir).) - info '(make)Prerequisite Types'
.PRECIOUS - info '(make)Chained Rules'
I thought I understood that simply-expanded variables got their value once, when the Makefile is read -- but I've got confused:
var := $$RANDOM
echo:
#echo v1: $(var)
#echo v2: $(var)
With result
$ make
v1: 11478
v2: 24064
So how come the shell env var is referenced twice?
I see that var := $(shell echo $$RANDOM) don't do the second value assignment -- how is this machinery different?
Simply-expanded variables are MAKE constructs. Indeed, that variable is only expanded one time, by make. But, it expands into a shell variable:
var := $$RANDOM
now the value of the var variable in make is the static string $RANDOM and make will never expand it again. You can determine this by doing something like:
var := $(info expanding RANDOM)$$RANDOM
and you'll see it only prints expanding RANDOM one time.
But, this rule:
echo:
#echo v1: $(var)
#echo v2: $(var)
invokes shell two times and each time passing the static string $RANDOM, which the shell expands, each time the shell is invoked, and you get different answers. Basically, make is running:
/bin/sh -c 'echo v1: $RANDOM'
/bin/sh -c 'echo v2: $RANDOM'
If you change it to this:
var := $(shell echo $$RANDOM)
Here, make is invoking the shell, one time, and assigning the result of that shell command to the variable var. So after this, var contains the literal string 12345 or whatever the result is, not $RANDOM.
BTW, you should add:
SHELL := /bin/bash
to your makefile, because $RANDOM is a bash feature that is not available in the POSIX shell, and make always invokes /bin/sh by default: on some systems /bin/sh is the same thing as bash but on other systems, it isn't.
Hello I would like to ask how I can create bash variable in makefile.
Below code does work but variable "targets" keeps being empty. How I can actually create "targets" and "current_target" variables in makefile?. Problem is that I have to create this variables from actual bash commands.
SHELL=/bin/bash
...
MAIN_APPS = 01_prep 02_prep 03_prep 04_prep 05_prep
foo : .check_targets .check_segment
#for f in $(MAIN_APPS) ; do \
targets=$(expr $(last_target) + 1); \
echo $(targets) + 1; \
curent_target=$(expr "$(echo $(f) | head -c 2) + 0"); \
if [ $(targets) = $(current_target)]; then \
break; \
else \
make $(f); \
fi \
done
output:
make foo last_target="02" segm=KI
+ 1
What I want to do is to make targets from my makefile which actually starts from 01 to 05 if I pass an argument last_target="05"
You mix up between make variables and shell variables. And you did not consider that make expands the recipes before passing them to the shell, reason why you frequently need to use $$ instead of $. Or, for command expansion, `...` instead of $(...). You are also using double quotes where you shouldn't (expr "..."). Finally, you use make in your recipe, which is not a good idea. You should use $(MAKE) (see the How the MAKE Variable Works section of the GNU make manual for the details).
Try the following (not tested) maybe:
SHELL=/bin/bash
...
MAIN_APPS = 01_prep 02_prep 03_prep 04_prep 05_prep
foo : .check_targets .check_segment
#for f in $(MAIN_APPS) ; do \
targets=`expr "$(last_target)" + 1`; \
echo "$$targets + 1"; \
tmp=`echo "$$f" | head -c 2`; \
curent_target=`expr "$$tmp" + 0`; \
if [ "$$targets" = "$$current_target" ]; then \
break; \
else \
$(MAKE) "$$f"; \
fi \
done
But I am almost 100% convinced that you are trying to use make as a kind of scripting language instead of as a sophisticated build system. If I understand well you are trying to rebuild only a subset of your MAIN_APPS by specifying a last target number. You could probably achieve the same with a much more make-ish style:
TARGETS := $(shell printf '%02d_prep\n' $$(seq $(last_target)))
.PHONY: foo
foo: $(TARGETS) .check_targets .check_segment
And that's all, no complex recipe for foo. Nothing at all (except, of course, the rules to build .check_targets, .check_segment and all xx_prep targets, that you do not show).
Explanation: I used the $(shell ...) make function and a small shell script to initialize make variable TARGETS with 01_prep 02_prep ... up to $(last_target)_prep. As noted in the comments this shell script does not even require bash and would work with the default make shell (sh). So, if you do not have other good reasons to use bash, get rid of the SHELL=/bin/bash line and your Makefile will be a bit more portable.
Then, I declared foo as phony because you probably don't have a file named foo and, in case you have one, you probably want make to build foo anyway.
Finally, I declared $(TARGETS), .check_targets, and .check_segment as prerequisites of foo. If you ask make to build foo and any of these prerequisites is out-of-date, make will build it using the rules you declared for it.
This is far more make-ish because it clearly tells make what depends on what instead of hiding it in a recipe. And make needs this to do its job correctly, which consist in comparing last modification dates of files to decide if something must be rebuilt or not. It also has extra benefits like, for instance, exploiting the parallelism of your computer to build several xx_prep in parallel if it is possible.
This is my first question on Stackoverflow so forgive me if I ask anything ridiculous :D.
Problem:
Suppose I want to compile a program that is in the directory "my dir/" with a space in it. Say the pathname of the program is "my dir/test.c".
Here is the sample makefile that I was trying out:
CC = gcc
DIR = my\ dir
$(DIR)/test.out: $(DIR)/test.c
# $(CC) $< -o $#
$(CC) $(DIR)/test.c -o $(DIR)/test.out
As you can see that in the last line(line-5) I have written the pathnames of the source and the output files directly as written in the prerequisite and the target, respectively. Doing this works fine because it yields the command:gcc my\ dir/test.c -o my\ dir/test.outwhich a syntactically correct way of passing filenames(with spaces) to gcc or any other shell command.
The second last line(line-4) is where the problem is(commented line). I've used automatic variables $# (Target) and $< (First and the only Prerequisite) to produce the filename arguments for gcc which I expected to bemy\ dir/test.out and my\ dir/test.c, respectively. But here, for some reason, the produced filenames are my dir/test.out and my dir/test.c and hence the yielded command is: gcc my dir/test.c -o my dir/test.out
Now here, gcc considers my and dir/test.c as different two different input filenames and the command generates errors.
Here is a screenshot of the generated error output when I uncomment line-4 and comment line-5 of the above Makefile:
My Question:
Is there any way to retain those backslashes even by using automatic variables the way I did? Or is there any alternative that will achieve the same goal as using automatic variables and also solve my problem? Because flexibility is important here.
Thanks in advance for your help!!!
Use double or single quotes for the automatic variables.
Use single quotes, if you want to avoid shell expansion of the values referenced by the automatic variables:
$(DIR)/test.out: $(DIR)/test.c
$(CC) '$<' -o '$#'
Double quotes allow shell expansion. For example, if there was a dollar sign in DIR:
DIR := $$my\ dir
then "$#" would expand to "$my dir", and the shell would interpret $my as variable.
I have a environment variable set with name $MY_ENV_VARIABLE.
How do I use this variable inside my makefile to (for example) include some source files?
LOCAL_SRC_FILES = $(MY_ENV_VARIABLE)/libDEMO.so
Something like above doesn't seem to work.
Note: in my case this is needed for building with the Android NDK but I guess this applies to make in general.
Just to add some information...
The syntax to access the environment variable in make is like other variables in make...
#export the variable. e.g. in the terminal,
export MY_ENV_VARIABLE="hello world"
...
#in the makefile (replace before call)
echo $(MY_ENV_VARIABLE)
This performs the substitution before executing the commmand. If you instead, want the substitution to happen during the command execution, you need to escape the $ (For example, echo $MY_ENV_VARIABLE is incorrect and will attempt to substitute the variable M in make, and append it to Y_ENV_VARIABLE)...
#in the makefile (replace during call)
echo $$MY_ENV_VARIABLE
Make sure you exported the variable from your shell. Running:
echo $MY_ENV_VARIABLE
shows you whether it's set in your shell. But to know whether you've exported it so that subshells and other sub-commands (like make) can see it try running:
env | grep MY_ENV_VARIABLE
If it's not there, be sure to run export MY_ENV_VARIABLE before running make.
That's all you need to do: make automatically imports all environment variables as make variables when it starts up.
I just had a similar issue (under Cygwin):
Running echo $OSTYPE on the shell prints the value, but
running env | grep OSTYPE doesn't give any output.
As I can't guarantee that this variable is exported on all machines I want to run that makefile on, I used the following to get the variable from within the makefile:
OSTYPE = $(shell echo $$OSTYPE)
Which of course can also be used within a condition like the following:
ifeq ($(shell echo $$OSTYPE),cygwin)
# ...do something...
else
# ...do something else...
endif
EDIT:
Some things I found after experimenting with the info from jozxyqk's answer, all from within the makefile:
If I run #echo $$OSTYPE or #echo "$$OSTYPE" in a recipe, the variable is successfully expanded into cygwin.
However, using that in a condition like ifeq ($$OSTYPE,cygwin) or ifeq ("$$OSTYPE","cygwin") doesn't expand it.
Thus it is logical that first setting a variable like TEST = "$$OSTYPE" will lead to echo $(TEST) printing cygwin (the expansion is done by the echo call) but that doesn't work in a condition - ifeq ($(TEST),cygwin) is false.