根据 git 描述输出重建目标

Rebuild make targets based upon git describe output

我正在尝试在我的二进制文件中集成 git 描述的输出:

// version.c.in
#include <stdio.h>

const char version[] __attribute__((used)) = "##BUILDVERSION##:@BUILDVERSION@";

int main() {
    printf("%s\n", version);
    return 0;
}

Make 从中生成一个 version.c:

// 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

我想实现仅当 git 的输出描述发生变化时才触发重建。使用我目前的方法,链接器步骤总是被调用,尽管 version.c 甚至没有改变。但它是 .PHONY,这可能会导致依赖于它的所有目标的重建。如果我不再将目标设置为 .PHONY,那么我将错过重建,例如,如果我提交或添加标签。

我也无法在 .git/ 中找到合适的文件作为 version.c 的可能依赖项。

有办法解决吗?

编辑:另见 to 这是我最喜欢的新解决方案。我会把这个留在这里作为记录。


But it is .PHONY, which probably causes the rebuild of all targets depending on it.

这是正确的:.PHONY 目标总是被重建,这意味着依赖于它的任何东西也 总是 通常被重建(参见 )。

由于 make 的文件和时间戳方向,您需要的是一个命令,当且仅当文件内容发生变化时更新某个文件的时间戳。然后您可以让输出 O 取决于输入文件 I,其时间戳取决于 git describe 输出。

这是一个完整的例子:

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}:" < $@.in > $@

version.o: version.c

即使在旧版本的 gmake 中也应该可以工作;较新的具有 $(file) 可用于完成大部分工作的功能。也就是说,您将使用 $(file) 读取 version.txt 的内容,然后只有当这些内容与 git describe 输出的内容不匹配时,才写入 version.txt,全部完成在读取 Makefile 期间(就像这里的情况:注意 $(shell) 调用是如何在早期发生的)。

(编辑以添加注释:all 确实应该在此处标记为 .PHONY,因为 。我显然没有打扰。是否将所有输出隔离到 build/ 由您决定。)

  1. 您将目标文件存储在 build 目录中,因此生成的 version.c 也应该放在该目录中。

  2. 您可以将当前提交哈希存储到 build 目录中的临时文件中,并使 build/version.c 依赖它。

  3. 目标 allclean 必须标记为 .PHONY

  4. Makefile 中的评论从 # 开始。

  5. build/version.c 每次 cause build/version.o 被移除时都会重建。为避免这种删除,可以使用 Makefile removes object files for no reason

    中的解决方案

所以,通常可能的解决方案可能是这样的:

# 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 > $@.tmp
    @cmp -s $@.tmp $@ || mv $@.tmp $@

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
.PHONY: force
version.c: force
        version=`git describe --always --dirty || echo unknown:non-git`; \
        grep -qsF \"$$version\" version.c \
        || printf >version.c 'const char version[]="%s";\n' $$version

和 version.c 配方将 运行 用于任何依赖版本字符串的构建,version.o 仅当版本号实际更改时才重建。

然后将 extern const char version[]; 放入 header 中,您将获得绝对最小的重建传播。