diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec4ec306..76ac0a0d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,10 @@ add_feature_info(OpenW3DHeadless W3D_FDS "Build OpenW3D headless server") option(W3D_TOOLS "Build additional tools." ON) add_feature_info(OpenW3DTools W3D_TOOLS "Build OpenW3D Mod Tools") +# Do we want to embed git information into the binary? +option(W3D_EMBED_GIT_INFO "Embed information from git." OFF) +add_feature_info(W3DEmbedGit W3D_EMBED_GIT_INFO "Embed information from git") + option(SYNTAX_CHECK_ONLY "Syntax check the files only?" OFF) add_feature_info(SyntaxCheck SYNTAX_CHECK_ONLY "Syntax check files only") if(SYNTAX_CHECK_ONLY) diff --git a/CMakePresets.json b/CMakePresets.json index 0cbab3f9b..a3665c268 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -15,7 +15,8 @@ "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_MSVC_DEBUG_INFORMATION_FORMAT": "$<$:Embedded>", - "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL" + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", + "W3D_EMBED_GIT_INFO": "ON" } }, { diff --git a/Code/CMakeLists.txt b/Code/CMakeLists.txt index b8d407cfc..1f096c8be 100644 --- a/Code/CMakeLists.txt +++ b/Code/CMakeLists.txt @@ -67,6 +67,7 @@ else() endif() # Module static libraries +add_subdirectory(GitInfo) add_subdirectory(BinkMovie) add_subdirectory(Combat) add_subdirectory(SControl) diff --git a/Code/Commando/CMakeLists.txt b/Code/Commando/CMakeLists.txt index c27d51454..7714e19db 100644 --- a/Code/Commando/CMakeLists.txt +++ b/Code/Commando/CMakeLists.txt @@ -477,6 +477,7 @@ if(W3D_CLIENT) # Full game client target_link_libraries(renegade PRIVATE commando + gitinfo ) embed_resources(renegade @@ -491,6 +492,7 @@ if(W3D_FDS) # Dedicated headless server target_link_libraries(renegadeserver PRIVATE commando + gitinfo ) target_compile_definitions(renegadeserver PRIVATE FREEDEDICATEDSERVER) diff --git a/Code/Commando/ConsoleMode.cpp b/Code/Commando/ConsoleMode.cpp index a314ed9e9..95ab7fe51 100644 --- a/Code/Commando/ConsoleMode.cpp +++ b/Code/Commando/ConsoleMode.cpp @@ -199,7 +199,7 @@ void ConsoleModeClass::Init(void) #else //FREEDEDICATEDSERVER Print("Renegade "); #endif //FREEDEDICATEDSERVER - Print("v%d.%.3d %s-%s %s\n", (version_major >> 16), (version_major & 0xFFFF), BuildInfoClass::Get_Builder_Initials(), BuildInfoClass::Get_Build_Number_String(), BuildInfoClass::Get_Build_Date_String()); + Print("v%s %s-%s %s\n", BuildInfoClass::Get_Build_Version_String(), BuildInfoClass::Get_Build_Commit_String(), BuildInfoClass::Get_Build_Number_String(), BuildInfoClass::Get_Build_Date_String()); Print("Console mode active\n"); LastKeypressTime = 0; diff --git a/Code/Commando/buildnum.cpp b/Code/Commando/buildnum.cpp index 24b329bd7..40083eb98 100644 --- a/Code/Commando/buildnum.cpp +++ b/Code/Commando/buildnum.cpp @@ -33,21 +33,11 @@ *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -#include "always.h" #include "buildnum.h" #include "wwdebug.h" +#include "gitinfo.h" #include -#include "win.h" - -/* -** -** This is just a placeholder for the build number. -** A post build step will stamp the build number into here. -** -*/ -char BuildInfoClass::BuildNumber [64] = {"Insert1Build2Number3Here4 xxxx "}; -char BuildInfoClass::BuildDate [64] = {"Insert1Build2Date3Here4 xxxx "}; +#include /*********************************************************************************************** * BuildInfoClass::Get_Build_Number -- Gets the 32 bit build number. * @@ -65,12 +55,9 @@ char BuildInfoClass::BuildDate [64] = {"Insert1Build2Date3Here4 xxxx *=============================================================================================*/ unsigned int BuildInfoClass::Get_Build_Number(void) { - return (*(unsigned int*)(&BuildNumber[28])); + return (GitRevision); } - - - /*********************************************************************************************** * BuildInfoClass::Get_Build_Number_String -- Gets the build number as a human readable string.* * * @@ -88,13 +75,10 @@ unsigned int BuildInfoClass::Get_Build_Number(void) const char *BuildInfoClass::Get_Build_Number_String(void) { static char _buffer[16]; - sprintf (_buffer, "%d", *(unsigned int*)(&BuildNumber[28])); + sprintf (_buffer, "%u", GitRevision); return (_buffer); } - - - /*********************************************************************************************** * BuildInfoClass::Get_Builder_Name -- Gets the name of the person who built this executable. * * * @@ -111,11 +95,9 @@ const char *BuildInfoClass::Get_Build_Number_String(void) *=============================================================================================*/ const char *BuildInfoClass::Get_Builder_Name(void) { - return(&BuildNumber[32]); + return BuildAuthorName; } - - /*********************************************************************************************** * BuildInfoClass::Get_Build_Date_String -- Gets the date this executable was built on. * * * @@ -132,47 +114,7 @@ const char *BuildInfoClass::Get_Builder_Name(void) *=============================================================================================*/ const char *BuildInfoClass::Get_Build_Date_String(void) { - static char _buffer[64]; - SYSTEMTIME systime; - - if (FileTimeToSystemTime ((LPFILETIME)(&BuildDate[28]), &systime) ) { - sprintf(_buffer, "%02d/%02d/%04d - %02d:%02d:%02d", systime.wMonth, systime.wDay, systime.wYear, systime.wHour, systime.wMinute, systime.wSecond); - } else { - _buffer[0] = 0; - } - - return(_buffer); -} - - - -/*********************************************************************************************** - * BuildInfoClass::Get_Builder_Initials -- Gets the initials of the builder * - * * - * * - * * - * INPUT: Nothing * - * * - * OUTPUT: Ptr to string containing initials * - * * - * WARNINGS: None * - * * - * HISTORY: * - * 10/29/2001 5:12PM ST : Created * - *=============================================================================================*/ -const char *BuildInfoClass::Get_Builder_Initials(void) -{ - static char _buffer[4]; - - _buffer[0] = BuildNumber[32]; - _buffer[1] = 0; - _buffer[2] = 0; - - char *lastname = strchr(&BuildNumber[32], '_'); - if (lastname) { - _buffer[1] = lastname[1]; - } - return (_buffer); + return(GitCommitDate); } /*********************************************************************************************** @@ -192,12 +134,22 @@ const char *BuildInfoClass::Get_Builder_Initials(void) const char *BuildInfoClass::Get_Build_Version_String(void) { static char _buffer[128]; - sprintf(_buffer, "%s-%s", Get_Builder_Initials(), Get_Build_Number_String()); + sprintf(_buffer, "%u.%u.%u.%u", GitMajor, GitMinor, GitPatch, GitRevision); return(_buffer); } +const char *BuildInfoClass::Get_Build_Commit_String(bool short_str) +{ + if (short_str) + return(GitShortSHA1); + else + return(GitSHA1); +} - +bool BuildInfoClass::Is_Build_Dirty(void) +{ + return(GitUncommittedChanges); +} /*********************************************************************************************** * BuildInfoClass::Get_Build_Type -- Get the type of build this is * @@ -226,8 +178,6 @@ BuildInfoClass::BuildType BuildInfoClass::Get_Build_Type(void) #endif //_DEBUG } - - /*********************************************************************************************** * BuildInfoClass::Get_Build_Type_String -- Get the build type as a string. * * * @@ -260,10 +210,6 @@ const char *BuildInfoClass::Get_Build_Type_String(void) } } - - - - /*********************************************************************************************** * BuildInfoClass::Composite_Build_Info -- Get lots of build info * * * @@ -281,12 +227,16 @@ const char *BuildInfoClass::Get_Build_Type_String(void) const char *BuildInfoClass::Composite_Build_Info(void) { static char _buffer[256]; - sprintf(_buffer, "%s Build %d by %s - Build time %s", Get_Build_Type_String(), Get_Build_Number(), Get_Builder_Name(), Get_Build_Date_String()); + [[maybe_unused]] int written = snprintf(_buffer, sizeof(_buffer), "%s %s Commit %s%s at %s", + BuildAuthorName, + Get_Build_Type_String(), + Is_Build_Dirty() ? "~" : "", + GitShortSHA1, + GitCommitDate); + WWASSERT(unsigned(written) < sizeof(_buffer)); return(_buffer); } - - /*********************************************************************************************** * BuildInfoClass::Log_Build_Info -- Dump build info to the logfile. * * * @@ -306,7 +256,3 @@ void BuildInfoClass::Log_Build_Info(void) WWDEBUG_SAY((Composite_Build_Info())); WWDEBUG_SAY(("\n")); } - - - - diff --git a/Code/Commando/buildnum.h b/Code/Commando/buildnum.h index 90d89b253..4ca387465 100644 --- a/Code/Commando/buildnum.h +++ b/Code/Commando/buildnum.h @@ -75,14 +75,19 @@ class BuildInfoClass static const char *Get_Build_Date_String(void); /* - ** Gets the initials of the person who built this executable. + ** Get a composite build number string with the works. */ - static const char *Get_Builder_Initials(void); + static const char *Get_Build_Version_String(void); /* - ** Get a composite build number string with the works. + ** Get a git hash string. */ - static const char *Get_Build_Version_String(void); + static const char *Get_Build_Commit_String(bool short_str = true); + + /* + ** Is build tree modified since commit? + */ + static bool Is_Build_Dirty(void); /* ** Get build type. @@ -99,14 +104,4 @@ class BuildInfoClass ** Lots of build info together. */ static const char *Composite_Build_Info(void); - - - - - private: - static char BuildNumber [64]; - static char BuildDate [64]; }; - - - diff --git a/Code/Commando/dlgmainmenu.cpp b/Code/Commando/dlgmainmenu.cpp index 6e93dddfd..67980416f 100644 --- a/Code/Commando/dlgmainmenu.cpp +++ b/Code/Commando/dlgmainmenu.cpp @@ -66,6 +66,7 @@ #include "translatedb.h" #include "string_ids.h" #include "gamespyadmin.h" +#include "gitinfo.h" //////////////////////////////////////////////////////////////// // Static member initialization @@ -503,11 +504,19 @@ MainMenuDialogClass::Update_Version_Number (void) // Put the version string into the dialog // WideStringClass version_string; - // Add build number temporarily. Will probably be removed for shipping. - WideStringClass build_number(BuildInfoClass::Get_Build_Number_String(), true); - WideStringClass build_initials(BuildInfoClass::Get_Builder_Initials(), true); + WideStringClass build_ver(BuildInfoClass::Get_Build_Version_String(), true); + + WideStringClass build_hash(BuildInfoClass::Get_Build_Commit_String(), true); + if(BuildInfoClass::Is_Build_Dirty()) + build_hash += L"~"; + WideStringClass build_date(BuildInfoClass::Get_Build_Date_String(), true); - version_string.Format (U_CHAR("v%d.%.3d %s-%s %s"), (version_major >> 16), (version_major & 0xFFFF), build_initials, build_number, build_date); + WideStringClass build_author(BuildInfoClass::Get_Builder_Name(), true); + version_string.Format (U_CHAR("%s %s %s %s"), + static_cast(build_ver), + static_cast(build_hash), + static_cast(build_author), + static_cast(build_date)); Set_Dlg_Item_Text (IDC_VERSION_STATIC, version_string); } diff --git a/Code/GitInfo/CMakeLists.txt b/Code/GitInfo/CMakeLists.txt new file mode 100644 index 000000000..119d51ac5 --- /dev/null +++ b/Code/GitInfo/CMakeLists.txt @@ -0,0 +1,16 @@ +# Embed some git version information in the binary. +set(PRE_CONFIGURE_FILE "gitinfo.cpp.in") +set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/gitinfo.cpp") +if(NOT W3D_BUILD_AUTHOR) + set(W3D_BUILD_AUTHOR $ENV{W3D_BUILD_AUTHOR}) +endif() +set(GIT_BUILD_AUTHOR ${W3D_BUILD_AUTHOR}) + +if(W3D_EMBED_GIT_INFO) + set(GIT_INFO_ENABLED TRUE) +endif() + +include(GitWatcher.cmake) + +add_library(gitinfo OBJECT ${CMAKE_CURRENT_BINARY_DIR}/gitinfo.cpp) +target_include_directories(gitinfo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Code/GitInfo/GitWatcher.cmake b/Code/GitInfo/GitWatcher.cmake new file mode 100644 index 000000000..a347f77f4 --- /dev/null +++ b/Code/GitInfo/GitWatcher.cmake @@ -0,0 +1,380 @@ +# git_watcher.cmake +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake +# +# Released under the MIT License. +# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE + + +# This file defines a target that monitors the state of a git repo. +# If the state changes (e.g. a commit is made), then a file gets reconfigured. +# Here are the primary variables that control script behavior: +# +# PRE_CONFIGURE_FILE (REQUIRED) +# -- The path to the file that'll be configured. +# +# POST_CONFIGURE_FILE (REQUIRED) +# -- The path to the configured PRE_CONFIGURE_FILE. +# +# GIT_STATE_FILE (OPTIONAL) +# -- The path to the file used to store the previous build's git state. +# Defaults to the current binary directory. +# +# GIT_WORKING_DIR (OPTIONAL) +# -- The directory from which git commands will be run. +# Defaults to the directory with the top level CMakeLists.txt. +# +# GIT_EXECUTABLE (OPTIONAL) +# -- The path to the git executable. It'll automatically be set if the +# user doesn't supply a path. +# +# GIT_FAIL_IF_NONZERO_EXIT (OPTIONAL) +# -- Raise a FATAL_ERROR if any of the git commands return a non-zero +# exit code. This is set to TRUE by default. You can set this to FALSE +# if you'd like the build to continue even if a git command fails. +# +# GIT_IGNORE_UNTRACKED (OPTIONAL) +# -- Ignore the presence of untracked files when detecting if the +# working tree is dirty. This is set to FALSE by default. +# +# DESIGN +# - This script was designed similar to a Python application +# with a Main() function. I wanted to keep it compact to +# simplify "copy + paste" usage. +# +# - This script is invoked under two CMake contexts: +# 1. Configure time (when build files are created). +# 2. Build time (called via CMake -P). +# The first invocation is what registers the script to +# be executed at build time. +# +# MODIFICATIONS +# You may wish to track other git properties like when the last +# commit was made. There are two sections you need to modify, +# and they're tagged with a ">>>" header. + +# Short hand for converting paths to absolute. +macro(PATH_TO_ABSOLUTE var_name) + get_filename_component(${var_name} "${${var_name}}" ABSOLUTE) +endmacro() + +# Check that a required variable is set. +macro(CHECK_REQUIRED_VARIABLE var_name) + if(NOT DEFINED ${var_name}) + message(FATAL_ERROR "The \"${var_name}\" variable must be defined.") + endif() + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value) + if(NOT DEFINED ${var_name}) + set(${var_name} ${default_value}) + endif() +endmacro() + +# Check that an optional variable is set, or, set it to a default value. +# Also converts that path to an abspath. +macro(CHECK_OPTIONAL_VARIABLE var_name default_value) + CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value}) + PATH_TO_ABSOLUTE(${var_name}) +endmacro() + +CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE) +CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE) +CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git-state-hash") +CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}") +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE) +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_IGNORE_UNTRACKED FALSE) +CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_BUILD_AUTHOR "") + +# Check the optional git variable. +# If it's not set, we'll try to find it using the CMake packaging system. +if(NOT DEFINED GIT_EXECUTABLE) + find_package(Git QUIET) +endif() +#CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE) + + +set(_state_variable_names + GIT_DIR + GIT_HAVE_INFO + GIT_IS_DIRTY + GIT_REVISION_HASH + GIT_REVISION + GIT_REVISION_COUNT + GIT_COMMITTER_DATE + GIT_COMMITTER_TIME_STAMP + GIT_LATEST_TAG + GIT_VERSION_MAJOR + GIT_VERSION_MINOR + GIT_VERSION_PATCH + # >>> + # 1. Add the name of the additional git variable you're interested in monitoring + # to this list. +) + + + +# Macro: RunGitCommand +# Description: short-hand macro for calling a git function. Outputs are the +# "exit_code" and "output" variables. The "_permit_git_failure" +# variable can locally override the exit code checking- use it +# with caution. +macro(RunGitCommand) + execute_process(COMMAND ${CMAKE_COMMAND} -E env TZ=UTC + "${GIT_EXECUTABLE}" ${ARGV} + WORKING_DIRECTORY "${_working_dir}" + RESULT_VARIABLE exit_code + OUTPUT_VARIABLE output + ERROR_VARIABLE stderr + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT exit_code EQUAL 0 AND NOT _permit_git_failure) + set(ENV{GIT_HAVE_INFO} "false") + + # Issue 26: git info not properly set + # + # Check if we should fail if any of the exit codes are non-zero. + # Most methods have a fall-back default value that's used in case of non-zero + # exit codes. If you're feeling risky, disable this safety check and use + # those default values. + if(GIT_FAIL_IF_NONZERO_EXIT ) + string(REPLACE ";" " " args_with_spaces "${ARGV}") + message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})") + endif() + endif() +endmacro() + + + +# Function: GetGitState +# Description: gets the current state of the git repo. +# Args: +# _working_dir (in) string; the directory from which git commands will be executed. +function(GetGitState _working_dir) + # This is an error code that'll be set to FALSE if the + # RunGitCommand ever returns a non-zero exit code. + set(ENV{GIT_HAVE_INFO} "true") + + # Get whether or not the working tree is dirty. + if (GIT_IGNORE_UNTRACKED) + set(untracked_flag "-uno") + else() + set(untracked_flag "-unormal") + endif() + RunGitCommand(status --porcelain ${untracked_flag}) + if(NOT exit_code EQUAL 0) + set(ENV{GIT_IS_DIRTY} "false") + else() + if(NOT "${output}" STREQUAL "") + set(ENV{GIT_IS_DIRTY} "true") + else() + set(ENV{GIT_IS_DIRTY} "false") + endif() + endif() + + # There's a long list of attributes grabbed from git show. + set(object HEAD) + RunGitCommand(show -s "--format=%H" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_REVISION_HASH} ${output}) + endif() + + RunGitCommand(show -s "--format=%h" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_REVISION} ${output}) + endif() + + RunGitCommand(show -s "--format=%cd" "--date=format-local:%Y-%m-%d %H:%M:%S" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_COMMITTER_DATE} "${output}") + endif() + + RunGitCommand(show -s "--format=%ct" ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_COMMITTER_TIME_STAMP} "${output}") + endif() + + # this command can fail if there are no tags on the repository. + set(_permit_git_failure TRUE) + RunGitCommand(describe --tags --match=v* --abbrev=0 ${object}) + set(_permit_git_failure FALSE) + if(exit_code EQUAL 0) + set(ENV{GIT_LATEST_TAG} ${output}) + endif() + + if (ENV{GIT_LATEST_TAG}) + RunGitCommand(rev-list "${GIT_LATEST_TAG}..${object}" --count) + if(exit_code EQUAL 0) + set(ENV{GIT_REVISION_COUNT} ${output}) + endif() + + if(ENV{GIT_LATEST_TAG} MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.*)?$") + set(ENV{GIT_VERSION_MAJOR} ${CMAKE_MATCH_1}) + set(ENV{GIT_VERSION_MINOR} ${CMAKE_MATCH_2}) + set(ENV{GIT_VERSION_PATCH} ${CMAKE_MATCH_3}) + else() + message(WARNING "Tag doesn't follow SEMVER.") + set(ENV{GIT_VERSION_MAJOR} 0) + set(ENV{GIT_VERSION_MINOR} 0) + set(ENV{GIT_VERSION_PATCH} 0) + endif() + else() + RunGitCommand(rev-list --count ${object}) + if(exit_code EQUAL 0) + set(ENV{GIT_REVISION_COUNT} ${output}) + else() + set(ENV{GIT_REVISION_COUNT} 0) + endif() + + set(ENV{GIT_VERSION_MAJOR} 0) + set(ENV{GIT_VERSION_MINOR} 0) + set(ENV{GIT_VERSION_PATCH} 0) + set(ENV{GIT_LATEST_TAG} "v0.0.0") + endif() + + # >>> + # 2. Additional git properties can be added here via the + # "execute_process()" command. Be sure to set them in + # the environment using the same variable name you added + # to the "_state_variable_names" list. + +endfunction() + + + +# Function: GitStateChangedAction +# Description: this function is executed when the state of the git +# repository changes (e.g. a commit is made). +function(GitStateChangedAction) + message("Running state change action.") + foreach(var_name ${_state_variable_names}) + set(${var_name} $ENV{${var_name}}) + set(${var_name} $ENV{${var_name}} PARENT_SCOPE) + endforeach() + configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY) +endfunction() + + + +# Function: HashGitState +# Description: loop through the git state variables and compute a unique hash. +# Args: +# _state (out) string; a hash computed from the current git state. +function(HashGitState _state) + set(ans "") + foreach(var_name ${_state_variable_names}) + string(SHA256 ans "${ans}$ENV{${var_name}}") + endforeach() + set(${_state} ${ans} PARENT_SCOPE) +endfunction() + + + +# Function: CheckGit +# Description: check if the git repo has changed. If so, update the state file. +# Args: +# _working_dir (in) string; the directory from which git commands will be ran. +# _state_changed (out) bool; whether or no the state of the repo has changed. +function(CheckGit _working_dir _state_changed) + + # Get the current state of the repo. + GetGitState("${_working_dir}") + + # Convert that state into a hash that we can compare against + # the hash stored on-disk. + HashGitState(state) + + # Issue 14: post-configure file isn't being regenerated. + # + # Update the state to include the SHA256 for the pre-configure file. + # This forces the post-configure file to be regenerated if the + # pre-configure file has changed. + file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash) + string(SHA256 state "${preconfig_hash}${state}") + + # Check if the state has changed compared to the backup on disk. + if(EXISTS "${GIT_STATE_FILE}") + file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS) + if(OLD_HEAD_CONTENTS STREQUAL "${state}") + # State didn't change. + set(${_state_changed} "false" PARENT_SCOPE) + return() + endif() + endif() + + # The state has changed. + # We need to update the state file on disk. + # Future builds will compare their state to this file. + file(WRITE "${GIT_STATE_FILE}" "${state}") + set(${_state_changed} "true" PARENT_SCOPE) +endfunction() + + + +# Function: SetupGitMonitoring +# Description: this function sets up custom commands that make the build system +# check the state of git before every build. If the state has +# changed, then a file is configured. +function(SetupGitMonitoring) + add_custom_target(check_git + ALL + DEPENDS ${PRE_CONFIGURE_FILE} + BYPRODUCTS + ${POST_CONFIGURE_FILE} + ${GIT_STATE_FILE} + COMMENT "Checking the git repository for changes..." + COMMAND + ${CMAKE_COMMAND} + -D_BUILD_TIME_CHECK_GIT=TRUE + -DGIT_INFO_ENABLED=TRUE + -DGIT_WORKING_DIR=${GIT_WORKING_DIR} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DGIT_STATE_FILE=${GIT_STATE_FILE} + -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE} + -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE} + -DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT} + -DGIT_IGNORE_UNTRACKED=${GIT_IGNORE_UNTRACKED} + -DGIT_BUILD_AUTHOR=${GIT_BUILD_AUTHOR} + -P "${CMAKE_CURRENT_LIST_FILE}") +endfunction() + +function(FakeGit) + set(ENV{GIT_DIR} "") + set(ENV{GIT_HAVE_INFO} "false") + set(ENV{GIT_IS_DIRTY} "false") + set(ENV{GIT_REVISION_HASH} "0123456789ABCFEF0123456789ABCFEF01234567") + set(ENV{GIT_REVISION} "0123456") + set(ENV{GIT_COMMITTER_DATE} "1970-01-01 00:00:00") + set(ENV{GIT_COMMITTER_TIME_STAMP} 0) + set(ENV{GIT_REVISION_COUNT} 0) + set(ENV{GIT_VERSION_MAJOR} 0) + set(ENV{GIT_VERSION_MINOR} 0) + set(ENV{GIT_VERSION_PATCH} 0) + set(ENV{GIT_LATEST_TAG} "v0.0.0") +endfunction() + +# Function: Main +# Description: primary entry-point to the script. Functions are selected based +# on whether it's configure or build time. +function(Main) + if (GIT_EXECUTABLE AND GIT_INFO_ENABLED) + if(_BUILD_TIME_CHECK_GIT) + # Check if the repo has changed. + # If so, run the change action. + CheckGit("${GIT_WORKING_DIR}" changed) + if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}") + GitStateChangedAction() + endif() + else() + # >> Executes at configure time. + SetupGitMonitoring() + GitStateChangedAction() + endif() + else() + FakeGit() + GitStateChangedAction() + endif() +endfunction() + +# And off we go... +Main() diff --git a/Code/GitInfo/gitinfo.cpp.in b/Code/GitInfo/gitinfo.cpp.in new file mode 100644 index 000000000..5493e9105 --- /dev/null +++ b/Code/GitInfo/gitinfo.cpp.in @@ -0,0 +1,31 @@ +/* +** Command & Conquer Renegade(tm) +** Copyright 2025 OpenW3D Contributors. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ +#include "gitinfo.h" + +const char GitSHA1[] = "@GIT_REVISION_HASH@"; +const char GitShortSHA1[] = "@GIT_REVISION@"; +const char GitCommitDate[] = "@GIT_COMMITTER_DATE@"; +const char GitTag[] = "@GIT_LATEST_TAG@"; +const char BuildAuthorName[] = "@GIT_BUILD_AUTHOR@"; +time_t GitCommitTimeStamp = @GIT_COMMITTER_TIME_STAMP@; +bool GitUncommittedChanges = @GIT_IS_DIRTY@; +bool GitHaveInfo = @GIT_HAVE_INFO@; +unsigned GitMajor = @GIT_VERSION_MAJOR@; +unsigned GitMinor = @GIT_VERSION_MINOR@; +unsigned GitPatch = @GIT_VERSION_PATCH@; +unsigned GitRevision = @GIT_REVISION_COUNT@; diff --git a/Code/GitInfo/gitinfo.h b/Code/GitInfo/gitinfo.h new file mode 100644 index 000000000..cf3811fc3 --- /dev/null +++ b/Code/GitInfo/gitinfo.h @@ -0,0 +1,46 @@ +/* +** Command & Conquer Renegade(tm) +** Copyright 2025 OpenW3D Contributors. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ +#ifndef GITINFO_H +#define GITINFO_H + +#include + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +extern const char GitSHA1[]; +extern const char GitShortSHA1[]; +extern const char GitCommitDate[]; +extern const char GitTag[]; +extern const char BuildAuthorName[]; +extern time_t GitCommitTimeStamp; +extern bool GitUncommittedChanges; +extern bool GitHaveInfo; +extern unsigned GitMajor; +extern unsigned GitMinor; +extern unsigned GitPatch; +extern unsigned GitRevision; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif