Get word before the last word inside GNU makefile - makefile

I need to extract the word before the last from $(MAKEFILE_LIST).
So far I could not come up with anything better than this kind of monstrosity:
LIST := a b c
LAST_WORD_INDEX = $(words $(LIST))
BEFORE_LAST := $(word $(shell echo $(LAST_WORD_INDEX) - 1 | bc),$(LIST))
$(info word before last is $(BEFORE_LAST))
When I run it:
word before last is b
make: *** No targets. Stop.
The result is correct, but is there more elegant and sane way to achieve the same?

$(words ${LIST}) will give you the index of the last-but-one word if you prepend an element to LIST.
BEFORE_LAST := $(word $(words ${LIST}),1st ${LIST})
Notice that 1st in there.

I ended up using GMSL library, which makes things quite a bit more coherent:
include gmsl-1.1.6/gmsl
$(lastword $(call chop,$(MAKEFILE_LIST)))

Related

Repeat element from list

In a makefile is there any way to specifiy the same element of a list (I think this is the correct term) more than once? For example, having a a list with 3 files A.txt B.txt C.txt, I'd like to create targets based on these elements that would be contained within a directory where the pattern would have to be repeated twice: A/A-rambo B/B-rambo C/C-rambo.
I tried:
TXT = A.txt B.txt C.txt
DIR := $(patsubst %.txt,%,$(TXT))
OUT := $(patsubst %,%/%-rambo,$(DIR))
$(info $(OUT))
but this prints A/%-rambo B/%-rambo C/%-rambo, where the second %is not being replaced.
And so does OUT := $(patsubst %,$(addsuffix /%-rambo,%),$(DIR))
Make cannot handle a pattern rule with two wildcards in the target name, even if they're required to have the same value.
There's more than one way to get the effect you want, none ideal. I'd suggest generating a rule for each txt file:
define template
$(1)/$(1)-rambo: $(1).txt
#echo building $$# from $$^
endef
STEMS := A B C
$(foreach x,$(STEMS),$(eval $(call template,$(x))))
Try:
OUT := $(foreach T,$(TXT),$(T:.txt=)/$(T:.txt=-rambo))

remove patterned prefix from list in gnu make

If I have a list of files:
files := xx_foo1.c yy_foo2.c zz_bar1.c aa_bb_bar2.c
Is there any way of removing everything up to the last underscore from the list to get foo1.c foo2.c bar1.c bar2.c?
I was looking into using patsubst, but I would need two%'s -- one for the first part to be ignored, and one for the last part to be kept.
It can be done but it's a little gross. You want something like this:
final := $(foreach F,$(files),$(word $(words $(subst _, ,$F)),$(subst _, ,$F)))
This says, for each element in files we convert the _ to a space, now we can use our per-word functions to manipulate it: extract the last word in the list of words.
ETA
ReAl points out below that this can be simplified using lastword:
final := $(foreach F,$(files),$(lastword $(subst _, ,$F)))
As I see it you are using the underscore as separating character between hierarchical names. GNUmake is well equipped to work with such a scheme if the character is /: file name functions.
So your example should simply boil down to
$(notdir $(subst _,/,$(files))
Use the external program — sed — and enjoy all its power
files := xx_foo1.c yy_foo2.c zz_bar1.c aa_bb_bar2.c
f := `echo $(files) | sed -e "s/[[:alnum:]]*_//g"`
all:
echo $(f)

How to know if a makefile variable is a string of char or numbers?

It probably sounds very elementary but I am unable to find a way to classify a makefile variable into text or number. My pseudocode is like this:
ifeq ($N, 'numeric')
CFLAGS+=-D$N
endif
How to do this? I am using the GNU Make (in cygwin/Windows). I read the make.pdf that comes with it but could not find a way.
Thanks in Advance
EDIT: adopted a suggestion by bobbogo that does not depend on the number of characters to purge.
I assume you use GNU make. Here is a make-only solution, without calling the shell. For performance reasons, depending on your use of it, it can be preferable. Moreover, it does not depend on which shell make uses. Last but not least, it uses recursion and I like recursion:
define PURGE
$(if $(2),$(call PURGE,$(subst $(firstword $(2)),,$(1)),$(filter-out $(firstword $(2)),$(2))),$(1))
endef
DIGITS := 0 1 2 3 4 5 6 7 8 9
define IS_NOT_A_NUMBER
$(call PURGE,$(1),$(DIGITS))
endef
CFLAGS += $(if $(call IS_NOT_A_NUMBER,$(N)),,-D$(N))
all:
$(info N=$(N) => CFLAGS=$(CFLAGS))
Demo:
host> make N=12345
N=12345 => CFLAGS=-D12345
make: 'all' is up to date.
host> make N=foobar
N=foobar => CFLAGS=
make: 'all' is up to date.
Explanation: PURGE is a recursive macro that takes two arguments. The first one ($(1)) is a string to test, the second one ($(2)) is a list of words to match. If $(2) is the empty list PURGE returns $(1). Else, it calls itself with two new parameters:
the value of $(1) where the first word of $(2) has been substituted by nothing,
$(2) from which the first word has been removed
and returns the result. So, if you call PURGE with a string and the list of all digits, it returns the empty string if and only if the string contained only digits.
All make variables are strings. To find out whether a string is in fact a number, you need some elementary text analysis functions. GNU make itself does not offer anything convenient in this area, but you could run a shell command to do the job, perhaps like this:
define is_number
$(shell test '$(1)' -eq '$(1)' 2>/dev/null && echo yes || echo no)
endef
ifeq ($(call is_number, $(N)),yes)
default:
#echo N is a number
else
default:
#echo N is not a number
endif
This results in:
$ make N=5
N is a number
$ make N=string
N is not a number
However, such string processing can be quite unreliable if the string contains special characters.

makefile: remove duplicate words without sorting

Is there a possibility to remove duplicates in a list of words without sorting in a makefile?
$(sort foo bar lose)
does remove duplicates (which is for me the main functionality in this case), but also sorts (for me an unfortunate side effect in this case). I want to avoid that.
[update]
bobbogo's answer works very nicely. Just remember to use define uniq for v3.81 and (did not check this) define uniq = for later versions.
larsmans' answer works very nicely too if your record separator is not a space, e.g. if you want to remove duplicates from _foo_bar_lose_lose_bar_baz_ or the like. Just remember to use the RS and ORS awk options instead of tr, and wrap it all with $(firstword $(shell ... ))
Boring $eval based method:
define uniq =
$(eval seen :=)
$(foreach _,$1,$(if $(filter $_,${seen}),,$(eval seen += $_)))
${seen}
endef
w := z z x x y c x
$(info $(sort $w))
$(info $(call uniq,$w))
Extremely fiendish make standard library recursive call (recursive make considered extremely fiendish?):
uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
It's worth noting that no variables are damaged in this second formulation (see seen in the first). It is preferable just for that (given the lack of locals in make)!
EDIT
My obscure comment about recursive make above seems to have muddied the waters somewhat.
"Recursive" in the context of this post means recursive function.
It really has nothing to do with the execrable recursive make.
The latter (recursive) definition of uniq is extremely nice, performant, small, and is definitely the one to use.
Depends on where you need it and whether you use GNU make. If you just want to uniq the list of target prerequisites, it's as easy as (http://www.gnu.org/software/make/manual/make.html#Quick-Reference) :
The value of $^ omits duplicate prerequisites, while $+ retains them and preserves their order.
So, a rule like
exe: $(OBJS)
$(LD) -o $# $^
will filter duplicates from $(OBJS) automagically, while still leaving order of other items the same.
You could echo the words through awk:
echo foo bar foo baz bar | tr ' ' '\n' | awk '!a[$0]++'
Deduping one-liner taken from catonmat.
(Don't forget to double the $ to $$ in a Makefile.)
The following works for me under GNU make v3.82:
uniq = $(eval _uniq := $1)$(strip $(foreach _,$(_uniq),$(if $(filter $_,$(_uniq)),$(eval _uniq := $(filter-out $_,$(_uniq)))$_)))
It doesn't modify its input by creating a copy in _uniq, and it's not recursive.

Simplest way to reverse the order of strings in a make variable

Let's say you have a variable in a makefile fragment like the following:
MY_LIST=a b c d
How do I then reverse the order of that list? I need:
$(warning MY_LIST=${MY_LIST})
to show
MY_LIST=d c b a
Edit: the real problem is that
ld -r some_object.o ${MY_LIST}
produces an a.out with undefined symbols because the items in MY_LIST are actually archives, but in the wrong order. If the order of MY_LIST is reversed, it will link correctly (I think). If you know a smarter way to get the link order right, clue me in.
A solution in pure GNU make:
default: all
foo = please reverse me
reverse = $(if $(1),$(call
reverse,$(wordlist 2,$(words
$(1)),$(1)))) $(firstword $(1))
all : #echo $(call reverse,$(foo))
Gives:
$ make
me reverse please
An improvement to the GNU make solution:
reverse = $(if $(wordlist 2,2,$(1)),$(call reverse,$(wordlist 2,$(words $(1)),$(1))) $(firstword $(1)),$(1))
better stopping condition, original uses the empty string wasting a function call
doesn't add a leading space to the reversed list, unlike the original
Doh! I could have just used a shell script-let:
(for d in ${MY_LIST}; do echo $$d; done) | tac
You can also define search groups with ld:
ld -r foo.o -( a.a b.a c.a -)
Will iterate through a.a, b.a, and c.a until no new unresolved symbols can be satisfied by any object in the group.
If you're using gnu ld, you can also do:
ld -r -o foo.o --whole-archive bar.a
Which is slightly stronger, in that it will include every object from bar.a regardless of whether it satisfies an unresolved symbol from foo.o.
Playing off of both Ben Collins' and elmarco's answers, here's a punt to bash which handles whitespace "properly"1
reverse = $(shell printf "%s\n" $(strip $1) | tac)
which does the right thing, thanks to $(shell) automatically cleaning whitespace and printf automatically formatting each word in its arg list:
$(info [ $(call reverse, one two three four ) ] )
yields:
[ four three two one ]
1...according to my limited test case (i.e., the $(info ...) line, above).

Resources