# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2021-2023 Maarten L. Hekkelman # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # This cmake extension writes out a revision.hpp file in a specified directory. # The file will contain a C++ inline function that can be used to write out # version information. cmake_minimum_required(VERSION 3.15) # We want the revision.hpp file to be updated whenever the status of the # git repository changes. Use the same technique as in GetGitRevisionDescription.cmake # from https://github.com/rpavlik/cmake-modules #[=======================================================================[.rst: .. command:: write_version_header Write a file named revision.hpp containing version info:: write_version_header( [FILE_NAME ] [LIB_NAME ] ) This command will generate the code to write a file name revision.hpp in the directory ````. ``FILE_NAME`` Specify the name of the file to create, default is ``revision.hpp``. ``LIB_NAME`` Specify the library name which will be used as a prefix part for the variables contained in the revision file. #]=======================================================================] # Record the location of this module now, not at the time the CMakeLists.txt # is being processed get_filename_component(_current_cmake_module_dir ${CMAKE_CURRENT_LIST_FILE} PATH) # First locate a .git file or directory. function(_get_git_dir _start_dir _variable) set(cur_dir "${_start_dir}") set(git_dir "${_start_dir}/.git") while(NOT EXISTS "${git_dir}") # .git dir not found, search parent directories set(prev_dir "${cur_dir}") get_filename_component(cur_dir "${cur_dir}" DIRECTORY) if(cur_dir STREQUAL prev_dir OR cur_dir STREQUAL ${_start_dir}) # we are not in git since we either hit root or # the ${_start_dir} which should be the top set(${_variable} "" PARENT_SCOPE) return() endif() set(git_dir "${cur_dir}/.git") endwhile() set(${_variable} "${git_dir}" PARENT_SCOPE) endfunction() # Locate the git refspec hash and load the hash # This code locates the file containing the git refspec/hash # and loads it. Doing it this way assures that each time the git # repository changes the revision.hpp file gets out of date. function(_get_git_hash _data_dir _variable) # Be pessimistic set(_variable "" PARENT_SCOPE) # Load git package if needed if(NOT GIT_FOUND) find_package(Git QUIET) endif() # And fail if not found if(NOT GIT_FOUND) return() endif() # Locate the nearest .git file or directory _get_git_dir(${CMAKE_CURRENT_SOURCE_DIR} GIT_DIR) # And fail if not found if("${GIT_DIR}" STREQUAL "") return() endif() # Check if the current source dir is a git submodule or a worktree. # In both cases .git is a file instead of a directory. # if(IS_DIRECTORY ${GIT_DIR}) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # The following git command will return a non empty string that # points to the super project working tree if the current # source dir is inside a git submodule. # Otherwise the command will return an empty string. # execute_process( COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT "${out}" STREQUAL "") # If out is not empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule file(READ ${GIT_DIR} submodule) string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule}) string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") else() # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree file(READ ${GIT_DIR} worktree_ref) # The .git directory contains a path to the worktree information directory # inside the parent git repo of the worktree. # string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref}) string(STRIP ${git_worktree_dir} git_worktree_dir) _get_git_dir("${git_worktree_dir}" GIT_DIR) set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") endif() endif() # Fail if the 'head' file was not found if(NOT EXISTS "${HEAD_SOURCE_FILE}") return() endif() # Make a copy of the head file set(HEAD_FILE "${_data_dir}/HEAD") configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) # Now we create a cmake file that will read the contents of this # head file in the appropriate way file(WRITE "${_data_dir}/grab-ref.cmake.in" [[ set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@VERSION_STRING_DATA@/head-ref" COPYONLY) else() configure_file("@GIT_DIR@/packed-refs" "@VERSION_STRING_DATA@/packed-refs" COPYONLY) file(READ "@VERSION_STRING_DATA@/packed-refs" PACKED_REFS) if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") set(HEAD_HASH "${CMAKE_MATCH_1}") endif() endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@VERSION_STRING_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@VERSION_STRING_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() ]]) configure_file("${VERSION_STRING_DATA}/grab-ref.cmake.in" "${VERSION_STRING_DATA}/grab-ref.cmake" @ONLY) # Include the aforementioned file, this will define # the HEAD_HASH variable we're looking for include("${VERSION_STRING_DATA}/grab-ref.cmake") set(${_variable} "${HEAD_HASH}" PARENT_SCOPE) endfunction() # Create a revision file, containing the current git version info, if any function(write_version_header dir) set(flags ) set(options LIB_NAME FILE_NAME) set(sources ) cmake_parse_arguments(VERSION_STRING_OPTION "${flags}" "${options}" "${sources}" ${ARGN}) # parameter check if(NOT IS_DIRECTORY ${dir}) message(FATAL_ERROR "First parameter to write_version_header should be a directory where the final revision.hpp file will be placed") endif() if(VERSION_STRING_OPTION_FILE_NAME) set(file_name "${VERSION_STRING_OPTION_FILE_NAME}") else() set(file_name "revision.hpp") endif() # Where to store intermediate files set(VERSION_STRING_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/VersionString") if(NOT EXISTS "${VERSION_STRING_DATA}") file(MAKE_DIRECTORY "${VERSION_STRING_DATA}") endif() # Load the git hash using the wizzard-like code above. _get_git_hash("${VERSION_STRING_DATA}" GIT_HASH) # If git was found, fetch the git description string if(GIT_HASH) execute_process( COMMAND "${GIT_EXECUTABLE}" describe --dirty --match=build WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(res EQUAL 0) set(REVISION_STRING "${out}") else() message(STATUS "Git hash not found, does this project have a 'build' tag?") endif() else() message(STATUS "Git hash not found") endif() # Check the revision string, if it matches we fill in the required info if(REVISION_STRING MATCHES "build-([0-9]+)-g([0-9a-f]+)(-dirty)?") set(BUILD_NUMBER ${CMAKE_MATCH_1}) if(CMAKE_MATCH_3) set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}*") else() set(REVISION_GIT_TAGREF "${CMAKE_MATCH_2}") endif() string(TIMESTAMP REVISION_DATE_TIME "%Y-%m-%dT%H:%M:%SZ" UTC) else() set(REVISION_GIT_TAGREF "") set(BUILD_NUMBER 0) set(REVISION_DATE_TIME "") endif() if(VERSION_STRING_OPTION_LIB_NAME) set(VAR_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}") set(IDENT_PREFIX "${VERSION_STRING_OPTION_LIB_NAME}_") set(BOOL_IS_MAIN "false") else() set(VAR_PREFIX "") set(IDENT_PREFIX "") set(BOOL_IS_MAIN "true") endif() configure_file("${_current_cmake_module_dir}/revision.hpp.in" "${dir}/${file_name}" @ONLY) endfunction()