Skip to content

Commit d0acf7c

Browse files
committed
Added version info system that generates version, build number, git hash, branch, dirty status, and branch offset via a generic CMake and C++ template; Breaking Change: VISUAL_NODE_SYSTEM_VERSION was deleted, to get the version, NODE_SYSTEM.GetVersion() should now be used.
1 parent 746edae commit d0acf7c

8 files changed

Lines changed: 367 additions & 3 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
imgui.ini
1919
CMakeCache.txt
2020
cmake_install.cmake
21+
VISUAL_NODE_SYSTEM_Version.h
2122

2223
#Folders
2324
Debug/

CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,14 @@ include_directories(
305305
)
306306

307307
set(VISUAL_NODE_SYSTEM_THIRDPARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ThirdParty PARENT_SCOPE)
308-
set(VISUAL_NODE_SYSTEM_DIR ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE)
308+
set(VISUAL_NODE_SYSTEM_DIR ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE)
309+
310+
add_custom_command(TARGET VisualNodeSystem PRE_BUILD
311+
COMMAND ${CMAKE_COMMAND}
312+
-D PROJECT_VERSION_PREFIX=VISUAL_NODE_SYSTEM_
313+
-D PROJECT_VERSION_MAJOR=0
314+
-D PROJECT_VERSION_MINOR=2
315+
-D PROJECT_VERSION_PATCH=0
316+
-D PROJECT_VERSION_DIR=${CMAKE_CURRENT_SOURCE_DIR}/VersionInfo
317+
-P ${CMAKE_CURRENT_SOURCE_DIR}/VersionInfo/UpdateProjectVersion.cmake
318+
)

VersionInfo/FEVersionInfo.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#ifndef FE_VERSION_INFO_H
2+
#define FE_VERSION_INFO_H
3+
4+
#include <string>
5+
6+
// Usage:
7+
// #include "${Project_PREFIX}Version.h"
8+
// #include "FEVersionInfo.h"
9+
// FE_DEFINE_VERSION_INFO(Project_PREFIX)
10+
11+
struct FEVersionInfo
12+
{
13+
int Major = 0;
14+
int Minor = 0;
15+
int Patch = 0;
16+
int BuildNumber = 0;
17+
int BranchOffset = 0;
18+
int Dirty = 0;
19+
std::string GitHash;
20+
std::string GitBranch;
21+
std::string DefaultBranch;
22+
std::string BuildTimestamp;
23+
24+
std::string GetVersion() const
25+
{
26+
return std::to_string(Major) + "." +
27+
std::to_string(Minor) + "." +
28+
std::to_string(Patch);
29+
}
30+
31+
std::string GetBuildInfo() const
32+
{
33+
std::string Result = "build " + std::to_string(BuildNumber) + " (" + GitHash;
34+
35+
if (!GitBranch.empty())
36+
Result += " " + GitBranch;
37+
38+
if (BranchOffset > 0)
39+
Result += " +" + std::to_string(BranchOffset) + " from " + DefaultBranch;
40+
41+
if (Dirty)
42+
Result += ", dirty";
43+
44+
Result += ")";
45+
return Result;
46+
}
47+
48+
std::string GetFullVersionString() const
49+
{
50+
return GetVersion() + " " + GetBuildInfo();
51+
}
52+
};
53+
54+
// Macro that reads the project-specific #defines and creates an accessor function.
55+
// PREFIX must match the prefix used in the generated *Version.h header.
56+
//
57+
// Safely stringify macros that may be empty or undefined, avoiding compilation errors.
58+
#define FE_VERSION_STRINGIFY(X) #X
59+
#define FE_VERSION_TOSTRING(X) ("" FE_VERSION_STRINGIFY(X))
60+
61+
#define FE_DEFINE_VERSION_INFO(PREFIX) \
62+
inline FEVersionInfo Get##PREFIX##VersionInfo() \
63+
{ \
64+
FEVersionInfo Info; \
65+
Info.Major = PREFIX##_VERSION_MAJOR; \
66+
Info.Minor = PREFIX##_VERSION_MINOR; \
67+
Info.Patch = PREFIX##_VERSION_PATCH; \
68+
Info.BuildNumber = PREFIX##_BUILD_NUMBER; \
69+
Info.BranchOffset = PREFIX##_BUILD_BRANCH_OFFSET; \
70+
Info.Dirty = PREFIX##_GIT_DIRTY; \
71+
Info.GitHash = PREFIX##_GIT_HASH; \
72+
Info.GitBranch = PREFIX##_GIT_BRANCH; \
73+
Info.DefaultBranch = PREFIX##_DEFAULT_BRANCH; \
74+
Info.BuildTimestamp = FE_VERSION_TOSTRING(PREFIX##_BUILD_TIMESTAMP); \
75+
return Info; \
76+
}
77+
78+
#endif // FE_VERSION_INFO_H

VersionInfo/ProjectVersion.h.in

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#define @PROJECT_VERSION_PREFIX@_VERSION_MAJOR @PROJECT_VERSION_PREFIX_VERSION_MAJOR@
4+
#define @PROJECT_VERSION_PREFIX@_VERSION_MINOR @PROJECT_VERSION_PREFIX_VERSION_MINOR@
5+
#define @PROJECT_VERSION_PREFIX@_VERSION_PATCH @PROJECT_VERSION_PREFIX_VERSION_PATCH@
6+
7+
#define @PROJECT_VERSION_PREFIX@_BUILD_NUMBER @PROJECT_VERSION_PREFIX_MASTER_COMMIT_COUNT@
8+
#define @PROJECT_VERSION_PREFIX@_BUILD_BRANCH_OFFSET @PROJECT_VERSION_PREFIX_BRANCH_COMMIT_COUNT@
9+
#define @PROJECT_VERSION_PREFIX@_GIT_HASH "@PROJECT_VERSION_PREFIX_GIT_HASH@"
10+
#define @PROJECT_VERSION_PREFIX@_GIT_BRANCH "@PROJECT_VERSION_PREFIX_GIT_BRANCH@"
11+
#define @PROJECT_VERSION_PREFIX@_BUILD_TIMESTAMP @PROJECT_VERSION_PREFIX_BUILD_TIMESTAMP@
12+
#define @PROJECT_VERSION_PREFIX@_GIT_DIRTY @PROJECT_VERSION_PREFIX_GIT_DIRTY@
13+
#define @PROJECT_VERSION_PREFIX@_DEFAULT_BRANCH "@PROJECT_VERSION_PREFIX_DEFAULT_BRANCH@"
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# ============================================================================
2+
# UpdateProjectVersion.cmake
3+
#
4+
# Generic pre-build script that generates version information for any project.
5+
# Runs every build to capture: git commit counts, branch, hash,
6+
# dirty status and timestamp.
7+
#
8+
# Required input variables (set before including this script):
9+
# PROJECT_VERSION_PREFIX - Macro prefix, e.g. "Focal Engine" or "HabiCAT3D"
10+
# PROJECT_VERSION_MAJOR - Semantic version major
11+
# PROJECT_VERSION_MINOR - Semantic version minor
12+
# PROJECT_VERSION_PATCH - Semantic version patch
13+
# PROJECT_VERSION_DIR - Source directory (where the generated header will be placed)
14+
#
15+
# Optional:
16+
# PROJECT_VERSION_TEMPLATE_DIR - Directory containing ProjectVersion.h.in
17+
# (defaults to the directory of this script)
18+
#
19+
# Generates:
20+
# ${PROJECT_VERSION_DIR}/${PROJECT_VERSION_PREFIX}Version.h
21+
#
22+
# Produces version strings like:
23+
# On master, clean: "1.0.0 build 231 (ed4c7ce master)"
24+
# On master, dirty: "1.0.0 build 231 (ed4c7ce master, dirty)"
25+
# On branch, with offset: "1.0.0 build 231 (ed4c7ce dev +52 from master, dirty)"
26+
# ============================================================================
27+
28+
# --- Validate required inputs ---
29+
foreach(_var PROJECT_VERSION_PREFIX PROJECT_VERSION_MAJOR PROJECT_VERSION_MINOR PROJECT_VERSION_PATCH PROJECT_VERSION_DIR)
30+
if(NOT DEFINED ${_var})
31+
message(FATAL_ERROR "UpdateProjectVersion.cmake: ${_var} must be set before including this script.")
32+
endif()
33+
endforeach()
34+
35+
find_package(Git QUIET)
36+
if(GIT_FOUND)
37+
# --- Detect default branch name (master or main) ---
38+
# Try origin/master first, fall back to origin/main.
39+
execute_process(
40+
COMMAND ${GIT_EXECUTABLE} rev-parse --verify origin/master
41+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
42+
RESULT_VARIABLE _MASTER_CHECK_RC
43+
OUTPUT_QUIET
44+
ERROR_QUIET
45+
)
46+
if(_MASTER_CHECK_RC EQUAL 0)
47+
set(_DEFAULT_BRANCH "origin/master")
48+
else()
49+
execute_process(
50+
COMMAND ${GIT_EXECUTABLE} rev-parse --verify origin/main
51+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
52+
RESULT_VARIABLE _MAIN_CHECK_RC
53+
OUTPUT_QUIET
54+
ERROR_QUIET
55+
)
56+
if(_MAIN_CHECK_RC EQUAL 0)
57+
set(_DEFAULT_BRANCH "origin/main")
58+
else()
59+
set(_DEFAULT_BRANCH "")
60+
endif()
61+
endif()
62+
63+
# --- Commit counts ---
64+
# Count total commits on the default branch - this is the stable build number.
65+
if(NOT "${_DEFAULT_BRANCH}" STREQUAL "")
66+
execute_process(
67+
COMMAND ${GIT_EXECUTABLE} rev-list --count ${_DEFAULT_BRANCH}
68+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
69+
OUTPUT_VARIABLE _MASTER_COMMIT_COUNT
70+
OUTPUT_STRIP_TRAILING_WHITESPACE
71+
ERROR_QUIET
72+
)
73+
endif()
74+
75+
# Fallback: count all commits on current branch.
76+
if("${_MASTER_COMMIT_COUNT}" STREQUAL "")
77+
execute_process(
78+
COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
79+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
80+
OUTPUT_VARIABLE _MASTER_COMMIT_COUNT
81+
OUTPUT_STRIP_TRAILING_WHITESPACE
82+
ERROR_QUIET
83+
)
84+
endif()
85+
if("${_MASTER_COMMIT_COUNT}" STREQUAL "")
86+
set(_MASTER_COMMIT_COUNT 0)
87+
endif()
88+
89+
# Count commits ahead of the default branch on current branch.
90+
# Will be 0 when building on the default branch itself.
91+
if(NOT "${_DEFAULT_BRANCH}" STREQUAL "")
92+
execute_process(
93+
COMMAND ${GIT_EXECUTABLE} rev-list --count ${_DEFAULT_BRANCH}..HEAD
94+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
95+
OUTPUT_VARIABLE _BRANCH_COMMIT_COUNT
96+
OUTPUT_STRIP_TRAILING_WHITESPACE
97+
ERROR_QUIET
98+
)
99+
endif()
100+
101+
# Fallback: if no default branch found or command fails, use 0.
102+
if("${_BRANCH_COMMIT_COUNT}" STREQUAL "")
103+
set(_BRANCH_COMMIT_COUNT 0)
104+
endif()
105+
106+
# --- Commit hash ---
107+
execute_process(
108+
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
109+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
110+
OUTPUT_VARIABLE _GIT_HASH
111+
OUTPUT_STRIP_TRAILING_WHITESPACE
112+
)
113+
114+
# --- Branch name ---
115+
execute_process(
116+
COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
117+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
118+
OUTPUT_VARIABLE _GIT_BRANCH
119+
OUTPUT_STRIP_TRAILING_WHITESPACE
120+
)
121+
122+
# --- Dirty status ---
123+
execute_process(
124+
COMMAND ${GIT_EXECUTABLE} diff --quiet HEAD
125+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
126+
RESULT_VARIABLE GIT_DIRTY_RC
127+
)
128+
if(GIT_DIRTY_RC EQUAL 0)
129+
set(_GIT_DIRTY 0)
130+
else()
131+
set(_GIT_DIRTY 1)
132+
endif()
133+
else()
134+
# Fallback when git is not available.
135+
set(_MASTER_COMMIT_COUNT 0)
136+
set(_BRANCH_COMMIT_COUNT 0)
137+
set(_GIT_HASH "unknown")
138+
set(_GIT_BRANCH "unknown")
139+
set(_GIT_DIRTY 0)
140+
endif()
141+
142+
# --- Detached HEAD resolution ---
143+
# Submodules are typically checked out in detached HEAD state.
144+
# Try multiple strategies to resolve the actual branch name.
145+
if("${_GIT_BRANCH}" STREQUAL "HEAD")
146+
# Strategy 1: Check if HEAD matches a known default branch exactly.
147+
# This is the most common case for submodules pinned to master/main.
148+
if(NOT "${_DEFAULT_BRANCH}" STREQUAL "")
149+
execute_process(
150+
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
151+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
152+
OUTPUT_VARIABLE _HEAD_SHA
153+
OUTPUT_STRIP_TRAILING_WHITESPACE
154+
ERROR_QUIET
155+
)
156+
execute_process(
157+
COMMAND ${GIT_EXECUTABLE} rev-parse ${_DEFAULT_BRANCH}
158+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
159+
OUTPUT_VARIABLE _DEFAULT_BRANCH_SHA
160+
OUTPUT_STRIP_TRAILING_WHITESPACE
161+
ERROR_QUIET
162+
)
163+
if("${_HEAD_SHA}" STREQUAL "${_DEFAULT_BRANCH_SHA}")
164+
string(REGEX REPLACE "^origin/" "" _GIT_BRANCH "${_DEFAULT_BRANCH}")
165+
endif()
166+
endif()
167+
168+
# Strategy 2: Try remote tracking branches containing this commit.
169+
if("${_GIT_BRANCH}" STREQUAL "HEAD")
170+
execute_process(
171+
COMMAND ${GIT_EXECUTABLE} branch -r --contains HEAD
172+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
173+
OUTPUT_VARIABLE _REMOTE_BRANCHES
174+
OUTPUT_STRIP_TRAILING_WHITESPACE
175+
ERROR_QUIET
176+
)
177+
if(NOT "${_REMOTE_BRANCHES}" STREQUAL "")
178+
# Filter out "HEAD ->" entries and take the first real branch.
179+
string(REGEX REPLACE "[^\n]*HEAD -> [^\n]*\n?" "" _REMOTE_BRANCHES "${_REMOTE_BRANCHES}")
180+
string(STRIP "${_REMOTE_BRANCHES}" _REMOTE_BRANCHES)
181+
if(NOT "${_REMOTE_BRANCHES}" STREQUAL "")
182+
string(REGEX MATCH "[^ \n]+" _GIT_BRANCH "${_REMOTE_BRANCHES}")
183+
string(REGEX REPLACE "^origin/" "" _GIT_BRANCH "${_GIT_BRANCH}")
184+
endif()
185+
endif()
186+
endif()
187+
188+
# Strategy 3: Try git describe --all for tag or branch reference.
189+
if("${_GIT_BRANCH}" STREQUAL "HEAD")
190+
execute_process(
191+
COMMAND ${GIT_EXECUTABLE} describe --all --always HEAD
192+
WORKING_DIRECTORY ${PROJECT_VERSION_DIR}
193+
OUTPUT_VARIABLE _DESCRIBE_OUTPUT
194+
OUTPUT_STRIP_TRAILING_WHITESPACE
195+
ERROR_QUIET
196+
)
197+
if(NOT "${_DESCRIBE_OUTPUT}" STREQUAL "")
198+
string(REGEX REPLACE "^(heads|remotes/origin)/" "" _GIT_BRANCH "${_DESCRIBE_OUTPUT}")
199+
endif()
200+
endif()
201+
202+
# Final fallback.
203+
if("${_GIT_BRANCH}" STREQUAL "HEAD" OR "${_GIT_BRANCH}" STREQUAL "")
204+
set(_GIT_BRANCH "detached")
205+
endif()
206+
endif()
207+
208+
# --- Build timestamp ---
209+
string(TIMESTAMP _BUILD_TIMESTAMP \"%Y%m%d%H%M%S\")
210+
211+
# --- Propagate into generic template variables ---
212+
# The generic ProjectVersion.h.in uses @PROJECT_VERSION_PREFIX@ for the macro prefix
213+
# and @PROJECT_VERSION_PREFIX_*@ for each value. configure_file replaces these as
214+
# flat string substitutions.
215+
set(PROJECT_VERSION_PREFIX_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
216+
set(PROJECT_VERSION_PREFIX_VERSION_MINOR ${PROJECT_VERSION_MINOR})
217+
set(PROJECT_VERSION_PREFIX_VERSION_PATCH ${PROJECT_VERSION_PATCH})
218+
set(PROJECT_VERSION_PREFIX_MASTER_COMMIT_COUNT ${_MASTER_COMMIT_COUNT})
219+
set(PROJECT_VERSION_PREFIX_BRANCH_COMMIT_COUNT ${_BRANCH_COMMIT_COUNT})
220+
set(PROJECT_VERSION_PREFIX_GIT_HASH ${_GIT_HASH})
221+
set(PROJECT_VERSION_PREFIX_GIT_BRANCH ${_GIT_BRANCH})
222+
set(PROJECT_VERSION_PREFIX_GIT_DIRTY ${_GIT_DIRTY})
223+
set(PROJECT_VERSION_PREFIX_BUILD_TIMESTAMP ${_BUILD_TIMESTAMP})
224+
225+
# Resolve default branch name without the "origin/" prefix.
226+
if(NOT "${_DEFAULT_BRANCH}" STREQUAL "")
227+
string(REGEX REPLACE "^origin/" "" _DEFAULT_BRANCH_NAME "${_DEFAULT_BRANCH}")
228+
else()
229+
set(_DEFAULT_BRANCH_NAME "master")
230+
endif()
231+
set(PROJECT_VERSION_PREFIX_DEFAULT_BRANCH ${_DEFAULT_BRANCH_NAME})
232+
233+
# --- Determine template location ---
234+
# Use PROJECT_VERSION_TEMPLATE_DIR if set, otherwise fall back to the
235+
# directory containing this script (i.e. the VersionInfo folder).
236+
if(NOT DEFINED PROJECT_VERSION_TEMPLATE_DIR)
237+
set(PROJECT_VERSION_TEMPLATE_DIR ${CMAKE_CURRENT_LIST_DIR})
238+
endif()
239+
240+
# --- Generate header ---
241+
# Uses the single generic template, outputs a project-specific header
242+
# in the project's source directory.
243+
configure_file(
244+
${PROJECT_VERSION_TEMPLATE_DIR}/ProjectVersion.h.in
245+
${PROJECT_VERSION_DIR}/${PROJECT_VERSION_PREFIX}Version.h
246+
@ONLY
247+
)

VisualNodeCore.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ namespace VisNodeSys
6565
\
6666
return result;
6767

68-
#define VISUAL_NODE_SYSTEM_VERSION "0.1.0"
69-
7068
class VISUAL_NODE_SYSTEM_API NodeCore
7169
{
7270
SINGLETON_PRIVATE_PART(NodeCore)

VisualNodeSystem.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,20 @@ NodeSystem::NodeSystem()
463463

464464
NodeSystem::~NodeSystem() {}
465465

466+
#include "VersionInfo/VISUAL_NODE_SYSTEM_Version.h"
467+
#include "VersionInfo/FEVersionInfo.h"
468+
FE_DEFINE_VERSION_INFO(VISUAL_NODE_SYSTEM_)
469+
470+
std::string NodeSystem::GetVersion()
471+
{
472+
return GetVISUAL_NODE_SYSTEM_VersionInfo().GetVersion();
473+
}
474+
475+
std::string NodeSystem::GetFullVersion()
476+
{
477+
return "Visual Node System " + GetVISUAL_NODE_SYSTEM_VersionInfo().GetFullVersionString();
478+
}
479+
466480
void NodeSystem::Initialize(bool bTestMode)
467481
{
468482
NODE_CORE.bIsInTestMode = bTestMode;

0 commit comments

Comments
 (0)