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.