Generate GNU Make build systems that define build targets, configure dependencies, set up phony targets, and implement parallel builds. Use when creating make/Makefile/.mk files, implementing compile rules, or building production-ready build automation for C/C++, Go, Python, and Java projects.
Overall
score
93%
Does it follow best practices?
Validation for skill structure
This guide covers target definitions, standard GNU targets, .PHONY declarations, dependencies, pattern rules, and best practices for organizing targets in Makefiles.
target: prerequisites
recipe
recipe
...hello: hello.c
gcc hello.c -o helloHow it works:
hello exists and is newer than hello.chelloPhony targets don't represent actual files. They represent actions:
.PHONY: clean
clean:
rm -f *.o myprogramWithout .PHONY:
make clean won't runWith .PHONY:
.PHONY: all clean install uninstall test check help
.PHONY: build dist distclean format lint docs# Option 1: All at once (recommended)
.PHONY: all clean install test help
# Option 2: Separate declarations
.PHONY: all
.PHONY: clean
.PHONY: installGNU Coding Standards define standard targets that users expect:
## Build all targets
.PHONY: all
all: $(TARGET)Requirements:
## Install built files to PREFIX
.PHONY: install
install: all
$(INSTALL) -d $(DESTDIR)$(BINDIR)
$(INSTALL_PROGRAM) $(TARGET) $(DESTDIR)$(BINDIR)/
$(INSTALL) -d $(DESTDIR)$(LIBDIR)
$(INSTALL_DATA) lib$(PROJECT).a $(DESTDIR)$(LIBDIR)/
$(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)
$(INSTALL_DATA) $(PROJECT).h $(DESTDIR)$(INCLUDEDIR)/
$(INSTALL) -d $(DESTDIR)$(MAN1DIR)
$(INSTALL_DATA) docs/$(PROJECT).1 $(DESTDIR)$(MAN1DIR)/Requirements:
all to build firstDESTDIR and PREFIX## Remove installed files
.PHONY: uninstall
uninstall:
$(RM) $(DESTDIR)$(BINDIR)/$(TARGET)
$(RM) $(DESTDIR)$(LIBDIR)/lib$(PROJECT).a
$(RM) $(DESTDIR)$(INCLUDEDIR)/$(PROJECT).h
$(RM) $(DESTDIR)$(MAN1DIR)/$(PROJECT).1## Remove built files (keep configuration)
.PHONY: clean
clean:
$(RM) $(OBJECTS) $(TARGET)
$(RM) -r $(BUILDDIR)
$(RM) *.o *.a *.soRequirements:
## Remove all generated files (including configuration)
.PHONY: distclean
distclean: clean
$(RM) config.h config.log config.status
$(RM) Makefile
$(RM) -r autom4te.cache/Requirements:
clean./configure should work## Run tests
.PHONY: test
test: $(TARGET)
@echo "Running tests..."
./run_tests.sh
$(TARGET) --self-test
@echo "All tests passed!"## Alias for test (GNU convention)
.PHONY: check
check: test## Create distribution tarball
.PHONY: dist
dist:
@mkdir -p dist
tar -czf dist/$(PROJECT)-$(VERSION).tar.gz \
--transform 's,^,$(PROJECT)-$(VERSION)/,' \
--exclude='.git*' \
--exclude='*.o' \
--exclude='*.a' \
--exclude='$(BUILDDIR)' \
.## Create and verify distribution tarball
.PHONY: distcheck
distcheck: dist
@echo "Verifying distribution..."
@mkdir -p $(BUILDDIR)/distcheck
tar -xzf dist/$(PROJECT)-$(VERSION).tar.gz -C $(BUILDDIR)/distcheck
cd $(BUILDDIR)/distcheck/$(PROJECT)-$(VERSION) && ./configure && make && make check
@echo "Distribution verified successfully!"
$(RM) -r $(BUILDDIR)/distcheck## Show available targets and usage
.PHONY: help
help:
@echo "$(PROJECT) - version $(VERSION)"
@echo ""
@echo "Available targets:"
@sed -n 's/^## //p' $(MAKEFILE_LIST) | column -t -s ':' | sed 's/^/ /'
@echo ""
@echo "Variables:"
@echo " PREFIX=$(PREFIX)"
@echo " CC=$(CC)"
@echo " CFLAGS=$(CFLAGS)"
@echo ""
@echo "Examples:"
@echo " make # Build the project"
@echo " make install # Install to PREFIX"
@echo " make PREFIX=/opt/local # Install to custom location"
@echo " make clean # Remove built files"Self-documenting pattern:
## Build the application
.PHONY: build
build: $(TARGET)
## Run all tests
.PHONY: test
test:
./run_tests.shThe ## comments are parsed by the help target.
# program depends on main.o and utils.o
program: main.o utils.o
$(CC) $^ -o $@
# main.o depends on main.c
main.o: main.c
$(CC) -c $< -o $@# Multiple targets with same recipe
main.o utils.o helper.o: common.h
$(CC) -c $*.c -o $@# Normal prerequisites: update target if prerequisite changes
# Order-only prerequisites (|): only check existence, not timestamp
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) -c $< -o $@
# $(OBJDIR) must exist, but changes to it don't trigger rebuild
$(OBJDIR):
mkdir -p $@Use cases:
# WRONG: Circular dependency
a: b
b: a
# Error: Circular a <- b dependency dropped
# RIGHT: Break the cycle
a: c
b: c
c:
touch c# Compile .c to .o
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Compile .c to .s (assembly)
%.s: %.c
$(CC) $(CFLAGS) -S $< -o $@
# Create .c from .y (yacc)
%.c: %.y
$(YACC) $(YFLAGS) -o $@ $<# Match files in specific directories
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
# Multiple directory patterns
build/obj/%.o: src/%.c
$(CC) -c $< -o $@
build/obj/%.o: lib/%.c
$(CC) -c $< -o $@More efficient than pattern rules for specific files:
OBJECTS := main.o utils.o helper.o
# Static pattern: $(targets): target-pattern: prereq-pattern
$(OBJECTS): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Equivalent to:
# main.o: main.c
# utils.o: utils.c
# helper.o: helper.cAdvantages:
# First matching rule is used
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.s
$(AS) $(ASFLAGS) -c $< -o $@
%.o: %.asm
$(NASM) $(NASMFLAGS) -f elf64 $< -o $@# Set variable only for specific target and its prerequisites
debug: CFLAGS += -g -O0 -DDEBUG
debug: $(TARGET)
release: CFLAGS += -O3 -DNDEBUG
release: $(TARGET)
# Pattern-specific variables
test_%: CFLAGS += -DTESTING
test_%: LDLIBS += -lcheck# Double-colon allows multiple recipes for same target
install::
@echo "Installing binaries..."
$(INSTALL) $(TARGET) $(BINDIR)/
install::
@echo "Installing libraries..."
$(INSTALL) lib$(PROJECT).a $(LIBDIR)/
# Each recipe runs independentlyUse cases:
# Mark files as intermediate (deleted after use)
.INTERMEDIATE: $(OBJECTS)
# Keep specific intermediate files
.SECONDARY: important.o
# Never delete these intermediate files
.PRECIOUS: %.o# Generate dependencies automatically
DEPENDS := $(OBJECTS:.o=.d)
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
# Include generated dependencies
-include $(DEPENDS).DELETE_ON_ERROR:
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
PREFIX ?= /usr/local
TARGET := hello
SOURCES := hello.c
OBJECTS := $(SOURCES:.c=.o)
.PHONY: all clean install uninstall help
## Build the program (default)
all: $(TARGET)
## Compile and link
$(TARGET): $(OBJECTS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
## Install to PREFIX
install: $(TARGET)
install -d $(DESTDIR)$(PREFIX)/bin
install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/
## Remove installed files
uninstall:
$(RM) $(DESTDIR)$(PREFIX)/bin/$(TARGET)
## Remove built files
clean:
$(RM) $(OBJECTS) $(TARGET)
## Show this help
help:
@echo "Available targets:"
@sed -n 's/^## //p' $(MAKEFILE_LIST).DELETE_ON_ERROR:
PROJECT := myapp
CC ?= gcc
CFLAGS ?= -Wall -Wextra -O2
PREFIX ?= /usr/local
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPENDS := $(OBJECTS:.o=.d)
TARGET := $(BUILDDIR)/$(PROJECT)
.PHONY: all clean install test help
## Build everything (default)
all: $(TARGET)
## Link executable
$(TARGET): $(OBJECTS)
@mkdir -p $(@D)
@echo " LD $@"
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
## Compile source files
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
@echo " CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@
-include $(DEPENDS)
## Install to PREFIX
install: $(TARGET)
install -d $(DESTDIR)$(PREFIX)/bin
install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(PROJECT)
## Run tests
test: $(TARGET)
@echo "Running tests..."
@$(TARGET) --test
## Remove build artifacts
clean:
$(RM) -r $(BUILDDIR)
## Show available targets
help:
@sed -n 's/^## //p' $(MAKEFILE_LIST).DELETE_ON_ERROR:
PROJECT := mylib
VERSION := 1.0.0
CC ?= gcc
AR ?= ar
RANLIB ?= ranlib
PREFIX ?= /usr/local
SRCDIR := src
BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
HEADERS := $(wildcard $(SRCDIR)/*.h)
STATIC_LIB := $(BUILDDIR)/lib$(PROJECT).a
SHARED_LIB := $(BUILDDIR)/lib$(PROJECT).so.$(VERSION)
.PHONY: all static shared clean install install-static install-shared
## Build both static and shared libraries
all: static shared
## Build static library
static: $(STATIC_LIB)
## Build shared library
shared: $(SHARED_LIB)
## Create static library
$(STATIC_LIB): $(OBJECTS)
@mkdir -p $(@D)
@echo " AR $@"
$(AR) rcs $@ $^
$(RANLIB) $@
## Create shared library
$(SHARED_LIB): $(OBJECTS)
@mkdir -p $(@D)
@echo " LD $@"
$(CC) -shared -Wl,-soname,lib$(PROJECT).so.1 $^ -o $@
ln -sf lib$(PROJECT).so.$(VERSION) $(BUILDDIR)/lib$(PROJECT).so.1
ln -sf lib$(PROJECT).so.1 $(BUILDDIR)/lib$(PROJECT).so
## Compile with -fPIC for shared library
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(@D)
@echo " CC $<"
$(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c $< -o $@
## Install both libraries
install: install-static install-shared
## Install static library
install-static: $(STATIC_LIB)
install -d $(DESTDIR)$(PREFIX)/lib
install -m 644 $(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/
install -d $(DESTDIR)$(PREFIX)/include/$(PROJECT)
install -m 644 $(HEADERS) $(DESTDIR)$(PREFIX)/include/$(PROJECT)/
## Install shared library
install-shared: $(SHARED_LIB)
install -d $(DESTDIR)$(PREFIX)/lib
install -m 755 $(SHARED_LIB) $(DESTDIR)$(PREFIX)/lib/
ln -sf lib$(PROJECT).so.$(VERSION) $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so.1
ln -sf lib$(PROJECT).so.1 $(DESTDIR)$(PREFIX)/lib/lib$(PROJECT).so
ldconfig
## Clean build artifacts
clean:
$(RM) -r $(BUILDDIR)Install with Tessl CLI
npx tessl i pantheon-ai/makefile-generator@0.1.1