如果编译器选项与以前使用的选项不同,则使目标过时
Making a target out-of-date if compiler options differ respect to the previously used ones
考虑以下目录树作为一个虚构的玩具示例,它说明了要解决的问题:
- Makefile
- lib.c
+ foo/
|-- config.mk
+ bar/
|-- config.mk
foo/config.mk
的内容:
CFLAGS += -DFOO
bar/config.mk
的内容:
CFLAGS += -DBAR
使用 Makefile
为目标 foo
和 bar
调用 make
会导致文件 foo/config.mk
和 bar/config.mk
分别被包含(通过 include
directive),并且 lib.o
被构建,即:
# build lib.o with the macro FOO defined
$ make foo
# build lib.o with the macro BAR defined
$ make bar
# build lib.o with both the macros FOO and BAR defined
$ make foo bar
$ make bar foo
构建 lib.o
的默认规则使用变量 COMPILE.c
,其定义(根据使用选项 --print-data-base
调用 make
获得的输出)为:
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
COMPILE.c
的扩展取决于变量 CFLAGS
的值,而这又取决于 CFLAGS
=16=] 或 bar/config.mk
被包含在内,因为这些 makefile 修改了 CFLAGS
变量。
我想要实现的是,如果变量 COMPILE.c
的扩展,将目标 lib.o
视为 过时的 目标当前使用的与之前 lib.o
构建中使用的不同。例如:
$ make foo
# It shouldn't rebuild anything since lib.o should be up-to-date
$ make foo
# It should rebuild lib.o since it should be out-of-date
$ make bar
# It should rebuild lib.o since it is again out-of-date
$ make foo bar
# It shouldn't rebuild lib.o since it is up-to-date
$ make bar foo
解释了到目前为止我是如何实现这种行为的。欢迎任何建议。
我会将变量的值转储到另一个包含的 makefile 中,并检查当前值是否与包含的 makefile 中的值不同。类似于:
ifeq ($(filter foo,$(MAKECMDGOALS)),foo)
include foo/config.mk
endif
ifeq ($(filter bar,$(MAKECMDGOALS)),bar)
include bar/config.mk
endif
-include old_compile.mk
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
ifneq ($(COMPILE.c),$(OLD_COMPILE.c))
FORCE := force
endif
lib.o: lib.c $(FORCE)
$(COMPILE.c) $< -o $@
echo 'OLD_COMPILE.c := $(COMPILE.c)' > old_compile.mk
.PHONY: foo bar all force
foo bar all: lib.o
演示:
$ make foo
cc -DFOO -c lib.c -o lib.o
echo 'OLD_COMPILE.c := cc -DFOO -c' > old_compile.mk
$ make foo
make: Nothing to be done for 'foo'.
$ make foo bar
cc -DFOO -DBAR -c lib.c -o lib.o
echo 'OLD_COMPILE.c := cc -DFOO -DBAR -c' > old_compile.mk
make: Nothing to be done for 'bar'.
$ make bar foo
make: Nothing to be done for 'bar'.
make: Nothing to be done for 'foo'.
到目前为止,我的方法包括生成一个文件,cc-options-dump
,每次构建 lib.o
时,都会包含 COMPILE.c
变量的扩展内容。
将当前 COMPILE.c
变量扩展产生的 MD5 散列与用于先前构建的散列进行比较,即内容存储在文件 cc-options-dump
中的散列,如果有的话(即,如果文件确实存在)。
.PHONY: foo bar require-rebuild cc-options-dump
# include the xxx/config.mk files
# sort built-in function to make it independent of the order (i.e., "foo bar" or "bar foo")
$(foreach opt,$(sort $(MAKECMDGOALS)),$(eval include $(opt)/config.mk))
# obtain the MD5 hash of the previously used flags
ifneq (,$(wildcard cc-options-dump))
prev-cc-options-hash := $(shell md5sum cc-options-dump | cut -f1 -d' ')
endif
# obtain the MD5 hash of the current flags
curr-cc-options-hash := $(shell echo "$(COMPILE.c)" | md5sum | cut -f1 -d' ')
# Do these hashes differ?
ifneq ($(prev-cc-options-hash),$(curr-cc-options-hash))
# keep track of the fact that a rebuilt needs to be triggered
does-need-rebuild := require-rebuild
# just for displaying information
$(info Rebuild required)
else
$(info Rebuild not required)
endif
# make both targets foo and bar dependent on the file with the flags
# so that it has to be generated, since it is a phony target as well
foo bar: cc-options-dump
# lib.o has now an additional prerequisite for determining whether it need to be rebuilt
lib.o: $(does-need-rebuild)
# save the used compiler flags for comparing them with the future flags
cc-options-dump: lib.o
@echo '$(COMPILE.c)' >$@
据我所知,为目标 foo
and/or bar
调用 make
时,此生成文件的行为对应于所需的行为:
$ make foo
Rebuild required
cc -DFOO -c -o lib.o lib.c
$ make foo
Rebuild not required
$ make bar
Rebuild required
cc -DBAR -c -o lib.o lib.c
$ make bar
Rebuild not required
$ make foo bar
Rebuild required
cc -DBAR -DFOO -c -o lib.o lib.c
$ make bar foo
Rebuild not required
sort
内置函数的使用对于上述最后两种情况的正常工作至关重要。
如果有人能提出更优雅的解决方案,那就太好了。
这是我的粗略模型,它直接在文件名中编码 compiler/linker 标志。这只是一个想法,实际的实现应该更健壮一些。
empty:=
space:= $(empty) $(empty)
comma:= ,
LDFLAGS_NS=$(subst $(space),$(comma),$(LDFLAGS))
CFLAGS_NS=$(subst $(space),$(comma),$(CFLAGS))
EXEDIR=./test-exedir-cflags=$(CFLAGS_NS)-ldflags=$(LDFLAGS_NS)
EXEDIR_PAT=./test-exedir-*
OBJDIR=./test-objdir-cflags=$(CFLAGS_NS)
OBJDIR_PAT=./test-objdir-*
test.exe: $(EXEDIR)/test.exe
rm -f test
ln -s $< $@
$(EXEDIR)/test.exe: test.o
rm -rf $(EXEDIR_PAT)
mkdir $(EXEDIR)
cc $(LDFLAGS) -o $@ $<
test.o: $(OBJDIR)/test.o
rm -f test.o
ln -s $< $@
$(OBJDIR)/test.o: test.c
rm -rf $(OBJDIR_PAT)
mkdir $(OBJDIR)
cc -c $(CFLAGS) -o $@ $<
使用 hello world test.c 文件和这些命令进行了测试:
make test.exe
make test.exe
make test.exe CFLAGS="-g -Wall"
make test.exe CFLAGS="-g -Wall"
make test.exe
考虑以下目录树作为一个虚构的玩具示例,它说明了要解决的问题:
- Makefile
- lib.c
+ foo/
|-- config.mk
+ bar/
|-- config.mk
foo/config.mk
的内容:CFLAGS += -DFOO
bar/config.mk
的内容:CFLAGS += -DBAR
使用
Makefile
为目标foo
和bar
调用make
会导致文件foo/config.mk
和bar/config.mk
分别被包含(通过include
directive),并且lib.o
被构建,即:# build lib.o with the macro FOO defined $ make foo # build lib.o with the macro BAR defined $ make bar # build lib.o with both the macros FOO and BAR defined $ make foo bar $ make bar foo
构建 lib.o
的默认规则使用变量 COMPILE.c
,其定义(根据使用选项 --print-data-base
调用 make
获得的输出)为:
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
COMPILE.c
的扩展取决于变量 CFLAGS
的值,而这又取决于 CFLAGS
=16=] 或 bar/config.mk
被包含在内,因为这些 makefile 修改了 CFLAGS
变量。
我想要实现的是,如果变量 COMPILE.c
的扩展,将目标 lib.o
视为 过时的 目标当前使用的与之前 lib.o
构建中使用的不同。例如:
$ make foo
# It shouldn't rebuild anything since lib.o should be up-to-date
$ make foo
# It should rebuild lib.o since it should be out-of-date
$ make bar
# It should rebuild lib.o since it is again out-of-date
$ make foo bar
# It shouldn't rebuild lib.o since it is up-to-date
$ make bar foo
我会将变量的值转储到另一个包含的 makefile 中,并检查当前值是否与包含的 makefile 中的值不同。类似于:
ifeq ($(filter foo,$(MAKECMDGOALS)),foo)
include foo/config.mk
endif
ifeq ($(filter bar,$(MAKECMDGOALS)),bar)
include bar/config.mk
endif
-include old_compile.mk
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
ifneq ($(COMPILE.c),$(OLD_COMPILE.c))
FORCE := force
endif
lib.o: lib.c $(FORCE)
$(COMPILE.c) $< -o $@
echo 'OLD_COMPILE.c := $(COMPILE.c)' > old_compile.mk
.PHONY: foo bar all force
foo bar all: lib.o
演示:
$ make foo
cc -DFOO -c lib.c -o lib.o
echo 'OLD_COMPILE.c := cc -DFOO -c' > old_compile.mk
$ make foo
make: Nothing to be done for 'foo'.
$ make foo bar
cc -DFOO -DBAR -c lib.c -o lib.o
echo 'OLD_COMPILE.c := cc -DFOO -DBAR -c' > old_compile.mk
make: Nothing to be done for 'bar'.
$ make bar foo
make: Nothing to be done for 'bar'.
make: Nothing to be done for 'foo'.
到目前为止,我的方法包括生成一个文件,cc-options-dump
,每次构建 lib.o
时,都会包含 COMPILE.c
变量的扩展内容。
将当前 COMPILE.c
变量扩展产生的 MD5 散列与用于先前构建的散列进行比较,即内容存储在文件 cc-options-dump
中的散列,如果有的话(即,如果文件确实存在)。
.PHONY: foo bar require-rebuild cc-options-dump
# include the xxx/config.mk files
# sort built-in function to make it independent of the order (i.e., "foo bar" or "bar foo")
$(foreach opt,$(sort $(MAKECMDGOALS)),$(eval include $(opt)/config.mk))
# obtain the MD5 hash of the previously used flags
ifneq (,$(wildcard cc-options-dump))
prev-cc-options-hash := $(shell md5sum cc-options-dump | cut -f1 -d' ')
endif
# obtain the MD5 hash of the current flags
curr-cc-options-hash := $(shell echo "$(COMPILE.c)" | md5sum | cut -f1 -d' ')
# Do these hashes differ?
ifneq ($(prev-cc-options-hash),$(curr-cc-options-hash))
# keep track of the fact that a rebuilt needs to be triggered
does-need-rebuild := require-rebuild
# just for displaying information
$(info Rebuild required)
else
$(info Rebuild not required)
endif
# make both targets foo and bar dependent on the file with the flags
# so that it has to be generated, since it is a phony target as well
foo bar: cc-options-dump
# lib.o has now an additional prerequisite for determining whether it need to be rebuilt
lib.o: $(does-need-rebuild)
# save the used compiler flags for comparing them with the future flags
cc-options-dump: lib.o
@echo '$(COMPILE.c)' >$@
据我所知,为目标 foo
and/or bar
调用 make
时,此生成文件的行为对应于所需的行为:
$ make foo
Rebuild required
cc -DFOO -c -o lib.o lib.c
$ make foo
Rebuild not required
$ make bar
Rebuild required
cc -DBAR -c -o lib.o lib.c
$ make bar
Rebuild not required
$ make foo bar
Rebuild required
cc -DBAR -DFOO -c -o lib.o lib.c
$ make bar foo
Rebuild not required
sort
内置函数的使用对于上述最后两种情况的正常工作至关重要。
如果有人能提出更优雅的解决方案,那就太好了。
这是我的粗略模型,它直接在文件名中编码 compiler/linker 标志。这只是一个想法,实际的实现应该更健壮一些。
empty:=
space:= $(empty) $(empty)
comma:= ,
LDFLAGS_NS=$(subst $(space),$(comma),$(LDFLAGS))
CFLAGS_NS=$(subst $(space),$(comma),$(CFLAGS))
EXEDIR=./test-exedir-cflags=$(CFLAGS_NS)-ldflags=$(LDFLAGS_NS)
EXEDIR_PAT=./test-exedir-*
OBJDIR=./test-objdir-cflags=$(CFLAGS_NS)
OBJDIR_PAT=./test-objdir-*
test.exe: $(EXEDIR)/test.exe
rm -f test
ln -s $< $@
$(EXEDIR)/test.exe: test.o
rm -rf $(EXEDIR_PAT)
mkdir $(EXEDIR)
cc $(LDFLAGS) -o $@ $<
test.o: $(OBJDIR)/test.o
rm -f test.o
ln -s $< $@
$(OBJDIR)/test.o: test.c
rm -rf $(OBJDIR_PAT)
mkdir $(OBJDIR)
cc -c $(CFLAGS) -o $@ $<
使用 hello world test.c 文件和这些命令进行了测试:
make test.exe
make test.exe
make test.exe CFLAGS="-g -Wall"
make test.exe CFLAGS="-g -Wall"
make test.exe