0

I have written a (GNU make) Makefile designed to perform automatic dependency tracking in header includes. Everything works great except that upon typing make a second time, the entire code base rebuilds. Only typing make the third time and successive times gives the message that nothing is to be done.

SRCDIR := src
INCDIR := inc
ifeq ($(DEBUG),1)
    OBJDIR := debug_obj
    BINDIR := debug_bin
else
    OBJDIR := obj
    BINDIR := bin
endif

BINS := prog1 prog2 prog3 prog4
SRCS := $(wildcard $(SRCDIR)/*.cpp)
OBJS := $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(SRCS:.cpp=.o))
DEPS := $(OBJS:.o=.d)

CC := g++
COMMON_FLAGS := -Wall -Wextra -Werror -std=c++11 -pedantic
ifeq ($(DEBUG),1)
    CXX_FLAGS := $(COMMON_FLAGS) -Og -g
else
    CXX_FLAGS := $(COMMON_FLAGS) -O3 -D NDEBUG
endif

all: $(addprefix $(BINDIR)/,$(BINS)) | $(BINDIR)

$(OBJDIR) $(BINDIR):
    @ mkdir -p $@;

$(BINDIR)/%: $(OBJDIR)/%.o | $(BINDIR)
    $(CC) $(CPP_FLAGS) $< -o $@;

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp | $(OBJDIR)
    $(CC) $(CPP_FLAGS) -MMD -MP -c $< -o $@;

-include $(DEPS)

.PHONY: all clean

clean:
    - rm -f $(OBJS);
    - rm -f $(DEPS);
    - rm -f $(addprefix $(BINDIR)/,$(BINS));
    - rmdir $(OBJDIR) $(BINDIR) 2> /dev/null || true

Clearly some dependency had changed, so I tried running make -n -d | grep 'newer' following the first invocation of make, which shows this:

Prerequisite obj/prog1.o' is newer than targetbin/prog1'.
Prerequisite obj/prog2.o' is newer than targetbin/prog2'.
Prerequisite obj/prog3.o' is newer than targetbin/prog3'.
Prerequisite obj/prog4.o' is newer than targetbin/prog4'.

And ls -la obj/*

Showed the existence of the dependency (*.d) files but not the object (*.o) files. I assume that this is related to how g++ -MMD -MP works, but despite the apparent absence of object files, binaries are present after the first make.

The answer to this question suggests that both are generated at the same time, and man g++ does not dispute this as far as I can tell.

I've read a couple other questions and answers related to automatic dependency tracking, but I don't see this issue arising. Why is this happening? Can you suggest a fix?

Update

A more careful look at the first invocation of make shows this unexpected (to me) line at the end:

rm obj/prog1.o obj/prog2.o obj/prog3.o obj/prog4.o

That answers one question but raises another.

Update

I also found this in the debugging output.

Considering target file `prog1'.
 File `prog1' does not exist.
make: *** No rule to make target `prog1'.  Stop.
 No implicit rule found for `prog1'.
 Finished prerequisites of target file `prog1'.
Must remake target `prog1'.

For which I note that prog1 is missing the bin/ prefix. Nothing explains why the first run removes the object files, but the second run leaves them, however. That seems to be at the heart of the issue.

4

2 回答 2

0

make将目标文件视为中间文件并相应地删除它们。添加:

.SECONDARY: $(OBJS)

解决了这个问题。我不知道为什么它在第一次调用而不是第二次调用时这样做。欢迎评论。

于 2013-05-05T14:50:07.910 回答
0

文件不存在的原因.o是它们被认为是中间文件,因此 make 删除它们。但是,这不应该在您的构建中造成任何问题,因为只要 make 可以设想中间文件,它就会意识到如果它的先决条件比它的父级旧(在这种情况下,只要prog1比例prog1.cpp如新)。

我无法重现您在第二次构建重建所有内容时的体验。将需要更多细节。您显示的输出并不有趣,因为这只是说 make 不需要重建 .o 文件(它比先决条件更新)。您需要在输出中找到解释为什么 make确实需要重建 .o 文件的行。如果您提供该信息,我们可能会提供帮助。

只是对您的 makefile 的一些评论:首先,我认为强制mkdir规则始终成功不是一个好主意。如果 mkdir 失败,您希望构建失败。可能你这样做了,所以如果目录已经存在就不会出现问题,但这不是必需的,因为mkdir -p调用永远不会因为目录存在而失败(但如果由于其他原因无法创建目录,它将失败,例如权限)。您也可以将它们组合成具有多个目标的单个规则:

$(BINDIR) $(OBJDIR):
        @mkdir -p $@

接下来,您不需要在命令行中使用分号,事实上,添加它们会导致您的构建稍微慢一些。

最后,一个小的 nit,但编译行中选项的正确顺序是-c -o $@ $<; 源文件不是(这是一个常见的误解)-c选项的参数。-c选项,如,-E-s告诉编译器要创建什么输出;在这种情况下-c,意味着编译成目标文件。这些选项不带参数。文件名是一个单独的参数。

于 2013-05-05T15:23:20.287 回答