Stripping leading whitespace from arguments passed to user defined functions - makefile

How can I strip leading whitespace from parameters passed to user defined functions with gnu-make?
For example:
define FOO
# --- some build rules
# run the built target
/home/user/dir/bin/$(1)
endef
The following call works correctly, since there is no leading whitespace:
$(eval $(call FOO,my_test ) )
Unfortunately the following fails, because $(1) has a leadning space
$(eval $(call FOO, my_test ) )
^
Additional space to 'prettier' formatting
this ends up expanding to:
/home/user/dir/bin/ $(1)
^
Leading whitespace
How can I strip leading whitespace from arguments passed to user defined functions?
Is it bad form to do this or should I simply assume that arguments are passed without leading whitespace?

Use the strip function:
define FOO
# ...
/home/user/dir/bin/$(strip $(1))
endef
For details, see Text Functions. Because of this, and to avoid using strip everywhere, it is best to avoid spaces after commas when passing arguments to functions.

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.

"%" in $(wildcard) not expanded?

I want to encode the the rule "to make <name>.done, you need all files of the pattern <name>.needed.*. I've attempted to write this with this Makefile:
%.done: $(wildcard %.needed.*)
cat $^ > $#
Yet when I run touch foo.needed.bar && make foo.done, all I get is
cat > foo.done
It appears the % inside $(wildcard) is being interpreted as a literal "%". How can I get it expanded to the right value ("foo" in this case)?
The % is just a placeholder for "any string" in pattern matching. It has no special meaning in the wildcard function and is interpreted literally.
You might attempt using $* instead (which would expand to the stem of the filename), but unfortunately it won't work either:
%.done: $(wildcard $*.needed.*)
The reason it doesn't work is that the automatic variables ($* is one of them) are not available for use in the dependency list.
The workaround is to request a secondary expansion for the target:
.SECONDEXPANSION:
%.done: $$(wildcard $$*.needed.*)
This will prompt GNU Make to go over the rule a second time after processing the Makefile as usual, expanding any escaped variables that weren't expanded the first time around. The second time around, the automatic variable have their appropriate values.

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.

Using ifdef and ifndef directives

I'm trying to check whether a variable is defined using ifndef/ifdef, but I keep getting a not found error from the execution. I'm using GNU Make 3.81, and here is a snippet of what I have:
all: _images
$(call clean, .)
$(call compile, .)
#$(OPENER) *.pdf &
_images:
$(call clean, "images")
$(call compile, "images")
define clean
#rm -f ${1}/*.log ${1}/*.aux ${1}/*.pdf
endef
define compile
ifdef ${1}
dir = ${1}
else
dir = .
endif
ifdef ${2}
outdir = ${2}
else
outdir = ${1}
endif
#$(COMPILER) -output-directory ${outdir} ${dir}/*.tex
endef
And the exact error:
$ make
ifdef "images"
/bin/sh: 1: ifdef: not found
make: *** [_images] Error 127
Edit:
Considering Barmar comments, here goes the conclusions:
The contents of a define are shell command lines, not make directives;
to break lines inside commands within a define block, the linebreak must be escaped -- with \;
also, each block corresponding to one-liner commands is executed separately, each in a different shell execution, which means that, defining local variables won't work if the intention is to access the variable value in the next one-liner block.
Thanks tripleee for the nice work around.
You can combine the shell's facilities with Make's to get a fairly succinct definition.
define compile
#dir="${1}"; outdir="${2}"; outdir=$${outdir:-"$dir"}; \
$(COMPILER) -output-directory "$${outdir}" "$${dir:-.}/*.tex
The double-dollar is an escape which passes a single dollar sign to the shell. The construct ${variable:-value} returns the value of $variable unless it is unset or empty, in which case it returns value. Because ${1} and ${2} are replaced by static strings before the shell evaluates this expression, we have to take the roundabout route of assigning them to variables before examining them.
This also demonstrates how to combine two "one-liners" into a single shell invocation. The semicolon is a statement terminator (basically equivalent to a newline) and the sequence of a backslash and a newline causes the next line to be merged with the current line into a single "logical line".
This is complex enough that I would recommend you omit the leading # but I left it in just to show where it belongs. If you want silent operation, once you have it properly debugged, run with make -s.

Resources