Makefile :*** unterminated call to function `foreach': missing `)'. Makefile - makefile

I am getting above error when i execute my target. I have terminated the foreach with a closing brace but still it persists.
$(addprefix c_lr, $(RLRG_LRGS)):c_lr%:
$(foreach i, $(days), \
if [ "$(day)" = "$(i)" ] ; then \
$(ECHO) This is a valid . ; \
fi ; \
)

On the first recipe line, there is a space after the backslash:
$(foreach i, $(days), \
^---space here
A backslash only escapes a newline if the newline comes directly after the backslash. If there's a space after the backslash, then it's not an escaped newline, it's just a backslash followed by a space.
I highly recommend using an editor (such as GNU Emacs) that has a good Makefile editing mode with the ability to highlight questionable whitespace. This makes it easy to find mistakes like that.

Related

How to write fancy-indented multi-line brace expansion in Bash?

I'm dealing with a line such :
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1","$DEST_DIR2", ..., "$DEST_DIRN"}
This line is quite long. I want to cut it so its width will fit into a 80 columns line. I tried to escape an end of line with a backslash, but space alignement breaks the expansion :
$ echo "ha"{a,b,\
> c}
ha{a,b, c}
You could use this disgusting hack.
echo "ha"{a,b,\
> ` `c}
It opens a subshell with nothing in it, but gets processed before the expansion so the expansion just sees an empty space
This is the normal behaviour. From the Bash reference manual:
3.5.1 Brace expansion
Brace expansion is a mechanism by which arbitrary strings may be
generated. This mechanism is similar to filename expansion (see
Filename Expansion), but the filenames generated need not exist.
Patterns to be brace expanded take the form of an optional preamble, followed by either a series of comma-separated strings or a sequence
expression between a pair of braces, followed by an optional
postscript. The preamble is prefixed to each string contained within
the braces, and the postscript is then appended to each resulting
string, expanding left to right.
Brace expansion does not allow spaces in between elements that get placed between \ and the next element in the following line.
And why? Because it gets removed when being processed:
3.1.2.1 Escape Character
A non-quoted backslash ‘\’ is the Bash escape character. It preserves
the literal value of the next character that follows, with the
exception of newline. If a \newline pair appears, and the backslash
itself is not quoted, the \newline is treated as a line continuation
(that is, it is removed from the input stream and effectively
ignored).
So when you say
something + \ + <new line> + another_thing
Bash converts it into
something + another_thing
What can you do, then?
Add a backslash and then start writing from the very beginning on the next line:
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1",\
"$DEST_DIR2",\
...,\
"$DEST_DIRN"}
Some examples
When you say:
$ echo "ha"{a,b\
> c}
ha{a,b c}
And then move the arrow up you'll see this is the command that was performed:
$ echo "ha"{a,b c}
So just say:
$ echo "ha"{a,b\
> c}
haa habc
And you'll see this when moving up:
$ echo "ha"{a,b,c}
Another example:
$ cat touch_files.sh
touch X{1,\
2,3}
$ bash touch_files.sh
$ ls X*
X1 X2 X3
Thus I accepted #123's answer, here's the one I choosed :
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR1","$DEST_DIR2"}
mkdir -p "$DEST_ROOT_PATH/"{"$DEST_DIR3","$DEST_DIR4"}
There are not a lot of destination directories here, so I think it's a good balance between the fancy-and-disgusting hack and the frustrating backslash which breaks the indentation.
I would do it is follows (though it only addresses your particular task of creating multiple directories and doesn't answer the question as stated in the title):
for d in \
"$DEST_DIR1" \
"$DEST_DIR2" \
... \
"$DEST_DIRn" \
;
do
mkdir -p "$DEST_ROOT_PATH/$d"
done
The advantage of this approach is that maintaining the list is a little easier.
In general, you should stop sticking to syntactic sugar when you notice that it starts causing inconveniences.

Backslash escaping and removal in makefile

Given this - rather quaint - makefile, which admittedly was specifically designed to clearly illustrate a host of inconsistencies, by trying to find the "rule" how Make handles quoting and backslashes in makefile.
So, the makefile:
prereq = \
\: \
\\: \
\\\: \
\\\\: \
\\\\\: \
\\\\\\:
all: % : $(prereq)
#echo '$+'
%:;
Running, I get:
: \\: \: \\\\: \\: \\\\\\:
Now, let's take a look.
We basically had 6 prerequisites:
\:
\\:
\\\:
\\\\:
\\\\\:
\\\\\\:
Which Make interprets respectively, as:
:
\\:
\:
\\\\:
\\:
\\\\\\:
Do you see a pattern here? Well sure there is.
For any even-number of backslashes, make take all of the backslashes literally, and so \\: remains \\:. \\\\: remains \\\\:. Finally: \\\\\\: remains \\\\\\:. No escaping or any interpretation is done at-all.
As for any odd-number of backslashes, make removes an half of them. To be exact, its an half in the sense of integer-division, where Make removes 1 for 3, 2 for 5. A small twist, for 1 backslash, Make removes 1 backslash. So \: becomes :, \\\: becomes \: and \\\\\: becomes \\:.
Now, sure it must be useful, that make bothers to build all this "theory" for removing backslashes.
But, what is it?

Make: How to escape spaces in $(addprefix)?

Here's what I am trying to do:
EXTRA_INCLUDE_PATHS = ../dir1/path with spaces/ \
../dir2/other path with spaces/
CPPFLAGS += $(addprefix -I,$(EXTRA_INCLUDE_PATHS))
I want to get “-I../dir1/path with spaces/ […]”, but I get instead: “-I../dir/path -Iwith -Ispaces/ […]”.
How do I espace spaces in addprefix? I've tried doing this trick, but it produces same result:
space =
space +=
#produces “-Isome -Ipath”
CPPFLAGS += $(addprefix -I,some$(space)path)
Don't do it! As #MadScientist points out, you need to eschew all space-containing filenames in a makefile unless you want to be very sad. A space is a list separator (including in lists of targets), and there is just no way around that. Use symbolic links in your file system to avoid them! (mklink on windows, or use cygwin make which understands cygwin symbolic links).
That said, in the current case (where you are not defining a list of targets, but merely a list of include dirs), you could use a single character to represent a space, only translating it right at the end.
EXTRA_INCLUDE_PATHS = ../dir1/path|with|spaces/ ../dir2/other|path|with|spaces/
CPPFLAGS += $(subst |, ,$(patsubst %,-I'%',${EXTRA_INCLUDE_PATHS}))
Check the result:
$(error [${CPPFLAGS}])
gives Makefile:3: *** [-I'../dir1/path with spaces/' -I'../dir2/other path with spaces/']. Stop.. Here, $CPPFLAGS is in sh format—the spaces are quoted with ' so that the compiler sees each -I as a single parameter. make simply does not have this level of quoting.
If all your include dirs start with the same character sequence, you can exploit this for use with the substitute command:
CPPFLAGS += $(subst ..,-I ..,$(EXTRA_INCLUDE_PATHS))
Check the result with:
$(info ${CPPFLAGS})
(this is a slightly different (maybe more elegant) version of #bobbogo's answer)
If you escape spaces with a backslash (\ ), you can replace the escaped space by a pipe symbol (|), add the prefix and then undo the replace operation:
EXTRA_INCLUDE_PATHS = ../dir1/path\ with\ spaces/ ../dir2/other\ path\ with spaces/
CPPFLAGS += $(subst |,\ ,$(addprefix -I,$(subst \ ,|,${EXTRA_INCLUDE_PATHS})))

How can I split a shell command over multiple lines when using an IF statement?

How can I split a command over multiple lines in the shell, when the command is part of an if statement?
This works:
if ! fab --fabfile=.deploy/fabfile.py --forward-agent --disable-known-hosts deploy:$target; then rc=1
fi
This doesn't work:
# does not work:
if ! fab --fabfile=.deploy/fabfile.py \
--forward-agent \
--disable-known-hosts deploy:$target; then
rc=1
fi
Instead of the whole command executing, I get:
./script.sh: line 73: --forward-agent: command not found
More importantly, what is missing from my understanding of Bash that will help me understand this and similar issues in the future?
The line-continuation will fail if you have whitespace (spaces or tab characters¹) after the backslash and before the newline. With no such whitespace, your example works fine for me:
$ cat test.sh
if ! fab --fabfile=.deploy/fabfile.py \
--forward-agent \
--disable-known-hosts deploy:$target; then
echo failed
else
echo succeeded
fi
$ alias fab=true; . ./test.sh
succeeded
$ alias fab=false; . ./test.sh
failed
Some detail promoted from the comments: the line-continuation backslash in the shell is not really a special case; it is simply an instance of the general rule that a backslash "quotes" the immediately-following character, preventing any special treatment it would normally be subject to. In this case, the next character is a newline, and the special treatment being prevented is terminating the command. Normally, a quoted character winds up included literally in the command; a backslashed newline is instead deleted entirely. But otherwise, the mechanism is the same. Most importantly, the backslash only quotes the immediately-following character; if that character is a space or tab, you just get a literal space or tab; the backslash will have no effect on a subsequent newline.
¹ or carriage returns, for that matter, as Czechnology points out. The POSIX shell does not get along with Windows-formatted text files, not even in WSL. Or Cygwin, but at least their Bash port has added an igncr option that you can set -o to make it carriage-return-tolerant.
For Windows/WSL/Cygwin etc users:
Make sure that your line endings are standard Unix line feeds, i.e. \n (LF) only.
Using Windows line endings \r\n (CRLF) line endings will break the command line break.
This is because having \ at the end of a line with Windows line ending translates to
\ \r \n.
As Mark correctly explains above:
The line-continuation will fail if you have whitespace after the backslash and before the newline.
This includes not just space () or tabs (\t) but also the carriage return (\r).

How do I properly escape data for a Makefile?

I'm dynamically generating config.mk with a bash script which will be used by a Makefile. The file is constructed with:
cat > config.mk <<CFG
SOMEVAR := $value_from_bash1
ANOTHER := $value_from_bash2
CFG
How do I ensure that the generated file really contains the contents of $value_from_bash*, and not something expanded / interpreted? I probably need to escape $ to $$ and \ to \\, but are there other characters that needs to be escaped? Perhaps there is a special literal assignment I've not heard of?
Spaces seems to be troublesome too:
$ ls -1
a b
a
$ cat Makefile
f := a b
default_target:
echo "$(firstword $(wildcard ${f}))"
$ make
a
If I use f := a\ b it works (using quotes like f := 'a b' did not work either, makefile just treats it as a regular character)
Okay, it turned out that Makefiles need little escaping for itself, but the commands which are executed by the shell interpreter need to be escaped.
Characters which have a special meaning in Makefile and that need to be escaped are:
sharp (#, comment) becomes \#
dollar ($, begin of variable) becomes $$
Newlines cannot be inserted in a variable, but to avoid breaking the rest of the Makefile, prepend it with a backslash so the line break will be ignored.
Too bad a backslash itself cannot be escaped (\\ will still be \\ and not \ as you might expect). This makes it not possible to put a literal slash on the end of a string as it will either eat the newline or the hash of a following comment. A space can be put on the end of the line, but that'll also be put in the variable itself.
The recipe itself is interpreted as a shell command, without any fancy escaping, so you've to escape data yourself, just imagine that you're writing a shellscript and inserting the variables from other files. The strategy here would be putting the variables between single quotes and escape only ' with '\'' (close the string, insert a literal ' and start a new string). Example: mornin' all becomes 'morning'\'' all' which is equivalent to "morning' all".
The firstword+wildcard issue is caused by the fact that filenames with spaces in them are treated as separate filenames by firstword. Furthermore, wildcard expands escapes using \ so x\ y is matches as one word, x y and not two words.
It seems that the full answer to this question is found nowhere on the internet, so I finally sat down and figured it out for the Windows case.
Specifically, the "Windows case" refers to file names that are valid in Windows, meaning that they do not contain the characters \, /, *, ?, ", ^, <, >, |, or line breaks. It also means \ and / are both considered valid directory separators for the purposes of Make.
An example will clear it up better than I can explain. Basically, if you are trying to match this file path:
Child\a$b {'}(a.o#$#,&+=~`),[].c
Then you have to write these rules:
all: Child\\a$$b\\\ \\\ {'}(a.o\#$$#,&+=~`),[].o
%.o: %.c
$(CC) '$(subst ','"'"',$(subst \,,$(subst \\,/,$+)))'
Stare at it for a long time and it'll sort of start making some remote sense.
This works in my MSYS2 environment, so I presume it is correct.
I don't see how that makefile can work as you say. A pattern rule cannot be the default.
You're missing a `$` in `$(wildcard ...)`, so I think you haven't posted what you're really testing.
You should escape newlines too.

Resources