1.2. Useful Shortcuts for Makefiles

When starting out new projects, Makefiles can be faster and easier to configure than cmake.

Makefiles take an opposing world-view to CMake. Where CMake tries to auto-detect install and configure options, make works best for a static build tree with fixed dependencies configured explicitly by the user.

Not second-guessing, autodetecting or otherwise overriding the user’s environment is make’s speciality.

There are many good guides online.

1.2.1. Syntax Example

Here’s an example of good practices from the quark project:

# See LICENSE file for copyright and license details
# quark - simple web server
.POSIX:

include config.mk

COMPONENTS = data http sock util

all: quark

data.o: data.c data.h http.h util.h config.mk
http.o: http.c config.h http.h util.h config.mk
main.o: main.c arg.h data.h http.h sock.h util.h config.mk
sock.o: sock.c sock.h util.h config.mk
util.o: util.c util.h config.mk

quark: config.h $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk
        $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS)

config.h:
        cp config.def.h $@

clean:
        rm -f quark main.o $(COMPONENTS:=.o)

dist:
        rm -rf "quark-$(VERSION)"
        mkdir -p "quark-$(VERSION)"
        cp -R LICENSE Makefile arg.h config.def.h config.mk quark.1 \
                $(COMPONENTS:=.c) $(COMPONENTS:=.h) main.c "quark-$(VERSION)"
        tar -cf - "quark-$(VERSION)" | gzip -c > "quark-$(VERSION).tar.gz"
        rm -rf "quark-$(VERSION)"

install: all
        mkdir -p "$(DESTDIR)$(PREFIX)/bin"
        cp -f quark "$(DESTDIR)$(PREFIX)/bin"
        chmod 755 "$(DESTDIR)$(PREFIX)/bin/quark"
        mkdir -p "$(DESTDIR)$(MANPREFIX)/man1"
        cp quark.1 "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
        chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/quark.1"

uninstall:
        rm -f "$(DESTDIR)$(PREFIX)/bin/quark"
        rm -f "$(DESTDIR)$(MANPREFIX)/man1/quark.1"

Notice that explicit dependencies ensure the project is properly rebuilt when relevant files are edited.

Also, special make substituions are used: * $@ = the name of the rule’s target (e.g. the config.h rule) * $^ = all dependencies of the rule * $(VAR:a=b) = substitute trailing a with b in each word.

1.2.2. General Rules

The example above doesn’t specifically say how to compile .c files to .o files. Instead, it relies on the default rule.

We reproduce this below, along with some other useful ones for modern software:

.SUFFIXES: c cx f cu

.c.o:
        $(CC) $(CFLAGS) -c $<

.f.o:
        $(FC) $(FFLAGS) -c $<

.cc.o:
        $(CXX) $(CXXFLAGS) -c $<

.cu.o:
        $(NVCC) $(NVCCFLAGS) -c $<

1.2.3. External Packages

The quark example above uses a small, separate config.mk file to let the user point the build at all system dependencies:

# quark version
VERSION = 0

# Customize below to fit your system

# paths
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man

# flags
CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE
CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os
LDFLAGS  = -s

# compiler and linker
CC = cc

GNU Make defines the shell function. This can be helpful for adding limited auto-detection to config.mk. For example, $(shell pkg-config --libs zlib).

1.2.4. Interesting Tricks

GCC can create Makefiles listing user header files with -MMD. The way to use this is like this:

CXX = g++
CXXFLAGS = -g -Wall -MMD      # The MMD flag causes x.d, etc. to be output
OBJECTS = x.o y.o z.o         # object files forming executable
DEPENDS = ${OBJECTS:.o=.d}    # substitutes ".o" with ".d"
EXEC = a.out                  # executable name

${EXEC} : ${OBJECTS}          # link step
    ${CXX} ${OBJECTS} -o ${EXEC}

-include ${DEPENDS}           # include x.d, y.d, and z.d

The - prefix on include prevents an error from being thrown if the include command fails.