Recently we switched our MCU and along with it our buildsystem from Make to eclipse managed build using the manufacturer provided plugin. The target management in eclipse doesn't cover our use case so it was my task to find a suitible buildsystem and I decided to give CMake a try. One task was to extract the versioning information of our VCS to generate a version.h header file. Today I show how this could be achieved without the need of external tools (except from CMake itself, and of course mercurial).

Our versioning scheme's using mercurial TAG and is build like v[MAJOR].[MINOR][RT][RN] :

  • MAJOR - one digit
  • Minor - two digits
  • RT - Release type: one of {ALPHA, BETA, RELEASE}
  • RN - one digit release number

log from mercurial; desired version.h

Header Prototyp

CMake needs a prototype of the header file. I called it version.h.in

/**
 * \brief Version information
 *
 * This file is automatically generated by the buildsystem.
 * Do not edit.
 */

#ifndef VERSION_H
#define VERSION_H

#define VERSION_NODE                    "${HG_NODE}"
#define VERSION_AUTHOR                  "${HG_AUTHOR}"
#define VERSION_TAGS                    "${HG_TAGS}"
#define VERSION_MAJOR                   ${HG_VERSION_MAJOR}
#define VERSION_MINOR                   ${HG_VERSION_MINOR}
#define VERSION_RELEASE_TYPE_ALPHA      0
#define VERSION_RELEASE_TYPE_BETA       1
#define VERSION_RELEASE_TYPE_RELEASE    2
#define VERSION_RELEASE_TYPE            ${HG_VERSION_RELEASE_TYPE}
#define VERSION_RELEASE_NUMBER          ${HG_VERSION_RELEASE_NUMBER}

#endif /* VERSION_H */

mercurial.cmake

CMake ships with the command find_package(Hg) to find out if mercurial is installed and also sets the variable for calling mercurial.

First of all we need to find out which commit is actually checkded out and also if the working directory was altered. The latter is signaled by a plus sign at the end of the node id.

Next hg log and a template can provide all neccessary information. I choose a semicolon as a sepeartor so CMake can directly treat the answer as a list.

Eventually configure_file is called with the template generated earlier.

To build the file CMake is called using cmake -P mercurial.cmake.

########################################################
# Check if mercurial is installed
find_package(Hg)
if(HG_FOUND)
    ####################################################
    # Some default values
    set(HG_VERSION_MAJOR -1)
    set(HG_VERSION_MINOR -1)
    set(HG_VERSION_RELEASE_TYPE VERSION_RELEASE_TYPE_ALPHA)
    set(HG_VERSION_RELEASE_NUMBER -1)

    ####################################################
    # Get the current changeset
    execute_process(
        COMMAND ${HG_EXECUTABLE} id -i
        OUTPUT_VARIABLE HG_NODE
        )
    string(STRIP ${HG_NODE} HG_NODE) # Remove any trailing or leading whitespaces

    ####################################################
    # Check if the working directory is clean
    string(FIND ${HG_NODE} "+" HG_WD_DIRTY)
    if(${HG_WD_DIRTY} LESS 0)
        set(HG_WD_DIRTY OFF)

        ################################################
        # Query all information needed...
        execute_process(
            COMMAND ${HG_EXECUTABLE} log -r${HG_NODE} --template="\;{node}\;{tags}\;{author}\;{date|shortdate}\;{date\(date,'%H:%M:%S'\)}\;"
            OUTPUT_VARIABLE HG_QUERY
            )
        ################################################
        # ...and sort them into the right variables
        list(GET HG_QUERY 1 HG_NODE_FULL)
        list(GET HG_QUERY 3 HG_AUTHOR)
        list(GET HG_QUERY 4 HG_DATE)
        list(GET HG_QUERY 5 HG_TIME)
        list(GET HG_QUERY 2 HG_TAGS)

        ################################################
        # Check if a 'version' Tag is found and extract
        # it's information
        string(FIND ${HG_TAGS} "v" HG_VERSION_IN_TAG)
        if(NOT ${HG_VERSION_IN_TAG} LESS 0)
            string(SUBSTRING ${HG_TAGS} ${HG_VERSION_IN_TAG} 7 HG_VERSION_TAG)
            string(SUBSTRING ${HG_VERSION_TAG} 1 1 HG_VERSION_MAJOR)
            string(SUBSTRING ${HG_VERSION_TAG} 3 2 HG_VERSION_MINOR)
            string(SUBSTRING ${HG_VERSION_TAG} 5 1 HG_VERSION_RELEASE_TYPE)
            string(SUBSTRING ${HG_VERSION_TAG} 6 1 HG_VERSION_RELEASE_NUMBER)
            if(${HG_VERSION_RELEASE_TYPE} STREQUAL "a")
                set(HG_VERSION_RELEASE_TYPE "VERSION_RELEASE_TYPE_ALPHA")
            elseif(${HG_VERSION_RELEASE_TYPE} STREQUAL "b")
                set(HG_VERSION_RELEASE_TYPE "VERSION_RELEASE_TYPE_BETA")
            elseif(${HG_VERSION_RELEASE_TYPE} STREQUAL "r")
                set(HG_VERSION_RELEASE_TYPE "VERSION_RELEASE_TYPE_RELEASE")
            endif()
        endif()
    else()
        set(HG_WD_DIRTY ON)
    endif()
    configure_file(version.h.in version.h)
endif()

Previous Post Next Post