Arvids Blog

Thoughts on programming and more

Minimal Makefile, Maximum Outcome

Makefiles are flexible in many ways, it’s a small language in itself.
As I said in a post before, I normally write the same Makefile over and over, even just for compiling a simple project with two or more files. The there proposed Makefile, however, grew quite big during the writing of the post.

Today I want to present you a variant which is tiny, yet compiles your small to medium projects just fine, while still having dependency tracking and incremental compilation. To do this, I’ll make use of some of makes very convenient implicit rules.

The implicit rules of make for compiling a C file look like this:

COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)
OUTPUT_OPTION = -o $@

%.o: %.c
    $(COMPILE.c) $(OUTPUT_OPTION) $<

%: %.o
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

Making use of these builtin rules, a basic Makefile can be as simple as:
(make sure the executable target contains at least one object file with the same name, or the executale won’t be linked)

CC = gcc
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)

.PHONY: clean

foo: foo.o

clean:
    rm -f $(OBJ) foo

While this works, and is a variant of the Makefile I write for every tiny project, it does not yet track dependencies on headers, or other include files, requiring you to manually clean the project and recompile.
Modern compilers (gcc > 4, clang > 3.0 and ICC > 12.1) support the options -MMD and -MP, which will generate dependency files (without using sed, which was required with -MM).
Neither does it respect the environment variable CC, nor does it generate debuggable executables.

After applying these changes, this is the resulting Makefile:

CC = gcc
DEBUG = -ggdb -O0 -march=native
CFLAGS := $(DEBUG) -W -Wall -Wextra -Wpedantic -pedantic -ansi
LDLIBS := -lm
OUTPUT_OPTION = -MMD -MP -o $@

SRC	:= $(wildcard *.c)
OBJ	:= $(SRC:.c=.o)
DEP	:= $(SRC:.c=.d)
-include $(DEP)

.PHONY: clean

all: test1
test1: test1.o

clean:
    -rm -f $(OBJ) $(DEP) test1

It now correctly tracks dependencies, respects the CC environment variable, generates gdb debugging symbols, and has some default compiler / warning flags.