# Support for \`git describe\` output in a SemVer prerelease string

**URL:** https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714
**Category:** Code
**Created:** [August 17, 2020, 7:47pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714 "2020-08-17T19:47:51Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![relliott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/relliott/32/790_2.png) [@relliott](https://discourse.cmake.org/u/relliott)
#### Post date: [August 17, 2020, 7:47pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714/1 "2020-08-17T19:47:51Z")

</div>

Hello, I would like to have my project use the current output from `git describe` as the prerelease component of its SemVer version.

I have a solution that seems to work in all but one corner case. I’m hoping to find a way to resolve the corner case OR to find an alternative workable approach.

I’ve created a git repo to illustrate the issue and facilitate the discussion  
[https://github.com/ellio167/cmake-and-git-describe](https://github.com/ellio167/cmake-and-git-describe). Here is the main `CMakeLists.txt` file showing a minimal form of my current solution.

```auto
cmake_minimum_required(VERSION 3.4)

project(MAIN VERSION 0.2.0 LANGUAGES C)

# FC: means "for conciseness"
find_package(Git) # FC: assume found
# FC: assume that ${CMAKE_SOURCE_DIR} is git repo toplevel dir

# set configuration to depend on _depend_file; then touch depend with every invocation of make
set(_git_describe_sentinel "git-describe-sentinel")
set(_depend_file "${CMAKE_CURRENT_BINARY_DIR}/${_git_describe_sentinel}-file")
execute_process(COMMAND ${CMAKE_COMMAND} -E touch "${_depend_file}")

set_property(DIRECTORY "${CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_depend_file}")
add_custom_target(${_git_describe_sentinel}-target ALL COMMAND ${CMAKE_COMMAND} -E touch "${_depend_file}")

# FC: assume the git index is refreshed
execute_process(
  COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_SOURCE_DIR}" describe --dirty --always
  OUTPUT_STRIP_TRAILING_WHITESPACE
  OUTPUT_VARIABLE _git_describe
  )
set(PROJECT_VERSION_STRING "${PROJECT_VERSION}+${_git_describe}")

add_executable(main "")

add_subdirectory(include)
add_subdirectory(src)

```

The corner case occurs because when configuration finishes the first time the sentinel file is older than the Makefile. So, when make is called the first time configuration is not triggered. If the git repo working directory is changed (files edited, new commits added, etc.) between the initial configuration and the first execution of `make`, then incorrect `git describe` results will be used for the build.

Here is a sequence of commands to reproduce the issue:

```bash
% git clone https://github.com/ellio167/cmake-and-git-describe.git
<...>
% cd cmake-and-git-describe.git
% mkdir build
% cd build
% git -C ../ describe --dirty --always
v0.2.0-2-g705268e
% cmake ../
<...>
% printf "/* add comment to main.c */\n" >> ../src/main.c # repo is now "dirty"
% make
<...>
% ./main # but main was build without the "-dirty" indicator
version is: 0.2.0
version string is: 0.2.0+v0.2.0-2-g705268e
% make # now configuration is triggered and correct behavior is obtained
<...>
% ./main
version is: 0.2.0
version string is: 0.2.0+v0.2.0-2-g705268e-dirty
%

```

One potential solution would be to not create the sentinel file as part of the initial configuration. However, if it does not exist then CMake prunes it from the `CMAKE_CONFIGURE_DEPENDS` list at the end of the configuration step. So, this does not seem possible (without a change to CMake behavior).

Any help/suggestions would be much appreciated!

---

<div class="post-metadata">

### Author: ![alex](https://discourse.cmake.org/user_avatar/discourse.cmake.org/alex/32/125_2.png) [@alex](https://discourse.cmake.org/u/alex)
#### Post date: [August 18, 2020, 9:13pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714/2 "2020-08-18T21:13:30Z")

</div>

To make life easier, I opened a PR here: [https://github.com/ellio167/cmake-and-git-describe/pull/1](https://github.com/ellio167/cmake-and-git-describe/pull/1)

Generally speaking, don’t use [`execute_process`](https://cmake.org/cmake/help/latest/command/execute_process.html) to generate sources. The best command for that is [`add_custom_command`](https://cmake.org/cmake/help/latest/command/add_custom_command.html), but it isn’t applicable here since you always need the command to run (need to ask git what changed). Therefore, you need to use [`add_custom_target`](https://cmake.org/cmake/help/latest/command/add_custom_target.html) + [`add_dependencies`](https://cmake.org/cmake/help/latest/command/add_dependencies.html) instead.

Here, [`add_custom_target`](https://cmake.org/cmake/help/latest/command/add_custom_target.html) calls a CMake script that finds Git, checks the describe string against an on-disk cache of the last run and, if they differ, writes out a `version.h` header.

There are some other changes, here, too. The [`project(PROJECT_NAME)`](https://cmake.org/cmake/help/v3.18/command/project.html) command creates variables [`PROJECT_SOURCE_DIR`](https://cmake.org/cmake/help/v3.18/variable/PROJECT_SOURCE_DIR.html) and [`<PROJECT-NAME>_SOURCE_DIR`](https://cmake.org/cmake/help/v3.18/variable/PROJECT-NAME_SOURCE_DIR.html) that you should use instead of [`CMAKE_SOURCE_DIR`](https://cmake.org/cmake/help/v3.18/variable/CMAKE_SOURCE_DIR.html)

You can also use the `REQUIRED` mode of [`find_package()`](https://cmake.org/cmake/help/v3.18/command/find_package.html) to fail when unable to find a package (Git in this case).

Your top-level CMake should be very simple, just:

```auto
cmake_minimum_required(VERSION 3.4)
project(MAIN VERSION 0.2.0 LANGUAGES C)

add_executable(main)

add_subdirectory(include)
add_subdirectory(src)

```

Then your `include/CMakeLists.txt` should look like:

```auto
add_custom_target(
  update-version-h
  COMMAND ${CMAKE_COMMAND}
    -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}
    -DPROJECT_VERSION=${PROJECT_VERSION}
    -P ${CMAKE_CURRENT_SOURCE_DIR}/update-version.cmake
  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/update-version.cmake
  VERBATIM)

add_dependencies(main update-version-h)

target_include_directories(
  main
  PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}"
    "${CMAKE_CURRENT_BINARY_DIR}")

```

This creates a target that always runs a script (code below) that writes `version.h` only when the version has actually changed. The main target is set to explicitly depend on it. Here’s the script:

```auto
cmake_minimum_required(VERSION 3.4)

# Expects variables:
# -DPROJECT_SOURCE_DIR - from parent build
# -DPROJECT_VERSION - from parent build

find_package(Git REQUIRED QUIET)

execute_process(
  COMMAND ${GIT_EXECUTABLE} -C "${PROJECT_SOURCE_DIR}" describe --dirty --always
  OUTPUT_STRIP_TRAILING_WHITESPACE
  OUTPUT_VARIABLE new_version)

if (EXISTS ".sentinel")
  file(READ ".sentinel" old_version)
endif ()

set(PROJECT_VERSION_STRING "${PROJECT_VERSION}+${new_version}")

if (NOT "${old_version}" STREQUAL "${new_version}")
  file(WRITE ".sentinel" "${new_version}")
  file(WRITE "version.h.in" [[
#define VERSION "@PROJECT_VERSION@"
#define VERSION_STRING "@PROJECT_VERSION_STRING@"
]])
  configure_file("version.h.in" "version.h" @ONLY)
endif ()

```

* * *

I tested this with the following script:

```auto
#!/bin/bash

echo "Dirty between configure and first make"
rm -rf build
cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build >/dev/null 2>&1
echo "// comment" >> src/main.c
cmake --build build >/dev/null 2>&1
./build/main

echo

echo "Reset repo and rebuild"
git checkout -- .
cmake --build build >/dev/null 2>&1
./build/main

echo

echo "Remove build dir and fresh build"
rm -rf build
cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build >/dev/null 2>&1
cmake --build build >/dev/null 2>&1
./build/main

```

and this is the output:

```auto
alex@Alex-Desktop:~/cmake-and-git-describe$ ./test.sh
Dirty between configure and first make
version is: 0.2.0
version string is: 0.2.0+v0.2.0-4-g9ec66b2-dirty

Reset repo and rebuild
version is: 0.2.0
version string is: 0.2.0+v0.2.0-4-g9ec66b2

Remove build dir and fresh build
version is: 0.2.0
version string is: 0.2.0+v0.2.0-4-g9ec66b2

```

---

<div class="post-metadata">

### Author: ![relliott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/relliott/32/790_2.png) [@relliott](https://discourse.cmake.org/u/relliott)
#### Post date: [August 18, 2020, 9:59pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714/3 "2020-08-18T21:59:53Z")

</div>

@alex Thanks for the suggestion. This is interesting. However, in your solution the `_get_describe` variable is not set in the top-level CMakeLists.txt file scope. In my real use-case (not the simplified example I’ve posted) the contents of the `_get_describe` variable are used multiple times in various other parts of the configuration (via other `add_subdirectory()` calls). It would be rather difficult, I think, to disentangle all the uses of this value and combine them all together in a custom target.

---

<div class="post-metadata">

### Author: ![alex](https://discourse.cmake.org/user_avatar/discourse.cmake.org/alex/32/125_2.png) [@alex](https://discourse.cmake.org/u/alex)
#### Post date: [August 18, 2020, 10:31pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714/4 "2020-08-18T22:31:09Z")

</div>

> [@relliott](#):
>
> However, in your solution the `_get_describe` variable is not set in the top-level CMakeLists.txt file scope. In my real use-case (not the simplified example I’ve posted) the contents of the `_get_describe` variable are used multiple times in various other parts of the configuration (via other `add_subdirectory()` calls).

That’s the trouble with simplified examples that don’t show your actual use-case. I couldn’t tell you weren’t just trying to generate a header. 🤷‍♂️

* * *

> [@relliott](#):
>
> It would be rather difficult, I think, to disentangle all the uses of this value and combine them all together in a custom target.

I would suggest that you first _try_. Otherwise you might be able to rig something up by using `file(GLOB ... CONFIGURE_DEPENDS ...)` such that the glob changes between the first configure and the first make. Maybe something like:

```auto
file(GLOB _depend_file CONFIGURE_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/*-sentinel")
if (NOT EXISTS "${_depend_file}")
  set(_depend_file "${CMAKE_CURRENT_BINARY_DIR}/git-describe-sentinel")
else ()
  file(READ "${_depend_file}" _old_describe)
endif ()

set_property(DIRECTORY "${CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_depend_file}")

execute_process(...)
set(PROJECT_VERSION_STRING "${PROJECT_VERSION}+${_git_describe}")

if (NOT "${_old_describe}" STREQUAL "${_git_describe}")
  file(WRITE "${_depend_file}" "${_git_describe}")
endif ()

```

I should note that your custom command will not always run. Doing `cmake --build . --target main` will avoid it because that doesn’t build `ALL` and main doesn’t depend on `git-describe-sentinel-target`. That’s _another_ edge case that makes this “always reconfigure” approach brittle and very much _not recommended_.

But unfortunately, I’ve spent all the time I can on this. Good luck!

---

<div class="post-metadata">

### Author: ![relliott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/relliott/32/790_2.png) [@relliott](https://discourse.cmake.org/u/relliott)
#### Post date: [August 19, 2020, 4:16pm UTC](https://discourse.cmake.org/t/support-for-git-describe-output-in-a-semver-prerelease-string/1714/5 "2020-08-19T16:16:14Z")

</div>

Thanks Alex, I appreciate the time you spent on this! (It’s always hard to find the right balance in creating a simplified example.)

> [@alex](#):
>
> I should note that your custom command will not always run. Doing `cmake --build . --target main` will avoid it because that doesn’t build `ALL` and main doesn’t depend on `git-describe-sentinel-target` . That’s _another_ edge case that makes this “always reconfigure” approach brittle and very much _not recommended_ .

Indeed, you are right. Thanks for pointing this out.

> [@alex](#):
>
> Otherwise you might be able to rig something up by using `file(GLOB ... CONFIGURE_DEPENDS ...)`

Thanks for this; I was unaware of this 3.12 and above feature.

With Alex’s help and my own additional investigations, I’ve gained some further insight and made some progress. First, I discovered that CMake itself will add a dirty flag to its version string (see `./Source/CMakeVersion.cmake`. However, there is no attempt to guarantee that it matches the current working tree state at build-time. **So, one can take this as president from the experts for the “right way” to do things.**

However, if one still wants to reconfigure every time, I’ve made two additional adjustments to my solution. If available, use the `file(GLOB <var> CONFIGURE_DEPENDS ...)` mechanism; otherwise, If a system `touch` command is present (assuming it is POSIX compliant) then the modification/access time can be set into the future. This version of my solution is [v0.4.0](https://github.com/ellio167/cmake-and-git-describe/releases/tag/v0.4.0) in the github repo, and has main CMakeLists.txt

```cmake
cmake_minimum_required(VERSION 3.4)

project(MAIN VERSION 0.4.0 LANGUAGES C)

find_package(Git)
unset(GIT_FOUND)
if(GIT_FOUND)
  execute_process(COMMAND ${GIT_EXECUTABLE} -C "${PROJECT_SOURCE_DIR}" rev-parse --show-toplevel
    OUTPUT_STRIP_TRAILING_WHITESPACE
    OUTPUT_VARIABLE _toplevel
    RESULT_VARIABLE _isGitRepo
    ERROR_QUIET
    )

  if((_isGitRepo EQUAL 0) AND ("${_toplevel}" STREQUAL "${PROJECT_SOURCE_DIR}"))
    # set configuration to depend on _depend_file
    set(_git_describe_sentinel "git-describe-sentinel")
    set(_depend_file "${CMAKE_CURRENT_BINARY_DIR}/${_git_describe_sentinel}-file")

    find_program(_touch touch)
    if(${CMAKE_MINOR_VERSION} GREATER 11) # use file(GLOB <var> CONFIGURE_DEPENDS ...) mechanism
      if(EXISTS "${_depend_file}")
        file(REMOVE "${_depend_file}")
      endif()
      file(GLOB _t CONFIGURE_DEPENDS "${_depend_file}")
      file(TOUCH "${_depend_file}")
    elseif(_touch) # use system 'touch' with future timestamp and CMAKE_CONFIGURE_DEPENDS mechanism
      string(TIMESTAMP _time "1%m%d%H%M")
      math(EXPR _time "${_time} + 1")
      string(REGEX REPLACE "^.(.*)$" "\\1" _time "${_time}")
      execute_process(COMMAND ${_touch} -t "${_time}" "${_depend_file}") # set modification/access time 1min in the future
      set_property(DIRECTORY "${CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_depend_file}")
    else() # use CMAKE_CONFIGURE_DEPENDS property mechanism [has a number of corner cases]
      execute_process(COMMAND ${CMAKE_COMMAND} -E touch "${_depend_file}")
      add_custom_target(${_git_describe_sentinel}-target ALL COMMAND ${CMAKE_COMMAND} -E touch "${_depend_file}")
      set_property(DIRECTORY "${CURRENT_SOURCE_DIR}" APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_depend_file}")
    endif()

    execute_process(COMMAND ${GIT_EXECUTABLE} -C "${PROJECT_SOURCE_DIR}" update-index -q --refresh
      TIMEOUT 5
      OUTPUT_QUIET
      ERROR_QUIET
      )
    execute_process(
      COMMAND ${GIT_EXECUTABLE} -C "${PROJECT_SOURCE_DIR}" describe --dirty --always
      OUTPUT_STRIP_TRAILING_WHITESPACE
      OUTPUT_VARIABLE _git_describe
      )
  endif()
endif()

if(_git_describe)
  set(_build_metadata "+${_git_describe}")
else()
  set(_build_metadata "")
endif()
set(PROJECT_VERSION_STRING "${PROJECT_VERSION}${_build_metadata}")

add_executable(main "")

add_subdirectory(include)
add_subdirectory(src)

```
