how to create local variable in a rule recipe in makefile? - makefile

how to create local variable in a Makefile recipe in which only it is assignable and also just used there only ?

The recipe is executed by the shell, usually line by line. So you'll have to use your shell definition of variables, use escape if your shell uses $ for variables, and use escapes so that the recipe is logically one line. For instance on Unix, you can do:
target: <dependencies>
v=42 ; \
echo $$v

You can use so-called target-specific variables:
foo: bar = 1
foo:
#echo $(bar)
Or, if they are not flexible enough for you, consider using eval.
eval creates/manipulates global variables, so to avoid name collisions, I'd use a prefix for the variables you create with it:
foo:
$(eval _local_bar = 1)
#echo $(_local_bar)

Related

Export variable for a target in POSIX Make

In GNU Make you can export a variable for a target:
foo: export X=42
foo:
echo $$X
# Call several more commands that use $X.
Is there a way to do this in portable POSIX Make? So far, I've found two ways. The first is to basically merge all commands into one:
foo:
export X=42; \
echo $$X; \
# Call several more commands that use $X.
This is bad because now everything is bundled together. The second is to call $(MAKE):
foo:
$(MAKE) foo_ X=42
foo_:
echo $$X
# Call several more commands that use $X.
But this has an extra call to make again. Is there a better way?
The simplest solution is probably to set the variable on the command line by invoking make with:
make X=42
This way:
The make X variable is defined and set to 42, even if it is set to another value in the Makefile.
The shell environment variable X is defined and set to 42 for all recipes.
If you cannot use this (for instance because it is make that computes the value) the recursive make solution is probably the best option:
ifeq ($(X),)
X := <some-make-magic>
all:
$(MAKE) X=$(X) all
else
all:
<recipe-that-uses-X-environment-variable>
endif

How to comment a line within a Makefile define directive?

I want to comment one or more line(s) within a define directive in a Makefile so as the line is ignored when the directive is expanded. The goal is to place the commented line as a hint for the users of my Makefile to show an example of what could be into the define directive. The directive is expanded into a target.
In other words, I want that Makefile
define ECHO_FOO =
# #echo foo
endef
all:
#echo Before call
$(ECHO_FOO)
#echo After call
.PHONY: all
to have the same behavior than this one :
define ECHO_FOO =
endef
all:
#echo Before call
$(ECHO_FOO)
#echo After call
.PHONY: all
The issue is that the first Makefile gives me the following error :
process_begin: CreateProcess(NULL, ##echo foo, ...) failed.
make (e=2): The system cannot find the file specified.
Makefile:6: recipe for target 'all' failed
make: *** [all] Error 2
The GNU make:Makefile contents page states that :
Within a define directive, comments are not ignored during the definition of the variable, but rather kept intact in the value of the variable. When the variable is expanded they will either be treated as make comments or as recipe text, depending on the context in which the variable is evaluated.
But this doesn't explain in which specific case the # symbol is treated as a make comment or as a recipe text (which seems to be the problem I meet).
Can someone tell me how to have the # symbol treated as a comment mark in a define function ?
I have already tried all of the following lines with the idea of escaping the # symbol or changing the indentation but none of them gave me a correct output :
##echo foo
##echo foo
###echo foo
###echo foo
\##echo foo
\##echo foo
/##echo foo
/##echo foo
I'm running MinGW make 3.82 on Windows but I have already tried other implementations of make v3.82.90 and 4.1.
There's no way to do what you're asking for directly. The contents of the variable are expanded in a recipe context, so no matter what the variable expands to it will be considered part of the recipe and whatever characters are there will be passed to the shell.
Note you can use : in UNIX shells as well as Windows command.com, because : is the shell no-op operator. You have to add a space after it though otherwise it will try to run the command :echo which is not a valid command. However, further note that the shell will still expand the line! This means that if you use backquotes etc. then those still are expanded. Also note that since it's a statement, semicolon will stop it. So for example:
define ECHO_FOO
: echo hi `echo there 1>&2` ; echo bye
endef
all: ; #$(ECHO_FOO)
Here, the hi won't be printed because the echo command is not run, but the backticks are still expanded so there will be printed (to stderr) and the semicolon ends the "no-op" command so bye will also be printed.
If your commands are simple enough then : will work, but if they're that simple one wonders why you're using define...
Another option is just to override the variable, rather than comment it out:
define ECHO_FOO =
#echo foo
endef
ECHO_FOO =
ETA:
In the comments you affirm that the command is simple. I don't quite know what you mean by could be expanded by the final user or why that makes a difference.
But what I was alluding to is that if you have a simple command you can just write:
ECHO_FOO = echo hi
and not use define. define is only needed for complicated commands: really it's only required for commands that contain un-escaped newlines.
And, if you write:
ECHO_FOO =# echo hi
then you ARE commenting out the content of the variable using make comments, not shell comments, so it will work everywhere.
On Windows, you can use : as a comment character. The traditional comment keyword in MS-DOS is REM (as in "remark").

Why doesn't gnu make's "override" pass through to sub-makes?

Note: This question was originally posted as a rant by a now-deleted user, but there was a valid question behind the rant; this is my attempt to provide an answer.
Given the Makefile:
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
export foo
all::
#echo outer: foo is "$(foo)"
#$(MAKE)
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
The expectation is that export foo will cause make to export the value defined in the override declaration. But it doesn't:
$ make -s foo=original
outer: foo is replaced
inner: foo is original
The expectation is probably reasonable, but it turns out that this is not the way Gnu make works. It could well be that the make documentation could be improved to clarify the process, but the hints all seem to be there.
How variables get their values
A variable can be set by the programmer in three ways:
On the command line with a var=value command-line argument
Explicitly in the make file
From the environment
The above list is the normal priority order; the first definition found in the list "wins". However, you can use the override directive to swap the priorities of the first two methods. (You can also use the -e flag to make to swap the priorities of the last two methods. The -e flag is required by Posix, but its use is discouraged and it does not interact will with override.)
How variables are passed to sub-makes
make is very similar to a shell in that the environment is used to pass variable values. If a variable is marked as exported, then its value is placed into the environment for any processes initiated by make, including sub-makes. As with the shell, a variable is marked as exported if its definition came from the environment or if it is explicitly marked as exported with the export directive. Variables are also exported if they were set on the command line.
However, there is another mechanism by which variables on the command-line are passed to subprocesses: the MAKEFLAGS exported variable.. MAKEFLAGS contains (most) command-line options as well as all of the command-line variable overrides. If make finds MAKEFLAGS in the environment, it merges the settings in that variable with the ones actually specified on its command line. That means that command-line variable settings in a make will also take priority in a sub-make.
Since command-line variable settings are passed through the MAKEFLAGS variable, they are not subject to any changes in the makefile. A makefile can unexport or override a variable set on the command-line, but that will only affect the value (or presence) of the variable in the environment; it does not remove or change the value from MAKEFLAGS.
Resolution
So if the intent is to override (or modify) a command-line variable both in the make itself and in the environment of sub-makes, it is necessary to use both an override and an explicit modification of MAKEFLAGS. (As explained in the make manual, MAKEFLAGS is actually recursively composed using the MAKEOVERRIDES variable, so we actually modify that variable.)
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
MAKEOVERRIDES += foo=replaced
all::
#echo outer: foo is "$(foo)"
#$(MAKE) -s
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
And now we get the expected result:
$ make -s foo=original
outer: foo is replaced
inner: foo is replaced
Real-life application: dealing with whitespace
The primary intention of overrides is to allow the makefile to append words to a variable possibly provided on the command line. The example provided in the gnu make manual is insisting that CFLAGS always includes the -g flag, even if it were specified on the make command line:
override CFLAGS += -g
Passing the append through to a sub-make needs a little caution; in particular, the obvious:
MAKEOVERRIDES += CFLAGS=$(CFLAGS) # Don't do this
won't work because the whitespace inside the CFLAGS variable will not be escaped when it is added to MAKEFLAGS; the result will be that MAKEFLAGS will look something like this:
-- CFLAGS=-O3 CFLAGS=-O3 -g
instead of the desired
-- CFLAGS=-O3 CFLAGS=-O3\ -g
If the value assigned to CFLAGS on the command line included whitespace, the whitespace is escaped in MAKEFLAGS. The particular escaping mechanism used is not documented, and Posix only requires that there be some mechanism; apparently, Gnu make uses backslash. It would be possible to manually backslash escape the whitespace, resulting in something like this:
# Don't do this either
MAKEOVERRIDES += CFLAGS=$(subst $(space),\ ,$(CFLAGS))
(The definition and use of space is based on an example in the gnu make manual.)
But it is actually easier to just use an append assignment in MAKEOVERRIDES, which is undocumented but appears to work. It works on the command line, too.
override CFLAGS+=-g
MAKEOVERRIDES += CFLAGS+=-g
Important Note as of make v4.1: A bit of testing revealed that the above stanza will only work if CFLAGS (or some other variable) is actually set on the command-line. I reported this bug as Savannah issue 46013, with a very simple fix in the bug report. In the meantime, if you really want to do this, use the following workaround:
override CFLAGS+=-g
MAKEOVERRIDES += CFLAGS+=-g
# This line is necessary in case there were no command-line overrides.
# In effect, it produces a command-line override, although that value
# will not be passed on to sub-makes.
MAKEFLAGS += dummy=dummy
Update May 19, 2019: Today I was informed that a fix for the bug referenced above has been committed, so it should be fixed in the next gmake release.
First of all, I want to point out that your suggestion to add to MAKEOVERRIDES, is dangerous!
And SHOULD NEVER BE DONE!!
You simply turn a recursive variable into a simple one, you will always get false results, if recursive expansion is done.
I can not believe that you got up-voted for this clearly wrong "suggestion".
And note this:
You can not even fix it with a quoted assignment, like MAKEOVERRIDES += foo=$$(bar)!!!
But, let me return to the main point of your post.
And, with which, I couldn't disagree more.
One simple example would be, if you run the very same makefile, that you have:
This one is copied verbatim, from your post:
ifeq "$(MAKELEVEL)" "0"
# Override the command-line specification of "foo".
override foo=replaced
export foo
all::
#echo outer: foo is "$(foo)"
#$(MAKE) -s
else
# Variable 'foo' was "exported" from the top-level Makefile.
all::
#echo inner: foo is "$(foo)"
endif
And running in any modern version, 4.0 and up:
# Sub-make does NOT get the value from the root-Make's command-line.
# Instead, it "inherits" the value from the root-Make's definition in the Makefile.
$ make -s foo=original -e
outer: foo is replaced
inner: foo is replaced
Now, given your assertion above:
However, there is another mechanism by which variables on the command-line are passed to subprocesses: the [MAKEFLAGS exported variable.][3]. MAKEFLAGS contains (most) command-line options as well as all of the command-line variable overrides. If make finds MAKEFLAGS in the environment, it merges the settings in that variable with the ones actually specified on its command line. That means that command-line variable settings in a make will also take priority in a sub-make.
Since command-line variable settings are passed through the MAKEFLAGS variable, they are not subject to any changes in the makefile. A makefile can unexport or override a variable set on the command-line, but that will only affect the value (or presence) of the variable in the environment; it does not remove or change the value from MAKEFLAGS.
You should get:
outer: foo is replaced
inner: foo is original
In other words, we should get for the sub-make, the value defined on the command-line (original)!
Because, you said yourself:
A makefile can't unexport or override a variable set on the command-line.
So, here, when we empower the environment over the makefile, which means that the makefile has less "power" in the total scheme of things. Right?
Sure, for such a case, you assertion will hold even stronger.

Overwrite variable from makefile

In my makefile I have a variable FOO:
FOO = /path/to/bar
Is it possible to overwrite this variable during the makefile call? Somthing like the following:
FOO=/path/to/foo make all
Specify them as Var=Value before you specify the target, like make FOO=/path/to/foo all.
$ cat Makefile
Foo = asdf
all:
echo $(Foo)
$ make all
echo asdf
asdf
$ make Foo=bar all
echo bar
bar
The ways that variables get assigned values is specified in the How Variables Get Their Values section of the GNU make Manual.
Variables can get values in several different ways:
You can specify an overriding value when you run make. See Overriding Variables.
You can specify a value in the makefile, either with an assignment (see Setting Variables) or with a verbatim definition (see Defining Multi-Line Variables).
Variables in the environment become make variables. See Variables from the Environment.
Several automatic variables are given new values for each rule. Each of these has a single conventional use. See Automatic Variables.
Several variables have constant initial values. See Variables Used by Implicit Rules.
So, as Colonel Thirty Two indicates, you can override variables set in the makefile on the command line.
You can also, if you expect this to be a variable that you want to set persistently, use the ?= assignment and then environment values for that variable will be used.

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

Resources