In my makefile, I need to make a variable assignment based on a command line variable value. for example, I do:
make var_1=xxx
where var_1 can have one of say 100 possible values. Based on the value of var_1, I need to assign a value to var_2 in my makefile. I could do:
ifeq ($(var_1), a)
var_2 = A
endif
ifeq ($(var_1), b)
var_2 = B
endif
and so on for all 100 possible combinations of var_1, var_2. Here a,A,b,B represent some strings. How do I do this to avoid 100's of if statements? I was thinking to define two variables:
var_1_values = a b c d
var_2_values = A B C D
I can use $(findstring $(var_1),$(var_1_values)) to see if $(var_1) is among $(var_1_values), but how do I locate the position of $(var_1) among all $(var_1_values)? That position is then to be used to pick the corresponding word inside $(var_2_values).
It's a little kludgey, but if there's a symbol you know won't be in any of the values (such as "_") you could do this:
var_1_values = a b c d
var_2_values = A B C D
# This will be a_A b_B c_C d_D
LIST1 = $(join $(addsuffix _,$(var_1_values)),$(var_2_values))
var_1 := a
# The filter gives a_A, the subst turns it into A
var_2 = $(subst $(var_1)_,,$(filter $(var_1)_%, $(LIST1)))
This can be done quite slickly using recursion inside GNU make functions as follows:
_pos = $(if $(findstring $1,$2),$(call _pos,$1,\
$(wordlist 2,$(words $2),$2),x $3),$3)
pos = $(words $(call _pos,$1,$2))
To use it you would $(call) the pos function with two arguments: the item to find and the list to find it in. For example,
$(call pos,a,a b c d e f g)
$(call pos,e,a b c d e f g)
$(call pos,g,a b c d e f g)
$(call pos,h,a b c d e f g)
$(call pos,e,))
It works by recursing through the $2 argument until it can no longer find the value in $1. Each time it recurses it lops the head off $2 using $(wordlist 2,$(words $2),$2). Each time is recurses it adds an x to the returned string so that there is one x for each position through $2 up to where $1 was found.
It then just uses $(words) to count the length of the return from _pos (the number of xs).
If you use the GMSL project this can be written more slickly write this as:
_pos = $(if $(findstring $1,$2),$(call $0,$1,$(call rest,$2),x $3),$3)
pos = $(words $(call _$0,$1,$2))
Note that I used $0 here which will contain the name of the current function (that's a standard GNU make feature) and the GMSL function rest to lop the head off the list.
There's a smooth way to do this using recursion as follows. First define a function called pos that finds the position of an element in a list and then use $(word) to extract the corresponding element in another list.
Here's pos:
_pos = $(if $(findstring $1,$2),$(call _pos,$1,\
$(wordlist 2,$(words $2),$2),x $3),$3)
pos = $(words $(call _pos,$1,$2))
Read this answer to understand how it works: Makefile: find function which returns position
Now it's easy to define a function that finds an element in a list and the finds the corresponding element in another list.
lookup = $(word $(call pos,$1,$2),$3)
And then try if out like this:
ALPHA := a b c d e f g h i j k l m n o p q r s t u v w x y z
NATO := alpha beta charlie delta echo foxtrot gamma hotel india\
juliet kilo lima mike november oscar papa quebec romeo\
sierra tango uniform victor whisky yankee zulu
to-nato = $(call lookup,$1,$(ALPHA),$(NATO))
To make a to-nato function that converts from a letter of the alphabet to the NATO alphabet.
One way simulate associative containers in make is to use computed variables. E.g.:
var_2.a := A
var_2.b := B
# ...
# lookup
var_2 = ${var_2.${var_1}}
# or, lookup and assign a default value if lookup fails
var_2_or_default = $(or ${var_2.${var_1}},<default-value>)
FAB = macrofab
# NOTE: base starts at 1 instead of 0
none := 1
seeed := 1
oshpark := 2
macrofab := 2
pcbng := 3
#NOTE: String must end with a space for word function to work
SUFFIX_DRILLS := "txt xln drl "
.PHONY: gerber
gerber:
#echo $(FAB) drill suffix is $(word $($(FAB)), $(SUFFIX_DRILLS))
Output of make is:
macrofab drill suffix is xln
This works in mingw gnu make 4.3 :
define get_index
$(shell \
$(eval _c = 1)\
$(eval _b = n n) \
$(foreach x,$(2), \
$(eval _b += n \
$(eval _c = $(words $(_b)) \
$(if $(filter $(1),$(x)), \
$(eval _r = $(_c)))))) \
echo $(_r) \
$(eval _c =) \
$(eval _b =) \
$(eval _r =) \
)
endef
Takes two arguments; the word to search for and the list where to search.
The index is 'returned' by echoing in $(shell ...)
Example:
list = cow horse chicken
index = $(call get_index,horse,$(list))
#echo $(index)
# output: 2
Related
Assuming there are lists xl and yl with the same number of items, and an existing string s. I want to susbtitute xl's items with yl's items in s. That is, for each x in xl, I want to call $(subst x,y,$(s)) where y is the corresponding item in yl. I tried doing things like:
ns := $(shell seq $(words $(xl)))
t := $(foreach i,$(ns),$(subst $(word $(i),$(xl)),$(word $(i),$(yl)),$(s))
but this does not work because foreach expands s every iteration leading to repetitions in t.
What's a good way to get the result I want? Thank you.
Just a recursive function if I understand you correctly:
multi-subst = $(if $1,$(call multi-subst,$(wordlist 2,10000,$1),$(wordlist 2,10000,$2),$(subst $(firstword $1),$(firstword $2),$3)),$3)
s = abc dca acacd
xl = a c
yl = 1 3
s_new := $(call multi-subst,$(xl),$(yl),$(s))
It is not possible to replace substrings containing spaces of course. For more functions on strings and list you may want to take a look at gmtt
Using a recursive macro to generate a number sequence and evaĺ to make the text substitution, for GNU make 4.2.1:
s := ABC def GHI bedeft ghi JKL xyz fnops
xl := abc def ghi nop
yl := jkl mno pqr (NOP)
# List natural numbers from 1 to length (< 1bn) of list in $1, void if $1 empty
seqn = $(strip $(if $(strip $1),$(call seqn,$(wordlist 2,999999999,$1)) $(words $1),))
ns := $(call seqn,$(patsubst %,x,$(xl)))
t := $(s)
$(foreach i,$(ns),\
$(eval t:= $$(subst $(word $(i),$(xl)),$(word $(i),$(yl)),$$(t))))
.PHONY: all
all : ; $(foreach v,ns s t,$(info $(v): [$($(v))]))
Output from make --silent:
ns: [1 2 3 4]
s: [ABC def GHI bedeft ghi JKL xyz fnops]
t: [ABC mno GHI bemnot pqr JKL xyz f(NOP)s]
EDIT
foreach should loop on indices of replacement list, not target string
call seqn macro with $(patsubst %,x,list) for speed
only $(subst …) and $(t) need escaping with $$ in eval stmt
You'll have to operate on each word of s separately, I think.
Something like:
t := $(foreach w,$(s),$(foreach i,$(ns),$(subst $(word $i,$(xl)),$(word $i,$(yl)),$w)))
This is a silly question, but.... with GNU Make:
VAR = MixedCaseText
LOWER_VAR = $(VAR,lc)
default:
#echo $(VAR)
#echo $(LOWER_VAR)
In the above example, what's the correct syntax for converting VAR's contents to lower case? The syntax shown (and everything else I've run across) result in LOWER_VAR being an empty string.
you can always spawn off tr
LOWER_VAR = `echo $(VAR) | tr A-Z a-z`
or
LOWER_VAR = $(shell echo $(VAR) | tr A-Z a-z)
The 'lc' functions you trying to call is from GNU Make Standard Library
Assuming that is installed , the proper syntax would be
LOWER_VAR = $(call lc,$(VAR))
You can do this directly in gmake, without using the GNU Make Standard Library:
lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
VAR = MixedCaseText
LOWER_VAR = $(call lc,$(VAR))
all:
#echo $(VAR)
#echo $(LOWER_VAR)
It looks a little clunky, but it gets the job done.
If you do go with the $(shell) variety, please do use := instead of just =, as in LOWER_VAR := $(shell echo $VAR | tr A-Z a-z). That way, you only invoke the shell one time, when the variable is declared, instead of every time the variable is referenced!
To handle capital letters with accents:
LOWER_VAR = $(shell echo $VAR | tr '[:upper:]' '[:lower:]')
Results:
$ VAR="Éclipse"
$ echo $VAR | tr A-Z a-z
Éclipse
$ echo $VAR | tr '[:upper:]' '[:lower:]'
éclipse
I find this slightly cleaner...
$(shell tr '[:upper:]' '[:lower:]' <<< $(VAR))
If Python is installed this runs even on Windows:
$(shell python -c "print('$(VAR)'.lower())")
GNU make doesn't include string functions for case conversion. Thus, there is no lc function defined, by default.
But GNU Make usually comes with GNU Guile support enabled (e.g. this is the case on Fedora 33).
Thus, you can just call a Guile function for converting the case:
VAR = MixedCaseText
LOWER_VAR = $(guile (string-downcase "$(VAR)"))
default:
#echo $(VAR)
#echo $(LOWER_VAR)
Or if you want to encapsulate the Guile call:
VAR = MixedCaseText
LOWER_VAR = $(call to_lower,$(VAR))
define to_lower
$(guile (string-downcase "$(1)"))
endef
default:
#echo $(VAR)
#echo $(LOWER_VAR)
I wrote this while looking for a solution.
It is a bit verbose but believe it explains the steps and keeps really long lines out on the Makefile.
You can easily be modify it to perform any substitution you may want.
Hope it helps someone.
# set the separator for the *_TABLE variables, needed as otherwise `$(addprefix ...)` fails
luc_JOIN ::= ,
# define the upper and lower cased characters
lc_CHARS ::= a b c d e f g h i j k l m n o p q r s t u v w x y z
uc_CHARS ::= A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
# join the above to create the *_TABLE variables (i.e `a,A b,B ...`, `A,a B,b ...`)
lc_TABLE ::= $(join $(uc_CHARS),$(addprefix $(luc_JOIN),$(lc_CHARS)))
uc_TABLE ::= $(join $(lc_CHARS),$(addprefix $(luc_JOIN),$(uc_CHARS)))
# an internal macro to recursively create `$(subst ...)` from provided *_TABLE and string, (e.g. `$(subst a,A,$(subst b,B,...))`)
luc_internal = $(if $1,$$(subst $(firstword $1),$(call luc_internal,$(wordlist 2,$(words $1),$1),$2)),$2)
# the actual macros to $(call ...), which calls the luc_internal with the correct *_TABLE
lc = $(eval lc_RESULT ::= $(call luc_internal,$(lc_TABLE),$1))$(lc_RESULT)
uc = $(eval uc_RESULT ::= $(call luc_internal,$(uc_TABLE),$1))$(uc_RESULT)
# a mixed case value
VAR = SOME text
default:
#echo $(call lc,$(VAR))
#echo $(call uc,$(VAR))
Being impressed by the Eric Melski answer, I was curious how make handles recursion (I'm looking at you C preprocessor). Somewhat more involved, than original answer, but it's fascinating what a 50 years old tool can do. Not saying you should use this code, but I guess you could.
pop2 = $(wordlist 3,$(words $(1)),$(1))
sub1 = $(subst $(word 1,$(1)),$(word 2,$(1)),$(2))
map = $(if $(1),$(call sub1,$(1),$(call map,$(call pop2,$(1)),$(2))),$(2))
upperMap := a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z
upper = $(call map,$(upperMap),$(1))
lowerMap := A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z
lower = $(call map,$(lowerMap),$(1))
#Usage:
x := $(call upper,AaBbCcDdEe)
In my makefile, I need to make a variable assignment based on a command line variable value. for example, I do:
make var_1=xxx
where var_1 can have one of say 100 possible values. Based on the value of var_1, I need to assign a value to var_2 in my makefile. I could do:
ifeq ($(var_1), a)
var_2 = A
endif
ifeq ($(var_1), b)
var_2 = B
endif
and so on for all 100 possible combinations of var_1, var_2. Here a,A,b,B represent some strings. How do I do this to avoid 100's of if statements? I was thinking to define two variables:
var_1_values = a b c d
var_2_values = A B C D
I can use $(findstring $(var_1),$(var_1_values)) to see if $(var_1) is among $(var_1_values), but how do I locate the position of $(var_1) among all $(var_1_values)? That position is then to be used to pick the corresponding word inside $(var_2_values).
It's a little kludgey, but if there's a symbol you know won't be in any of the values (such as "_") you could do this:
var_1_values = a b c d
var_2_values = A B C D
# This will be a_A b_B c_C d_D
LIST1 = $(join $(addsuffix _,$(var_1_values)),$(var_2_values))
var_1 := a
# The filter gives a_A, the subst turns it into A
var_2 = $(subst $(var_1)_,,$(filter $(var_1)_%, $(LIST1)))
This can be done quite slickly using recursion inside GNU make functions as follows:
_pos = $(if $(findstring $1,$2),$(call _pos,$1,\
$(wordlist 2,$(words $2),$2),x $3),$3)
pos = $(words $(call _pos,$1,$2))
To use it you would $(call) the pos function with two arguments: the item to find and the list to find it in. For example,
$(call pos,a,a b c d e f g)
$(call pos,e,a b c d e f g)
$(call pos,g,a b c d e f g)
$(call pos,h,a b c d e f g)
$(call pos,e,))
It works by recursing through the $2 argument until it can no longer find the value in $1. Each time it recurses it lops the head off $2 using $(wordlist 2,$(words $2),$2). Each time is recurses it adds an x to the returned string so that there is one x for each position through $2 up to where $1 was found.
It then just uses $(words) to count the length of the return from _pos (the number of xs).
If you use the GMSL project this can be written more slickly write this as:
_pos = $(if $(findstring $1,$2),$(call $0,$1,$(call rest,$2),x $3),$3)
pos = $(words $(call _$0,$1,$2))
Note that I used $0 here which will contain the name of the current function (that's a standard GNU make feature) and the GMSL function rest to lop the head off the list.
There's a smooth way to do this using recursion as follows. First define a function called pos that finds the position of an element in a list and then use $(word) to extract the corresponding element in another list.
Here's pos:
_pos = $(if $(findstring $1,$2),$(call _pos,$1,\
$(wordlist 2,$(words $2),$2),x $3),$3)
pos = $(words $(call _pos,$1,$2))
Read this answer to understand how it works: Makefile: find function which returns position
Now it's easy to define a function that finds an element in a list and the finds the corresponding element in another list.
lookup = $(word $(call pos,$1,$2),$3)
And then try if out like this:
ALPHA := a b c d e f g h i j k l m n o p q r s t u v w x y z
NATO := alpha beta charlie delta echo foxtrot gamma hotel india\
juliet kilo lima mike november oscar papa quebec romeo\
sierra tango uniform victor whisky yankee zulu
to-nato = $(call lookup,$1,$(ALPHA),$(NATO))
To make a to-nato function that converts from a letter of the alphabet to the NATO alphabet.
One way simulate associative containers in make is to use computed variables. E.g.:
var_2.a := A
var_2.b := B
# ...
# lookup
var_2 = ${var_2.${var_1}}
# or, lookup and assign a default value if lookup fails
var_2_or_default = $(or ${var_2.${var_1}},<default-value>)
FAB = macrofab
# NOTE: base starts at 1 instead of 0
none := 1
seeed := 1
oshpark := 2
macrofab := 2
pcbng := 3
#NOTE: String must end with a space for word function to work
SUFFIX_DRILLS := "txt xln drl "
.PHONY: gerber
gerber:
#echo $(FAB) drill suffix is $(word $($(FAB)), $(SUFFIX_DRILLS))
Output of make is:
macrofab drill suffix is xln
This works in mingw gnu make 4.3 :
define get_index
$(shell \
$(eval _c = 1)\
$(eval _b = n n) \
$(foreach x,$(2), \
$(eval _b += n \
$(eval _c = $(words $(_b)) \
$(if $(filter $(1),$(x)), \
$(eval _r = $(_c)))))) \
echo $(_r) \
$(eval _c =) \
$(eval _b =) \
$(eval _r =) \
)
endef
Takes two arguments; the word to search for and the list where to search.
The index is 'returned' by echoing in $(shell ...)
Example:
list = cow horse chicken
index = $(call get_index,horse,$(list))
#echo $(index)
# output: 2
I wish to compile partial targets which is $(APPS) but excluding targets in $(OFF) in the makefile:
APPS = a b c d e f g
OFF = d e
all: $(APPS)
partial: $(APPS) - $(OFF)
How can I do this?
I'd probably just take a slightly different approach:
ON = a b c f g
OFF = d e
APPS = $(ON) $(OFF)
partial: $(ON)
But if this is too simplistic for your needs, there is a filter-out function that can provide the exact operation you want:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
I think re-writing your example in this format would be:
APPS = a b c d e f g
OFF = c d
partial: $(filter-out $(APPS),$(OFF))
This is a silly question, but.... with GNU Make:
VAR = MixedCaseText
LOWER_VAR = $(VAR,lc)
default:
#echo $(VAR)
#echo $(LOWER_VAR)
In the above example, what's the correct syntax for converting VAR's contents to lower case? The syntax shown (and everything else I've run across) result in LOWER_VAR being an empty string.
you can always spawn off tr
LOWER_VAR = `echo $(VAR) | tr A-Z a-z`
or
LOWER_VAR = $(shell echo $(VAR) | tr A-Z a-z)
The 'lc' functions you trying to call is from GNU Make Standard Library
Assuming that is installed , the proper syntax would be
LOWER_VAR = $(call lc,$(VAR))
You can do this directly in gmake, without using the GNU Make Standard Library:
lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
VAR = MixedCaseText
LOWER_VAR = $(call lc,$(VAR))
all:
#echo $(VAR)
#echo $(LOWER_VAR)
It looks a little clunky, but it gets the job done.
If you do go with the $(shell) variety, please do use := instead of just =, as in LOWER_VAR := $(shell echo $VAR | tr A-Z a-z). That way, you only invoke the shell one time, when the variable is declared, instead of every time the variable is referenced!
To handle capital letters with accents:
LOWER_VAR = $(shell echo $VAR | tr '[:upper:]' '[:lower:]')
Results:
$ VAR="Éclipse"
$ echo $VAR | tr A-Z a-z
Éclipse
$ echo $VAR | tr '[:upper:]' '[:lower:]'
éclipse
I find this slightly cleaner...
$(shell tr '[:upper:]' '[:lower:]' <<< $(VAR))
If Python is installed this runs even on Windows:
$(shell python -c "print('$(VAR)'.lower())")
GNU make doesn't include string functions for case conversion. Thus, there is no lc function defined, by default.
But GNU Make usually comes with GNU Guile support enabled (e.g. this is the case on Fedora 33).
Thus, you can just call a Guile function for converting the case:
VAR = MixedCaseText
LOWER_VAR = $(guile (string-downcase "$(VAR)"))
default:
#echo $(VAR)
#echo $(LOWER_VAR)
Or if you want to encapsulate the Guile call:
VAR = MixedCaseText
LOWER_VAR = $(call to_lower,$(VAR))
define to_lower
$(guile (string-downcase "$(1)"))
endef
default:
#echo $(VAR)
#echo $(LOWER_VAR)
I wrote this while looking for a solution.
It is a bit verbose but believe it explains the steps and keeps really long lines out on the Makefile.
You can easily be modify it to perform any substitution you may want.
Hope it helps someone.
# set the separator for the *_TABLE variables, needed as otherwise `$(addprefix ...)` fails
luc_JOIN ::= ,
# define the upper and lower cased characters
lc_CHARS ::= a b c d e f g h i j k l m n o p q r s t u v w x y z
uc_CHARS ::= A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
# join the above to create the *_TABLE variables (i.e `a,A b,B ...`, `A,a B,b ...`)
lc_TABLE ::= $(join $(uc_CHARS),$(addprefix $(luc_JOIN),$(lc_CHARS)))
uc_TABLE ::= $(join $(lc_CHARS),$(addprefix $(luc_JOIN),$(uc_CHARS)))
# an internal macro to recursively create `$(subst ...)` from provided *_TABLE and string, (e.g. `$(subst a,A,$(subst b,B,...))`)
luc_internal = $(if $1,$$(subst $(firstword $1),$(call luc_internal,$(wordlist 2,$(words $1),$1),$2)),$2)
# the actual macros to $(call ...), which calls the luc_internal with the correct *_TABLE
lc = $(eval lc_RESULT ::= $(call luc_internal,$(lc_TABLE),$1))$(lc_RESULT)
uc = $(eval uc_RESULT ::= $(call luc_internal,$(uc_TABLE),$1))$(uc_RESULT)
# a mixed case value
VAR = SOME text
default:
#echo $(call lc,$(VAR))
#echo $(call uc,$(VAR))
Being impressed by the Eric Melski answer, I was curious how make handles recursion (I'm looking at you C preprocessor). Somewhat more involved, than original answer, but it's fascinating what a 50 years old tool can do. Not saying you should use this code, but I guess you could.
pop2 = $(wordlist 3,$(words $(1)),$(1))
sub1 = $(subst $(word 1,$(1)),$(word 2,$(1)),$(2))
map = $(if $(1),$(call sub1,$(1),$(call map,$(call pop2,$(1)),$(2))),$(2))
upperMap := a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z
upper = $(call map,$(upperMap),$(1))
lowerMap := A a B b C c D d E e F f G g H h I i J j K k L l M m N n O o P p Q q R r S s T t U u V v W w X x Y y Z z
lower = $(call map,$(lowerMap),$(1))
#Usage:
x := $(call upper,AaBbCcDdEe)