为什么我的 GNU 让 Makefile 构建我的库两次?

Why does my GNU make Makefile build my library twice?

我的 GNU make Makefile:

# TODOs so I don't forget:
# - make debugging an option
# - make 64 below an actual option
# - figure out why make test seems to rebuild the DLL [note: this TODO is this question]
# - __declspec(dllimport)

ifeq ($(MAKECMDGOALS),64)
    CC = x86_64-w64-mingw32-gcc
    RC = x86_64-w64-mingw32-windres
    mflag = -m64
else
    CC = i686-w64-mingw32-gcc
    RC = i686-w64-mingw32-windres
    mflag = -m32
endif

OBJDIR = .objs
OUTDIR = out

BASENAME = wintable
DLLFILE = $(OUTDIR)/$(BASENAME).dll
LIBFILE = $(OUTDIR)/$(BASENAME).lib
TESTEXEFILE = $(OUTDIR)/$(BASENAME).exe

CFILES = \
    alloc.c \
    api.c \
    checkboxdraw.c \
    checkboxevents.c \
    children.c \
    coord.c \
    debug.c \
    draw.c \
    enablefocus.c \
    events.c \
    header.c \
    hscroll.c \
    main.c \
    metrics.c \
    modelhelpers.c \
    modelnotify.c \
    nullmodel.c \
    resize.c \
    scroll.c \
    select.c \
    tooltips.c \
    update.c \
    util.c \
    visibility.c \
    vscroll.c

HFILES = \
    table.h \
    tablepriv.h

TESTCFILES = \
    test.c

OFILES = $(CFILES:%.c=$(OBJDIR)/%.o)
TESTOFILES = $(TESTCFILES:%.c=$(OBJDIR)/%.o)

xCFLAGS = \
    --std=c99 \
    -Wall \
    -Wextra \
    -Wno-unused-parameter \
    $(mflag) \
    $(CFLAGS)

xLDFLAGS = \
    -static-libgcc \
    -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lole32 -loleaut32 -loleacc -luuid -lmsimg32 \
    $(mflag) \
    $(LDFLAGS)

default:
    $(MAKE) clean
    $(MAKE) it
    $(MAKE) test

it: $(DLLFILE)

$(DLLFILE): $(OFILES)
    $(CC) -g -o $(DLLFILE) -shared -Wl,--out-implib,$(LIBFILE) $(OFILES) $(xLDFLAGS)

test: $(TESTEXEFILE)
# see 
.PHONY: test

$(TESTEXEFILE): $(DLLFILE) $(TESTOFILES)
    $(CC) -g -o $(TESTEXEFILE) $(TESTOFILES) $(LIBFILE) $(xLDFLAGS)

$(OBJDIR)/%.o: %.c $(HFILES) dirs
    $(CC) -g -o $@ -c $< $(xCFLAGS)

dirs:
    mkdir -p $(OBJDIR) $(OUTDIR)

clean:
    rm -rf $(OBJDIR) $(OUTDIR)

我使用 make 进行构建并希望进行清理和测试,我经常这样做,所以目前我的 default 清理($(MAKE) clean),构建 DLL ($(MAKE) it),并构建测试程序 ($(MAKE) test)。

但是,make确实

make clean
make[1]: Entering directory '/home/pietro/src/github.com/andlabs/wintable'
rm -rf .objs out
make[1]: Leaving directory '/home/pietro/src/github.com/andlabs/wintable'
make it
make[1]: Entering directory '/home/pietro/src/github.com/andlabs/wintable'
mkdir -p .objs out
i686-w64-mingw32-gcc -g -o .objs/alloc.o -c alloc.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/api.o -c api.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/checkboxdraw.o -c checkboxdraw.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/checkboxevents.o -c checkboxevents.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/children.o -c children.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/coord.o -c coord.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/debug.o -c debug.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/draw.o -c draw.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/enablefocus.o -c enablefocus.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/events.o -c events.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/header.o -c header.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/hscroll.o -c hscroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/main.o -c main.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/metrics.o -c metrics.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/modelhelpers.o -c modelhelpers.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/modelnotify.o -c modelnotify.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/nullmodel.o -c nullmodel.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/resize.o -c resize.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/scroll.o -c scroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/select.o -c select.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/tooltips.o -c tooltips.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/update.o -c update.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/util.o -c util.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/visibility.o -c visibility.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/vscroll.o -c vscroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o out/wintable.dll -shared -Wl,--out-implib,out/wintable.lib .objs/alloc.o .objs/api.o .objs/checkboxdraw.o .objs/checkboxevents.o .objs/children.o .objs/coord.o .objs/debug.o .objs/draw.o .objs/enablefocus.o .objs/events.o .objs/header.o .objs/hscroll.o .objs/main.o .objs/metrics.o .objs/modelhelpers.o .objs/modelnotify.o .objs/nullmodel.o .objs/resize.o .objs/scroll.o .objs/select.o .objs/tooltips.o .objs/update.o .objs/util.o .objs/visibility.o .objs/vscroll.o -static-libgcc -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lole32 -loleaut32 -loleacc -luuid -lmsimg32 -m32 
make[1]: Leaving directory '/home/pietro/src/github.com/andlabs/wintable'
make test
make[1]: Entering directory '/home/pietro/src/github.com/andlabs/wintable'
mkdir -p .objs out
i686-w64-mingw32-gcc -g -o .objs/alloc.o -c alloc.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/api.o -c api.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/checkboxdraw.o -c checkboxdraw.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/checkboxevents.o -c checkboxevents.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/children.o -c children.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/coord.o -c coord.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/debug.o -c debug.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/draw.o -c draw.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/enablefocus.o -c enablefocus.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/events.o -c events.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/header.o -c header.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/hscroll.o -c hscroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/main.o -c main.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/metrics.o -c metrics.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/modelhelpers.o -c modelhelpers.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/modelnotify.o -c modelnotify.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/nullmodel.o -c nullmodel.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/resize.o -c resize.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/scroll.o -c scroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/select.o -c select.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/tooltips.o -c tooltips.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/update.o -c update.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/util.o -c util.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/visibility.o -c visibility.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o .objs/vscroll.o -c vscroll.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o out/wintable.dll -shared -Wl,--out-implib,out/wintable.lib .objs/alloc.o .objs/api.o .objs/checkboxdraw.o .objs/checkboxevents.o .objs/children.o .objs/coord.o .objs/debug.o .objs/draw.o .objs/enablefocus.o .objs/events.o .objs/header.o .objs/hscroll.o .objs/main.o .objs/metrics.o .objs/modelhelpers.o .objs/modelnotify.o .objs/nullmodel.o .objs/resize.o .objs/scroll.o .objs/select.o .objs/tooltips.o .objs/update.o .objs/util.o .objs/visibility.o .objs/vscroll.o -static-libgcc -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lole32 -loleaut32 -loleacc -luuid -lmsimg32 -m32 
i686-w64-mingw32-gcc -g -o .objs/test.o -c test.c --std=c99 -Wall -Wextra -Wno-unused-parameter -m32 
i686-w64-mingw32-gcc -g -o out/wintable.exe .objs/test.o out/wintable.lib -static-libgcc -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lole32 -loleaut32 -loleacc -luuid -lmsimg32 -m32 
make[1]: Leaving directory '/home/pietro/src/github.com/andlabs/wintable'

注意 $(MAKE) test 步骤如何重建 DLL,就好像 $(MAKE) it 步骤没有发生一样!步骤之间没有清理,也没有发生其他变化,所以我不知道为什么要重建DLL。

我认为制作 defaultitcleandirs 目标虚假会修复它(根据 ),但是没用。 Google 只是告诉我如何让 make 构建我的目标两次,而不是如何阻止它这样做。

这是 Ubuntu GNOME 14.10 上的 GNU make 4.0。

这是怎么回事?谢谢。

更新 2015 年 4 月 22 日
我开始认为问题实际上是 here 概述的问题,因为我在其他项目上看到类似的重建问题,但我没有做 make test 我在这里做的事情:

This works well for this simple example, but there\'s a major problem. Since the timestamp on a directory is typically updated when any of the files inside the directory are updated this Makefile does too much work.

For example, just touching a random file inside /out/ forces a rebuild of /out/foo.o. In a complex example this could mean that many object files are rebuilt for no good reason, just because other files were rebuilt in the same directory.

我会确认确实如此,并在适当的时候提供答案。

问题是您正在使用 make -n,它实际上并没有做任何事情,并结合了 make 的递归调用。 make -n it 命令调用一个子 make,它假装构建所有内容,但实际上并不构建任何内容。然后那个 make 实例退出,当它退出时,它所有关于它假装构建但实际上没有构建的目标的内部知识都丢失了。

然后你开始一个新的make -n test,它依赖于那些相同的目标,这些目标仍然不存在,但是这个新的 make 实例不知道之前的实例假装已经构建了它们。

如果你 运行 是一个真正的 make,而不是 make -n,那么你不应该看到这个重建。

如果你想make -n在这种情况下工作,你不能运行递归地进行。

ETA:您的另一个问题是您的所有目标文件都依赖于 dirs 目标,但该目标从不存在(从来没有名为 dirs 的文件)。因此,当 make 启动时,它发现 dirs 不存在并且 运行 是构建它的规则,然后假设所有依赖于该目标的目标都已过时并重建它们。然后下次调用 make 时,它​​发现 dirs 不存在并且 运行 是构建它的规则,然后假设所有依赖于该目标的目标都已过时并重建他们...等等

好吧,原来是这样:目录修改时间变了,所以make想重新构建一切。使用仅订单先决条件有效。无论如何谢谢!