Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use CMake to get build-time Subversion revision

Tags:

svn

cmake

With CMake I can get the Subversion revision using Subversion_WC_INFO. However, this only happens at configure time -- every subsequent make will use this cached revision.

I'd like to get the svn revision at build time (ie, every time Make is run). How can I do this?

like image 202
paleozogt Avatar asked Sep 23 '10 16:09

paleozogt


2 Answers

This is more of a pain to do than it needs to be. You can run a program at build time to extract the version information from subversion. CMake itself can be used as this program using its scripting language. You can run any CMake script using the -P command line flag. The piece of code to do this would be (To be placed in file getsvn.cmake):

# the FindSubversion.cmake module is part of the standard distribution
include(FindSubversion)
# extract working copy information for SOURCE_DIR into MY_XXX variables
Subversion_WC_INFO(${SOURCE_DIR} MY)
# write a file with the SVNVERSION define
file(WRITE svnversion.h.txt "#define SVNVERSION ${MY_WC_REVISION}\n")
# copy the file to the final header only if the version changes
# reduces needless rebuilds
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        svnversion.h.txt svnversion.h)

This extracts the subversion revision information and writes it to a header file. This header file is only updated when needed to avoid recompiling all the time. In your program, you would include that header file to get at the SVNVERSION define.

The CMakeLists.txt file that you would need to run the script would be something like this:

# boilerplate
cmake_minimum_required(VERSION 2.8)
project(testsvn)

# the test executable
add_executable(test main.c ${CMAKE_CURRENT_BINARY_DIR}/svnversion.h)

# include the output directory, where the svnversion.h file is generated
include_directories(${CMAKE_CURRENT_BINARY_DIR})

# a custom target that is always built
add_custom_target(svnheader ALL
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/svnheader.h)

# creates svnheader.h using cmake script
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/svnheader.h
    COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/getsvn.cmake)

# svnversion.h is a generated file
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/svnversion.h
    PROPERTIES GENERATED TRUE
    HEADER_FILE_ONLY TRUE)

# explicitly say that the executable depends on the svnheader
add_dependencies(test svnheader)

You need to use add_custom_target and add_custom_command since there are no inputs to the svnheader.h file, it "appears from nowhere" to cmake. I already dealt with the issue of needless rebuilds in the getsvn.cmake file though, so rebuilding the "svnheader" target is basically a no-op if the subversion revision has not changed.

Finally, an example main.c file:

#include "svnversion.h"

int main(int argc, const char *argv[])
{
    printf("%d\n", SVNVERSION);
    return 0;
}
like image 127
richq Avatar answered Sep 18 '22 12:09

richq


I believe I found the problem @janitor048 had with @richq's answer. Unfortunately, I don't have enough reputation to comment on his answer - maybe someone else can copy and paste.

@richq uses both a custom command and a custom target. The custom command is necessary to convince CMake that the header will be created, otherwise the CMake script could be executed as a command for the custom target. While a custom target will always be executed, a custom command won't if its output file already exists.

The workaround is to add a bogus dependency (an imaginary file) to the custom target, and tell CMake that the custom command creates that file. This is enough to ensure that the custom command is always executed. Fortunately, CMake doesn't actually check whether this file is created or not.

richq has:

# a custom target that is always built
add_custom_target(svnheader ALL
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/svnheader.h)

# creates svnheader.h using cmake script
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/svnheader.h
    COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/getsvn.cmake)

This works for me:

# a custom target that is always built
add_custom_target(svnheader ALL
    DEPENDS svn_header ) # svn_header is nothing more than a unique string

# creates svnheader.h using cmake script
add_custom_command(OUTPUT svn_header ${CMAKE_CURRENT_BINARY_DIR}/svnheader.h
    COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/getsvn.cmake)

Also, if someone wishes to use Git, use this for the CMake script:

#create a pretty commit id using git
#uses 'git describe --tags', so tags are required in the repo
#create a tag with 'git tag <name>' and 'git push --tags'

find_package(Git)
if(GIT_FOUND)
    execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags RESULT_VARIABLE res_var OUTPUT_VARIABLE GIT_COM_ID )
    if( NOT ${res_var} EQUAL 0 )
        set( GIT_COMMIT_ID "git commit id unknown")
        message( WARNING "Git failed (not a repo, or no tags). Build will not contain git revision info." )
    endif()
    string( REPLACE "\n" "" GIT_COMMIT_ID ${GIT_COM_ID} )
else()
    set( GIT_COMMIT_ID "unknown (git not found!)")
    message( WARNING "Git not found. Build will not contain git revision info." )
endif()

set( vstring "//version_string.hpp - written by cmake. changes will be lost!\n"
             "const char * VERSION_STRING = \"${GIT_COMMIT_ID}\"\;\n")

file(WRITE version_string.hpp.txt ${vstring} )
# copy the file to the final header only if the version changes
# reduces needless rebuilds
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        version_string.hpp.txt ${CMAKE_CURRENT_BINARY_DIR}/version_string.hpp)
like image 36
Mark Avatar answered Sep 18 '22 12:09

Mark