Table of Contents
Makefile Advanced
Recursive Make handles multi-directory projects. Each subdirectory has its own Makefile; the top-level Makefile calls them.
# Top-level Makefile SUBDIRS = src lib test .PHONY: all clean $(SUBDIRS) all: $(SUBDIRS) $(SUBDIRS): $(MAKE) -C $@ clean: for dir in $(SUBDIRS); do \ $(MAKE) -C $$dir clean; \ done
$(MAKE) -C $@ runs Make in the subdirectory. Each subdirectory builds independently.
Include files:
include common.mk include config.mk
Include other Makefiles to share rules and variables. Useful for project-wide settings.
Defining and calling functions:
define build_obj gcc $(CFLAGS) -c -o $(1) $(2) endef main.o: main.c $(call build_obj, main.o, main.c)
define ... endef defines a function. $(call ...) invokes it with arguments. Powerful for reusable recipes.
Variable scope: Variables are global. Use functions for local scope:
define compile_files
CC = gcc
$(COMPILE_OBJS)
endef
Variables inside functions are still global (no true local scope in Make).
Order-only prerequisites: Some files should be prerequisites for creation, not modification:
build/%.o: src/%.c | build gcc -c $< -o $@ build: mkdir -p build
The | separates normal prerequisites from order-only. Changes to build/ don't trigger rebuilds of .o files—only its existence matters.
Secondary expansion:
.SECONDEXPANSION: app: $$^ gcc -o app $^
.SECONDEXPANSION allows two-pass variable expansion. Rare; use for advanced metaprogramming.
Reading from files:
VERSION = $(file < VERSION.txt) app: main.o gcc -o app main.o -DVERSION=\"$(VERSION)\"
$(file < filename) reads the file's contents.
Writing to files:
$(file > build.log, Building...) $(file >> build.log, Done)
$(file > ...) writes, $(file >> ...) appends.
Debugging Makefiles: Use @echo to print variables:
debug: @echo "SRCS: $(SRCS)" @echo "OBJS: $(OBJS)" @echo "CFLAGS: $(CFLAGS)"
See Makefile Debugging for more techniques.
