Linux 如果编译器选项与以前使用的不同,则使目标过期

Linux 如果编译器选项与以前使用的不同,则使目标过期,linux,bash,shell,makefile,gnu-make,Linux,Bash,Shell,Makefile,Gnu Make,考虑下面的目录树,它是一个虚构的玩具示例,演示了要解决的问题: - Makefile - lib.c + foo/ |-- config.mk + bar/ |-- config.mk foo/config.mk的内容: CFLAGS += -DFOO CFLAGS += -DBAR bar/config.mk的内容: CFLAGS += -DFOO CFLAGS += -DBAR 对目标foo和bar调用make,使用Makefile分别包括foo/config.m

考虑下面的目录树,它是一个虚构的玩具示例,演示了要解决的问题:

- Makefile
- lib.c
+ foo/
   |-- config.mk
+ bar/
   |-- config.mk
  • foo/config.mk的内容

    CFLAGS += -DFOO
    
    CFLAGS += -DBAR
    
  • bar/config.mk的内容

    CFLAGS += -DFOO
    
    CFLAGS += -DBAR
    
  • 对目标
    foo
    bar
    调用
    make
    ,使用
    Makefile
    分别包括
    foo/config.mk
    bar/config.mk
    ,并生成
    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
,该变量定义为(根据通过使用选项调用
make
获得的输出):

COMPILE.c
的扩展除其他外,取决于变量
CFLAGS
的值,这反过来又取决于是否包含
foo/config.mk
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

到目前为止,我的方法是生成一个文件,
cc options dump
,每次构建
lib.o
时都包含
COMPILE.c
变量的扩展内容

将当前
COMPILE.c
变量扩展后产生的MD5哈希值与前一个构建中使用的MD5哈希值进行比较,即内容存储在文件
cc options dump
(如果有)中的MD5哈希值(即,如果文件确实存在)

就我所见,当为目标
foo
和/或
bar
调用
make
时,此makefile的行为与所需的行为相对应:

$ 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
要使上述最后两种情况正常工作,使用内置函数至关重要


如果有人能提供更优雅的解决方案,那就太好了。

这是我的原始模型,它直接在文件名中编码编译器/链接器标志。这只是一个想法,一个实际的实现应该更健壮一些

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 $@ $<

非常感谢。我刚研究完你非常好的解决方案。我还将使
old_compile.mk
成为一个虚假的目标,并且是
lib.o
的一个仅限订单的先决条件(即,规则是
lib.o:lib.c$(FORCE)| old_compile.mk
),这样它总是在匹配目标
lib.o
时生成,而不必触发其配方的执行。生成目标
old_compile.mk
将有一个专用规则。您对这个修改有何看法???嗯,我不确定我是否完全理解包含makefile的效果,该makefile包含一个显式构建它的规则,声明为虚假目标,并且是另一个目标的仅限订单的先决条件。对不起,这对我理解make的行为来说有点太多了。所以我不会那么做。你为什么要这么做?此外,我怀疑我的解决方案将不再有效:如果包含的makefile有构建它的规则,则生成它并从头开始重新启动;因此,包含的makefile将始终为您提供最新的标志,而不是上次编译时使用的标志。此外,由与
lib.o
相同的规则生成的
old_compile.mk
,以及
lib.o
之后,具有一个有趣的属性:如果编译失败,不会生成旧的编译.mk,并保留以前的状态。而如果它是由另一个规则生成的先决条件,则即使随后的编译失败,它也将被更新。。。
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 $@ $<
make test.exe
make test.exe
make test.exe CFLAGS="-g -Wall"
make test.exe CFLAGS="-g -Wall"
make test.exe