
Makefile
- 158 installs
- 22 repo stars
- Updated August 1, 2026
- itechmeat/llm-code
Author or refactor Makefiles that standardize local dev commands, test runs, builds, and codegen tasks so teams and coding agents invoke consistent project workflows.
About
makefile from itechmeat/llm-code helps craft clear, maintainable Makefiles with sensible targets, dependencies, and variables so local development, testing, and packaging workflows stay documented and automatable for humans and LLM agents.
- Target naming conventions
- Phony targets and dependencies
- Cross-platform compatibility tips
- Variable and pattern rules
- Common dev/test/build recipes
Makefile by the numbers
- 158 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #647 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/itechmeat/llm-code --skill makefileAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 158 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 1, 2026 |
| Repository | itechmeat/llm-code ↗ |
What it does
Author or refactor Makefiles that standardize local dev commands, test runs, builds, and codegen tasks so teams and coding agents invoke consistent project workflows.
Files
Makefile Skill
Guidance for creating and maintaining GNU Make build automation.
Quick Navigation
| Topic | Reference |
|---|---|
| Rules, prerequisites, targets | syntax.md |
| Variable types and assignment | variables.md |
| Built-in functions | functions.md |
| Special and phony targets | targets.md |
| Recipe execution, parallel | recipes.md |
| Implicit and pattern rules | implicit.md |
| Common practical patterns | patterns.md |
---
Core Concepts
Rule Structure
target: prerequisites
recipeCritical: Recipe lines MUST start with TAB character.
File vs Phony Targets
# File target - creates/updates a file
build/app.o: src/app.c
$(CC) -c $< -o $@
# Phony target - action, not a file
.PHONY: clean test install
clean:
rm -rf build/Variable Assignment
| Operator | Name | When Expanded |
|---|---|---|
:= | Simple | Once, at definition |
?= | Conditional | If not already set |
= | Recursive | Each use (late binding) |
+= | Append | Adds to existing value |
CC := gcc # Immediate
CFLAGS ?= -O2 # Default, overridable
DEBUG = $(VERBOSE) # Late binding
CFLAGS += -Wall # AppendAutomatic Variables
| Variable | Meaning |
|---|---|
$@ | Target |
$< | First prerequisite |
$^ | All prerequisites (unique) |
$? | Prerequisites newer than target |
$* | Stem in pattern rules |
---
Essential Patterns
Self-Documenting Help
.DEFAULT_GOAL := help
help: ## Show available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
install: ## Install dependencies
uv sync
test: ## Run tests
uv run pytestPlatform Detection
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
OPEN := open
else ifeq ($(UNAME_S),Linux)
OPEN := xdg-open
endifBuild Directory
BUILDDIR := build
SOURCES := $(wildcard src/*.c)
OBJECTS := $(patsubst src/%.c,$(BUILDDIR)/%.o,$(SOURCES))
$(BUILDDIR)/%.o: src/%.c | $(BUILDDIR)
$(CC) -c $< -o $@
$(BUILDDIR):
mkdir -p $@Environment Export
export PYTHONPATH := $(PWD)/src
export DATABASE_URL
test:
pytest tests/ # sees exported variables---
Common Targets
Quality Checks
.PHONY: lint format check test
lint: ## Run linters
ruff check .
mypy src/
format: ## Format code
ruff format .
check: format lint test ## All quality checksCleanup
.PHONY: clean clean-all
clean: ## Remove build artifacts
rm -rf build/ dist/ *.egg-info
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
clean-all: clean ## Remove all generated files
rm -rf .venv .pytest_cache .mypy_cacheDocker Integration
IMAGE := myapp
VERSION := $(shell git describe --tags --always)
docker-build: ## Build Docker image
docker build -t $(IMAGE):$(VERSION) .
docker-run: ## Run container
docker run -d -p 8000:8000 $(IMAGE):$(VERSION)---
Recipe Execution
Each Line = Separate Shell
# Won't work - cd lost between lines
bad:
cd subdir
pwd # Still in original dir!
# Correct - combine commands
good:
cd subdir && pwd
# Or use line continuation
also-good:
cd subdir && \
pwd && \
makeSilent and Error Handling
target:
@echo "@ suppresses command echo"
-rm -f maybe.txt # - ignores errorsParallel Execution
make -j4 # 4 parallel jobs
make -j4 lint test # Run lint and test in parallel---
Output Discipline
One line in, one line out. Avoid echo spam.
# ❌ Too chatty
start:
@echo "Starting services..."
docker compose up -d
@echo "Waiting..."
@sleep 3
@echo "Done!"
# ✅ Concise
start: ## Start services
@echo "Starting at http://localhost:8000 ..."
@docker compose up -d
@echo "Logs: docker compose logs -f"---
Conditionals
DEBUG ?= 0
ifeq ($(DEBUG),1)
CFLAGS += -g -O0
else
CFLAGS += -O2
endif
ifdef CI
TEST_FLAGS := --ci
endif---
Including Files
# Required include (error if missing)
include config.mk
# Optional include (silent if missing)
-include local.mk
-include .env---
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Spaces in recipes | Recipes need TAB | Use actual TAB character |
| Missing .PHONY | make test fails if test file exists | Declare .PHONY: test |
| cd in recipes | Each line is new shell | Use cd dir && command |
= vs := confusion | Unexpected late expansion | Use := by default |
| Unexported vars | Subprocesses don't see vars | export VAR |
| Complex shell in make | Hard to maintain | Move to external script |
---
Quick Reference
# Makefile Template
.DEFAULT_GOAL := help
SHELL := /bin/bash
.SHELLFLAGS := -ec
.PHONY: help install test lint format clean
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
install: ## Install dependencies
uv sync --extra dev
test: ## Run tests
uv run pytest tests/ -v
lint: ## Run linters
uv run ruff check .
format: ## Format code
uv run ruff format .
clean: ## Clean artifacts
rm -rf build/ dist/ .pytest_cache---
Links
See Also
- patterns.md - Extended patterns and recipes
Makefile Functions Reference
Source: GNU Make Manual - Functions for Transforming Text
Function Call Syntax
$(function arguments)
${function arguments}Arguments separated by commas. Spaces after commas are significant.
String Functions
subst - Simple Substitution
$(subst from,to,text)
# Example
$(subst ee,EE,feet on the street)
# Result: "fEEt on the strEEt"patsubst - Pattern Substitution
$(patsubst pattern,replacement,text)
# Example
$(patsubst %.c,%.o,foo.c bar.c)
# Result: "foo.o bar.o"
# Shorthand for variables
$(var:pattern=replacement)
$(sources:.c=.o)strip - Remove Whitespace
$(strip string)
# Example
$(strip a b c )
# Result: "a b c"findstring - Find Substring
$(findstring find,in)
# Returns find if found, empty otherwise
$(findstring a,abc) # "a"
$(findstring x,abc) # ""filter - Select Matching Words
$(filter pattern...,text)
# Example
sources := foo.c bar.c baz.s qux.h
c_sources := $(filter %.c,$(sources))
# Result: "foo.c bar.c"filter-out - Remove Matching Words
$(filter-out pattern...,text)
# Example
objects := main.o foo.o test.o
prod_objects := $(filter-out test.o,$(objects))
# Result: "main.o foo.o"sort - Sort and Deduplicate
$(sort list)
# Example
$(sort foo bar baz foo bar)
# Result: "bar baz foo"word - Select Nth Word
$(word n,text)
# Example
$(word 2,foo bar baz)
# Result: "bar"wordlist - Select Word Range
$(wordlist start,end,text)
# Example
$(wordlist 2,4,a b c d e f)
# Result: "b c d"words - Count Words
$(words text)
# Example
$(words foo bar baz)
# Result: "3"firstword / lastword
$(firstword text)
$(lastword text)
# Example
$(firstword foo bar baz) # "foo"
$(lastword foo bar baz) # "baz"File Name Functions
dir - Directory Part
$(dir names...)
# Example
$(dir src/foo.c hacks)
# Result: "src/ ./"notdir - File Name Part
$(notdir names...)
# Example
$(notdir src/foo.c hacks)
# Result: "foo.c hacks"suffix - File Suffix
$(suffix names...)
# Example
$(suffix src/foo.c src-1.0/bar.c hacks)
# Result: ".c .c"basename - Remove Suffix
$(basename names...)
# Example
$(basename src/foo.c src-1.0/bar.c hacks)
# Result: "src/foo src-1.0/bar hacks"addsuffix - Add Suffix
$(addsuffix suffix,names...)
# Example
$(addsuffix .c,foo bar)
# Result: "foo.c bar.c"addprefix - Add Prefix
$(addprefix prefix,names...)
# Example
$(addprefix src/,foo bar)
# Result: "src/foo src/bar"join - Pairwise Join
$(join list1,list2)
# Example
$(join a b,1 2 3)
# Result: "a1 b2 3"wildcard - Glob Expansion
$(wildcard pattern)
# Example
$(wildcard *.c) # All .c files
$(wildcard src/*.c) # All .c in src/
$(wildcard src/*/*.c) # .c in src subdirsrealpath / abspath
$(realpath names...) # Canonical path (resolves symlinks)
$(abspath names...) # Absolute path (no symlink resolution)Conditional Functions
if - Conditional
$(if condition,then-part)
$(if condition,then-part,else-part)
# Example
$(if $(DEBUG),debug-flags,release-flags)or - First Non-Empty
$(or condition1,condition2,...)
# Example
CC := $(or $(CC),gcc) # Use CC if set, else gccand - All Non-Empty
$(and condition1,condition2,...)
# Example
$(and $(CC),$(CFLAGS),ready)
# "ready" only if both CC and CFLAGS setLoop Functions
foreach - Iterate Over List
$(foreach var,list,text)
# Example
dirs := a b c
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*.c))call - User-Defined Functions
$(call function,arg1,arg2,...)
# Define function
reverse = $(2) $(1)
# Use it
$(call reverse,a,b)
# Result: "b a"Inside function: $(0) is function name, $(1), $(2) are arguments.
let - Local Variables (GNU Make 4.4+)
$(let var1 var2 ...,value1 value2 ...,text)
# Example
$(let a b,1 2,$(a) and $(b))
# Result: "1 and 2"Shell and Control Functions
shell - Execute Command
$(shell command)
# Example
git_hash := $(shell git rev-parse --short HEAD)
today := $(shell date +%Y-%m-%d)eval - Parse as Makefile
$(eval text)
# Example - generate rules dynamically
define PROGRAM_template
$(1): $$($(1)_OBJS)
$$(CC) -o $$@ $$^
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))value - Unexpanded Value
$(value variable)
# Example
FOO = $$PATH
# $(FOO) expands $PATH
# $(value FOO) returns "$$PATH"origin - Variable Origin
$(origin variable)
# Returns: undefined, default, environment, environment override,
# file, command line, override, automaticflavor - Variable Type
$(flavor variable)
# Returns: undefined, recursive, simpleControl Functions
error - Abort with Message
$(error text)
# Example
ifndef JAVA_HOME
$(error JAVA_HOME is not set)
endifwarning - Print Warning
$(warning text)
# Example
ifeq ($(DEBUG),1)
$(warning Building in debug mode)
endifinfo - Print Message
$(info text)
# Example
$(info Building version $(VERSION))File Function
Read/write files directly:
# Read file
$(file <filename)
# Write to file (overwrite)
$(file >filename,text)
# Append to file
$(file >>filename,text)Common Patterns
Building file lists
sources := $(wildcard src/*.c)
objects := $(patsubst src/%.c,build/%.o,$(sources))Conditional compilation flags
CFLAGS := -Wall
CFLAGS += $(if $(DEBUG),-g -O0,-O2)Dynamic rule generation
PROGRAMS := prog1 prog2
define make-program
$(1): $($(1)_OBJS)
$$(CC) -o $$@ $$^
endef
$(foreach prog,$(PROGRAMS),$(eval $(call make-program,$(prog))))Safe file existence check
ifneq ($(wildcard config.mk),)
include config.mk
endifMakefile Implicit Rules Reference
Source: GNU Make Manual - Using Implicit Rules
What Are Implicit Rules?
Built-in rules that make knows how to build common file types:
# You write:
prog: main.o utils.o
$(CC) -o $@ $^
# Make automatically knows:
# main.o comes from main.c using cc -c
# utils.o comes from utils.c using cc -cCommon Built-in Rules
C Compilation
# .c → .o
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# .c → executable (single file)
%: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@C++ Compilation
# .cc/.cpp/.C → .o
%.o: %.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@Other Languages
# Fortran: .f → .o
# Pascal: .p → .o
# Assembly: .s → .o
# Yacc: .y → .c
# Lex: .l → .cVariables Used by Implicit Rules
Program Names
| Variable | Default | Purpose |
|---|---|---|
CC | cc | C compiler |
CXX | g++ | C++ compiler |
AS | as | Assembler |
AR | ar | Archiver |
LEX | lex | Lex |
YACC | yacc | Yacc |
RM | rm -f | File removal |
Flags
| Variable | Purpose |
|---|---|
CFLAGS | C compiler flags |
CXXFLAGS | C++ compiler flags |
CPPFLAGS | C preprocessor flags (-I, -D) |
LDFLAGS | Linker flags (-L) |
LDLIBS | Libraries (-l) |
ARFLAGS | Archiver flags |
Customize Built-in Rules
CC := gcc
CFLAGS := -Wall -O2
CPPFLAGS := -I./include -DDEBUG
LDFLAGS := -L./lib
LDLIBS := -lm -lpthreadPattern Rules
Define custom implicit rules:
# Pattern rule syntax
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
# Multiple prerequisites
%.o: %.c %.h
$(CC) -c $(CFLAGS) $< -o $@
# Multiple targets (grouped)
%.tab.c %.tab.h: %.y
bison -d $<Pattern Rule Variables
| Variable | Meaning |
|---|---|
$@ | Target file |
$< | First prerequisite |
$^ | All prerequisites |
$* | Stem (part matched by %) |
$(@D) | Directory of target |
$(@F) | File part of target |
Stem Matching
# Rule: %.o: %.c
# Target: src/foo.o
# $* = src/foo (the stem)
# Looking for: src/foo.cStatic Pattern Rules
Apply pattern to explicit list of targets:
objects := foo.o bar.o baz.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@Advantage: More control, faster matching, no accidental matches.
With Filter
files := foo.c bar.c baz.s qux.h
$(filter %.o,$(files:.c=.o)): %.o: %.c
$(CC) -c $< -o $@Suffix Rules (Old Style)
Legacy syntax, prefer pattern rules:
# Double-suffix: .c → .o
.c.o:
$(CC) -c $<
# Single-suffix: anything → .c
.c:
$(CC) $< -o $@
# Define suffixes
.SUFFIXES: .c .o .hImplicit Rule Search
How Make finds applicable rules:
1. Look for explicit rule with recipe 2. Look for pattern rule matching target 3. Check if prerequisites exist or can be made 4. First applicable rule wins
Rule Ordering
Rules are tried in order:
1. Pattern rules defined in makefile 2. Built-in rules 3. Match-anything rules (%)
Canceling Rules
# Cancel specific implicit rule
%.o: %.c
# Cancel all built-in rules
.SUFFIXES:
MAKEFLAGS += -rChains of Implicit Rules
Make can chain rules:
# Given: foo.y
# Make can: foo.y → foo.c → foo.o → foo
# Intermediate files are auto-deleted
# Mark as precious to keep:
.PRECIOUS: %.c.INTERMEDIATE and .SECONDARY
# Explicitly mark as intermediate
.INTERMEDIATE: generated.c
# Keep intermediate (don't delete)
.SECONDARY: generated.c
.SECONDARY: # Keep allMatch-Anything Rules
Pattern with just %:
# Last-resort rule
%:
@echo "Don't know how to make $@"
# Terminal rule (can't chain further)
%:: default-recipeAutomatic Prerequisites
Generate dependencies automatically:
# Generate .d files with gcc
%.d: %.c
$(CC) -MM $(CPPFLAGS) $< > $@
# Include generated dependencies
-include $(sources:.c=.d)Modern approach:
CFLAGS += -MMD -MP
-include $(objects:.o=.d)Common Custom Rules
Documentation
%.html: %.md
pandoc -o $@ $<
%.pdf: %.tex
pdflatex $<Archive Files
%.tar.gz: %
tar czf $@ $<
%.zip: %
zip -r $@ $<Code Generation
%.pb.go: %.proto
protoc --go_out=. $<
%_generated.py: %.yaml
python generate.py $< > $@Best Practices
1. Prefer pattern rules over suffix rules - More readable 2. Use static patterns for explicit lists - Faster, clearer 3. Set implicit variables - CC, CFLAGS, etc. 4. Generate dependencies automatically - -MMD -MP 5. Cancel unused implicit rules - Performance 6. Mark precious intermediates - If you need them
# Optimized implicit rule setup
MAKEFLAGS += -rR # Disable built-in rules
.SUFFIXES: # Clear suffix list
CC := gcc
CFLAGS := -Wall -O2 -MMD -MP
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
-include $(objects:.o=.d)Common Makefile Patterns
Practical patterns for modern development workflows.
Project Setup Pattern
# Header
.DEFAULT_GOAL := help
SHELL := /bin/bash
.SHELLFLAGS := -ec
# Phony declarations (group at top)
.PHONY: all help install test lint format clean
# Help target (self-documenting)
help: ## Show available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'Platform Detection
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
PLATFORM := linux
OPEN_CMD := xdg-open
endif
ifeq ($(UNAME_S),Darwin)
PLATFORM := macos
OPEN_CMD := open
endif
ifeq ($(OS),Windows_NT)
PLATFORM := windows
OPEN_CMD := start
endifTool Detection
HAS_DOCKER := $(shell command -v docker 2>/dev/null)
HAS_UV := $(shell command -v uv 2>/dev/null)
install:
ifdef HAS_UV
uv sync
else
pip install -r requirements.txt
endif
require-docker:
@command -v docker >/dev/null 2>&1 || \
(echo "Error: docker required" && exit 1)Version Management
# From pyproject.toml
VERSION := $(shell grep -m1 version pyproject.toml | cut -d'"' -f2)
# From git
VERSION := $(shell git describe --tags --always)
# From file
VERSION := $(shell cat VERSION)
build: ## Build version $(VERSION)
@echo "Building $(VERSION)"Python/UV Project
UV := uv
PYTHON := python
.PHONY: install install-dev test lint format clean
install: ## Install production dependencies
$(UV) sync
install-dev: ## Install with dev dependencies
$(UV) sync --extra dev
test: ## Run tests
$(UV) run pytest tests/ -v
lint: ## Run linters
$(UV) run ruff check .
$(UV) run mypy src/
format: ## Format code
$(UV) run ruff format .
clean: ## Remove artifacts
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov .coverageNode.js Project
NPM := npm
NODE := node
.PHONY: install test lint build clean
install: node_modules ## Install dependencies
node_modules: package.json
$(NPM) install
touch $@
test: ## Run tests
$(NPM) test
lint: ## Run linter
$(NPM) run lint
build: ## Build project
$(NPM) run build
clean: ## Clean build artifacts
rm -rf node_modules distDocker Patterns
IMAGE := myapp
TAG := $(VERSION)
.PHONY: docker-build docker-run docker-stop docker-clean
docker-build: ## Build Docker image
docker build -t $(IMAGE):$(TAG) .
docker tag $(IMAGE):$(TAG) $(IMAGE):latest
docker-run: ## Run container
docker run -d --name $(IMAGE) -p 8000:8000 $(IMAGE):$(TAG)
docker-stop: ## Stop container
-docker stop $(IMAGE)
-docker rm $(IMAGE)
docker-clean: docker-stop ## Remove images
-docker rmi $(IMAGE):$(TAG)
-docker rmi $(IMAGE):latestBuild Directory Pattern
BUILDDIR := build
SRCDIR := src
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SOURCES))
$(BUILDDIR)/%.o: $(SRCDIR)/%.c | $(BUILDDIR)
$(CC) -c $< -o $@
$(BUILDDIR):
mkdir -p $@
clean:
rm -rf $(BUILDDIR)Multi-Environment Configs
ENV ?= development
ifeq ($(ENV),production)
CFLAGS := -O3 -DNDEBUG
DEBUG := 0
else ifeq ($(ENV),test)
CFLAGS := -O0 -g -DTEST
DEBUG := 1
else
CFLAGS := -O0 -g -DDEBUG
DEBUG := 1
endif
# Usage: make build ENV=productionVerbose/Quiet Mode
V ?= 0
ifeq ($(V),0)
Q := @
VFLAG :=
else
Q :=
VFLAG := -v
endif
compile:
$(Q)$(CC) $(CFLAGS) -c $< -o $@
# Usage: make compile V=1Parallel Quality Checks
.PHONY: check lint test typecheck
check: ## Run all checks in parallel
$(MAKE) -j3 lint typecheck test
lint:
ruff check .
typecheck:
mypy src/
test:
pytest tests/Dependency Chains
.PHONY: deploy build test
# Deploy requires build, which requires tests
deploy: build ## Deploy to production
./scripts/deploy.sh
build: test ## Build application
python -m build
test: ## Run tests
pytest tests/Colored Output
# Colors (may not work on all terminals)
RED := \033[31m
GREEN := \033[32m
CYAN := \033[36m
RESET := \033[0m
info:
@echo "$(CYAN)Building...$(RESET)"
success:
@echo "$(GREEN)Done!$(RESET)"
error:
@echo "$(RED)Failed!$(RESET)"Guard Patterns
# Require variable to be set
guard-%:
@if [ -z '${${*}}' ]; then \
echo "ERROR: $* is not set"; \
exit 1; \
fi
deploy: guard-AWS_REGION guard-AWS_ACCOUNT_ID
./deploy.shFile Generation Pattern
# Generate file if missing
.env:
cp .env.example .env
@echo "Created .env - please configure"
# Always regenerate
.PHONY: config
config:
envsubst < config.template > config.yamlInclude Pattern
# Main Makefile
-include .env # Load environment
-include local.mk # Local overrides
-include .devcontainer/Makefile # DevContainer targets
# Include if exists, ignore if missing
ifneq ($(wildcard config.mk),)
include config.mk
endifArchive/Release Pattern
DIST := dist
NAME := myproject-$(VERSION)
.PHONY: dist dist-clean
dist: dist-clean ## Create release archive
mkdir -p $(DIST)/$(NAME)
cp -r src/ docs/ README.md $(DIST)/$(NAME)/
cd $(DIST) && tar czf $(NAME).tar.gz $(NAME)
cd $(DIST) && zip -r $(NAME).zip $(NAME)
dist-clean:
rm -rf $(DIST)Watch Pattern (requires entr/fswatch)
.PHONY: watch watch-test
watch: ## Watch and rebuild
find src -name '*.c' | entr -c make build
watch-test: ## Watch and run tests
find src tests -name '*.py' | entr -c make testOutput Discipline
# ❌ Too verbose
start:
@echo "Starting services..."
docker compose up -d
@echo "Waiting for health..."
sleep 5
@echo "Services started!"
@echo "Done!"
# ✅ One line in, one line out
start: ## Start services
@echo "Starting at http://localhost:8000 ..."
@docker compose up -d
@echo "Logs: docker compose logs -f"Makefile Recipe Execution Reference
Source: GNU Make Manual - Writing Recipes in Rules
Recipe Basics
Recipes are shell commands that update targets.
target: prerequisites
command1
command2Critical: Recipe lines MUST begin with TAB character (not spaces).
Alternative prefix:
.RECIPEPREFIX := >
target:
> echo "Using > as prefix"Recipe Execution Model
Each line runs in a separate shell:
# Each line is independent shell
target:
cd subdir
pwd # Still in original directory!
# Combine with && or \
target:
cd subdir && pwd
target:
cd subdir; \
pwd; \
make.ONESHELL Mode
Run entire recipe in single shell:
.ONESHELL:
target:
cd subdir
pwd # Now in subdir!
for f in *.c; do
echo $$f
doneRecipe Echoing
By default, commands are printed before execution.
# @ suppresses echo
target:
@echo "This prints, but command doesn't"
# -s / --silent flag suppresses all echoing
# make -s target
# .SILENT target
.SILENT: quiet-target
quiet-target:
echo "Command not shown"Error Handling
Default: Stop on Error
Make stops if any command returns non-zero:
target:
command1 # If fails, stops here
command2 # Never runs if command1 failsIgnore Errors
# - prefix ignores error
clean:
-rm -f *.o # Continue even if no .o files
# -i flag ignores all errors
# make -i target
# .IGNORE target
.IGNORE: risky-targetContinue on Error
# -k / --keep-going: continue other targets
# make -k allShell Selection
Default shell is /bin/sh:
SHELL := /bin/bash
# Enable bash features
target:
[[ -f file.txt ]] && echo "exists"Shell Flags
.SHELLFLAGS := -ec
# -e: exit on first error
# -c: execute string (required)Parallel Execution
# Run with -j flag
# make -j4 # 4 parallel jobs
# make -j # Unlimited parallel
# Control parallelism in Makefile
.NOTPARALLEL: # Disable for entire make
.NOTPARALLEL: target # Disable for target's prereqs
# Force order with .WAIT
all: setup .WAIT build .WAIT testParallel Output
# Output synchronization (GNU Make 4.0+)
# make -j4 -O # Group output by target
# make -j4 -Oline # Group by lineRecursive Make
Call make from make:
# Always use $(MAKE), not "make"
subsystem:
$(MAKE) -C subdir
# Equivalent
subsystem:
cd subdir && $(MAKE)
# Pass variables
subsystem:
$(MAKE) -C subdir CFLAGS="$(CFLAGS)"Export Variables to Sub-make
export CFLAGS
export CC
# Or export all
.EXPORT_ALL_VARIABLES:
subsystem:
$(MAKE) -C subdirMAKEFLAGS
Flags automatically passed to sub-makes:
# -j, -k, -s, etc. are inherited
# Add flags
MAKEFLAGS += --no-print-directoryCanned Recipes
Reusable recipe blocks:
define compile-cmd
$(CC) $(CFLAGS) -c $< -o $@
endef
%.o: %.c
$(compile-cmd)With multiple commands:
define run-tests =
echo "Running tests..."
pytest tests/
echo "Done"
endef
test:
$(run-tests)Variables in Recipes
Make variables vs shell variables:
target:
echo $(MAKE_VAR) # Make variable
echo $$SHELL_VAR # Shell variable
echo $$HOME # Environment variable
# Loop with shell variable
target:
for i in 1 2 3; do \
echo $$i; \
doneEmpty Recipes
Explicitly do nothing:
target: ;
# Or
target:
@: # : is shell no-opUse to:
- Prevent implicit rule search
- Override inherited recipes
- Mark file as always up-to-date
Common Recipe Patterns
Check command exists
require-docker:
@command -v docker >/dev/null 2>&1 || \
(echo "Error: docker not found" && exit 1)Conditional execution
target:
@if [ -f config.mk ]; then \
echo "Config found"; \
else \
echo "Using defaults"; \
fiLoop over files
process-all:
@for f in $(SOURCES); do \
echo "Processing $$f"; \
process $$f; \
doneCreate directory if missing
$(BUILDDIR)/%.o: %.c | $(BUILDDIR)
$(CC) -c $< -o $@
$(BUILDDIR):
mkdir -p $@Atomic file creation
# Write to temp, then move (safe update)
output.txt: input.txt
process $< > $@.tmp
mv $@.tmp $@Recipe Debugging
# Dry run - show commands without executing
# make -n target
# Print database
# make -p
# Debug mode
# make -d target
# make --debug=all targetBest Practices
1. Use `@` for cosmetic echo - Don't show echo commands 2. Use `-` sparingly - Only for truly optional commands 3. Use `$(MAKE)` for recursion - Preserves flags 4. Combine commands with `&&` - Fail fast 5. Use `.ONESHELL` for complex scripts - Or move to external script 6. Export only needed variables - Don't pollute environment 7. Test recipes with `-n` - Verify before running
Makefile Syntax Reference
Source: GNU Make Manual - Writing Rules
Rule Structure
Basic rule format:
target … : prerequisites …
recipe
…Alternative with semicolon:
target : prerequisites ; recipe
recipe
…Critical: Recipe lines MUST start with a TAB character, not spaces.
Targets
- Usually file names to be generated (e.g.,
main.o,program) - Can be action names (e.g.,
clean,install) - First target in makefile becomes default goal
- Targets starting with
.are not default unless contain/
Multiple targets in one rule:
# Independent targets (each built separately)
prog1 prog2 : utils.o
cc -o $@ $^
# Grouped targets (recipe creates all at once)
foo.h foo.c &: foo.y
bison -d $<Prerequisites
Normal prerequisites - trigger rebuild if newer than target:
main.o : main.c defs.hOrder-only prerequisites - must exist, but don't trigger rebuild:
$(OBJDIR)/%.o : %.c | $(OBJDIR)
$(CC) -c $< -o $@
$(OBJDIR):
mkdir -p $@Syntax: target : normal-prereqs | order-only-prereqs
Prerequisite Types Summary
| Type | Triggers Rebuild? | Use Case |
|---|---|---|
Normal (:) | Yes, if newer | Source files, headers |
| Order-only (`\ | `) | No |
Wildcards
Wildcard characters: *, ?, […], ~
# In targets and prerequisites - expanded by Make
clean:
rm -f *.o
print: *.c
lpr -p $?
touch print
# In variables - NOT expanded automatically
objects = *.o # Literal "*.o"
objects := $(wildcard *.o) # Expanded listVPATH - Directory Search
Search directories for prerequisites:
# Search all prerequisites in these dirs
VPATH = src:../headers
# Pattern-specific search
vpath %.c src
vpath %.h ../headers
vpath % foo:bar # Match anything
vpath %.c # Clear pattern search
vpath # Clear all vpathDouble-Colon Rules
Independent rules for same target:
newfile:: file1
recipe1
newfile:: file2
recipe2Each rule executed if its prerequisites are newer.
Static Pattern Rules
Apply pattern to explicit list:
objects = foo.o bar.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@Format: targets : target-pattern: prereq-pattern
Multiple Rules for One Target
Prerequisites merge; only one recipe allowed:
# OK - prerequisites merge
objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h
# ERROR - multiple recipes
foo.o : foo.c
$(CC) -c foo.c
foo.o : foo.c # Warning: overriding recipe
gcc -c foo.cAutomatic Variables
| Variable | Meaning |
|---|---|
$@ | Target file name |
$< | First prerequisite |
$^ | All prerequisites (deduplicated) |
$+ | All prerequisites (with duplicates) |
$? | Prerequisites newer than target |
$* | Stem in pattern rules |
$% | Archive member name |
$(@D), $(@F) | Directory and file parts of $@ |
Line Continuation
# Long lines
objects = main.o foo.o bar.o \
baz.o qux.o
# In recipes (each logical line runs in separate shell)
target:
cd subdir && \
$(MAKE)Comments
# This is a comment
target: prereq # End-of-line comment
# Comments in recipes are passed to shell
recipe:
# Shell sees this comment
echo "hello"Escaping Special Characters
# Dollar sign
price = $$100 # Becomes "$100"
shell_var = $$HOME # Shell variable, not Make variable
# Percent in pattern
files = a%b # Literal % in file name
%.txt: $$(files) # Use secondary expansion
# Hash/pound sign in variable value
hash := \# # Must escape
comment := text$(hash)moreSecondary Expansion
Enable late prerequisite expansion:
.SECONDEXPANSION:
main_SRCS := main.c utils.c
lib_SRCS := lib.c api.c
main lib: $$(patsubst %.c,%.o,$$($$@_SRCS))Best Practices
1. First target = default goal - Make it all or help 2. Use variables for file lists - Easier maintenance 3. Explicit prerequisites - Don't rely on implicit rules alone 4. Group related rules - Organize by feature/component 5. Document with comments - Especially non-obvious dependencies
Makefile Special Targets Reference
Source: GNU Make Manual - Special Built-in Target Names
Phony Targets
Targets that don't represent files:
.PHONY: clean test install all
clean:
rm -rf build/
test:
pytest tests/Why use .PHONY:
1. Prevents confusion with actual files named clean, test, etc. 2. Improves performance (skips file existence check) 3. Recipe always runs when target is requested
# Without .PHONY, if file named "clean" exists,
# "make clean" says "clean is up to date"
# With .PHONY: clean, recipe always runsPhony Target Dependencies
.PHONY: all clean test
# Phony with dependencies
all: prog1 prog2 prog3
# Chained phony targets
cleanall: cleanobj cleandiff
rm program
cleanobj:
rm *.o
cleandiff:
rm *.diffNote: Phoniness is NOT inherited. Prerequisites of phony targets are not automatically phony.
Special Built-in Targets
.PHONY
.PHONY: target1 target2 ...Declare targets that are not files.
.SUFFIXES
.SUFFIXES: # Clear default suffixes
.SUFFIXES: .c .o .h # Set custom suffix listControls old-style suffix rules.
.DEFAULT
.DEFAULT:
@echo "No rule for $@"Recipe for targets with no rules.
.PRECIOUS
.PRECIOUS: %.o intermediate.txtDon't delete these targets on interrupt or as intermediates.
.INTERMEDIATE
.INTERMEDIATE: intermediate.oMark as intermediate (delete after use).
.NOTINTERMEDIATE
.NOTINTERMEDIATE: important.o
.NOTINTERMEDIATE: # All targets not intermediatePrevent intermediate file deletion.
.SECONDARY
.SECONDARY: generated.c
.SECONDARY: # All targets are secondaryLike intermediate but never auto-deleted.
.SECONDEXPANSION
.SECONDEXPANSION:
prog: $$(prog_OBJS)Enable secondary expansion of prerequisites.
.DELETE_ON_ERROR
.DELETE_ON_ERROR:Delete target if recipe fails.
.IGNORE
.IGNORE: cleanup # Ignore errors in cleanup target
.IGNORE: # Ignore all errors (discouraged).LOW_RESOLUTION_TIME
.LOW_RESOLUTION_TIME: dst
dst: src
cp -p src dstFor commands that can't preserve sub-second timestamps.
.SILENT
.SILENT: install # Don't echo install recipe
.SILENT: # Silent mode for all (discouraged).EXPORT_ALL_VARIABLES
.EXPORT_ALL_VARIABLES:Export all variables to sub-processes.
.NOTPARALLEL
.NOTPARALLEL: # Disable parallel for entire make
.NOTPARALLEL: target # Disable parallel for target's prereqs.ONESHELL
.ONESHELL:
target:
cd subdir
pwd # Still in subdir!
makeRun entire recipe in single shell invocation.
.POSIX
.POSIX:Enable POSIX-conforming behavior.
.WAIT
# Force sequential execution in parallel builds
all: dep1 .WAIT dep2 .WAIT dep3Pattern Rules
Define how to build file types:
# Implicit rule: .c to .o
%.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
# Multiple targets (grouped)
%.tab.c %.tab.h: %.y
bison -d $<Pattern Rule Automatic Variables
| Variable | Meaning |
|---|---|
$@ | Target |
$< | First prerequisite |
$^ | All prerequisites |
$* | Stem (matched by %) |
Canceling Implicit Rules
# Cancel built-in .c.o rule
%.o: %.cEmpty recipe with pattern cancels that implicit rule.
Static Pattern Rules
Apply pattern to explicit target list:
objects := foo.o bar.o baz.o
$(objects): %.o: %.c
$(CC) -c $< -o $@Double-Colon Rules
Multiple independent rules for same target:
# Each rule runs if its prereqs are newer
file.txt:: source1.txt
cat source1.txt >> file.txt
file.txt:: source2.txt
cat source2.txt >> file.txtEmpty Targets (Timestamps)
Record when action was last performed:
print: *.c
lpr -p $?
touch printForce Targets
Always out-of-date:
clean: FORCE
rm *.o
FORCE:
# Equivalent to:
.PHONY: clean
clean:
rm *.oTarget-Specific Variables
debug: CFLAGS += -g -DDEBUG
debug: all
release: CFLAGS += -O3 -DNDEBUG
release: all
# Pattern-specific
%.debug.o: CFLAGS += -gCommon Target Conventions
| Target | Purpose |
|---|---|
all | Build everything (usually default) |
clean | Remove generated files |
install | Install to system |
uninstall | Remove from system |
test | Run tests |
check | Alias for test |
dist | Create distribution archive |
distclean | Clean + remove configure artifacts |
help | Show available targets |
Best Practices
1. Always use .PHONY for non-file targets 2. Make `help` or `all` the default (first target) 3. Group .PHONY declarations at top of file 4. Use standard target names for common operations 5. Document targets with ## comment for help generation
.PHONY: all clean test install help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "%-15s %s\n", $$1, $$2}'Makefile Variables Reference
Source: GNU Make Manual - How to Use Variables
Variable Reference Syntax
$(variable) # Standard form
${variable} # Also valid
$x # Single-character variable (discouraged)Assignment Operators
Recursive (=) - Lazy Evaluation
Expanded each time referenced:
foo = $(bar)
bar = hello
# $(foo) expands to "hello"
bar = world
# $(foo) now expands to "world"Use for: Dynamic values, computed at use time.
Warning: Can cause infinite loops:
CFLAGS = $(CFLAGS) -Wall # ERROR: infinite recursionSimply Expanded (:= or ::=) - Immediate Evaluation
Expanded once at definition:
x := foo
y := $(x) bar
# y is "foo bar"
x := later
# y is still "foo bar"Use for: Constants, one-time computations, avoiding recursion.
Immediately Expanded (:::=) - GNU Make 4.4+
Like := but in POSIX mode:
x :::= $(shell date)Conditional (?=) - Set If Undefined
Only sets if variable has no value:
CC ?= gcc # Use gcc unless CC already set
DEBUG ?= 0 # Default to 0Use for: Defaults, allowing command-line overrides.
Append (+=)
Add to existing value:
CFLAGS := -Wall
CFLAGS += -O2
# CFLAGS is "-Wall -O2"Behavior depends on original assignment type:
- If originally
=, append uses= - If originally
:=, append uses:=
Shell Assignment (!=)
Assign command output:
hash != git rev-parse --short HEAD
# Same as: hash := $(shell git rev-parse --short HEAD)Expansion Timing Summary
| Operator | When Expanded | Typical Use |
|---|---|---|
= | Each use | Dynamic values, late binding |
:= | Definition time | Constants, one-time shell calls |
?= | Definition (if unset) | Overridable defaults |
+= | Depends on original | Building up lists |
!= | Definition time | Shell command output |
Multi-Line Variables
define run-tests =
echo "Running tests..."
pytest tests/
echo "Tests complete"
endef
test:
$(run-tests)To prevent expansion, use $$:
define script
for i in 1 2 3; do \
echo $$i; \
done
endefUndefining Variables
foo := bar
undefine foo
# $(foo) is now emptyVariable Scope
Global Variables
Default scope - accessible everywhere:
CC := gccTarget-Specific Variables
Set for one target and its prerequisites:
debug: CFLAGS += -g -DDEBUG
debug: all
release: CFLAGS += -O3
release: allPattern-Specific Variables
Set for targets matching pattern:
%.o: CFLAGS += -MMDPrivate Variables
Don't inherit to prerequisites:
all: private CFLAGS := -Wall
all: prog
# prog does NOT see CFLAGS from allEnvironment Variables
Make reads environment variables as defaults:
# $HOME from environment if not set in Makefile
homedir := $(HOME)
# Override environment (use -e to reverse)
PATH := /custom/bin:$(PATH)Command-line overrides everything:
make CFLAGS="-O3" # Overrides Makefile and environmentExporting Variables
Pass to sub-makes and shell commands:
export PYTHONPATH := $(PWD)/src
export DATABASE_URL
# Export all variables
.EXPORT_ALL_VARIABLES:Unexport:
unexport CDPATHOverride Directive
Allow Makefile to override command-line:
override CFLAGS += -Wall # Add even if CFLAGS set on command lineSubstitution References
Replace suffix in variable:
sources := foo.c bar.c
objects := $(sources:.c=.o)
# objects is "foo.o bar.o"
# Pattern form
$(sources:%.c=%.o)Computed Variable Names
Variable name from variable:
prog_srcs := main.c utils.c
lib_srcs := lib.c api.c
# Single indirection
sources := $($(target)_srcs)
# Example usage
target := prog
# $(sources) expands to main.c utils.cSpecial Variables
| Variable | Meaning |
|---|---|
MAKEFILE_LIST | List of makefiles being parsed |
MAKEFLAGS | Flags passed to make |
.DEFAULT_GOAL | Target if none specified |
.RECIPEPREFIX | Recipe line prefix (default: TAB) |
.VARIABLES | All defined variable names |
.FEATURES | List of make features |
CURDIR | Current working directory |
MAKE | Path to make executable |
MAKELEVEL | Recursion depth |
MAKECMDGOALS | Command line goals |
Automatic Variables
See syntax.md for complete list.
Best Practices
1. Use `:=` by default - Predictable, efficient 2. Use `?=` for overridable settings - CLI flexibility 3. Avoid recursive `=` unless needed - Prevents surprises 4. Export only what's needed - Don't pollute subprocess environment 5. Document non-obvious variables - Especially computed ones 6. Use UPPERCASE for configuration - DEBUG, VERBOSE 7. Use lowercase for internal - sources, objects