How to transfer version number from a git tag to the CPack-generated source package?

2.5k views Asked by At

In a CMake project, I am using git tags for version numbers. Using the approach from https://github.com/iPenguin/version_git, I am able to get the version number into the source codes (that is, CMake creates a version.cpp file with a const string containing the version number that I can then use in my C++ source codes). The problem is that when I create a source package using CPack, I exclude the .git directory and when compiling the sources extracted from the package, the version number is not available.

How to make CPack put the version number into the source package?

I have the following requirements:

  1. I do not want the build directory to be included in my source package. It is important to note that I do out-of-source builds from a directory under the project root. That is, if my toplevel CMakeLists.txt is in directory my_project, then I run "cmake .." and "make" in directory my_project/build.

  2. I do not want to include the .git directory in my source package.

  3. I want all the files generated by cmake and "make package_source" to be inside the build directory. Additionally, I don't want cmake to modify or delete any file outside of the build directory.

  4. I do not want to use undocumented behavior of cmake and cpack, in particular the paths used by cpack.

So far I was not able to find a solution that would satisfy all my requirements. Here is what I have tried:

I let cmake create a file versionForSourcePackage.cmake, which, when later included by cmake, will set the VERSION variable. Then I want to put this file into the source package, so that when cmake is run in the extracted package, it will have the VERSION variable set. This file is created under the build directory, but I do not know how to properly let CPack copy it to the source packages.

The first possibility is a slight modification of https://github.com/iPenguin/version_git, but it does not satisfy my requirement number 4. Full example is at https://github.com/josefcibulka/version_git.

When building from repository, I get the version number from git and save it in ${PROJECT_BINARY_DIR}/versionForSourcePackage.cmake. Then I use CPACK_INSTALL_COMMANDS to let CPack copy this file to ${PROJECT_BINARY_DIR}/_CPack_Packages/Linux-Source/${CPACK_SOURCE_GENERATOR}/${CPACK_SOURCE_PACKAGE_FILE_NAME}/ so that it is included in the package. This violates the requirement number 4 and moreover, if I want to create both TGZ and ZIP packages in the future, I need to make some changes. Is there some variable that I could use within CPACK_INSTALL_COMMANDS to get the path to the directory where CPack is preparing the contents of the package?

CMakeLists.txt looks like:

cmake_minimum_required(VERSION 2.8)

project("version_from_git")

# Appends the cmake/modules path to MAKE_MODULE_PATH variable.
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})

# When in the extracted package, use the previously generated file, otherwise get the current version from git.
if(EXISTS "${PROJECT_SOURCE_DIR}/versionForSourcePackage.cmake")
  include("${PROJECT_SOURCE_DIR}/versionForSourcePackage.cmake")
else()
  include(GetGitRevisionDescription)
  git_describe(VERSION --tags --dirty=-dirty)
endif()

# Parse the version information into pieces.
string(REGEX REPLACE "^v([0-9]+)\\..*" "\\1" VERSION_MAJOR "${VERSION}")
string(REGEX REPLACE "^v[0-9]+\\.([0-9]+).*" "\\1" VERSION_MINOR "${VERSION}")
string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${VERSION}")
string(REGEX REPLACE "^v[0-9]+\\.[0-9]+\\.[0-9]+(.*)" "\\1" VERSION_SHA1 "${VERSION}")
set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")

set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}")
# The following will copy the version file to the source package.
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_SHORT}-Source")
set(CPACK_INSTALL_COMMANDS "${CMAKE_COMMAND} -E make_directory \
    ${PROJECT_BINARY_DIR}/_CPack_Packages/Linux-Source/${CPACK_SOURCE_GENERATOR}/${CPACK_SOURCE_PACKAGE_FILE_NAME}/"
    "${CMAKE_COMMAND} -E copy \
    ${PROJECT_BINARY_DIR}/versionForSourcePackage.cmake \
    ${PROJECT_BINARY_DIR}/_CPack_Packages/Linux-Source/${CPACK_SOURCE_GENERATOR}/${CPACK_SOURCE_PACKAGE_FILE_NAME}/")
# Exclude the build and .git directory from the source package.
set(CPACK_SOURCE_IGNORE_FILES "${PROJECT_SOURCE_DIR}/.git/;${PROJECT_BINARY_DIR}/;${CPACK_SOURCE_IGNORE_FILES}")

include (CPack)

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/version.cpp.in
                ${CMAKE_CURRENT_BINARY_DIR}/version.cpp)
# Prepare the versionForSourcePackage.cmake file that will be included in the source package.
configure_file(
   ${PROJECT_SOURCE_DIR}/versionForSourcePackage.cmake.in
   ${PROJECT_BINARY_DIR}/versionForSourcePackage.cmake @ONLY)
set(version_file "${CMAKE_CURRENT_BINARY_DIR}/version.cpp")

set(source_files "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp")

#Add the version_file to the project build.
add_executable(${PROJECT_NAME} ${source_files} ${version_file})

The file versionForSourcePackage.cmake.in:

set(VERSION "v@VERSION_SHORT@")

The second possibility is the approach used at https://github.com/lcw/cmake_git_version. Full example is in the second_possibility branch in https://github.com/josefcibulka/version_git. The file versionForSourcePackage.cmake is placed to the subdirectory version_file of the build directory and this directory is added among CPACK_SOURCE_INSTALLED_DIRECTORIES. This works well when the project and build directory are at the same level. The problem is that if the build directory is under the project directory, I add the build directory to CPACK_SOURCE_IGNORE_FILES to satisfy requirement 1. Then, even when I set CPACK_SOURCE_INSTALLED_DIRECTORIES to include the version_file directory, it will be ignored. It can be done so that I ignore only everything in the build directory except for the version_file directory. Then the build directory in the source package contains only the version_file directory, which is better than the whole build directory, but still not perfect.

Update: It seems that requirement 4 is not satisfied either, because I cannot find CPACK_SOURCE_INSTALLED_DIRECTORIES in the CMake documentation.

The differences from the first possibility are, in CMakeLists.txt:

# The following will transfer the version from git to the source package.
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${PROJECT_SOURCE_DIR};/;${PROJECT_BINARY_DIR}/version_file;/")
# Exclude the build and .git directory from the source package.
set(CPACK_SOURCE_IGNORE_FILES "${PROJECT_SOURCE_DIR}/.git/;${PROJECT_BINARY_DIR}/([^v].*|v[^e].*|ve[^r].*|ver[^s].*|vers[^i].*|vers[^i].*|versi[^o].*|versio[^n].*|version[^_].*|version_[^f].*|version_f[^i].*|version_fi[^l].*|version_fil[^e].*);${CPACK_SOURCE_IGNORE_FILES}")

include (CPack)

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/version.cpp.in
                ${CMAKE_CURRENT_BINARY_DIR}/version.cpp)
configure_file(
   ${PROJECT_SOURCE_DIR}/versionForSourcePackage.cmake.in
   ${PROJECT_BINARY_DIR}/version_file/versionForSourcePackage.cmake @ONLY)
1

There are 1 answers

0
pepan On

At the end, I am using a modification of the first possibility, but the issues are solved only partially.

Instead of CPACK_INSTALL_COMMANDS, I use CPACK_INSTALL_SCRIPT and a CMake script. This script is executed for every package that is being created and the nice thing is that inside of this script, CMAKE_CURRENT_BINARY_DIR points to the directory where the contents of the package are being gathered. That is, whatever I copy to that directory will get inside the package.

This, however, looks to be an undocumented behavior of CMAKE_CURRENT_BINARY_DIR. For example, the CMAKE_BINARY_DIR and PROJECT_BINARY_DIR are empty when used in a CMake script executed by CPack. So maybe in some other version of CMake, CMAKE_CURRENT_BINARY_DIR will also be empty when executed by CPack.

Another issue is that I do want the version file to be copied only to source packages and not to binary packages. The way how I do the distinction is that if CMAKE_CURRENT_BINARY_DIR ends with "Source", I assume that the script is running during the preparation of a source package.

Full example is in the using_cpack_install_script branch of https://github.com/josefcibulka/version_git. The changed parts of CMakeLists.txt:

# The following will transfer the version from git to the source package.
set(CPACK_INSTALL_SCRIPT "${CMAKE_BINARY_DIR}/versionNumberToPackage.cmake")

configure_file(${CMAKE_SOURCE_DIR}/versionNumberToPackage.cmake.in
               ${CMAKE_BINARY_DIR}/versionNumberToPackage.cmake @ONLY)

The contents of versionNumberToPackage.cmake.in are:

# Copies VersionForSourcePackage.cmake to the source package (it should not be copied to binary package).
# Detection if this is the source package is that the path ends with "Source" and an optional slash.
if("${CMAKE_CURRENT_BINARY_DIR}" MATCHES "Source/?$")
   file(COPY @CMAKE_BINARY_DIR@/versionForSourcePackage.cmake DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
endif()