Makefile: get short version string from version string - makefile

How to get the short version string from the version string in Makefile? Suppose the short version string is got by sed 's/\.[0-9][0-9]$//g' $version.
version=v3.2.11
version_short=$(patsubst %.11,%,$(version))
print-% : ; #echo $* = $($*)
all: print-version_short

You don't list GNU make in your tags but your makefile is clearly GNU make so I'll respond in kind.
If you know there are always exactly two "."s it can be done like this:
combine = $(word 1,$1).$(word 2,$1)
short := $(call combine,$(subst ., ,$(version)))
This converts the "." to spaces then calls a user-defined function with the resulting arguments, which puts back together the first two arguments.

Related

How to isolate the version number out of a string in makefile?

I want to obtain the Boost Library Version number 1.58 out of the string "Version: 1.58.0.1ubuntu1" or generally any other version of Boost. This would allow me to compare the current version of boost to my specific version that I need to match. This is what I have so far.
Configure:
if ( test -d $(Boost) )
then
CurrVer=$$(dpkg -s libboost-dev | grep 'Version'
echo $$CurrVer
echo $$CurrVer | tr -cd [:digit:]
else
make DLBoostV1_58
fi
The problem is that I can narrow the string down to the digits 158011 but I can't figure out how to remove the digits 011.
I have read geeksforgeeks website for grep, sed, and awk commands but what got me to this point are, How to extract numbers from a string?, https://askubuntu.com/questions/147474/how-can-i-find-boost-version, https://www.geeksforgeeks.org/tr-command-in-unix-linux-with-examples/nd .
Expecting output: 158
Resulting output: 158011
Supposing you have captured the Boost version message in shell variable CurrVer, you can use the prefix-removal option of parameter expansion to remove the lead text (${CurrVer##* } removes everything up to the last space character), inside an array assignment (a=(...)) with the period (.) as a field separator (IFS=.) to split the version string at the right delimiters. Then you just need to read back the array elements you want.
For example,
ShortVer=$( IFS=.; a=(${CurrVer##* }); echo "${a[0]}.${a[1]}" )
Escaping that for make is left as an exercise.
You can use gmtt which can take apart strings per glob-match:
include gmtt-master/gmtt-master/gmtt.mk
VERSION := Version: 1.58.0.1ubuntu1
VERS-AS-LIST := $(call glob-match,$(VERSION),Version: *.*.*.*ubuntu*)
$(info $(VERS-AS-LIST))
MAJOR := $(word 2,$(VERS-AS-LIST))
MINOR := $(word 4,$(VERS-AS-LIST))
BUGFIX := $(word 6,$(VERS-AS-LIST))
$(info $(MAJOR)-$(MINOR)-$(BUGFIX))
Output:
$ make
Version:§ 1 . 58 . 0 . 1 ubuntu 1
1-58-0
Notice that the first part of the glob pattern was Version: with a space character at the end, which is conserved via the § character (contained in the gmtt variable $(-spacereplace) and removable with the function $(call spc-unmask,_string_))

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.

Makefiles - How do I use an eval'd variables with ifndef/ifdef?

I'm trying to figure out how to get an eval'd variable (using output from the shell) to pass conditional checks like ifndef or ifdef. I need to use the shell because I'm actually using a script that returns some output.
foo::
$(eval var := $(shell echo 'hello'))
ifndef var
#printf 'ifndef is true. var is ${var}'
else
#printf 'ifndef is false. var is ${var}'
endif
Running "make foo" will output the following:
'ifndef is true. var is hello'
As you can see, ifndef doesn't find anything in var, even though printf is showing that var contains the string "hello".
What am I missing here?
You can't do this because ifdef et. al. are parsed when the makefile is read in (note that they do not begin with a TAB character so they are not part of the recipe), and the recipe (which includes $(eval ...)) is not parsed until much later when make wants to build the target foo.
And, you cannot put ifdef et. al. into the recipe because if you prefix them with TAB then they get passed to the shell, not parsed by make. In general if you want a conditional inside a recipe you have to use shell conditionals, NOT make conditionals, because the recipe is a shell script.
You can use the $(if ...) function:
foo::
$(eval var := $(shell echo 'hello'))
#printf 'ifndef is $(if $(var),true,false). var is ${var}'
But to me this looks like a mistake and you might reconsider what you're trying to do at a more fundamental level.

How to split in GNU makefile list of files into separate lines

I have a variable containing list of files separated with string _NEWLINE_. I need to output that variable into a file so that each file is in a separate line. The trick is that it needs to works on FreeBSD and Solaris.
This is what I am trying now:
echo "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf" | sed 's|_NEWLINE_|\'$'\n|g'
This works on FreeBSD and in shell on Solaris. But when run in GNUmakefile on Solaris I am getting this (notice $ at the end of each line):
lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf$
lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src$
lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf$
If I remove \'$' from sed then it works on Solaris but doesn't on FreeBSD. Maybe there is a way of telling which version to use depending on which system the makefile is executed?
EDIT:
Thanks to the solution proposed by bobbogo I created an exemplary makefile that provides the desired outcome and seems to be working on both FreeBSD and Solaris:
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/\
priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/comm\
unity.conf: lib/alarms/priv/snmp_conf/community.conf
many-lines := { echo '$(subst _NEWLINE_,' && echo ',${one-line})'; }
.PHONY: all
all:
$(shell $(many-lines) > test.txt)
If this is GNU make, then do it all in make.
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf
define \n
endef
many-lines := $(subst _NEWLINE_,${\n},${one-line})
Now ${many-lines} has just what you want. Annoyingly, it's quite hard to use in shell lines. If you do this:
tgt:
echo '${many-lines}'
make will invoke a separate shell for each line. The first shell invocation will get an un-paired ' and exit with an error.
.ONESHELL:
tgt:
echo '${many-lines}'
will work in an invasive sort of way. The proper fix is to ensure each line of ${many-lines} has valid sh syntax. Some mouthfull like:
echolines = $(subst ${\n},'${\n}echo ',echo '${many-lines}')
.PHONY: aa
aa:
$(call echolines,${many-lines})
Sheesh.
Tried many different solutions, including defining \n as mentioned in this answer: Add a newline in Makefile 'foreach' loop
The real problem is inconsistent implementation of the echo command across platforms, and the fact that by default make invokes shell commands using sh, which itself is quite inflexible.
I found a better way thanks to this answer: "echo -e" when called directly and when called via a shell-script
The better way is to use printf instead of echo
Construct the string with \n instead of _NEWLINE_ to separate parts that go into separate lines in the output file:
some_string = lib/alarms-1.2/priv/snmp_conf/target_params.conf: lib/alarms/priv/snmp_conf/target_params.conf\nlib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf\n
and then in the makefile print it simply as this:
#printf "$(some_string)" >> $(some_file)
Works on both, FreeBSD and Solaris.
Disclaimer: I have no experience with Solaris or FreeBSD... here goes anyway.
In make, you can use $(patsubst pattern,replacement,text) to substitute a pattern.
try this...
FILENAMES := "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf"
.PHONY: all
all:
#echo $(patsubst _NEWLINE_,${\n},$(FILENAMES))
As an alternative, I think your first approach will work, if you just double the $ to "escape" it:
sed 's|_NEWLINE_|\'$$'\n|g'

Makefile: How to apply an equivalent to filter on multiple wildcards

I am writing a Makefile and I get stuck on a filter function limitation.
Indeed, filter takes only one wildcard.
What I would like to do is:
I have a list a files, some matching the regexp blabla, some not. But for this I need 2 wildcards, thus i cannot use filter function.
I would like to split my original list in 2 lists, one containing all the element containing the blabla string (filter equivalent) and the other one containing the not matching one (filter-out equivalent).
thanks for your help.
You can do this without running any external commands. Define the two macros
containing = $(foreach v,$2,$(if $(findstring $1,$v),$v))
not-containing = $(foreach v,$2,$(if $(findstring $1,$v),,$v))
Now you can do
LIST := a_old_tt x_old_da a_new_da q_ty_we
LIST_OLD := $(call containing,old,$(LIST))
LIST_NOT_OLD := $(call not-containing,old,$(LIST))
One of Make's greatest shortcomings is its poor ability to handle regular expressions. The functions filter and filter-out can't find "old" in the middle of a word. I'd suggest this hack:
NOT_OLD = $(shell echo $(LIST) | sed 's/[^ ]*old[^ ]* *//g')
OLD = $(filter-out $(NOT_OLD), $(LIST))
You could take advantage of your shell's more advanced string handling capabilities. Assuming that you have bash, you could use the following in your makefile:
LIST := a_old_tt x_old_da a_new_da q_ty_we
LIST_NOT_OLD := $(shell l=($(LIST)); echo $${l[#]//*old*})
LIST_OLD := $(filter-out $(LIST_NOT_OLD),$(LIST))
You can find an explanation of the bash string replacement mechanism in how to delete elements from an array based on a pattern. The double $ is required to keep the $ sign in the shell invocation.

Resources