Using tab character in Makefile $(info ...) - makefile

I would like to add tabs specifically to the stdout text in a makefile. I've tried placing tabs directly into the text as well as using a \t command and neither work.
I could always achieve the same with spaces but it is just annoying.
$(info test text with tabs)
tabs are completely removed on output
$(info \ttest text)
becomes : "\ttest text" on output
Is there a reasonable way to do this? I am aware I can do this with
#echo -e "\tNow the tabs work!"
However I was trying to get rid of using echos.
Thanks
edit :
I was wrong about using spaces instead -- they are just deleted on the output just like inserted tabs are.

make strips strings before using them as arguments of various commands or statements. So, let's first define a variable containing... nothing, use it to define a variable containing a tab and use that variable when needed:
$ cat Makefile
NULL :=
TAB := $(NULL)<tab>$(NULL)
all:
$(info X$(TAB)X)
$ make
X X
make: 'all' is up to date.
I used <tab> to show where the tab character must go. Use the real tab character instead, of course. Note that NULL alone is enough for what you want:
$ cat Makefile
NULL :=
all:
$(info $(NULL)<tab>X)
$ make
X
But having a TAB variable is maybe more convenient.
Note also that make variables can have very strange names. If you prefer naming the variable \t instead of TAB, you can:
$ cat Makefile
NULL :=
\t := $(NULL)<tab>$(NULL)
all:
$(info X$(\t)X)
$ make
X X
make: 'all' is up to date.
You can even define \n for end-of-line:
$ cat Makefile
NULL :=
\t := $(NULL)<tab>$(NULL)
define \n
endef
all:
$(info X$(\t)X$(\n)Y$(\t)Y)
$ make
X X
Y Y
make: 'all' is up to date.
The reason why we need two empty lines in the define-endef can be found in the GNU make manual:
The value in an ordinary assignment cannot contain a newline; but the
newlines that separate the lines of the value in a define become part
of the variable’s value (except for the final newline which precedes
the endef and is not considered part of the value).

Related

How to print leading whitespace with $(info)

I want to print indented output with $(info ...)
Make ignores whitespace after info, so the following doesn't work:
$(info There is a space and then a tab before this text)
It will print:
There is a space and then a tab before this text
So right now I'm doing this (which isn't exactly what I want, but close enough):
$(info - Here there is a space, a dash, and then a tab)
It will print:
- Here there is a space, a dash, and then a tab
Is it possible to print the following?:
Is it possible to print me?
I know I can echo(1), but I've seen considerable performance loss by doing that, so I'd prefer to use $(info), even if I have to print a leading dash.
You can put something that expands as the empty string on front of your spaces:
NULL :=
$(info $(NULL) X)
The $(NULL) token plays the role of delimiter between info and the string parameter.
Instead of NULL you could even use a macro that would expand as the specified number of spaces. It would play the role of the NULL variable in the previous example, plus add the requested number of spaces.
$ cat Makefile
info-spaces = $(subst -, ,$(subst $(eval) ,,$(wordlist 1,$(1),\
$(foreach n,0 1 2 3 4 5 6 7 8 9,- - - - - - - - - -))))
.PHONY: all
N ?= 0
all:
$(info 0123456789)
$(info $(call info-spaces,$(N))X)
#:
$ make
0123456789
X
$ make N=10
0123456789
X
$ make N=5
0123456789
X
Note: you can add up to 100 spaces, no more. Modify the foreach call to increase or reduce this maximum.
Explanations:
The $(foreach ...) call creates a list of 100 words, all equal to -.
$(wordlist ...) returns the N first words where N is the parameter passed to the info-spaces macro (If N=0 it returns the empty string, and if N>100 it returns 100 words).
$(subst $(eval) ,,...) removes the spaces from the list of words, leaving one single word with min(N,100) times the - character. Here $(eval) does absolutely nothing, it's just a way to use a space as first parameter of subst. It is the same as $(NULL) in the first solution. We could probably use something else as long as it expands to the empty string.
Finally, the outermost subst substitutes each - character by one space and returns a string of min(N,100) spaces.
Note: We could also use $(shell printf '%$(1)s' "") instead of this complicated stuff but if you have performance issues spawning a new shell for each info call is probably not a good idea.
If $(info)'s text parameter is empty it will output nothing and
expand to nothing, making it usable as a zero-width character, e.g.
$(info $(info) indented text)
\t := $(info) $(info)
which outputs indented text and defines \t as a tab character.

how to record exact recipe line in Makefile

I'm looking to record the exact commands used to build artifacts within a makefile. I'd like this to be stored in a file for later consumption. I am running into issues due to quotes. Basically, what I want is:
define record_and_run_recipe
#echo '$(2)' > $1
$2
endef
all:
$(call record_and_run_recipe,out.cmd,\
#echo 'hello world "$$1"' )
cat out.cmd
I would like this to output (exactly)
#echo 'hello world "$1"'
Of course, the quotes end up matching with the quotes in the expansion of the variable, and this messes everything up. (I get #echo hello world instead). Bash doesn't like '\'' either, so I can't simply do $(2:'=\'). I also seem to have issues with , characters...
I'm not looking to debug the entire makefile, just dump a couple of recipes. I'm wondering if anyone has a robust way of accomplishing this.
As I said in my comment above, you can use GNU make's $(info ...) function. It's not exactly clear from your example above what you want to do; why are you trying to put the output into a file, then cat it? Is that important?
If you can't use info, the canonical way to handle quoting in shell is to surround the string with single quotes, then replace every single quote with '\''. You say "bash doesn't like" that, but I don't know what that means. Normally you'd do something like:
define record_and_run_recipe
#echo '$(subst ','\'',$2)' > $1
$2
endef
As far as commas you will absolutely have a problem with commas if you want to use the $(call ...) function. The only way to avoid that is to put the string into a variable, like:
output = foo, bar
... $(call blah,$(output))
to "hide" the comma from call.

GNU Make: shell cat file yields contents without newlines

Makefile:
.PHONY: all
SHELL:=/usr/bin/env bash
all:
$(eval x=$(shell cat file))
#echo "$x"
File:
foo
bar
Output:
foo bar
How do I get the contents of the file into the make variable without losing the newlines?
You can't do this with shell, as described in its documentation.
If you have a sufficiently new version of GNU make, you can use the file function however.
Make converts newlines from shell outputs to spaces (see here):
The shell function performs the same function that backquotes (‘`’)
perform in most shells: it does command expansion. This means that it
takes as an argument a shell command and evaluates to the output of
the command. The only processing make does on the result is to convert
each newline (or carriage-return / newline pair) to a single space. If
there is a trailing (carriage-return and) newline it will simply be
removed.
So, you cannot preserve spaces from the $(shell) command directly. That being said, make does allow multiline variables using define -- but beware, attempting to use such variables is problematic. Consider:
define x
foo
bar
endef
all:
#echo "$x"
Make expands the $x in place, and you end up with:
all:
#echo " foo
bar"
(where the newline is considered the end of the recipe line..).
Depending on what you want this for, you may be able to get around this is using a bash variable:
all:
#x=$$(cat file); \
echo $$x
Or potentially storing your output in a file, and referencing that when necessary.
all:
eval (cat file >> output.txt)
cat output.txt
(and yes, the last one is convoluted as written, but I'm not sure what you're trying to do, and this allows the output of your command to be persistent across recipe lines).
If the file contents are ensured not to contain any binary data, and if you're willing to to extra processing each time you access the variable, then you could:
foo:=$(shell cat file | tr '\n' '\1')
all:
#echo "$(shell echo "$(foo)" | tr '\1' '\n')"
Note that you cannot use nulls \0, and I suspect that probably means there's a buffer overflow bug in my copy of Make.

Functions "filter" and "filter-out" do not remove newlines

From the docs:
$(filter PATTERN...,TEXT)
Returns all whitespace-separated words in TEXT that do match any
of the PATTERN words, removing any words that do not match. The
patterns are written using %, just like the patterns used in the
patsubst function above.
$(filter-out PATTERN...,TEXT)
Returns all whitespace-separated words in TEXT that do not match
any of the PATTERN words, removing the words that do match one or
more. This is the exact opposite of the filter function.
What does "whitespace - separated words" mean?
Well, we think we know. At-least, when assuming a "normal" locale.
So, for a "C" ("POSIX") locale we have:
"space"
Define characters to be classified as white-space characters.
In the POSIX locale, at a minimum, the <space>, <form-feed>, <newline>, <carriage-return>, <tab>, and <vertical-tab> shall be included.
Now, a makefile, like this:
define foo
a
b
endef
all :
echo '$(filter a b,$(foo))'
Running, I get:
echo ''
Let's try the filter-out case:
define foo
a
b
endef
all :
-echo '$(filter-out a b,$(foo))'
Running, I get:
echo 'a
/bin/sh: 1: Syntax error: Unterminated quoted string
makefile:8: recipe for target 'all' failed
make: [all] Error 2 (ignored)
b'
/bin/sh: 1: Syntax error: Unterminated quoted string
makefile:8: recipe for target 'all' failed
make: [all] Error 2 (ignored)
So, clearly Make does not handle here properly a legitimate white-space (newline).
Right?
The thing is you need to escape the newline characters in your foo variable or pass its value to a proper place.
The same as writing any embeded shell script inside the makefile, you need to escape every new line. $(foo) will simply copy-paste a content from foo multi-line variable. Hence, for your given foo value, below recipe will raise a syntax error:
test1:
echo '$(foo)'
Similar thing is for your filter-out example. I'm not sure why filter function gives no syntax error.
1st solution. As mentioned above, escaping a newline character is one of the solutions:
define foo
a\
b
endef
test1:
echo '$(foo)'
The benefit is that you don't need to change your all recipe.
2nd solution. In most cases, you probably don't want to change/edit/parse your multi-line variable. Then you'll need to use a shell function that will directly invoke a shell command instead of pasting a script into the makefile contents and then parsing it. Our test recipe will look like this:
define foo
a
b
endef
test2:
echo $(shell echo '$(foo)')
Note that output newlines are being converted to single spaces by shell function.

Can GNU make handle filenames with spaces?

I have a directory containing several files, some of which have spaces in their names:
Test workspace/
Another directory/
file1.ext
file2.ext
demo 2012-03-23.odp
I use GNU's $(wildcard) command on this directory, and then iterate over the result using $(foreach), printing everything out. Here's the code:
FOO := $(wildcard *)
$(info FOO = $(FOO))
$(foreach PLACE,$(FOO),$(info PLACE = $(PLACE)))
Here's what I would expect to see printed out:
Test workspace
Another directory
file1.ext
file2.ext
demo 2012-03-23.odp
Here's what I would actually get:
Test
workspace
Another
directory
file1.ext
file2.ext
demo
2012-03-23.odp
The latter is obviously of no use to me. The documentation for $(wildcard) flat-out states that it returns a "space-separated list of names" but completely fails to acknowledge the huge problems this raises. Nor does the documentation for $(foreach).
Is it possible to work around this? If so, how? Renaming every file and directory to remove the spaces is not an option.
The bug #712 suggests that make does not handle names with spaces. Nowhere, never.
I found a blog post saying it's partially implemented by escaping the spaces with \ (\\ seems to be typo or formatting artefact), but:
It does not work in any functions except $(wildcard).
It does not work when expanding lists of names from variables, which includes the special variables $?, $^ and $+ as well as any user-defined variable. Which in turn means that while $(wildcard) will match correct files, you won't be able to interpret the result anyway.
So with explicit or very simple pattern rules you can get it to work, but beyond that you are out of luck. You'll have to look for some other build system that does support spaces. I am not sure whether jam/bjam does, scons, waf, ant, nant and msbuild all should work.
GNU Make does very poorly with space-separated filenames.
Spaces are used as delimiters in word list all over the place.
This blog post summarizes the situation well, but WARNING: it incorrectly uses \\ rather than \
target: some\ file some\ other\ file
some\ file some\ other\ file:
echo done
You can also use variables, so this would also work
VAR := some\ file some\ other\ file
target: $(VAR)
$(VAR):
echo done
Only the wildcard function recognizes the escaping, so you can't do anything fancy without lots of pain.
But don't forget that your shell uses spaces as delimiters too.
If I wanted to change the echo done to touch $#, I'd have to add slash to escape it for my shell.
VAR := some\ file
target: $(VAR)
$(VAR):
touch $(subst \,\\,$#)
or, more likely, use quotes
VAR := some\ file some\ other\ file
target: $(VAR)
$(VAR):
touch '$#'
In the end, if you want to avoid a lot of pain, both in GNU make, and in your shell, don't put spaces in your filenames. If you do, hopefully the limited capabilities of Make will be sufficient.
This method will also allow use of listed file names such as $? and user variables that are lists of files.
The best way to deal with spaces in Make is to substitute spaces for other characters.
s+ = $(subst \ ,+,$1)
+s = $(subst +,\ ,$1)
$(call s+,foo bar): $(call s+,bar baz) $(call s+,bar\ baz2)
# Will also shows list of dependencies with spaces.
#echo Making $(call +s,$#) from $(call +s,$?)
$(call s+,bar\ baz):
#echo Making $(call +s,$#)
$(call s+,bar\ baz2):
#echo Making $(call +s,$#)
Outputs
Making bar baz
Making bar baz2
Making foo bar from bar baz bar baz2
You can then safely manipulate lists of file names using all the GNU Make
functions. Just be sure to remove the +'s before using these names in a rule.
SRCS := a\ b.c c\ d.c e\ f.c
SRCS := $(call s+,$(SRCS))
# Can manipulate list with substituted spaces
OBJS := $(SRCS:.c=.o)
# Rule that has object files as dependencies.
exampleRule:$(call +s,$(OBJS))
# You can now use the list of OBJS (spaces are converted back).
#echo Object files: $(call +s,$(OBJS))
a\ b.o:
# a b.o rule commands go here...
#echo in rule: a b.o
c\ d.o:
e\ f.o:
Outputs
in rule: a b.o
Object files: a b.o c d.o e f.o
This info is all from the blog that everyone else was posting.
Most people seem to be recommending using no spaces in paths or using Windows 8.3 paths, but if you must use spaces, escaping spaces and substitution works.
If you are willing to rely on your shell a bit more, this gives a list which can hold names with spaces just fine:
$(shell find | sed 's: :\\ :g')
The original question said that "renaming is not an option", yet many commenters have pointed out that renaming is pretty much the only way Make can handle spaces. I suggest a middle way: Use Make to temporarily rename the files and then rename them back. This gives you all the power of Make with implicit rules and other goodness, but doesn't mess up your file naming scheme.
# Make cannot handle spaces in filenames, so temporarily rename them
nospaces:
rename -v 's/ /%20/g' *\ *
# After Make is done, rename files back to having spaces
yesspaces:
rename -v 's/%20/ /g' *%20*
You could call these targets by hand with make nospaces and make yesspaces, or you can have other targets depends on them. For example, you might want to have a "push" target which makes sure to put the spaces back in filenames before syncing files back with a server:
# Put spaces back in filenames before uploading
push: yesspaces
git push
[Sidenote: I tried the answer which suggested using +s and s+ but it made my Makefile harder to read and debug. I gave up on it when it gave me guff over implicit rules likes: %.wav : %.ogg ; oggdec "$<".]

Resources