Makefile Includes and Modular Structure
Organize large Makefiles using the include directive to split configurations into reusable modules for variables, rules, and environment-specific settings.
Detailed Explanation
Modular Makefiles with include
Large projects benefit from splitting Makefiles into multiple files. The include directive reads and inserts the contents of other Makefiles.
Basic Include
# Makefile
include config.mk
include rules.mk
all: $(TARGET)
# config.mk
CC ?= gcc
CFLAGS ?= -Wall -O2
TARGET = myapp
SRCS = $(wildcard src/*.c)
OBJS = $(SRCS:.c=.o)
# rules.mk
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
Silent Include with -include
-include local.mk
-include $(DEPS)
The -include (or sinclude) variant does not error if the file does not exist. This is essential for:
- Dependency files (
.d) that do not exist on first build - Local overrides (
local.mk) that developers may optionally create
Environment-Specific Overrides
ENV ?= development
include config/$(ENV).mk
# config/development.mk
CFLAGS += -g -O0 -DDEBUG
LDFLAGS += -fsanitize=address
# config/production.mk
CFLAGS += -O3 -DNDEBUG -flto
LDFLAGS += -flto -s
Switching environments: make ENV=production.
Monorepo Pattern
SERVICES = auth api gateway worker
define SERVICE_RULES
.PHONY: build-$(1) test-$(1)
build-$(1):
$(MAKE) -C services/$(1) build
test-$(1):
$(MAKE) -C services/$(1) test
endef
$(foreach svc,$(SERVICES),$(eval $(call SERVICE_RULES,$(svc))))
build-all: $(addprefix build-,$(SERVICES))
test-all: $(addprefix test-,$(SERVICES))
The $(MAKE) -C pattern delegates to sub-Makefiles in each service directory, and the foreach/eval/call pattern generates build and test targets for each service.
Key Points
includepaths are relative to the working directory, not the Makefile location- Variables defined before
includeare available in included files - Circular includes are not detected and will cause infinite loops
- Use
MAKEFILE_LISTto get the path of the current Makefile
Use Case
Organizing a large project or monorepo build system where splitting the Makefile into environment configs, shared rules, and service-specific targets improves maintainability.