0
votes

My 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 https://stackoverflow.com/a/29021641/3408572
.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)

I build with make and wanted to make cleaning and testing, which I do often, convenient, so for the moment my default cleans ($(MAKE) clean), builds the DLL ($(MAKE) it), and builds the test program ($(MAKE) test).

However, make does

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'

Notice how the $(MAKE) test step of that rebuilds the DLL as if the $(MAKE) it step didn't happen! There's no cleaning in between steps, and no other changes happen, so I don't know why the DLL is being rebuilt.

I thought making the default, it, clean, and dirs targets phony would fix it (as per one of my previous questions), but that didn't work. Google is only telling me how to tell make to build my target twice, not how to stop it from doing so.

This is GNU make 4.0 on Ubuntu GNOME 14.10.

What's going on? Thanks.

UPDATE 22 April 2015
I'm starting to think the problem is actually the one outlined here since I'm seeing similar rebuilding issues on other projects I have that don't do the make test thing I'm doing here:

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.

I'll confirm that this is actually the case and provide an answer when appropriate.

2
Your question is confusing because you don't show what command line you're running. However, you run make clean every time you build the default target, and make clean deletes all the object files, so the fact that those object files are gone means that the DLLFILE target is out of date because they all have to get rebuilt. Why are you running make clean between every build?MadScientist
I build with just make; the output above is for make -n, as the question states. I know I'm cleaning before each build, but the $(MAKE) it should rebuild $DLLFILE; my question is why the $(MAKE) test step disregards that (despite not cleaning). I'll reword the question shortly. (I'm cleaning and building the tests in default for now as a convenience; the final version of the Makefile won't have that step. I'd still like to fix this issue for future reference.)andlabs
@MadScientist edited question; is it less confusing now?andlabs

2 Answers

1
votes

The problem is you're using make -n, which doesn't actually do anything, combined with recursive invocations of make. The make -n it command invokes a sub-make which pretends to build everything, but doesn't actually build anything. Then that instance of make exits, and all its internal knowledge about targets it pretended to build but didn't actually build are lost when it exits.

Then you start a new make -n test which depends on those same targets, which still don't exist, but this new make instance has no idea that the previous instance pretended to build them already.

If you run a real make, not make -n, then you shouldn't see this rebuild.

If you want make -n to work in this situation, you can't run make recursively.

ETA: Your other problem is that all your object files depend on the dirs target, but that target never exists (there's never a file named dirs). So when make starts up it sees that dirs doesn't exist and runs the rule to build it, then assumes that all the targets that depend on that target are out of date and rebuilds them. Then the next time make is invoked, it sees that dirs doesn't exist and runs the rule to build it, then assumes that all the targets that depend on that target are out of date and rebuilds them... etc.

0
votes

Okay, turns out it's exactly as it was: the directory modification time had changed, so make thought to build everything again. Using an order-only prerequisite worked. Thanks anyway!