Reputation: 625
Consider the following user-defined function:
define func =
if [ -f $(1) ]; then \
printf "'%s' is a file\n" '$(1)'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
endef
all:
$(call func,foo)
This will output the following:
$ make
if [ -f foo ]; then printf "'%s' is a file\n" 'foo'; printf "This is a rel
atively long command that"; printf " won't fit on one line\n"; fi
For readability, I would like make
to print the command on multiple lines,
as written in the Makefile. How do I accomplish this?
The following works the way I want, but does not allow the parameterized function:
filename := foo
.PHONY: foo
foo:
if [ -f $(filename) ]; then \
printf "'%s' is a file\n" '$(filename)'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
Output:
$ make foo
if [ -f foo ]; then \
printf "'%s' is a file\n" 'foo'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
My obvious first instinct was to escape the backslashes:
define func =
if [ -f $(1) ]; then \\
printf "'%s' is a file\n" '$(1)'; \\
printf "This is a relatively long command that"; \\
printf " won't fit on one line\n"; \\
fi
endef
Output:
$ make
if [ -f foo ]; then \\
printf "'%s' is a file\n" 'foo'; \\
printf "This is a relatively long command that"; \\
printf " won't fit on one line\n"; \\
fi
/bin/sh: \: command not found
'foo' is a file
/bin/sh: line 1: \: command not found
This is a relatively long command that/bin/sh: line 2: \: command not found
won't fit on one line
/bin/sh: line 3: \: command not found
make: *** [Makefile:11: all] Error 127
Okay, so why not try \\\
?
$ make
if [ -f foo ]; then \ printf "'%s' is a file\n" 'foo'; \ printf "This is a
relatively long command that"; \ printf " won't fit on one line\n"; \ fi
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [Makefile:11: all] Error 1
Interesting. Let's go for four...
$ make
if [ -f foo ]; then \\\\
printf "'%s' is a file\n" 'foo'; \\\\
printf "This is a relatively long command that"; \\\\
printf " won't fit on one line\n"; \\\\
fi
/bin/sh: \\: command not found
'foo' is a file
/bin/sh: line 1: \\: command not found
This is a relatively long command that/bin/sh: line 2: \\: command not found
won't fit on one line
/bin/sh: line 3: \\: command not found
make: *** [Makefile:11: all] Error 127
Now we're back to where we were last time.
This is the only thing that seems to work:
define func =
if [ -f $(1) ]; then #\\
printf "'%s' is a file\n" '$(1)'; #\\
printf "This is a relatively long command that"; #\\
printf " won't fit on one line\n"; #\\
fi
endef
Output:
$ make
if [ -f foo ]; then #\\
printf "'%s' is a file\n" 'foo'; #\\
printf "This is a relatively long command that"; #\\
printf " won't fit on one line\n"; #\\
fi
But man, that looks ugly, and it feels hackish. There's got to be a better way to do this. Or am I just going about this the wrong way in the first place?
It seems to me that make
is just confused by the magic that happens when
escaping newlines within a recipe. The lines printed to the terminal during
execution do not match what the shell sees. Should this be considered a bug?
I am using GNU Make 4.2.1 on Cygwin.
To clarify: make
normally gives special treatment to trailing backslashes within a recipe. They do not indicate line continuation, as they do elsewhere. Instead, they indicate that multiple recipe lines are to be treated as a single command, and they are passed to the shell intact.
When not in a recipe, but defining a variable, this special treatment does not apply. The lines are simply joined, as in Example 1. This is expected.
I would expect that a double backslash would be translated to a single literal backslash in the variable, but instead both backslashes are retained. When the variable is expanded in the recipe, I would expect make
to behave as if the recipe had \\
at the end of every line. If this were the case, each line would be executed separately. But as you can see from Examples 3 and 6, the lines are executed together.
The point is, it is possible to get magic backslash parsing from the expansion of a variable. The problem is the mechanics of this behavior are inconsistent and confusing.
Upvotes: 5
Views: 3686
Reputation: 625
I found a nicer (albeit still hacky) solution that seems to work:
define func =
if [ -f $(1) ]; then $\\
printf "'%s' is a file\n" '$(1)'; $\\
printf "This is a relatively long command that"; $\\
printf " won't fit on one line\n"; $\\
fi
endef
all:
@echo vvvvvvvv
$(call func,foo)
@echo ^^^^^^^^
Output:
$ make
vvvvvvvv
if [ -f foo ]; then \
printf "'%s' is a file\n" 'foo'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
'foo' is a file
This is a relatively long command that won't fit on one line
^^^^^^^^
I think this is how it works:
During the first scan for line continuations, the $
is ignored, so the
parser sees \\
, assumes the backslash is escaped, and leaves the lines
intact.
When expanding the function, $\
is recognized as a variable. Assuming
you haven't actually assigned a variable named \
, this expands to
nothing.
Now we are left with a single backslash at the end of the line, which is treated as if it were literally typed into the recipe.
Upvotes: 1
Reputation: 15493
Urk! This is due to make's noddy parser.
Recipes are stored as-is. They are expanded as-and-when make is about to call the shell. Once the entire recipe has been expanded, the first line is passed to a shell. If that command succeeds, then the second is run. Wash, rinse, repeat. Backslashes at the end of line are preserved, with the effect that the following line is passed at the same time as the first.
In recursive variable definition however, backslashes at the end of line are removed as the definition is read
define oneline =
aa \
bb \
cc
endef
$(error [$(value oneline)])
which gives
$ make
Makefile:9: *** [ aa bb cc]. Stop.
We need to massage make's syntax so that a variable expands to exactly this text:
target:
if [ -f foo ]; then \
printf "'%s' is a file\n" 'foo'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
which we then simply feed to make via something like
$(eval ${var})
Just replace each newline with a space-backslash-newline-tab quad using $(subst)
.
A function to do that:
empty :=
tab := ${empty} # Trailing tab
define \n :=
endef
stanza = $(subst ${\n}, \${\n}${tab})
To check it works:
define func =
if [ -f $1 ]; then
printf "'%s' is a file\n" '$1';
printf "This is a relatively long command that";
printf " won't fit on one line\n";
fi
endef
$(error [$(call stanza,$(call func,foo))])
giving:
Makefile:23: *** [ if [ -f foo ]; then \
printf "'%s' is a file\n" 'foo'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi]. Stop.
Note that the definition of func
now has no annotation at the end of its lines.
define \n :=
endef
empty :=
tab := ${empty} # Trailing tab
stanza = $(subst ${\n}, \${\n}${tab},$1)
define func =
if [ -f $1 ]; then
printf "'%s' is a file\n" '$1';
printf "This is a relatively long command that";
printf " won't fit on one line\n";
fi
endef
define rule =
target:
echo vvv
$(call stanza,$(call func,foo))
echo ^^^
endef
$(eval ${rule})
Leading to
$ touch foo; make -R --warn
echo vvv
vvv
if [ -f foo ]; then \
printf "'%s' is a file\n" 'foo'; \
printf "This is a relatively long command that"; \
printf " won't fit on one line\n"; \
fi
'foo' is a file
This is a relatively long command that won't fit on one line
echo ^^^
^^^
An interesting academic exercise. Still can't put literal backslashes in either :)
Upvotes: 3
Reputation: 2898
define newline :=
$(strip)
$(strip)
endef
define func =
if [ -f $(1) ]; then \$(newline)\
printf "'%s' is a file\n" '$(1)'; \$(newline)\
printf "This is a relatively long command that"; \$(newline)\
printf " won't fit on one line\n"; \$(newline)\
fi
endef
func2 = if [ -f $(1) ]; then \$(newline) printf "'%s' is a file\n" '$(1)'; \$(newline) printf "This is a relatively long command that"; \$(newline) printf " won't fit on one line\n"; \$(newline)fi
all:
$(call func,foo)
@echo --------------
$(call func2,foo)
The first one seems to be space-stripped. The second looks nice at the output but.. oh well, seems like being stuck between a rock and a hard place :/
Upvotes: 1