CMakeCompareVersionStrings

From KitwarePublic
Revision as of 17:38, 24 September 2007 by Bigler (talk | contribs) (Filled in text for COMAPRE_VERSION_STRING macro)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

I needed a way to compare two version strings to make sure that I had a version of a package that was sufficient for my needs. The first go around I used regular expressions to pull out all the dot versions, create a number, and finally compare the number.

SET(THREE_PART_VERSION_REGEX "[0-9]+\\.[0-9]+\\.[0-9]+")

# Breaks up a string in the form n1.n2.n3 into three parts and stores
# them in major, minor, and patch.  version should be a value, not a
# variable, while major, minor and patch should be variables.
MACRO(THREE_PART_VERSION_TO_VARS version major minor patch)
  IF(${version} MATCHES ${THREE_PART_VERSION_REGEX})
    STRING(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" ${major} "${version}")
    STRING(REGEX REPLACE "^[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" ${minor} "${version}")
    STRING(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" ${patch} "${version}")
  ELSE(${version} MATCHES ${THREE_PART_VERSION_REGEX})
    MESSAGE("MACRO(THREE_PART_VERSION_TO_VARS ${version} ${major} ${minor} ${patch}")
    MESSAGE(FATAL_ERROR "Problem parsing version string, I can't parse it properly.")
  ENDIF(${version} MATCHES ${THREE_PART_VERSION_REGEX})
ENDMACRO(THREE_PART_VERSION_TO_VARS)

THREE_PART_VERSION_TO_VARS(${version-string} major_vers minor_vers patch_vers)
MESSAGE("version        = ${major_vers}.${minor_vers}.${patch_vers}")

# Compute a version number
MATH(EXPR version_number "${major_vers} * 1000000 + ${minor_vers} * 1000 + ${patch_vers}" )
MESSAGE("version_number = ${version_number}")

This was somewhat cumbersome, and was hard coded for three dot versions (x.y.z). I wanted to be able to use a single macro for version strings with 1 to any number of dot versions. In addition I wanted to be able to compare version strings with different number of dot versions (i.e. 0.99.1 and 0.99.1.2).

# Computes the realtionship between two version strings.  A version
# string is a number delineated by '.'s such as 1.3.2 and 0.99.9.1.
# You can feed version strings with different number of dot versions,
# and the shorter version number will be padded with zeros: 9.2 <
# 9.2.1 will actually compare 9.2.0 < 9.2.1.
#
# Input: a_in - value, not variable
#        b_in - value, not variable
#        result_out - variable with value:
#                         -1 : a_in <  b_in
#                          0 : a_in == b_in
#                          1 : a_in >  b_in
#
# Written by James Bigler.
MACRO(COMPARE_VERSION_STRINGS a_in b_in result_out)
  # Since SEPARATE_ARGUMENTS using ' ' as the separation token,
  # replace '.' with ' ' to allow easy tokenization of the string.
  STRING(REPLACE "." " " a ${a_in})
  STRING(REPLACE "." " " b ${b_in})
  SEPARATE_ARGUMENTS(a)
  SEPARATE_ARGUMENTS(b)

  # Check the size of each list to see if they are equal.
  LIST(LENGTH a a_length)
  LIST(LENGTH b b_length)

  # Pad the shorter list with zeros.

  # Note that range needs to be one less than the length as the for
  # loop is inclusive (silly CMake).
  IF(a_length LESS b_length)
    # a is shorter
    SET(shorter a)
    MATH(EXPR range "${b_length} - 1")
    MATH(EXPR pad_range "${b_length} - ${a_length} - 1")
  ELSE(a_length LESS b_length)
    # b is shorter
    SET(shorter b)
    MATH(EXPR range "${a_length} - 1")
    MATH(EXPR pad_range "${a_length} - ${b_length} - 1")
  ENDIF(a_length LESS b_length)

  # PAD out if we need to
  IF(NOT pad_range LESS 0)
    FOREACH(pad RANGE ${pad_range})
      # Since shorter is an alias for b, we need to get to it by by dereferencing shorter.
      LIST(APPEND ${shorter} 0)
    ENDFOREACH(pad RANGE ${pad_range})
  ENDIF(NOT pad_range LESS 0)

  SET(result 0)
  FOREACH(index RANGE ${range})
    IF(result EQUAL 0)
      # Only continue to compare things as long as they are equal
      LIST(GET a ${index} a_version)
      LIST(GET b ${index} b_version)
      # LESS
      IF(a_version LESS b_version)
        SET(result -1)
      ENDIF(a_version LESS b_version)
      # GREATER
      IF(a_version GREATER b_version)
        SET(result 1)
      ENDIF(a_version GREATER b_version)
    ENDIF(result EQUAL 0)
  ENDFOREACH(index)

  # Copy out the return result
  SET(${result_out} ${result})
ENDMACRO(COMPARE_VERSION_STRINGS)

I tested it with the following input:

  SET(version-string "1.3.31")

  COMPARE_VERSION(${version-string} "1.3.30")
  COMPARE_VERSION(${version-string} "1.3.31")
  COMPARE_VERSION(${version-string} "1.3.32")
  COMPARE_VERSION(${version-string} "1.2.32")
  COMPARE_VERSION(${version-string} "1.10.1")

  COMPARE_VERSION("9.2" "9.1")
  COMPARE_VERSION("9.2" "9.2")
  COMPARE_VERSION("9.2" "9.3")

  COMPARE_VERSION("9.1" "9.2")
  COMPARE_VERSION("9.2" "9.2")
  COMPARE_VERSION("9.3" "9.2")

  COMPARE_VERSION("9.10" "9.2")
  COMPARE_VERSION("9.2"  "9.10")

  COMPARE_VERSION("0.92.1.0" "0.92.1.1")
  COMPARE_VERSION("0.92.1.1" "0.92.1.1")
  COMPARE_VERSION("0.92.1.2" "0.92.1.1")

  COMPARE_VERSION("0.92.1.2" "0.99.1.1")
  COMPARE_VERSION("0.99.1.2" "0.92.1.1")

  COMPARE_VERSION("0.92.1.2" "0.99.1")
  COMPARE_VERSION("0.99.1" "0.92.1.1")

  COMPARE_VERSION("0.99.1.2" "0.99.1")
  COMPARE_VERSION("0.99.1" "0.99.1.1")

  COMPARE_VERSION( "1" "10")
  COMPARE_VERSION("10"  "1")

Here's the output:

1.3.31 >  1.3.30
1.3.31 == 1.3.31
1.3.31 <  1.3.32
1.3.31 >  1.2.32 (note this is 1.2 not 1.3)
1.3.31 <  1.10.1
9.2 >  9.1
9.2 == 9.2
9.2 <  9.3
9.1 <  9.2
9.2 == 9.2
9.3 >  9.2
9.10 >  9.2
9.2 <  9.10
0.92.1.0 <  0.92.1.1
0.92.1.1 == 0.92.1.1
0.92.1.2 >  0.92.1.1
0.92.1.2 <  0.99.1.1
0.99.1.2 >  0.92.1.1
0.92.1.2 <  0.99.1
0.99.1 >  0.92.1.1
0.99.1.2 >  0.99.1
0.99.1 <  0.99.1.1
1 <  10
10 >  1