17

In a makefile, I'd like to define a variable specifying whether the current redhat-release is greater than 5.3. (This variable will be passed to gcc as a #define)

So far I've come up with:

# Find out which version of Red-Hat we're running
RH_VER_NUM = $(shell /bin/grep -o [0-9].[0-9] /etc/redhat-release)
RH_GT_5_3 = $RH_VER_NUM > '5.3'

What would be the correct way to define RH_GT_5_3?

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
tsotso
  • 322
  • 1
  • 2
  • 9

10 Answers10

21

GNU Make doesn't contain any string comparisons other than equality, and test can only do less-than/greater-than tests on integers. Split the version number into its integral parts and do the comparison that way. Try this (note also that := is better than = here, as make's lazy evaluation would call your $(shell) commands many more times than required:

RH_VER_MAJOR := $(shell echo $(RH_VER_NUM) | cut -f1 -d.)
RH_VER_MINOR := $(shell echo $(RH_VER_NUM) | cut -f2 -d.)
RH_GT_5_3 := $(shell [ $(RH_VER_MAJOR) -gt 5 -o \( $(RH_VER_MAJOR) -eq 5 -a $(RH_VER_MINOR) -ge 3 \) ] && echo true)

ifeq ($(RH_GT_5_3),true)
CPPFLAGS += -DRH_GT_5_3=1
endif
Jack Kelly
  • 18,264
  • 2
  • 56
  • 81
  • 5
    P.S. Not only is this ugly, but it will re-do the version check each time you run `make`. A cleaner solution would be to use `autoconf` to generate your `Makefile` and/or some header like `config.h`. – Jack Kelly Sep 17 '10 at 03:21
9

A little bit shorter solution is:

RH_GT_5_3 := $(shell echo -e "5.4\n$(RH_VER_NUM)"|sort -ct. -k1,1n -k2,2n && echo YES)

This will set RH_GT_5_3 to "YES" if RH_VER_NUM is greater then or equal to 5.4 (so greater then 5.3). Otherwise RH_GT_5_3 will be set to empty.

If multiple version numbers need to be checked we can define a function:

IF_VER_GE = $(shell echo -e "$2\n$1"|sort -ct. -k1,1n -k2,2n && echo YES)
GLIBC := $(word 2,$(shell getconf GNU_LIBC_VERSION))
...
all:
ifeq "$(call IF_VER_GE, $(GLIBC), 2.5)" "YES"
    echo "GE"
else
    echo "LT"
endif

I used the "$(word 2,..." instead of "$(lastword,..." because the later does not work in make 3.8. And shorter...

... some aeons later

I tried to solve the version comparison with internal makefile functions. I found a project (GNU Make Standard Library (GMSL)), which adds an include to the makefile which implements integer arithmetic. Unfortunately with the common unary numeral system. But comparing version numbers are more complex. As I worked with versions having numbers greater then 100_000 I decided to implement a more general solution. It works with arbitrary number of subversion numbers each with arbitrary digits. Some ideas were borrowed from the GMSL project

It implements the ver.lt function. It returns 'T' if the first version number is less then the second. It returns an empty string otherwise. Of course, the subversion numbers are compared numerically not lexicographically. So 1.20 is greater then 1.3. There is some issues. 1.2 < 1.2.0, 1.0.1 < 1.00.1, 1.9.1 < 1.01.1 (as it is expected that a number starts with a nonzero digit. Except the 0 itself.). I do not want to solve them now.

The solution

It is testAed under gnu make 3.82.90. There are some very long lines as makefile add spaces if the '\' is used. I left some implemented, but not used functions in the code. Maybe I would use better temporary variable names (like GMSL uses _gmsl). Sometimes temporary variables could be left off, but the code would be more cryptic.

.SILENT:
S :=
SP := $S $S

# For non empty strings
#not = $(if $1,$S,T)
#str.ne = $(if $(subst $1,,$2),T,$S)
str.eq = $(if $(subst $1,,$2),$S,T)
str.le = $(call str.eq,$(word 1,$(sort $1 $2)),$1)
#str.ge = $(call str.eq,$(word 1,$(sort $1 $2)),$2)

# Creates a list of digits from a number
mklist = $(eval __tmp := $1)$(foreach i,0 1 2 3 4 5 6 7 8 9,$(eval __tmp := $$(subst $$i,$$i ,$(__tmp))))$(__tmp)
# reverse: $(subst $(SP),,$(list))

#pop = $(wordlist 2, $(words $1), x $1)
#push = $1 $2
shift = $(wordlist 2, $(words $1), $1)
#unshift = $2 $1

num.le = $(eval __tmp1 := $(call mklist,$1))$(eval __tmp2 := $(call mklist,$2))$(if $(call str.eq,$(words $(__tmp1)),$(words $(__tmp2))),$(call str.le,$1,$2),$(call str.le,$(words $(__tmp1)),$(words $(__tmp2))))

#num.ge = $(eval __tmp1 := $(call mklist,$1))$(eval __tmp2 := $(call mklist,$2))$(if $(call str.eq,$(words $(__tmp1)),$(words $(__tmp2))),$(call str.ge,$1,$2),$(call str.ge,$(words $(__tmp1)),$(words $(__tmp2))))

#Strip zeroes from the beginning of a list
list.strip = $(eval __flag := 1)$(foreach d,$1,$(if $(__flag),$(if $(subst 0,,$d),$(eval __flag :=)$d,$S),$d))
#Strip zeroes from the beginning of a number
#num.strip = $(subst $(SP),,$(call list.strip,$(call mklist,$1)))

# temp string: 0 - two number equals, L first LT, G first GT or second is short,
gen.cmpstr = $(eval __Tmp1 := $(subst ., ,$1))$(eval __Tmp2 := $(subst ., ,$2))$(foreach i,$(__Tmp1),$(eval j := $(word 1,$(__Tmp2)))$(if $j,$(if $(call str.eq,$i,$j),0,$(if $(call num.le,$i,$j),L,G)),G)$(eval __Tmp2 := $$(call shift,$(__Tmp2))))$(if $(__Tmp2), L)

ver.lt = $(call str.eq,$(word 1,$(call list.strip,$(call gen.cmpstr,$1,$2))),L)

all:
    echo ver.lt,1.20,1.3:$(call ver.lt,1.20,1.3)%
    echo ver.lt,1.5.9,1.5:$(call ver.lt,1.5.9,1.5)%
    echo ver.lt,1.4.9,1.5:$(call ver.lt,1.4.9,1.5)%
    echo ver.lt,1.2,1.2.0:$(call ver.lt,1.2,1.2.0)%
    echo ver.lt,1.20.3.4.5,1.10.5:$(call ver.lt,1.20.3.4.5,1.10.5)%
    echo ver.lt,1.20.3.4.5,1.0.5:$(call ver.lt,1.20.3.4.5,1.0.5)%
    echo ver.lt,1.0,1.0.5:$(call ver.lt,1.0,1.0.5)%
    echo ver.lt,1.20,1.10.3:$(call ver.lt,1.20,1.10.3)%
    echo ver.lt,1.20,1.30.3::$(call ver.lt,1.20,1.30.3)%
    echo ver.lt,1.10.3,1.10.3:$(call ver.lt,1.10.3,1.10.3)%

And the output

ver.lt,1.20,1.3:%
ver.lt,1.5.9,1.5:%
ver.lt,1.4.9,1.5:T%
ver.lt,1.2,1.2.0:T%
ver.lt,1.20.3.4.5,1.10.5:%
ver.lt,1.20.3.4.5,1.0.5:%
ver.lt,1.0,1.0.5:T%
ver.lt,1.20,1.10.3:%
ver.lt,1.20,1.30.3::T%
ver.lt,1.10.3,1.10.3:%

More music

I have found another interesting project called makepp (makepp.sourceforge.net). It enables to implement new functions in perl inside the makefile.

TrueY
  • 7,360
  • 1
  • 41
  • 46
  • Actually I have used this to check the version number of glibc. I used the following command to get the version number: `GLIBC := $(lastword $(shell getconf GNU_LIBC_VERSION))` – TrueY Mar 26 '13 at 13:36
  • It was not intended, but it also works if `RH_VER_NUM` is 5.4.3.2. If the checked version is also has multiple dots, then the sort should be extended. In case of three dots a `-k3,3n -k4,4n` could be added to the end of the `sort`. – TrueY Mar 27 '13 at 12:03
  • You may want to take a look at [gmtt](https://github.com/markpiffer/gmtt) which has support for octal, decimal and hexadecimal numbers, positive as well as negative. – Vroomfondel Feb 03 '20 at 09:08
5

The simplest solution I think of is by using bc:

# Find out which version of Red-Hat we're running
RH_VER_NUM = $(shell /bin/grep -o [0-9].[0-9] /etc/redhat-release)
RH_GT_5_3 = $(shell echo $(RH_VER_NUM)\>=5.3 | bc )

ifeq ($(RH_GT_5_3),1)
CPPFLAGS += -DRH_GT_5_3=1
endif

This will be equal to 1 if bigger than 5.3, and 0 otherwise.

bc is an arbitrary precision calculator language. Here are some documentation about bc:

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
3

Kudos to TrueY for coming up with a version that doesn't use any expensive shell commands. I have a simpler version that only compares 2 small integers (often you only care about the major version).

The idea is simply

x > y

= y is member of {0,1,2,3,4 ..., x - 1)

The member of operator can be implemented with GNU make's $(filter) and generating the set can be done with $(wordlist)

# returns all integers less than x
LessThanSubset=$(wordlist 1,$(1),0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)

# if x > y, return 1, empty otherwise
GreaterThan=$(if $(filter $(2),$(call LessThanSubset,$(1))),1)
GreaterOrEqual=$(if $(filter $(2),$(call LessThanSubset,$(1)) $(1)),1)

# example
$(error 5 > 4 = $(call GreaterThan,5,4))
Yale Zhang
  • 1,447
  • 12
  • 30
2

I realize this is an old question, but still. Actually you can resort to lsb_release, which I found to be installed on every single recent RedHat system.

To be precise you'd make use of

lsb_release -sr

inside $(shell ...) and then split at the . like this:

RH_VER:=$(shell lsb_release -sr)
RH_MAJVER:=$(word 1, $(subst ., ,$(RH_VER)))
RH_MINVER:=$(word 2, $(subst ., ,$(RH_VER)))

you now have the major/minor parts of the release version and can check that against whatever you like.

The classic case would be $(filter ...) and the other text functions that GNU make provides.

Nevertheless I agree that a kind of a config.h would make more sense, though Autotools is by no means perfect for all scenarios (there are other build tools that attempt the same, though).

0xC0000022L
  • 20,597
  • 9
  • 86
  • 152
2

What about using sort function:

ifeq "$(firstword $(sort $(RH_VER_NUM),5.3))" "5.3"

RH_GT_5_3 = 1

endif

(well it would actually test for greater or equal, but you get the idea)

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
Yevgeniy P
  • 1,480
  • 1
  • 15
  • 23
  • 2
    This code believes 5.10 is smaller than 5.2. Sorting version numbers is not that easy. – MarcH Jan 31 '20 at 23:45
1

A modern way would be to get the version into make (using one shell invocation), e.g.:

SHELL := /bin/bash
# Define redhat_release as major_version * 100 + minor_version
redhat_release := $(shell [[ "$$(cat /etc/redhat-release)" =~ ([0-9]+)\.([0-9]+) ]] && echo $$(( $${BASH_REMATCH[1]} * 100 + $${BASH_REMATCH[2]} )))

And then have version specific flags:

# Flags for different redhat versions
redhat_flags.604 := abc
redhat_flags.605 := ${redhat_flags.604} def 
redhat_flags := ${redhat_flags.${redhat_release}}

And print them out:

$(info redhat_release=${redhat_release})
$(info redhat_flags=${redhat_flags})

Output:

$ make
redhat_release=605
redhat_flags=abc def
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

Building on TrueY's smart use of sort and making it a re-usable Make function:

version_greater_equal = $(shell if printf '%s\n%s\n' '$(2)' '$(1)' | \
    sort -Ct. -k1,1n -k2,2n ; then echo YES; else echo NO; fi )

ifeq (YES,$(call version_greater_equal,${SOME_VERSION},8.2))
...
MarcH
  • 18,738
  • 1
  • 30
  • 25
  • Unfortunately, it doesn't work within Android 10 build while it works outside with the host env :/. The sort function used does not have options C and t. It's a very special case... – vhamon Feb 02 '21 at 15:50
  • Android uses busybox or similar. – MarcH Feb 03 '21 at 16:05
  • 1
    I was not talking about the sort command on the target machine running Android. I was talking about the sort command used on the host (the computer that builds). These are two different things. This is a cross-compilation. Anyway, I found out which sort binary was used so it should be fine. This is something new when building Android as explained [here](https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools). It uses a different PATH in order to avoid dependence with host machine. Also, it uses "toybox" as prebuilt for the cross-compilation. – vhamon Feb 04 '21 at 08:08
0

With the arithmetic capabilities of the GNUmake table toolkit you can write a quite general test - the one thing you need to adapt is the Lisp-like functional style ("function op1,op2,opN"):

include gmtt/gmtt.mk

# Simulate some nasty versioning style:
# X.Y.Z.rN for release versions,
# X.rN-beta.Y.Z for beta versions towards a release
VERSION_NR_REL := $(call glob-match,$(VERSION_NR),*.*.*.r*)
VERSION_NR_BETA := $(call glob-match,$(VERSION_NR),*.r*-beta.*.*)

# glob-match only produces a non-empty result if the match succeeds,
# thus one of the following will be an empty string
$(info Release version: $(VERSION_NR_REL))
$(info Beta version: $(VERSION_NR_BETA))

# we use makes "if" function which sees empty strings as false and everything else as true
BETA_OR_RELEASE := $(if $(VERSION_NR_REL),release,beta)

# react to version numbers on different positions in release and beta
ifeq ($(BETA_OR_RELEASE),beta)
MAJOR_V := $(word 1,$(VERSION_NR_BETA))
MINOR_V := $(word 5,$(VERSION_NR_BETA))
BUGFIX_V := $(word 7,$(VERSION_NR_BETA))
REL_NR := $(word 3,$(VERSION_NR_BETA))
else
MAJOR_V := $(word 1,$(VERSION_NR_REL))
MINOR_V := $(word 3,$(VERSION_NR_REL))
BUGFIX_V := $(word 5,$(VERSION_NR_REL))
REL_NR := $(word 7,$(VERSION_NR_REL))
endif

GT_5_3 := $(if $(or $(call int-gt,$(MAJOR_V),5),\
                    $(and $(call int-eq,$(MAJOR_V),5),\
                          $(call int-gt,$(MINOR_V),3))),\
            yes,no)
$(info Major: $(MAJOR_V), Minor: $(MINOR_V), Bugfix: $(BUGFIX_V), Release: $(REL_NR), greater than 5.3: $(GT_5_3))

Test with a larger version number:

make VERSION_NR=18.r01-beta.3.5

Output:

Release version:
Beta version:  18 .r 01 -beta. 3 . 5
Major: 18, Minor: 3, Bugfix: 5, Release: 01, greater than 5.3:  yes

Test with a version equal to 5.3:

make VERSION_NR=5.3.12.r3

Output:

Release version:  5 . 3 . 12 .r 3
Beta version:
Major: 5, Minor: 3, Bugfix: 12, Release: 3, greater than 5.3: no
Vroomfondel
  • 2,704
  • 1
  • 15
  • 29
0

I had to compare GLIBC versions, I used:

GLIBC_VERSION := $(shell ldd --version | head -1 | awk '{print $$NF}')
ifeq ($(shell awk -v a="$(GLIBC_VERSION)" -v b="2.35" 'BEGIN{print(a<b)}'), 1)
   # do something if GLIBC less than 2.35
endif

this works because awk can handle floating-point comparison and glibc doesn't have a third version number field