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.

Advanced Patterns

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

  • include paths are relative to the working directory, not the Makefile location
  • Variables defined before include are available in included files
  • Circular includes are not detected and will cause infinite loops
  • Use MAKEFILE_LIST to 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.

Try It — Makefile Generator

Open full tool