将变量从 Makefile 传递到可执行文件的最佳方式是什么?

Which is the best way to pass a variable from a Makefile to an executable?

我想在编译时在 Makefile 中生成一个变量,并在 运行 时在我的可执行文件中使用它。

最小示例:在 Makefile 中我生成变量 COMMIT_ID,其中包含最新提交的 ID:COMMIT_ID=$(git rev-parse --verify HEAD).

Makefile 还生成一个可执行文件,例如 executable.elf。 生成 executable.elfC 源代码输出 COMMIT_ID.

最好的处理方法是什么?

这取决于您的 makefile 的外观。如果您有访问它的 c 文件的专用规则,您可以将 -DCOMMIT_ID=$(COMMIT_ID) 添加到配方中,如下所示:

foo.o: foo.c
   $(CC) $(CFLAGS) -DCOMMIT_ID=$(COMMIT_ID) $^ -o $@

或者,如果您正在使用模式规则,您可以将它添加到 CFLAGS,然后每个 .c 文件都可以访问它(当然假设模式规则使用 CFLAGS ):

CFLAGS += -DCOMMIT_ID=$(COMMIT_ID)

完成此操作后,COMMIT_ID 将成为 c 文件中的一个宏,因此您可以使用字符串化运算符 # 来访问其值:

printf("commit id is " #COMMIT_ID "\n");

----- 编辑 -----

请注意,这确实涉及到一个尖锐的棍子——具体来说,如果您构建一次,然后在不修改 foo.c 的情况下修改 git 存储库,那么 make 将考虑 foo.o 是最新的,并且不会重建它,这意味着 foo.o 将包含旧的提交 ID。我会 post 第二个答案,如果你担心这个问题,你会如何解决这个问题

您当然可以使用 make 变量,如下所示:

    printf("%s\n", COMMIT_ID);

在那里你会有一个 Makefile 类似

executable.elf: CFLAGS+=-D'COMMIT_ID="$(shell git rev-parse --verify HEAD)"'

(这使用了一些不可移植的 GNU make 特性。)

但是,如果这实际上是可重现的,可能会更好。为此,也许将值写入文件,以便您可以看到上次编译该项目时使用的内容。

executable.elf: commitid.c
.PHONY: commitid.c
commitid.c: 
    git rev-parse --verify HEAD \
    | sed 's/.*/#define COMMIT_ID "&"/' >$@

然后显然 #include "commitid.c" 来自 executable.c 或任何调用的 C 源文件。

强制commitid.c每次都重建的.PHONY声明有点生硬;也许改为 commitid.c 依赖于每个版本控制的文件。

如果 git 存储库发生变化,我的第一个答案是不会重建 executable.elf,但您的源代码不会。如果您需要在存储库更改时始终重建它,您可以使用一些 makefile 技巧来做到这一点:(可能有更好的方法来做到这一点,如果有人能想到一个,我很想听听) :

首先在您的 makefile 中执行:

$(shell \
   COMMIT_ID=$(git rev-parse --verify HEAD) && \
     echo "#define COMMIT_ID $${COMMIT_ID}" > commitid.h.tmp; \
   if [ ! -f commitid.h ] || ! cmp -s commitid.h.tmp commitid.h; then \
      cp commitid.h.tmp commitid.h; \
   fi; \
   rm commitid.h.tmp \
)

然后在 foo.c 中,您只需执行

#include commitid.h

只要您的 makefile 生成 .d 文件,那么 foo.c 将依赖于 commitid.h,并且如果此文件更新,将会重建。 (如果没有,请将 foo.c: commitid.h 行添加到您的 makefile 中)。文件本身只会在提交 ID 更改时更新,因此 foo.o 只会在必要时重建。

书呆子狙击手 HardcoreHenry 引诱我寻求更好(或至少不同)的解决方案。想出了这个,在存储库中有一个文件,其名称表示上次构建存储库的提交:

COMMIT_ID := $(shell git rev-parse --verify HEAD)

# target is made if it does not exist
$(COMMIT_ID).commit_id:
    @# clean other commits than current
    rm -f *.commit_id
    @# placeholder for current commit
    touch $@

commit_id.h: $(COMMIT_ID).commit_id
    echo "#define COMMIT_ID ${COMMIT_ID}" > $@