diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt index 5a34a5eeabaac806958b0bb9a7c181a24525acd2..15f25f46aa971776e7b59dc495d2c6ef393b3dbd 100644 --- a/{{cookiecutter.project_slug}}/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -1,15 +1,19 @@ cmake_minimum_required(VERSION 3.16) -project({{cookiecutter.project_slug}} VERSION 0.1) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(CheckCXXCompilerFlag) +include(GitVersion) + +get_version_info({{cookiecutter.project_slug}} "${CMAKE_CURRENT_SOURCE_DIR}") +set(project_slug {{cookiecutter.project_slug}}) + +project({{cookiecutter.project_slug}} VERSION ${${project_slug}_VERSION}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") - option(BUILD_TESTING "Build the test suite" OFF) option(BUILD_DOCUMENTATION "Build the documentation" OFF) diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 0aa801286c7d7d0c7146a4f626b1a41e2c00eeca..ec725008ec5787351ab1371dd7b50b2ce206778f 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -36,6 +36,15 @@ For instance: - Install the project globally: ``ninja -C ./build/ install`` - Clean caches: `ninja -C ./build/ clean` +## Versioning + +This project uses git tags or branch names to determine the current version of the +software. The version information is automatically made available in CMake and can be +made available in C/C++ by dynamically adding a generated header. + +See for details: +- https://github.com/jahnf/CMake-GitVersion/blob/develop/cmake/GitVersion.cmake + ## Contributing To contribute, please create a feature branch and a "Draft" merge request. Upon diff --git a/{{cookiecutter.project_slug}}/cmake/ArchiveExportInfo.cmake b/{{cookiecutter.project_slug}}/cmake/ArchiveExportInfo.cmake new file mode 100644 index 0000000000000000000000000000000000000000..f5302b5c985db68a0b27eacbcabc628946eb2c9f --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/ArchiveExportInfo.cmake @@ -0,0 +1,6 @@ +# Fallback for version generation from pure git archive exports + +set(GIT_EXPORT_VERSION_SHORTHASH "$Format:%h$") +set(GIT_EXPORT_VERSION_FULLHASH "$Format:%H$") +set(GIT_EXPORT_VERSION_BRANCH "$Format:%D$") # needs parsing in cmake... +set(HAS_GIT_EXPORT_INFO 1) diff --git a/{{cookiecutter.project_slug}}/cmake/ArchiveVersionInfo.cmake.in b/{{cookiecutter.project_slug}}/cmake/ArchiveVersionInfo.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..cb43354392617bc91270c846c4d68711200fac7d --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/ArchiveVersionInfo.cmake.in @@ -0,0 +1,15 @@ +# Auto generated archive version information +# Included in created source archives + +set(@PREFIX@_VERSION "@VERSION@") +set(@PREFIX@_VERSION_MAJOR "@VERSION_MAJOR@") +set(@PREFIX@_VERSION_MINOR "@VERSION_MINOR@") +set(@PREFIX@_VERSION_PATCH "@VERSION_PATCH@") +set(@PREFIX@_VERSION_FLAG "@VERSION_FLAG@") +set(@PREFIX@_VERSION_DISTANCE "@VERSION_DISTANCE@") +set(@PREFIX@_VERSION_SHORTHASH "@VERSION_SHORTHASH@") +set(@PREFIX@_VERSION_FULLHASH "@VERSION_FULLHASH@") +set(@PREFIX@_VERSION_STRING "@VERSION_STRING@") +set(@PREFIX@_VERSION_ISDIRTY "@VERSION_ISDIRTY@") +set(@PREFIX@_VERSION_BRANCH "@VERSION_BRANCH@") +set(@PREFIX@_VERSION_SUCCESS 1) diff --git a/{{cookiecutter.project_slug}}/cmake/GitVersion.cc.in b/{{cookiecutter.project_slug}}/cmake/GitVersion.cc.in new file mode 100644 index 0000000000000000000000000000000000000000..514debdc9eca26daba3b9f008f434a7fa9cbaf12 --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/GitVersion.cc.in @@ -0,0 +1,15 @@ +#include "@PREFIX@-GitVersion.h" + +namespace @PREFIX@ { + const char* version() { return "@VERSION@"; } + const char* version_string() { return "@VERSION_STRING@"; } + unsigned int version_major() { return @VERSION_MAJOR@; } + unsigned int version_minor() { return @VERSION_MINOR@; } + unsigned int version_patch() { return @VERSION_PATCH@; } + const char* version_flag() { return "@VERSION_FLAG@"; } + unsigned int version_distance() { return @VERSION_DISTANCE@; } + const char* version_shorthash() { return "@VERSION_SHORTHASH@"; } + const char* version_fullhash() { return "@VERSION_FULLHASH@"; } + bool version_isdirty() { return @VERSION_ISDIRTY@; } + const char* version_branch() { return "@VERSION_BRANCH@"; } +} diff --git a/{{cookiecutter.project_slug}}/cmake/GitVersion.cmake b/{{cookiecutter.project_slug}}/cmake/GitVersion.cmake new file mode 100644 index 0000000000000000000000000000000000000000..1480f1d584098275d50b8fef70ef8299379c4f44 --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/GitVersion.cmake @@ -0,0 +1,540 @@ +# https://github.com/jahnf/CMake-GitVersion/blob/develop/cmake/GitVersion.cmake +# CMake-GitVersion. +# (Yet another) automatic version generation for C++ CMake projects. + +# * Generates version information using information from git (tags) with fallback options +# when building without git (e.g. when building from sources with exported archives). +# * Use custom templates for generated sources (e.g. also create resource files (see examples)) +# * Uses semantic versioning (https://semver.org/) +# * Best suited with git-flow workflows +# * See: https://www.atlassian.com/de/git/tutorials/comparing-workflows/gitflow-workflow +# * See: https://nvie.com/posts/a-successful-git-branching-model/ +# * Generated version strings are compatible to/and can be used by debian and rpm packages. + +# ## Version Generation + +# Defines +# - `LAST_TAG_VERSION`: latest tagged version (e.g. 1.2.0 for tag v1.2.0) or 0.0.0 if no tag exists. +# - `DIST`: commit count distance to latest version tag. + +# Version Number rules: +# - on _master_: `X.Y.Z`[-`DIST`] (using `LAST_TAG_VERSION`), while `DIST` should always be 0 on the master branch. +# - on _develop_ and other branches: `X.Y.Z`-`ALPHA_FLAG`.`DIST` (using `LAST_TAG_VERSION`, `Y` incremented by 1) +# - on release branches: `X.Y.Z`-`RC_FLAG`.`DIST` (extracting `X.Y.Z` from release branch name or from _develop_ as fallback). \ +# `DIST` is either calculated to last version tag or to the closest `rc-X.Y.Z` tag. +# - `DIST` is added to all version numbers, except: +# - Versions on _master_ and on _hotfix_ branches with `DIST` equal to 0 +# - All version numbers have a pre-release identifier set, except: +# - Version on _master_ and +# - versions on _hotfix_ branches with `DIST` equal to 0 +# - When creating the version string and the PATCH number is 0 - the patch number is omitted. +# (e.g. 1.2.0 will be 1.2) + +### Configuration - this may be adjusted to the personal and project requirements +set(VERSION_TAG_PREFIX v) # Should be the same as configured in git-flow +set(VERSION_ALPHA_FLAG alpha) # Pre-release identifier for all builds besides release and hotfix branches +set(VERSION_RC_FLAG rc) # Pre-release identifier for all builds from release and hotfix branches +set(VERSION_RC_START_TAG_PREFIX "rc-") # If available tags with the given prefix are used for distance calculation on release branches. +set(RC_BRANCH_PREFIX release) # e.g. release/0.2 +set(HOTFIX_BRANCH_PREFIX hotfix) # e.g. hotfix/2.0.3 + +# Let functions in this module know their own directory +set(_GitVersion_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}") + +# -------------------------------------------------------------------------------------------------- +# Helper method - usually never called directly: +# Get the version information for a directory, sets the following variables +# ${prefix}_VERSION_SUCCESS // 0 on error (e.g. git not found), 1 on success +# ${prefix}_VERSION +# ${prefix}_VERSION_MAJOR +# ${prefix}_VERSION_MINOR +# ${prefix}_VERSION_PATCH +# ${prefix}_VERSION_FLAG +# ${prefix}_VERSION_DISTANCE +# ${prefix}_VERSION_SHORTHASH +# ${prefix}_VERSION_FULLHASH +# ${prefix}_VERSION_ISDIRTY // 0 or 1 if tree has local modifications +# ${prefix}_VERSION_STRING // Full version string, e.g. 1.2.3-rc.239 +# +# A created version number can be overruled if the following variables are set and the version number is GREATER +# than the dynamically created one. +# - ${prefix}_CUSTOM_VERSION_MAJOR +# - ${prefix}_CUSTOM_VERSION_MINOR +# - ${prefix}_CUSTOM_VERSION_PATCH +# +# A version 'type' (release or develop) in case the branch cannot be determined via git +# - #{prefix}_FALLBACK_VERSION_TYPE +# +# The environment variable FALLBACK_BRANCH will be used if the branch cannot be determined +function(get_version_info prefix directory) + set(${prefix}_VERSION_SUCCESS 0 PARENT_SCOPE) + set(${prefix}_VERSION "0.0.0") + set(${prefix}_VERSION_MAJOR 0) + set(${prefix}_VERSION_MINOR 0) + set(${prefix}_VERSION_PATCH 0) + set(${prefix}_VERSION_BRANCH unknown) + set(${prefix}_VERSION_FLAG unknown) + set(${prefix}_VERSION_DISTANCE 0) + set(${prefix}_VERSION_STRING 0.0.0-unknown) + set(${prefix}_VERSION_ISDIRTY 0 PARENT_SCOPE) + + if("${${prefix}_CUSTOM_VERSION_MAJOR}" STREQUAL "") + set(${prefix}_CUSTOM_VERSION_MAJOR 0) + endif() + if("${${prefix}_CUSTOM_VERSION_MINOR}" STREQUAL "") + set(${prefix}_CUSTOM_VERSION_MINOR 0) + endif() + if("${${prefix}_CUSTOM_VERSION_PATCH}" STREQUAL "") + set(${prefix}_CUSTOM_VERSION_PATCH 0) + endif() + + find_package(Git) + if(GIT_FOUND) + # Get the version info from the last tag (using the configured version tag prefix) + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --match "${VERSION_TAG_PREFIX}[0-9].[0-9]*" + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_TAG_VERSION + ERROR_VARIABLE error_out + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${directory} + ) + if(result EQUAL 0) + # Extract version major, minor, patch from the result + if(GIT_TAG_VERSION MATCHES "^${VERSION_TAG_PREFIX}?([0-9]+)\\.([0-9]+)(\\.([0-9]+))?(-([0-9]+))?.*$") + set(${prefix}_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(${prefix}_VERSION_MINOR ${CMAKE_MATCH_2}) + if(NOT ${CMAKE_MATCH_4} STREQUAL "") + set(${prefix}_VERSION_PATCH ${CMAKE_MATCH_4}) + endif() + if(NOT ${CMAKE_MATCH_6} STREQUAL "") + set(${prefix}_VERSION_DISTANCE ${CMAKE_MATCH_6}) + endif() + endif() + else() + # git describe return with error - just get the distance + execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_DISTANCE + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE error_out + WORKING_DIRECTORY ${directory} + ) + if(result EQUAL 0) + set(${prefix}_VERSION_DISTANCE ${GIT_DISTANCE}) + endif() + endif() + + # Check for local modifications ... + execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --dirty + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_ALWAYS_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE error_out + WORKING_DIRECTORY ${directory} + ) + if(result EQUAL 0) + if(GIT_ALWAYS_VERSION MATCHES "^.*-dirty$") + set(${prefix}_VERSION_ISDIRTY 1 PARENT_SCOPE) + endif() + endif() + + # Check the branch we are on + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE error_out + WORKING_DIRECTORY ${directory} + ) + + if(result EQUAL 0) + if("${GIT_BRANCH}" STREQUAL "HEAD" + AND NOT "$ENV{FALLBACK_BRANCH}" STREQUAL "") + set(GIT_BRANCH "$ENV{FALLBACK_BRANCH}") + endif() + + set(${prefix}_VERSION_BRANCH "${GIT_BRANCH}") + set(${prefix}_VERSION_BRANCH "${GIT_BRANCH}" PARENT_SCOPE) + + # Check for release branch + string(LENGTH ${RC_BRANCH_PREFIX} PREFIX_LEN) + string(SUBSTRING ${GIT_BRANCH} 0 ${PREFIX_LEN} COMPARE_PREFIX) + string(COMPARE EQUAL ${RC_BRANCH_PREFIX} ${COMPARE_PREFIX} ON_RELEASE_BRANCH) + # Check for hotfix branch + string(LENGTH ${HOTFIX_BRANCH_PREFIX} PREFIX_LEN) + string(SUBSTRING ${GIT_BRANCH} 0 ${PREFIX_LEN} COMPARE_PREFIX) + string(COMPARE EQUAL ${HOTFIX_BRANCH_PREFIX} ${COMPARE_PREFIX} ON_HOTFIX_BRANCH) + # Check for master branch + string(COMPARE EQUAL "master" ${GIT_BRANCH} ON_MASTER) + + if(ON_RELEASE_BRANCH) + set(${prefix}_VERSION_FLAG ${VERSION_RC_FLAG}) + set(RC_VERSION_MAJOR 0) + set(RC_VERSION_MINOR 0) + set(RC_VERSION_PATCH 0) + # Check release branch name for version information (e.g. release/0.8) + if(GIT_BRANCH MATCHES "^${RC_BRANCH_PREFIX}.*([0-9]+)\\.([0-9]+)(\\.([0-9]+))?.*$") + set(RC_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(RC_VERSION_MINOR ${CMAKE_MATCH_2}) + if(NOT ${CMAKE_MATCH_4} STREQUAL "") + set(RC_VERSION_PATCH ${CMAKE_MATCH_4}) + endif() + endif() + + # If the release branch version is greater, use that version... + if("${RC_VERSION_MAJOR}.${RC_VERSION_MINOR}.${RC_VERSION_PATCH}" VERSION_GREATER + "${${prefix}_VERSION_MAJOR}.${${prefix}_VERSION_MINOR}.${${prefix}_VERSION_PATCH}") + set(${prefix}_VERSION_MAJOR ${RC_VERSION_MAJOR}) + set(${prefix}_VERSION_MINOR ${RC_VERSION_MINOR}) + set(${prefix}_VERSION_PATCH ${RC_VERSION_PATCH}) + else() # ... else auto increment the version minor number + # Auto increment minor number, patch = 0 + MATH(EXPR ${prefix}_VERSION_MINOR "${${prefix}_VERSION_MINOR}+1") + set(${prefix}_VERSION_PATCH 0) + endif() + + # Try to get distance from last rc start tag + execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --match "${VERSION_RC_START_TAG_PREFIX}[0-9].[0-9]*" + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_RC_TAG_VERSION + ERROR_VARIABLE error_out + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${directory} + ) + if(result EQUAL 0) + if(GIT_RC_TAG_VERSION MATCHES "^${VERSION_RC_START_TAG_PREFIX}?([0-9]+)\\.([0-9]+)(\\.([0-9]+))?(-([0-9]+))?.*$") + if(NOT ${CMAKE_MATCH_6} STREQUAL "") + set(${prefix}_VERSION_DISTANCE ${CMAKE_MATCH_6}) + else() + set(${prefix}_VERSION_DISTANCE 0) + endif() + endif() + endif() + + elseif(ON_HOTFIX_BRANCH) + set(${prefix}_VERSION_FLAG ${VERSION_RC_FLAG}) + set(RC_VERSION_MAJOR 0) + set(RC_VERSION_MINOR 0) + set(RC_VERSION_PATCH 0) + if(GIT_BRANCH MATCHES "^${RC_BRANCH_PREFIX}.*([0-9]+)\\.([0-9]+)(\\.([0-9]+))?.*$") + set(RC_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(RC_VERSION_MINOR ${CMAKE_MATCH_2}) + if(NOT ${CMAKE_MATCH_4} STREQUAL "") + set(RC_VERSION_PATCH ${CMAKE_MATCH_4}) + endif() + endif() + + if("${RC_VERSION_MAJOR}.${RC_VERSION_MINOR}.${RC_VERSION_PATCH}" VERSION_GREATER + "${${prefix}_VERSION_MAJOR}.${${prefix}_VERSION_MINOR}.${${prefix}_VERSION_PATCH}") + set(${prefix}_VERSION_MAJOR ${RC_VERSION_MAJOR}) + set(${prefix}_VERSION_MINOR ${RC_VERSION_MINOR}) + set(${prefix}_VERSION_PATCH ${RC_VERSION_PATCH}) + else() + # Auto increment patch number + MATH(EXPR ${prefix}_VERSION_PATCH "${${prefix}_VERSION_PATCH}+1") + endif() + elseif(ON_MASTER) + set(${prefix}_VERSION_FLAG "") + endif() + endif() + + if(NOT ON_MASTER AND NOT ON_RELEASE_BRANCH AND NOT ON_HOTFIX_BRANCH) + # Auto increment version number, set alpha flag + MATH(EXPR ${prefix}_VERSION_MINOR "${${prefix}_VERSION_MINOR}+1") + set(${prefix}_VERSION_PATCH 0) + set(${prefix}_VERSION_FLAG ${VERSION_ALPHA_FLAG}) + endif() + + set(${prefix}_VERSION_FLAG ${${prefix}_VERSION_FLAG} PARENT_SCOPE) + set(${prefix}_VERSION_DISTANCE ${${prefix}_VERSION_DISTANCE} PARENT_SCOPE) + + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + RESULT_VARIABLE resultSH + OUTPUT_VARIABLE GIT_SHORT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE error_out + WORKING_DIRECTORY ${directory} + ) + if(resultSH EQUAL 0) + set(${prefix}_VERSION_SHORTHASH ${GIT_SHORT_HASH} PARENT_SCOPE) + else() + message(STATUS "Version-Info: Could not fetch short version hash.") + set(${prefix}_VERSION_SHORTHASH "unknown" PARENT_SCOPE) + endif() + + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + RESULT_VARIABLE resultFH + OUTPUT_VARIABLE GIT_FULL_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE error_out + WORKING_DIRECTORY ${directory} + ) + if(resultFH EQUAL 0) + set(${prefix}_VERSION_FULLHASH ${GIT_FULL_HASH} PARENT_SCOPE) + else() + message(STATUS "Version-Info: Could not fetch full version hash.") + set(${prefix}_VERSION_FULLHASH "unknown" PARENT_SCOPE) + endif() + + if(resultSH EQUAL 0 AND resultFH EQUAL 0) + set(${prefix}_VERSION_SUCCESS 1 PARENT_SCOPE) + endif() + else() # Git not found ... + message(STATUS "Version-Info: Git not found. Possible incomplete version information.") + endif() + + if("${${prefix}_VERSION_BRANCH}" STREQUAL "unknown" OR "${${prefix}_VERSION_BRANCH}" STREQUAL "") + if("${${prefix}_FALLBACK_VERSION_TYPE}" STREQUAL "release") + set(ON_MASTER ON) + set(${prefix}_VERSION_FLAG "") + set(${prefix}_VERSION_FLAG "" PARENT_SCOPE) + endif() + set(${prefix}_VERSION_BRANCH "not-within-git-repo" PARENT_SCOPE) + endif() + + # Check if overrule version is greater than dynamically created one + if("${${prefix}_CUSTOM_VERSION_MAJOR}.${${prefix}_CUSTOM_VERSION_MINOR}.${${prefix}_CUSTOM_VERSION_PATCH}" VERSION_GREATER + "${${prefix}_VERSION_MAJOR}.${${prefix}_VERSION_MINOR}.${${prefix}_VERSION_PATCH}") + set(${prefix}_VERSION_MAJOR ${${prefix}_CUSTOM_VERSION_MAJOR}) + set(${prefix}_VERSION_MINOR ${${prefix}_CUSTOM_VERSION_MINOR}) + set(${prefix}_VERSION_PATCH ${${prefix}_CUSTOM_VERSION_PATCH}) + endif() + + set(${prefix}_VERSION_MAJOR ${${prefix}_VERSION_MAJOR} PARENT_SCOPE) + set(${prefix}_VERSION_MINOR ${${prefix}_VERSION_MINOR} PARENT_SCOPE) + set(${prefix}_VERSION_PATCH ${${prefix}_VERSION_PATCH} PARENT_SCOPE) + set(${prefix}_VERSION_DISTANCE ${${prefix}_VERSION_DISTANCE} PARENT_SCOPE) + + # build version + set(VERSION "${${prefix}_VERSION_MAJOR}.${${prefix}_VERSION_MINOR}.${${prefix}_VERSION_PATCH}") + set(${prefix}_VERSION "${VERSION}" PARENT_SCOPE) + + # Build version string... + set(VERSION_STRING "${${prefix}_VERSION_MAJOR}.${${prefix}_VERSION_MINOR}") + if(NOT ${${prefix}_VERSION_PATCH} EQUAL 0) + set(VERSION_STRING "${VERSION_STRING}.${${prefix}_VERSION_PATCH}") + endif() + if(NOT ON_MASTER OR NOT ${${prefix}_VERSION_DISTANCE} EQUAL 0) + set(VERSION_STRING "${VERSION_STRING}-${${prefix}_VERSION_FLAG}") + endif() + if(NOT ${${prefix}_VERSION_FLAG} STREQUAL "") + set(VERSION_STRING "${VERSION_STRING}.") + endif() + if(NOT ON_MASTER OR (NOT ON_MASTER AND NOT ${${prefix}_VERSION_DISTANCE} EQUAL 0)) + set(VERSION_STRING "${VERSION_STRING}${${prefix}_VERSION_DISTANCE}") + endif() + set(${prefix}_VERSION_STRING "${VERSION_STRING}" PARENT_SCOPE) +endfunction() + +# -------------------------------------------------------------------------------------------------- +# Add version information to a target, header and source file are configured from templates. +# (defaults to GitVersion.h.in and GitVersion.cc.in if no other templates are defined) +# Variables available to input templates +# @PREFIX@ = target prefix name given in the function call, must be a valid C-identifier +# @VERSION_MAJOR@, @VERSION_MINOR@, @VERSION_PATCH@, @VERSION_FLAG@, @VERSION_DISTANCE@ +# @VERSION_SHORTHASH@, @VERSION_FULLHASH@, @VERSION_STRING@, @VERSION_ISDIRTY, @VERSION_BRANCH@ +function(add_version_info) + set(oneValueArgs + TARGET # The build target + PREFIX # The prefix/name for templates, must be a valid C identifier + DIRECTORY # Directory from which to get the git version info + ) + set(multiValueArgs TEMPLATES) # Templates that are configured and added to the target + set(requiredArgs TARGET) + cmake_parse_arguments(VI "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(arg IN LISTS requiredArgs) + if("${VI_${arg}}" STREQUAL "") + message(FATAL_ERROR "Required argument '${arg}' is not set.") + endif() + endforeach() + + if(NOT TARGET ${VI_TARGET}) + message(FATAL_ERROR "Argument 'TARGET' needs to be a valid target.") + endif() + + list(LENGTH VI_TEMPLATES NUM_TEMPLATES) + if(NUM_TEMPLATES EQUAL 0) + # Add default templates + list(APPEND VI_TEMPLATES "${_GitVersion_DIRECTORY}/GitVersion.h.in") + list(APPEND VI_TEMPLATES "${_GitVersion_DIRECTORY}/GitVersion.cc.in") + endif() + string(MAKE_C_IDENTIFIER "${VI_TARGET}" targetid) + + if(NOT VI_PREFIX) + string(MAKE_C_IDENTIFIER "${VI_TARGET}" VI_PREFIX) + endif() + + if(NOT VI_DIRECTORY) + # message(STATUS "add_version_info: defaulting DIRECTORY to '${CMAKE_CURRENT_SOURCE_DIR}'") + set(VI_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + # Set default values, in case sth goes wrong + set(VERSION "0.0.0") + set(VERSION_MAJOR 0) + set(VERSION_MINOR 0) + set(VERSION_PATCH 0) + set(VERSION_FLAG unknown) + set(VERSION_DISTANCE 0) + set(VERSION_SHORTHASH unknown) + set(VERSION_FULLHASH unknown) + set(VERSION_STRING "0.0-unknown.0") + set(VERSION_ISDIRTY 0) + set(VERSION_BRANCH unknown) + set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/version/${targetid}") + + get_target_property(TARGET_VMAJOR ${VI_TARGET} VERSION_MAJOR) + if(TARGET_VMAJOR) + set(${VI_PREFIX}_CUSTOM_VERSION_MAJOR ${TARGET_VMAJOR}) + endif() + get_target_property(TARGET_VMINOR ${VI_TARGET} VERSION_MINOR) + if(TARGET_VMINOR) + set(${VI_PREFIX}_CUSTOM_VERSION_MINOR ${TARGET_VMINOR}) + set(VERSION_MINOR ${TARGET_VMINOR}) + endif() + get_target_property(TARGET_VPATCH ${VI_TARGET} VERSION_PATCH) + if(TARGET_VPATCH) + set(${VI_PREFIX}_CUSTOM_VERSION_PATCH ${TARGET_VPATCH}) + endif() + get_target_property(TARGET_VTYPE ${VI_TARGET} VERSION_TYPE) + if(TARGET_VTYPE) + set(${VI_PREFIX}_FALLBACK_VERSION_TYPE ${TARGET_VTYPE}) + endif() + + # Check for ArchiveVersionInfo within same directory and use that info is available ... + include(ArchiveVersionInfo_${VI_PREFIX} OPTIONAL RESULT_VARIABLE ARCHIVE_VERSION_PRESENT) + if(ARCHIVE_VERSION_PRESENT AND ${VI_PREFIX}_VERSION_SUCCESS) + message(STATUS "Info: Version information from archive file.") + + # ... get version info via git otherwise. + else() + get_version_info(${VI_PREFIX} "${VI_DIRECTORY}") + + # Check if valid version information could be aquired... + if("${${VI_PREFIX}_VERSION_FULLHASH}" STREQUAL "unknown" + OR "${${VI_PREFIX}_VERSION_SHORTHASH}" STREQUAL "unknown" + OR "${${VI_PREFIX}_VERSION_FULLHASH}" STREQUAL "" + OR "${${VI_PREFIX}_VERSION_SHORTHASH}" STREQUAL "") + + # ...and check for ArchiveExportInfo as fallback solution.. + include(ArchiveExportInfo OPTIONAL RESULT_VARIABLE GIT_EXPORT_INFO_FILE_PRESENT) + if("${GIT_EXPORT_VERSION_SHORTHASH}" MATCHES "(.?Format:).*") + set(HAS_GIT_EXPORT_INFO OFF) + endif() + if(GIT_EXPORT_INFO_FILE_PRESENT AND HAS_GIT_EXPORT_INFO) + message(STATUS "Using ArchiveExportInfo as fallback for version info.") + set(${VI_PREFIX}_VERSION_SHORTHASH "${GIT_EXPORT_VERSION_SHORTHASH}") + set(${VI_PREFIX}_VERSION_FULLHASH "${GIT_EXPORT_VERSION_FULLHASH}") + set(${VI_PREFIX}_VERSION_BRANCH "${GIT_EXPORT_VERSION_BRANCH}") + if("${${VI_PREFIX}_VERSION_BRANCH}" MATCHES ".*[ \t]+[->]+[\t ]+(.*)([,]?.*)") + set(${VI_PREFIX}_VERSION_BRANCH "${CMAKE_MATCH_1}") + elseif("${${VI_PREFIX}_VERSION_BRANCH}" MATCHES ".*,[ \t](.*)") + if("${CMAKE_MATCH_1}" STREQUAL "master") + set(ON_MASTER ON) + endif() + endif() + # Check for release branch + string(LENGTH ${RC_BRANCH_PREFIX} PREFIX_LEN) + string(SUBSTRING ${${VI_PREFIX}_VERSION_BRANCH} 0 ${PREFIX_LEN} COMPARE_PREFIX) + string(COMPARE EQUAL ${RC_BRANCH_PREFIX} ${COMPARE_PREFIX} ON_RELEASE_BRANCH) + # Check for hotfix branch + string(LENGTH ${HOTFIX_BRANCH_PREFIX} PREFIX_LEN) + string(SUBSTRING ${${VI_PREFIX}_VERSION_BRANCH} 0 ${PREFIX_LEN} COMPARE_PREFIX) + string(COMPARE EQUAL ${HOTFIX_BRANCH_PREFIX} ${COMPARE_PREFIX} ON_HOTFIX_BRANCH) + # Check for master branch + if(NOT ON_MASTER) + string(COMPARE EQUAL "master" ${${VI_PREFIX}_VERSION_BRANCH} ON_MASTER) + endif() + if(ON_MASTER) + set(${VI_PREFIX}_VERSION_FLAG "") + elseif(ON_RELEASE_BRANCH) + set(${VI_PREFIX}_VERSION_FLAG "${VERSION_RC_FLAG}") + elseiF(ON_HOTFIX_BRANCH) + set(${VI_PREFIX}_VERSION_FLAG "hotfix") + else() + set(${VI_PREFIX}_VERSION_FLAG "${VERSION_ALPHA_FLAG}") + endif() + # Build version string... + set(VERSION_STRING "${${VI_PREFIX}_VERSION_MAJOR}.${${VI_PREFIX}_VERSION_MINOR}") + if(NOT ${${VI_PREFIX}_VERSION_PATCH} EQUAL 0) + set(VERSION_STRING "${VERSION_STRING}.${${VI_PREFIX}_VERSION_PATCH}") + endif() + if(NOT ON_MASTER OR NOT ${${VI_PREFIX}_VERSION_DISTANCE} EQUAL 0) + set(VERSION_STRING "${VERSION_STRING}-${${VI_PREFIX}_VERSION_FLAG}") + endif() + if(NOT ${${VI_PREFIX}_VERSION_FLAG} STREQUAL "") + set(VERSION_STRING "${VERSION_STRING}.") + endif() + if(NOT ON_MASTER OR (NOT ON_MASTER AND NOT ${${VI_PREFIX}_VERSION_DISTANCE} EQUAL 0)) + set(VERSION_STRING "${VERSION_STRING}${${VI_PREFIX}_VERSION_DISTANCE}") + endif() + set(${VI_PREFIX}_VERSION_STRING "${VERSION_STRING}") + endif() + endif() + endif() + + if(${${VI_PREFIX}_VERSION_SUCCESS}) + # All informations gathered via git + else() + message(STATUS "Version-Info: Failure during version retrieval. Possible incomplete version information!") + endif() + # Test if we are building from an archive that has generated version information + set(VERSION ${${VI_PREFIX}_VERSION}) + set(VERSION_MAJOR ${${VI_PREFIX}_VERSION_MAJOR}) + set(VERSION_MINOR ${${VI_PREFIX}_VERSION_MINOR}) + set(VERSION_PATCH ${${VI_PREFIX}_VERSION_PATCH}) + set(VERSION_FLAG ${${VI_PREFIX}_VERSION_FLAG}) + set(VERSION_DISTANCE ${${VI_PREFIX}_VERSION_DISTANCE}) + set(VERSION_SHORTHASH ${${VI_PREFIX}_VERSION_SHORTHASH}) + set(VERSION_FULLHASH ${${VI_PREFIX}_VERSION_FULLHASH}) + set(VERSION_STRING ${${VI_PREFIX}_VERSION_STRING}) + set(VERSION_ISDIRTY ${${VI_PREFIX}_VERSION_ISDIRTY}) + set(VERSION_BRANCH ${${VI_PREFIX}_VERSION_BRANCH}) + set_target_properties(${VI_TARGET} PROPERTIES + VERSION "${VERSION}" + VERSION_MAJOR "${VERSION_MAJOR}" + VERSION_MINOR "${VERSION_MINOR}" + VERSION_PATCH "${VERSION_PATCH}" + VERSION_FLAG "${VERSION_FLAG}" + VERSION_DISTANCE "${VERSION_DISTANCE}" + VERSION_SHORTHASH "${VERSION_SHORTHASH}" + VERSION_FULLHASH "${VERSION_FULLHASH}" + VERSION_STRING "${VERSION_STRING}" + VERSION_ISDIRTY "${VERSION_ISDIRTY}" + VERSION_BRANCH "${VERSION_BRANCH}" + ) + + set(TARGET ${VI_PREFIX}) + set(PREFIX ${VI_PREFIX}) + foreach(template_file ${VI_TEMPLATES}) + if(template_file MATCHES "(.*)(\.in)$") + get_filename_component(output_basename "${CMAKE_MATCH_1}" NAME) + else() + get_filename_component(output_basename "${template_file}" NAME) + endif() + set(output_file "${output_dir}/${VI_PREFIX}-${output_basename}") + configure_file("${template_file}" "${output_file}") + list(APPEND output_files "${output_file}") + endforeach() + + # Generate archive version info. File can be included in source archives to have detailed + # version information available without being a git repository. + # See https://github.com/jahnf/Projecteur, where it is used for the 'source-archive' build target. + configure_file("${_GitVersion_DIRECTORY}/ArchiveVersionInfo.cmake.in" + "archive_append/cmake/modules/ArchiveVersionInfo_${VI_PREFIX}.cmake" @ONLY) + + get_target_property(type ${VI_TARGET} TYPE) + if(type STREQUAL "SHARED_LIBRARY") + set_target_properties(${VI_TARGET} PROPERTIES SOVERSION "${VERSION_MAJOR}.${VERSION_MINOR}") + set_property(TARGET ${VI_TARGET} PROPERTY VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") + endif() + + # If any templates where configured, add them to the project sources + list(LENGTH output_files NUM_CONFIGURED_FILES) + if(NOT NUM_CONFIGURED_FILES EQUAL 0) + set_property(TARGET ${VI_TARGET} APPEND PROPERTY SOURCES ${output_files}) + target_include_directories(${VI_TARGET} PUBLIC $<BUILD_INTERFACE:${output_dir}>) + endif() + message(STATUS "Version info for '${VI_TARGET}': ${VERSION_STRING} (prefix=${VI_PREFIX})") +endfunction() diff --git a/{{cookiecutter.project_slug}}/cmake/GitVersion.h.in b/{{cookiecutter.project_slug}}/cmake/GitVersion.h.in new file mode 100644 index 0000000000000000000000000000000000000000..3b0aa0fce8dfa78dfcbd66675811a36359a3e009 --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/GitVersion.h.in @@ -0,0 +1,18 @@ +#ifndef @PREFIX@_GITVERSION_H +#define @PREFIX@_GITVERSION_H + +namespace @PREFIX@ { + const char* version(); + const char* version_string(); + unsigned int version_major(); + unsigned int version_minor(); + unsigned int version_patch(); + const char* version_flag(); + unsigned int version_distance(); + const char* version_shorthash(); + const char* version_fullhash(); + bool version_isdirty(); + const char* version_branch(); +} + +#endif diff --git a/{{cookiecutter.project_slug}}/src/CMakeLists.txt b/{{cookiecutter.project_slug}}/src/CMakeLists.txt index 8ac26da18add662af2dc5c319f19df8b8f831902..7d78654956effc00c051cc8fc28ac42246e6226d 100644 --- a/{{cookiecutter.project_slug}}/src/CMakeLists.txt +++ b/{{cookiecutter.project_slug}}/src/CMakeLists.txt @@ -4,5 +4,6 @@ add_subdirectory(lib) add_executable(cpp main.cpp) +add_version_info(TARGET cpp) target_link_libraries(cpp PRIVATE hello) diff --git a/{{cookiecutter.project_slug}}/src/main.cpp b/{{cookiecutter.project_slug}}/src/main.cpp index 55a9a34a33772b641538966c5454405e9b5715a9..89098e418902670874f439d25610907f96531e78 100644 --- a/{{cookiecutter.project_slug}}/src/main.cpp +++ b/{{cookiecutter.project_slug}}/src/main.cpp @@ -1,6 +1,21 @@ // Copyright (C) ASTRON (Netherlands Institute for Radio Astronomy) // SPDX-License-Identifier: Apache-2.0 +#include <iostream> + #include "hello.hpp" +#include "cpp-GitVersion.h" + +int main(int, const char **) { + lib::Hello(); -int main(int, const char **) { lib::Hello(); } + std::cout << "CMake-GitVersion example"<< std::endl; + std::cout << "- version: " << cpp::version() << std::endl; + std::cout << "- version_string: " << cpp::version_string() << std::endl; + std::cout << "- version_branch: " << cpp::version_branch() << std::endl; + std::cout << "- version_fullhash: " << cpp::version_fullhash() << std::endl; + std::cout << "- version_shorthash: " << cpp::version_shorthash() << std::endl; + std::cout << "- version_isdirty: " << cpp::version_isdirty() << std::endl; + std::cout << "- version_distance: " << cpp::version_distance() << std::endl; + std::cout << "- version_flag: " << cpp::version_flag() << std::endl; +}