Home Articles Columns Ask Mr. Make GNU Make escaping: a walk on the wild side
|
GNU Make escaping: a walk on the wild side |
|
|
Written by John Graham-Cumming
|
|
Sunday, 08 July 2007 |
Sometimes you find yourself needing to insert a 'special' character in
a Makefile: perhaps you need a newline inside a $(error) message, or a
space character in a $(subst)
or a comma as the argument to a GNU Make function. Those three simple
things can be frustratingly hard in GNU Make; this article takes you
through simple GNU Make syntax that removes the frustration.
GNU Make's escaping rules
GNU
Make's handling of 'tab' as the start of a command is a legendary
language feature, but some other special characters can trip you up.
GNU Make's handling of $, %, ?, *, [, ~, \, and # are all special.
Every GNU Make user is familiar with $ for starting a macro reference. It's possible to write $(macro) or ${macro} to get the value of macro and if the macro name is a single character (such as a) then the parens can be dropped and the short hand $a used.
To get a literal $ you write $$. So to define a macro containing a single $ symbol you'd write:
dollar := $$
The % character rears its ugly head in three different places: in the vpath directive, in a $(patsubst) and in a pattern or static-pattern rule.
Escaping % is not as simple as $, but it only needs to be done in the three situation above, and the same rules apply for each.
The three rules for % escaping are:
1. % can be escaped with a single \ character (i.e. \% becomes a literal %.
2. If you need to put a literal \ in front of a % (i.e. you want the \ to not escape the %) then escape it with \ (i.e. \\% becomes a literal \ followed by a % character that will be used for the pattern match).
3. Don't worry about escaping \ anywhere else in a pattern, it will be treated as literal (i.e. \hello is \hello).
?, *, [, and ] get treated specially when they appear in a filename. A Makefile that does
*.c:
@command
Will actually search for all .c
files in the current directory and define a rule for each. The target
(and the same applies for prerequisites and files mentioned in the include
directive) are globbed if they contain a wildcard character. The
globbing characters have the same meaning as in the Bourne shell.
The ~ character is also handled specially in filenames and is expanded to the home directory of the current user.
All of those special filename characters can be escaped with a \. The following Makefile defines a rule for the file named literally *.c.
\*.c:
@command
As well as the escaping function mentioned in this section, the \ can also be used as an continuation character at the end of a line:
all: \
prerequisite \
something else
@command
The # character is used to start a comment and it can be made literal with a \ escape:
pound := \#
I just want a newline!
GNU
Make does its best to insulate you from the newline character. You
can't escape a newline, there's no syntax for special characters (e.g.
you can't write \n), and even the $(shell) function strips newlines from the returned value.
But you can define a macro that contains a newline using the define syntax:
define newline
endef
(Note that there are two blanks lines in the definition above). With that definition $(newline) will expand to a newline and it can be used to format an error message nicely:
$(error This is an error message$(newline)with two lines)
Because of GNU Make's rather liberal macro naming rules it's possible to actually define a macro called \n so if you line to keep things looking familiar you can do
define \n
endef
$(error This is an error message $(\n)with two lines)
(More on special macro names in The Twilight Zone below)
Function arguments: space and comma
A
problem that many GNU Make users run into is that handling of spaces
and commas in GNU Make function arguments. Consider the following use
of $(subst) (which takes three arguments separated by commas: the from text, the to text and the string to change):
spaces-to-commas = $(subst ,,,$1)
It defines a function called spaces-to-commas
to convert all spaces in its argument to commas (might be handy for
making a CSV file for example). Unfortunately, it doesn't work.
It doesn't work for two reasons:
1. The first argument of the $(subst)
is a space. Unfortunately, GNU Make strips all leading and trailing
whitespace around function arguments. In this case, the first argument
is interpreted as an empty string.
2. The second argument is a
comma. Unfortunately, GNU Make cannot distinguish between the commas
used for argument separators and the comma as an argument. In
addition, there's no way to 'escape' the comma.
Fortunately,
it's possible to work around this by knowning that GNU Make does the
whitespace stripping, and separation of arguments before it doesn any
expansion of the arguments themselves. So, if we can define a macro
containing a space and a macro containing a comma it will be possible
to write:
spaces-to-commas = $(subst $(space),$(comma),$1)
to get the desired effect.
Defining a macro containing a comma is easy:
comma := ,
but
space is a bit harder. There are a couple of ways of defining a
space. The first uses the fact that whenever you append to a macro
(using +=) a space is inserted before the appended text.
space :=
space +=
Another
way is to first define a macro that contains nothing and then use it to
surround the space so that it doesn't get stripped by GNU Make
blank :=
space := $(blank) $(blank)
This technique can also be used to get a literal tab character into a macro
blank :=
tab := $(blank) $(blank)
(The whitespace in the definition of tab was created by hitting the tab key).
Much in the way I defined $(\n) above it's possible to define specially named space and comma macros. GNU Make's rules are liberal enough to allow us to do:
, := ,
blank :=
space := $(blank) $(blank)
$(space) := $(space)
The first line defines a macro called , (which can be used as $(,) or even $,) containing a comma.
The last three lines define a macro called space containing a space character and then use it to define a macro name (that's right, it's name is a space character) containing a space.
With that definition it's possible to write $( ) or even $ to get a space character.
Using those definitions the spaces-to-commas function can be written:
spaces-to-commas = $(subst $( ),$(,),$1)
which I think it very clear.
The Twilight Zone
If
you read this column regularly, you'll know that I'm not going to let a
chance to really mess with GNU Make go by without pushing it to its
limits. Here are some other interesting macro definitions:
# Defining the $= or $(=) macro which has the value =
equals := =
$(equals) := =
# Define the $# or $(#) macro which has the value #
hash := \#
$(hash) := \#
# Define the $: or $(:) macro which has the value :
colon := :
$(colon) := :
# Define the $($$) macro which has the value $
dollar := $$
$(dollar) := $$
These probably aren't useful, but if you really want to push GNU Make syntax to its limits try this:
+:=+
Yes, that defines a variable called + containing a +.
Paul's warning
When I first blogged about some of this (see http://www.jgc.org/blog/labels/gnu%20make.html) I received the following warning from Paul Smith (the maintainer of GNU Make):
I
don't recommend using a space as a variable name. I've toyed with the
idea of allowing some special syntactic sugar for user-defined
functions, that would allow you to omit the $(call ...) prefix and just
invoke the user-defined function directly, like:
$(my-func arg1, arg2)
Obviously
the way this works is that if make detects a space in the variable
name, it would assume that it is a user-defined function. If this were
to happen I'd probaby make whitespace in variable names illegal, just
to cut down on confusion and bizarre misbehaviors.
OK, I'll be careful.
John Graham-Cumming is Mr Make and Chief Scientist at Electric Cloud, Inc., a Silicon Valley start-up that speeds up builds by 10-20x using cluster technology.
Trackback(0)
|
|
Recent Issues of CM Journal
Whitepaper SpotlightStay up to date with Configuration Management and Application Lifecycle
Management technology products and services by browsing our featured white
papers below:
See all the Featured Whitepapers>>
|
Tool Spotlight
|
|
|
CollabNet
CollabNet Subversion is an enterprise-ready distribution of Subversion® that includes, in one package,...
Read More
|
|
|
AccuRev
AccuRev is a best-of-breed, process-centric software configuration
management (SCM) solution for...
Read More
|
|
|
|
|
|
|
.PHONY: all
define nl
endef
TEST:= a$(nl)b
all:
echo $(TEST)
> gmake
echo a
a
b
gmake: b: Command not found
gmake: *** [all] Error 127