I'm trying to integrate the output of git describe in my binaries:
// version.c.in
#include <stdio.h>
const char version[] __attribute__((used)) = "##BUILDVERSION##:@BUILDVERSION@";
int main() {
printf("%s\n", version);
return 0;
}
Make generates a version.c out of it:
// Makefile
SOURCEFILES = $(wildcard *.c) version.c
PROGRAMS = $(addprefix build/,$(SOURCEFILES:.c=.out))
.PHONY: version.c
all: $(PROGRAMS)
clean:
rm -fR build
build/%.out: build/%.o Makefile
mkdir -p build
gcc -g $< -o $@
build/%.o: %.c Makefile
mkdir -p build
gcc -Wall -Werror -g -c $< -o $@
version.c: version.c.in Makefile
cp $< $@_temp
sed -i "s/@BUILDVERSION@/$$(git describe --always --dirty=-dirty)/g" $@_temp
@cmp -s $@_temp $@ || mv $@_temp $@
rm -f $@_temp
I would like to achieve to trigger a rebuild only if the output of git describe changes. With my current approach the linker step is always called, although version.c does not even change. But it is .PHONY, which probably causes the rebuild of all targets depending on it. If I make the target not .PHONY anymore I'm missing a rebuild for instance if I commit or add a tag.
I also could not find a suitable file in .git/ as a possible dependency of version.c.
Is there a way to solve this?
CodePudding user response:
You store object files in
build
directory, so generatedversion.c
should probably go there as well.You can store current commit hash into a temporary file in
build
directory and makebuild/version.c
depend on it.Targets
all
andclean
must be marked as.PHONY
.Comments in
Makefile
start from#
.build/version.c is rebuilt every time cause build/version.o is removed. To avoid this removal one can use solutions from Makefile removes object files for no reason
So, in general possible solution can be something like this:
# Makefile
SOURCEFILES = $(wildcard *.c.in)
OBJS = $(addprefix build/,$(SOURCEFILES:.c.in=.o))
PROGRAMS = $(addprefix build/,$(SOURCEFILES:.c.in=.out))
.PHONY: all
all: $(PROGRAMS)
.PHONY: clean
clean:
rm -fR build
build/%.out: build/%.o Makefile
mkdir -p build
gcc -g $< -o $@
build/%.o: %.c build/%.c Makefile
mkdir -p build
gcc -Wall -Werror -g -c $< -o $@
# .SECONDARY: build/version.o
.PRECIOUS: build/version.o
build/current_hash:
mkdir -p build
git log --pretty=%h > [email protected]
@cmp -s [email protected] $@ || mv [email protected] $@
build/version.c: version.c.in Makefile build/current_hash
cp $< $@_temp
sed -i "s/@BUILDVERSION@/$$(git describe --always --dirty=-dirty)/g" $@_temp
@cmp -s $@_temp $@ || mv $@_temp $@
rm -f $@_temp
CodePudding user response:
But it is .PHONY, which probably causes the rebuild of all targets depending on it.
That's correct: a .PHONY
target is always rebuilt, which means anything that depends on it is also always rebuilt.
Because of make
's file-and-time-stamp orientation, what you need is a command that updates the timestamp of some file if and only if the contents of the file will change. You can then have output O depend on input file I, whose time-stamp depends upon the git describe
output.
Here is a complete example:
version=$(shell git describe --always --dirty=-dirty)
$(shell echo ${version} > version.tmp && \
{ cmp -s version.tmp version.txt || cp version.tmp version.txt; } && \
rm -f version.tmp)
all: version.o
version.c: version.c.in version.txt
sed "s:@BUILDVERSION@:${version}:" < [email protected] > $@
version.o: version.c
This should work even in older versions of gmake; newer ones have $(file)
functions that can be used to do most of the work. That is, you would read the contents of version.txt
with $(file)
and then only if those don't match the contents of the git describe
output, write to version.txt
, all done during the reading of the Makefile (as is the case here: note how the $(shell)
invocations happen early on).
(Edit to add note: all
should indeed be marked .PHONY
here, as
Andrey Starodubtsev mentions in his answer. I obviously didn't bother. Whether to segregate all output into a build/
is up to you.)