How to make Git commit hash available in C++ code without needless recompiling? How to make Git commit hash available in C++ code without needless recompiling? git git

How to make Git commit hash available in C++ code without needless recompiling?


I found a nice solution here:

In your CMakeLists.txt put:

# Get the current working branchexecute_process(    COMMAND git rev-parse --abbrev-ref HEAD    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}    OUTPUT_VARIABLE GIT_BRANCH    OUTPUT_STRIP_TRAILING_WHITESPACE)# Get the latest commit hashexecute_process(    COMMAND git rev-parse HEAD    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}    OUTPUT_VARIABLE GIT_COMMIT_HASH    OUTPUT_STRIP_TRAILING_WHITESPACE)

and then define it in your source:

target_compile_definitions(${PROJECT_NAME} PRIVATE    "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")

In the source it will now be available as a #define. One might want to make sure that the source still compiles correctly by including:

#ifndef GIT_COMMIT_HASH#define GIT_COMMIT_HASH "?"#endif

Then you are ready to use, with for example:

std::string hash = GIT_COMMIT_HASH;


It turns out my third approach was fine after all: $(shell) does run before make figures out what to rebuild. The problem was that, during my isolated tests, I accidentally committed version.h to the repository, which caused the double rebuild.

But there is room for improvement still, thanks to @BasileStarynkevitch and @RenaudPacalet: if version.h is used from multiple files, it's nicer to store the hash in a version.cpp file instead, so we only need to recompile one tiny file and re-link.

So here's the final solution:

version.h

#ifndef VERSION_H#define VERSION_Hextern char const *const GIT_COMMIT;#endif

Makefile

$(shell echo -e "#include \"version.h\"\n\nchar const *const GIT_COMMIT = \"$$(git describe --always --dirty --match 'NOT A TAG')\";" > version.cpp.tmp; if diff -q version.cpp.tmp version.cpp >/dev/null 2>&1; then rm version.cpp.tmp; else mv version.cpp.tmp version.cpp; fi)# Normally generated by CMake, qmake, ...main: main.o version.o    g++ -o$< $?main.o: main.cpp version.h    g++ -c -o$@ $<version.o: version.cpp version.h    g++ -c -o$@ $<

Thanks everyone for chiming in with alternatives!


First of all, you could generate a phony version.h but use it only in version.cpp that defines the print_version function used everywhere else. Each invocation of make while nothing changed would then cost you only one ultra-fast compilation of version.cpp plus the fairly lengthy link stage. No other re-compilations.

Next, you can probably solve your problem with a bit of recursive make:

TARGETS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) ...ifeq ($(MODE),)$(TARGETS): version    $(MAKE) MODE=1 $@.PHONY: versionversion:    VERSION=$$(git describe --always --dirty) && \    printf '#define GIT_COMMIT "%s"\n' "$$VERSION" > version.tmp && \    if [ ! -f version.h ] || ! diff --brief version.tmp version.h &> /dev/null; then \        cp version.tmp version.h; \    fielsemain.o: main.cpp version.h    g++ -c -o$@ $<...endif

The $(MAKE) MODE=1 $@ invocation will do something if and only if version.h has been modified by the first make invocation (or if the target had to be re-built anyway). And the first make invocation will modify version.h if and only if the commit hash changed.