Skip to content

feature(gitinfo): Embed version info into binary.#18

Open
OmniBlade wants to merge 1 commit intow3dhub:mainfrom
OmniBlade:feature/gitinfo
Open

feature(gitinfo): Embed version info into binary.#18
OmniBlade wants to merge 1 commit intow3dhub:mainfrom
OmniBlade:feature/gitinfo

Conversation

@OmniBlade
Copy link
Copy Markdown
Collaborator

Embeds information from git into binary.
Refactors printing version information for main menu and debug logs.

@OmniBlade OmniBlade assigned rm5248 and unassigned rm5248 Aug 13, 2025
@OmniBlade OmniBlade requested a review from rm5248 August 13, 2025 13:40
@madebr
Copy link
Copy Markdown

madebr commented Aug 13, 2025

At SDL, we're using GetGitRevisionDescription.cmake and GetGitRevisionDescription.cmake.in to get the git hash.
This implementation is cmake-only and thus integrates neatly in CMake's dependency-tracking.

Comment thread Code/GitInfo/GitInfo.cmake Outdated
execute_process(COMMAND ${GIT_EXECUTABLE} show -s "--format=%H" HEAD
WORKING_DIRECTORY ${source}
OUTPUT_VARIABLE GIT_REVISION_HASH OUTPUT_STRIP_TRAILING_WHITESPACE)
set(GIT_REVISION_HASH "${GIT_REVISION_HASH}" CACHE INTERNAL "git revision full hash")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this caching cause the revision to be stuck on the value of the first CMake configuration?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested rerunning the cmake configurations after changing things like the commit tag or creating a new commit and it updated the information as expected.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you reconfigure CMake, then it works indeed.
But it does not update the git parameters when you forget to configure, and only build the target.

The following sequence of commands do not trigger rebuilds, so the executables contain wrong hashes.

git commit -m hello --allow-empty
cmake --build . --target renegade
git commit -m hello --allow-empty
cmake --build . --target renegade
git commit -m hello --allow-empty
cmake --build . --target renegade

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done something similar to this before, as long as all the builds come from CI it's fine(since the buildserver would do a clean build every time). It does stay stuck at the previously cached value otherwise.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

At SDL, we're using GetGitRevisionDescription.cmake and GetGitRevisionDescription.cmake.in to get the git hash. This implementation is cmake-only and thus integrates neatly in CMake's dependency-tracking.

Looking at that, it still only sets a CMake variable that would ultimately have to be either passed as a define to the build system or used to configure a file that would be compiled into the target?

@madebr
Copy link
Copy Markdown

madebr commented Aug 13, 2025

At SDL, we're using GetGitRevisionDescription.cmake and GetGitRevisionDescription.cmake.in to get the git hash. This implementation is cmake-only and thus integrates neatly in CMake's dependency-tracking.

Looking at that, it still only sets a CMake variable that would ultimately have to be either passed as a define to the build system or used to configure a file that would be compiled into the target?

I think the magic is it depends on @GIT_DIR@/HEAD so if that file changes (due to changing HEAD), it triggers a reconfigure. Other git metadata is updated through git commands.

@rm5248
Copy link
Copy Markdown
Collaborator

rm5248 commented Aug 13, 2025

At SDL, we're using GetGitRevisionDescription.cmake and GetGitRevisionDescription.cmake.in to get the git hash. This implementation is cmake-only and thus integrates neatly in CMake's dependency-tracking.

Looking at that, it still only sets a CMake variable that would ultimately have to be either passed as a define to the build system or used to configure a file that would be compiled into the target?

I think the magic is it depends on @GIT_DIR@/HEAD so if that file changes (due to changing HEAD), it triggers a reconfigure. Other git metadata is updated through git commands.

That definitely sounds like the better way to do this, so you don't need to reconfigure in order to get updated variables.

A thought on some of the string manipulation in cmake: if you want to be fancy, you could probably replace most of that with constexpr functions in the C++ code.

For the version that is shown to the user, what I generally do is to use CMAKE_PROJECT_VERSION instead of parsing it from the git tag. What I've been doing recently is for release builds, the CI compares the tag with the project version defined in CMake to ensure that they match.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

For the version that is shown to the user, what I generally do is to use CMAKE_PROJECT_VERSION instead of parsing it from the git tag. What I've been doing recently is for release builds, the CI compares the tag with the project version defined in CMake to ensure that they match.

My main issue with getting it from the CMake project version is that it involves pushing a commit specifically to bump the version rather than deciding a given commit is the next version, tagging it and making it so. Plus this doesn't rely on anything matching, any untagged commit will be built as a revision bump on the previous version tag. It will even work with other tagging if we started releasing nightlies or weeklies or something with a different tagging scheme treating them all as revisions on the last version patch until we do a full release. Just some of my thought process that went into the versioning anyhow.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

At SDL, we're using GetGitRevisionDescription.cmake and GetGitRevisionDescription.cmake.in to get the git hash. This implementation is cmake-only and thus integrates neatly in CMake's dependency-tracking.

Looking at that, it still only sets a CMake variable that would ultimately have to be either passed as a define to the build system or used to configure a file that would be compiled into the target?

I think the magic is it depends on @GIT_DIR@/HEAD so if that file changes (due to changing HEAD), it triggers a reconfigure. Other git metadata is updated through git commands.

We did use a different CMake module for Vanilla Conquer that set up a custom target that forced a reconfigure, but it was a bit convoluted though this one seems to be also. I can't believe it takes so much complication to get something seemingly simple to work.

@rm5248
Copy link
Copy Markdown
Collaborator

rm5248 commented Aug 14, 2025

For the version that is shown to the user, what I generally do is to use CMAKE_PROJECT_VERSION instead of parsing it from the git tag. What I've been doing recently is for release builds, the CI compares the tag with the project version defined in CMake to ensure that they match.

My main issue with getting it from the CMake project version is that it involves pushing a commit specifically to bump the version rather than deciding a given commit is the next version, tagging it and making it so. Plus this doesn't rely on anything matching, any untagged commit will be built as a revision bump on the previous version tag. It will even work with other tagging if we started releasing nightlies or weeklies or something with a different tagging scheme treating them all as revisions on the last version patch until we do a full release. Just some of my thought process that went into the versioning anyhow.

I like to do more intentional releases, so that's where I'm coming from. Just as a point of comparison, Linux will do a single commit that bumps the version and then tag it like that(example).

The other problem that just occurred to me is that without having the version number in the code, if you download the code you would have no idea what version it is - in other words, it's entirely driven by the git tag. So you wouldn't be able to download the code and build a binary that is exactly the same.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

I like to do more intentional releases, so that's where I'm coming from. Just as a point of comparison, Linux will do a single commit that bumps the version and then tag it like that(example).

The other problem that just occurred to me is that without having the version number in the code, if you download the code you would have no idea what version it is - in other words, it's entirely driven by the git tag. So you wouldn't be able to download the code and build a binary that is exactly the same.

The issue I have with that is that there is no single point of truth though and the tag and in code version could differ. If you just download the code without cloning the repo (which should pull the tags as well) then you are missing a bunch of other information like the commit hash, commit date and other information that would be needed to match the build so you still won't be able to match it exactly without recreating that data offline. The release can still be deliberate, you only create and puch a tag when you want a release and then the CI should build a new version of that commit with the updated tag and create the new release on github ready to download. All automated with the push of the tag only. The Linux process makes sense for Linux because it predates the git workflow so why mess with established convention for such a long lived project.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

I've refactored this PR to use a variation of the git watcher code that vanilla conquer uses, so it should address the issues with not picking up changes to the repo state.

@OmniBlade OmniBlade requested a review from madebr August 16, 2025 21:52
@rm5248
Copy link
Copy Markdown
Collaborator

rm5248 commented Aug 17, 2025

Almost all of the big open source projects that I'm aware of do embed the version into the source bundle. I would propose that we do something like the following:

  1. Keep this as-is, more for development purposes
  2. Add a script to do a release that will 'lock' in the versions for the release.

Option A for releasing:

  1. tag commit
  2. github picks up commit
  3. the gitinfo.cpp file is generated and packaged into the source code release zip
  4. source code release zip is uploaded as part of release

Option B for releasing(my preferred way):

  1. Make sure there are no local changes
  2. Set release version(e.g. 1.2). This would be either in the main CMakeLists.txt or in a separate file that contains just the version
  3. commit the file that changed the version
  4. tag the release
  5. Update the version file that we did in step 2 to be the next dev version(1.3) and commit it

Option B would be done locally with some sort of script in order to automate the process. We can follow a odd/even scheme for the releases that some other projects do, where even means "release version" and odd means "development version." The idea behind option B is that we also don't need any git information in the binary, although I would be open to keeping the git SHA. The commit author doesn't provide value IMHO.

Thoughts?

@madebr
Copy link
Copy Markdown

madebr commented Aug 17, 2025

The SDL version is also embedded in various places.
See the SDL headers, CMakeLists.txt, SDL3.rc, ...

Because this version is present in multiple locations, we have scripts to check and update the version.

What we do in SDL to get the git revision during the build process::

  1. Check whether REVISION.txt exists. If it does, use its content. This file is created during the release process. It contains the git hash of the release. You'll notice all release assets contain a .git-hash file (for tracking purposes). The source archive contains a REVISION.txt file.
  2. If SDL_REVISION is defined, use that. This is useful for users who use SDL as a subproject and want to avoid recompilation/relinking of their project because of a changed SDL git hash.
  3. Else, calculate a git hash (using the CMake scripts I posted above)

Our release checklist contains lots of points to make sure the versioning is handled correctly.

I don't think this process is overly complicated, and can be automated.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

My preference is to follow something like semver, major version releases for when the API/ABI for scripts changes or the network protocol changes in an incompatible way with previous builds, minor for changes that add features or fixes in a backward compatible and patch for minor bug fixes that don't affect compatibility.

I think that the more automated the process for release is, the less chance there is for mistakes to crawl in. I don't object with a generated gitinfo.cpp being embedded in a release source zip.

I can remove the git author stuff and just leave the ability to pass in a build author so we can stamp it as an OpenW3D release on the CI. It is in there more to replicate the author info in the original build info stamped into the binary.

@OmniBlade OmniBlade force-pushed the feature/gitinfo branch 2 times, most recently from ee432f6 to 59b001f Compare September 12, 2025 15:43
Embeds information from git into binary.
Refactors printing version information for main menu and debug logs.
@madebr
Copy link
Copy Markdown

madebr commented Mar 19, 2026

If we want to do this right, I think we should also do something with the version info in Code/Commando/chat.rc.
The version in there is currently 1.37, which is the same as game.exe of the steam release.

@caseychaos1212
Copy link
Copy Markdown
Collaborator

I can look into what TT is doing but it's not as robust as what's being discussed here I don't think.

Comment thread CMakeLists.txt

# 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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should make embedding git information optional.
When we ever do a source release, we should then make sure that it can build without git.

@OmniBlade
Copy link
Copy Markdown
Collaborator Author

If we want to do this right, I think we should also do something with the version info in Code/Commando/chat.rc. The version in there is currently 1.37, which is the same as game.exe of the steam release.

There is removed code for retrieving the version info from the embedded resources, not sure what the requirement for that was but it was removed before the source code was released. In Vanilla Conquer I generate the embedded resources from cmake to compile in on windows builds.

@madebr
Copy link
Copy Markdown

madebr commented Mar 24, 2026

There is removed code for retrieving the version info from the embedded resources, not sure what the requirement for that was but it was removed before the source code was released. In Vanilla Conquer I generate the embedded resources from cmake to compile in on windows builds.

I've added that code back in (temporarily) in my current active sdl3 PR #105 (please review).
In my next PR, I'm going to auto generate chat.rc and a version header to get wwlib building on Linux.

Dadud pushed a commit to Dadud/OpenW3D that referenced this pull request Apr 23, 2026
Resolves 4 merge conflicts:
- buildnum.cpp/h: replaced old BuildNumber[char] post-build stamping with git-embedded info
- dlgmainmenu.cpp: updated Update_Version_Number to use git info format
- ConsoleMode.cpp: removed Get_Version_Number call (no longer needed)

PR w3dhub#107 fixes applied on top:
- rawfile.cpp: READ|WRITE data loss fix (r+b then w+b)
- verchk.cpp: FAT date month decode (0xf → 0x1f)
- rcfile: static init order fix (Meyers singleton)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants