Home > database >  Rebuild make targets based upon git describe output
Rebuild make targets based upon git describe output

Time:09-21

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:

  1. You store object files in build directory, so generated version.c should probably go there as well.

  2. You can store current commit hash into a temporary file in build directory and make build/version.c depend on it.

  3. Targets all and clean must be marked as .PHONY.

  4. Comments in Makefile start from #.

  5. 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.)

  • Related