build: generate a BuildInfo translation unit with version, commit, branch, dirty flag and build kind

cmake/BuildInfo.cmake runs on every build and rewrites the generated
BuildInfo.cpp only when something changed, so a new commit recompiles one
file and relinks. The build kind comes from DLU_BUILD_KIND
(release/ci/local) or CI=true, defaulting to local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 15:43:08 -05:00
parent e74a7ea701
commit 97a6e27d56
4 changed files with 156 additions and 1 deletions

71
cmake/BuildInfo.cmake Normal file
View File

@@ -0,0 +1,71 @@
# Writes BuildInfo.cpp (the data behind dCommon/BuildInfo.h) at build time.
# Run as a script:
# cmake -DSOURCE_DIR=... -DTEMPLATE=... -DOUTPUT=... -DVERSION_MAJOR=... -DVERSION_MINOR=... -DVERSION_PATCH=... -P BuildInfo.cmake
# The output is only rewritten when its contents change, so an unchanged tree recompiles nothing.
#
# Build kind comes from the environment of the build: DLU_BUILD_KIND=release|ci|local wins, otherwise CI=true
# (set by GitHub Actions and most CI systems) means ci, otherwise local.
find_package(Git QUIET)
set(BUILD_COMMIT "")
set(BUILD_BRANCH "")
set(BUILD_DIRTY "false")
if(GIT_FOUND)
execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${SOURCE_DIR}" rev-parse HEAD
OUTPUT_VARIABLE BUILD_COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE result)
if(NOT result EQUAL 0)
set(BUILD_COMMIT "")
endif()
endif()
if(BUILD_COMMIT)
execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${SOURCE_DIR}" rev-parse --abbrev-ref HEAD
OUTPUT_VARIABLE BUILD_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
if(BUILD_BRANCH STREQUAL "HEAD")
# Detached HEAD (CI checkouts): use the branch the CI names, if any.
if(NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "")
set(BUILD_BRANCH "$ENV{GITHUB_HEAD_REF}")
else()
set(BUILD_BRANCH "$ENV{GITHUB_REF_NAME}")
endif()
endif()
execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${SOURCE_DIR}" update-index -q --refresh OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${SOURCE_DIR}" diff-index --quiet HEAD --
RESULT_VARIABLE result OUTPUT_QUIET ERROR_QUIET)
if(NOT result EQUAL 0)
set(BUILD_DIRTY "true")
endif()
endif()
string(TOLOWER "$ENV{DLU_BUILD_KIND}" kind)
string(TOLOWER "$ENV{CI}" ci)
if(kind STREQUAL "release")
set(BUILD_KIND "RELEASE")
elseif(kind STREQUAL "ci" OR (kind STREQUAL "" AND (ci STREQUAL "true" OR ci STREQUAL "1")))
set(BUILD_KIND "CI")
else()
set(BUILD_KIND "LOCAL")
endif()
# Readable identifier, e.g. 3.0.0-experimental+g1a2b3c4d-dirty. Release builds leave out the branch.
set(BUILD_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(BUILD_BRANCH AND NOT BUILD_KIND STREQUAL "RELEASE")
string(REGEX REPLACE ".*/" "" branchName "${BUILD_BRANCH}")
string(REGEX REPLACE "[^0-9A-Za-z.-]" "-" branchName "${branchName}")
string(APPEND BUILD_STRING "-${branchName}")
endif()
if(BUILD_COMMIT)
string(SUBSTRING "${BUILD_COMMIT}" 0 8 shortCommit)
string(APPEND BUILD_STRING "+g${shortCommit}")
if(BUILD_DIRTY)
string(APPEND BUILD_STRING "-dirty")
endif()
endif()
# Git allows " in branch names; keep the C++ string literal valid.
string(REPLACE "\"" "\\\"" BUILD_BRANCH "${BUILD_BRANCH}")
configure_file("${TEMPLATE}" "${OUTPUT}" @ONLY)

13
dCommon/BuildInfo.cpp.in Normal file
View File

@@ -0,0 +1,13 @@
// Generated by cmake/BuildInfo.cmake on every build. Do not edit.
#include "BuildInfo.h"
namespace BuildInfo {
const uint8_t versionMajor = @VERSION_MAJOR@;
const uint8_t versionMinor = @VERSION_MINOR@;
const uint8_t versionPatch = @VERSION_PATCH@;
const eBuildKind buildKind = eBuildKind::@BUILD_KIND@;
const bool dirty = @BUILD_DIRTY@;
const std::string_view commit = "@BUILD_COMMIT@";
const std::string_view branch = "@BUILD_BRANCH@";
const std::string_view buildString = "@BUILD_STRING@";
}

51
dCommon/BuildInfo.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef BUILDINFO_H
#define BUILDINFO_H
#include <cstdint>
#include <string_view>
// What this binary was built from. The values live in BuildInfo.cpp, which cmake/BuildInfo.cmake regenerates on
// every build (only rewriting it when something changed), so a new commit recompiles that one file and relinks.
namespace BuildInfo {
// Stored in 2 bits of Flags().
enum class eBuildKind : uint8_t {
UNKNOWN = 0,
LOCAL = 1,
CI = 2,
RELEASE = 3,
};
constexpr uint8_t BUILD_KIND_MASK = 0b011;
constexpr uint8_t DIRTY_FLAG = 0b100;
extern const uint8_t versionMajor;
extern const uint8_t versionMinor;
extern const uint8_t versionPatch;
extern const eBuildKind buildKind;
extern const bool dirty; // The build had uncommitted changes to tracked files
extern const std::string_view commit; // Full hex commit hash, empty without git
extern const std::string_view branch; // Empty when detached or without git
extern const std::string_view buildString; // e.g. 3.0.0-experimental+g1a2b3c4d-dirty
// The build kind in bits 0-1 and the dirty flag in bit 2.
inline uint8_t Flags() {
return (static_cast<uint8_t>(buildKind) & BUILD_KIND_MASK) | (dirty ? DIRTY_FLAG : 0);
}
// The first 32 bits (8 hex digits) of the commit hash, 0 without git.
inline uint32_t CommitPrefix() {
uint32_t value = 0;
for (size_t i = 0; i < 8 && i < commit.size(); i++) {
const char c = commit[i];
uint32_t digit = 0;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10;
else return 0;
value = (value << 4) | digit;
}
return commit.size() >= 8 ? value : 0;
}
}
#endif // BUILDINFO_H

View File

@@ -88,7 +88,27 @@ else ()
)
endif ()
# Build identifier (BuildInfo.h). The generator runs on every build but only rewrites BuildInfo.cpp when the
# commit, branch, dirty state or build kind changed, so only that one file recompiles.
set(BUILDINFO_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/generated/BuildInfo.cpp")
add_custom_target(BuildInfoGenerate
COMMAND "${CMAKE_COMMAND}"
"-DSOURCE_DIR=${PROJECT_SOURCE_DIR}"
"-DTEMPLATE=${CMAKE_CURRENT_SOURCE_DIR}/BuildInfo.cpp.in"
"-DOUTPUT=${BUILDINFO_SOURCE}"
"-DVERSION_MAJOR=${PROJECT_VERSION_MAJOR}"
"-DVERSION_MINOR=${PROJECT_VERSION_MINOR}"
"-DVERSION_PATCH=${PROJECT_VERSION_PATCH}"
-P "${PROJECT_SOURCE_DIR}/cmake/BuildInfo.cmake"
BYPRODUCTS "${BUILDINFO_SOURCE}"
COMMENT "Checking build identifier"
VERBATIM
)
add_library(dBuildInfo STATIC "${BUILDINFO_SOURCE}")
target_include_directories(dBuildInfo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
add_dependencies(dBuildInfo BuildInfoGenerate)
target_link_libraries(dCommon
PUBLIC glm::glm
PUBLIC glm::glm dBuildInfo
PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase)