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 two or more files. The proposed Makefile there, however, grew to big while writing the post, since I wanted it to be small, but still have a few convinient features.

Today I want to present you a variant which is tiny, yet compiles your small to medium projects just fine. I'll make use of some of makes every 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, like before, with only -MM).
Neither does it respect the environment variable CC, nor generate any debug files.

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.

comments powered by Disqus