依赖于Makefile中的目录路径

21
这是对我早前问题的跟进:SO 4403861,因为建议的解决方案破坏了依赖项,使得Makefile无用。我弄不清为什么会这样。

我正在使用GNU Make 3.82。如果已创建obj目录,则有一条规则可用:

objdir:=../obj
$(objdir)/%.o: %.C
    $(COMPILE) -MM -MT$(objdir)/$(notdir $@) $< -o $(DEPDIR)/$(notdir $(basename $<).d )
    $(COMPILE) -o $(objdir)/$(notdir $@ ) -c $<

然而,如果obj目录不存在,make就会失败。我希望make能够在需要时自动创建../obj目录,所以我添加了我认为非常简单的内容:

$(objdir)/%.o: %.C $(objdir)
    $(COMPILE) -MM -MT$(objdir)/$(notdir $@) $< -o $(DEPDIR)/$(notdir $(basename $<).d )
    $(COMPILE) -o $(objdir)/$(notdir $@ ) -c $<

$(objdir):
   if [ ! -d $(objdir) ] ; then mkdir $(objdir) ; fi

我这么做时,make每次都会强制编译。为什么?只有在没有目录时,mkdir才应该发生。为什么这个简单的更改会破坏依赖关系呢?


正如我在回答4403861上你的问题/评论中所说:“你的规则表明目标文件依赖于对象目录 - 这意味着如果自上次构建对象文件以来对象目录已更改(例如,因为你编译了另一个文件),那么你的文件需要重新构建。你的整体构建目标取决于对象目录的存在;单个对象文件不需要重新构建,只因为目录发生了变化。” - Jonathan Leffler
4个回答

41

你还可以尝试使用仅限于顺序的先决条件

有一个类似于你问题的示例可供参考。

 OBJDIR := objdir
 OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)
 
 $(OBJDIR)/%.o : %.c
         $(COMPILE.c) $(OUTPUT_OPTION) $<
 
 all: $(OBJS)
 
 $(OBJS): | $(OBJDIR)
 
 $(OBJDIR):
         mkdir $(OBJDIR)

20

就像其他人所说,问题在于当目录成员被添加或删除时,目录被认为是“更改”的,因此make一直看到你的输出目录在不停地变化,并重新运行所有依赖于输出目录的编译。

作为他人描述的解决方法的替代方案,GNU make 的最新版本支持“仅顺序先决条件”。手册中对仅顺序先决条件的描述包括如何使用它们创建目录的示例。


2
我不认为这是完全正确的。当目录中的任何成员被添加、删除或重命名时,目录会发生变化。(目录是一个包含文件名到文件映射的文件。) - reinierpost
我在这里解释:https://dev59.com/UHA75IYBdhLWcg3wGU-I#3554442 - Jack Kelly
reinierpost:谢谢,已经更正了答案。 - slowdog

4

某种哨兵文件可以很好地解决这个问题。它允许您编写一个单一的模式规则来创建所有文件夹,不会有任何流氓重新编译问题,并且是-j安全的。

OBJDIR := objdir/
OBJS := $(addprefix ${OBJDIR},foo.o bar.o baz.o)

all: ${OBJS}

${OBJDIR}%.o : %.c ${OBJDIR}.sentinel
     ${COMPILE.c} ${OUTPUT_OPTION} $<

# A single pattern rule will create all appropriate folders as required
.PRECIOUS: %/.sentinel # otherwise make (annoyingly) deletes it
%/.sentinel:
     mkdir -p ${@D}
     touch $@

2

如前所述,您不能将包含输出文件的目录作为依赖项,因为它每次都会更新。

一种解决方案(如上所述)是在每次构建之前放置一个mkdir -p命令,如下所示:

objdir:=../obj
$(objdir)/%.o: %.C
    @mkdir -p $(objdir) $(DEPDIR) ...
    $(COMPILE) -MM -MT$(objdir)/$(notdir $@) $< -o $(DEPDIR)/$(notdir $(basename $<).d )
    $(COMPILE) -o $(objdir)/$(notdir $@ ) -c $<

但我不喜欢这个解决方案,因为它会强制运行额外的进程很多次。然而,知道错误的原因,还有另一种方法:

exe: dirs $(OBJ)

在主要的目标中,即构建可执行文件的目标之前,插入一个目标目录。由于它是按顺序执行的:

dirs:
    @mkdir -p $(objdir) $(DESTDIR) ...

这只需要在整个构建过程中执行一次,而不是每个文件都要执行。


2
这是一个鲜为人知的烦恼,但你不能在并行 make 中使用 mkdir -p。原因是如果同时创建 /a/b/c 和 /a/b/cc 文件夹,那么在 mkdir -p 检查 /a 目录是否存在并创建它之间存在竞争条件。另一个进程(make 作业)可以在检查和操作之间创建目录,导致第一个作业在创建目录时失败并出现 EEXIST 错误。 - Maxim Egorushkin
1
依赖目录必须是仅限顺序,因为它们已经被提到过。 - Maxim Egorushkin
如前所述,此解决方案在 -j 下失效。您_可以_使用 mkdir -p,但必须正确获取依赖项。基本上,您必须使所有 对象 依赖于该目录。此外,为了防止由于目录时间更改而进行重新编译,请使用哨兵文件(和模式规则)。请参见以下内容(我无法在此框中输入解决方案,grrrr.)。 - bobbogo
根据您发布的内容,不能保证dirs目标会在$(OBJ)之前被处理,也不能保证它们不会同时发生(因此可能会创建dirs,但由于$(OBJ)不存在而导致失败)。 - iheanyi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接