make subst not working as I want it to be - makefile

I am trying to use makefile "subst" function to split the text. Here is my code.
$(subst :,\nvalue:,peter:value:2)
what I want to do is that is to split from the first of occurrence of the ':'. but it also splits on the second occurrence. Can someone help me how to solve that issue. the result she be similar to below.
peter\nvalue:value:2

As the documentation states, the subst function replaces every occurrence of the original text with the new text.
If you want only the first one you'll have to get a lot more fancy, if you want to do it completely within make and you don't know anything about the text before or after the first colon. Something like this should work:
VAL := peter:value:2
NEW := $(patsubst $(firstword $(subst :, ,$(VAL)))%,$(firstword $(subst :, ,$(VAL)))\nvalue%,$(VAL))
There might be a simpler way to do it; I'll have to think about it.

If you don't mind having make invoke a shell to do this, you could do:
VAL := peter:value:2
NEW := $(shell echo "$(VAL)" | sed -e 's/:/\\nvalue:/')
I'm not sure if your \n is a literal "\n" or a newline. I'm assuming the former. This means the \n in the sed expression needs to be escaped as \\n.
Another way of doing it completely within make (without any shell invocations):
empty:=
space:= $(empty) $(empty)
VAL := peter:value:2
VL := $(subst :, ,$(VAL))
NEW := $(firstword $(VL))\nvalue:$(subst $(space),:,$(wordlist 2,$(words $(VL)),$(VL)))
Caveat: This method will give incorrect results if the original value contains whitespace.

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.

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

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).

GNU Make Convert Spaces to Colons

Given a colon-delimited list of paths, getting a space-delimited list with GNU Make is straightforward:
CPATHS := /usr/bin/foo:/usr/bin/baz:/usr/bin/baz
SPATHS := $(subst :, ,$(CPATHS))
However, I couldn't find a nice way to go the opposite direction. The following hack does work (at least if sed is installed) but I'm pretty sure there will be a nicer way to solve this just using Make's internal functions.
SPATHS := /usr/bin/foo /usr/bin/baz /usr/bin/baz
CPATHS := $(shell echo $(SPATHS) > tmp; sed 's/ \+/:/g' tmp; rm tmp)
The only tricky part here is to define a literal space:
space := $(subst ,, )
SPATHS := /usr/bin/foo /usr/bin/baz /usr/bin/baz
CPATHS := $(subst $(space),:,$(SPATHS))
The shortest way to get a literal space would be via $() $(). Thus:
$(subst $() $(),:,$(CPATHS))
Or, for brevity:
_=$() $()
$(subst $(_),:,$(CPATHS))
It is perhaps an interesting curiosity that the same trick works with cmake's macros, i.e. that ${} is a separator but introduces no whitespace by itself.

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