开发者

Use CMake to get build-time Subversion revision

With CMake I can g开发者_StackOverflow社区et 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?


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;
}


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)


I found that the answer given by richq does not exactly do what one would expect - namely it does not automatically update the file svnversion.h (in which the svn revision info is stored) when only building (but not configuring) the source, i.e. when simply calling make.

Here is a slightly modified version of his procedure that should provide the necessary behavior. Credit for the original solution goes to richq of course.

His getsvn.cmake script remains unchanged, here it is for the sake of completeness (see his answer for comments and explanation)

INCLUDE(FindSubversion)
Subversion_WC_INFO(${SOURCE_DIR} MY)
FILE(WRITE svnversion.h.txt "#define SVNVERSION ${MY_WC_REVISION}\n")
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy_if_different
      svnversion.h.txt svnversion.h)

Now the trick is to use ADD_CUSTOM_COMMAND with a different signature (see CMake Documentation), namely specifying the target with which the command should be associated. It then gets executed each and every time the target is built - together with an empty target from ADD_CUSTOM_TARGET that is always considered out-of-date this gives the desired behavior. Here's what the CMakeLists.txt could look like (based again on the script by richq):

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(testsvn)

# the test executable
ADD_EXECUTABLE(test main.c)

# 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(revisiontag ALL)

# creates svnversion.h using cmake script
ADD_CUSTOM_COMMAND(TARGET revisiontag COMMAND ${CMAKE_COMMAND}
   -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} 
   -P ${CMAKE_CURRENT_SOURCE_DIR}/getsvn.cmake)

# explicitly say that the executable depends on custom target
add_dependencies(test revisiontag)

Now the script getsvn.cmake is executed every time the target revisiontag is built, which means every time make is issued. Test by commenting out the line with FILE(..) in getsvn.cmake and manually editing svnversion.h.txt.


I don't know of any CMake-specific functionality to do this, but it's easy enough to do yourself, as one of your build steps. Run svnversion from a script and parse its output, or (easier) run TortoiseSVN's subwcrev program (which has also been ported to other operating systems, so it's not Windows-specific).


Ideally this would be a comment on richq's answer, but I don't have enough reputation yet. If an admin could convert it to one, that would be great.

One issue I found (apart from the dependency one that was resolved by Mark) is that the SVN revision support in CMake only includes the "base revision", not any modifiers (in particular whether it is an "M" (modified) build).

So instead of the suggested getsvn.cmake, I used this:

# Set current SVN version into a variable
execute_process(COMMAND svnversion ${SOURCE_ROOT} OUTPUT_VARIABLE MY_WC_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE)
# write a file with the SVNVERSION define
file(WRITE svnversion.h.txt "#define SVN_REV \"${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)

And because I don't just want the revision of the current directory, I am passing SOURCE_ROOT (the directory for the top of the SVN tree) in to the custom command, instead of SOURCE_DIR, and I work out the correct value for that elsewhere in my CMakeLists.txt tree


I'm actually looking for the same thing and I found this: howto-use-cmake-with-cc-projects It seems much simpler but I'm wondering if it is as efficient as rq's answer.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜