Back to skills
SkillHub ClubShip Full StackFull Stack
makefile
GNU Make automation and build system guidance
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Stars
10
Hot score
84
Updated
March 20, 2026
Overall rating
C1.9
Composite score
1.9
Best-practice grade
C62.8
Install command
npx @skill-hub/cli install itechmeat-llm-code-makefile
automationbuild-systemmakefilecodingproductivity
Repository
itechmeat/llm-code
Skill path: skills/makefile
GNU Make automation and build system guidance
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: itechmeat.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install makefile into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/itechmeat/llm-code before adding makefile to shared team environments
- Use makefile for development workflows
Works across
Claude CodeCodex CLIGemini CLIOpenCode
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: makefile
description: GNU Make automation and build system guidance
version: 2.0.0
release_date: "2023-02-26"
---
# Makefile Skill
Guidance for creating and maintaining GNU Make build automation.
## Quick Navigation
| Topic | Reference |
| ----------------------------- | --------------------------------------- |
| Rules, prerequisites, targets | [syntax.md](references/syntax.md) |
| Variable types and assignment | [variables.md](references/variables.md) |
| Built-in functions | [functions.md](references/functions.md) |
| Special and phony targets | [targets.md](references/targets.md) |
| Recipe execution, parallel | [recipes.md](references/recipes.md) |
| Implicit and pattern rules | [implicit.md](references/implicit.md) |
| Common practical patterns | [patterns.md](references/patterns.md) |
---
## Core Concepts
### Rule Structure
```makefile
target: prerequisites
recipe
```
**Critical:** Recipe lines MUST start with TAB character.
### File vs Phony Targets
```makefile
# 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 |
```makefile
CC := gcc # Immediate
CFLAGS ?= -O2 # Default, overridable
DEBUG = $(VERBOSE) # Late binding
CFLAGS += -Wall # Append
```
### Automatic Variables
| Variable | Meaning |
| -------- | ------------------------------- |
| `$@` | Target |
| `$<` | First prerequisite |
| `$^` | All prerequisites (unique) |
| `$?` | Prerequisites newer than target |
| `$*` | Stem in pattern rules |
---
## Essential Patterns
### Self-Documenting Help
```makefile
.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 pytest
```
### Platform Detection
```makefile
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
OPEN := open
else ifeq ($(UNAME_S),Linux)
OPEN := xdg-open
endif
```
### Build Directory
```makefile
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
```makefile
export PYTHONPATH := $(PWD)/src
export DATABASE_URL
test:
pytest tests/ # sees exported variables
```
---
## Common Targets
### Quality Checks
```makefile
.PHONY: lint format check test
lint: ## Run linters
ruff check .
mypy src/
format: ## Format code
ruff format .
check: format lint test ## All quality checks
```
### Cleanup
```makefile
.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_cache
```
### Docker Integration
```makefile
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
```makefile
# 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 && \
make
```
### Silent and Error Handling
```makefile
target:
@echo "@ suppresses command echo"
-rm -f maybe.txt # - ignores errors
```
### Parallel Execution
```bash
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.
```makefile
# ❌ 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
```makefile
DEBUG ?= 0
ifeq ($(DEBUG),1)
CFLAGS += -g -O0
else
CFLAGS += -O2
endif
ifdef CI
TEST_FLAGS := --ci
endif
```
---
## Including Files
```makefile
# 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
# 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
```
---
## See Also
- [GNU Make Manual](https://www.gnu.org/software/make/manual/make.html)
- [patterns.md](references/patterns.md) - Extended patterns and recipes
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/syntax.md
```markdown
# Makefile Syntax Reference
> Source: [GNU Make Manual - Writing Rules](https://www.gnu.org/software/make/manual/make.html#Rules)
## Rule Structure
Basic rule format:
```makefile
target … : prerequisites …
recipe
…
```
Alternative with semicolon:
```makefile
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:
```makefile
# 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:
```makefile
main.o : main.c defs.h
```
**Order-only prerequisites** - must exist, but don't trigger rebuild:
```makefile
$(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 | Directories, setup tasks |
## Wildcards
Wildcard characters: `*`, `?`, `[…]`, `~`
```makefile
# 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 list
```
## VPATH - Directory Search
Search directories for prerequisites:
```makefile
# 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 vpath
```
## Double-Colon Rules
Independent rules for same target:
```makefile
newfile:: file1
recipe1
newfile:: file2
recipe2
```
Each rule executed if its prerequisites are newer.
## Static Pattern Rules
Apply pattern to explicit list:
```makefile
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:
```makefile
# 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.c
```
## Automatic 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
```makefile
# 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
```makefile
# 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
```makefile
# 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)more
```
## Secondary Expansion
Enable late prerequisite expansion:
```makefile
.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
```
### references/variables.md
```markdown
# Makefile Variables Reference
> Source: [GNU Make Manual - How to Use Variables](https://www.gnu.org/software/make/manual/make.html#Using-Variables)
## Variable Reference Syntax
```makefile
$(variable) # Standard form
${variable} # Also valid
$x # Single-character variable (discouraged)
```
## Assignment Operators
### Recursive (`=`) - Lazy Evaluation
Expanded each time referenced:
```makefile
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:
```makefile
CFLAGS = $(CFLAGS) -Wall # ERROR: infinite recursion
```
### Simply Expanded (`:=` or `::=`) - Immediate Evaluation
Expanded once at definition:
```makefile
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:
```makefile
x :::= $(shell date)
```
### Conditional (`?=`) - Set If Undefined
Only sets if variable has no value:
```makefile
CC ?= gcc # Use gcc unless CC already set
DEBUG ?= 0 # Default to 0
```
**Use for:** Defaults, allowing command-line overrides.
### Append (`+=`)
Add to existing value:
```makefile
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:
```makefile
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
```makefile
define run-tests =
echo "Running tests..."
pytest tests/
echo "Tests complete"
endef
test:
$(run-tests)
```
To prevent expansion, use `$$`:
```makefile
define script
for i in 1 2 3; do \
echo $$i; \
done
endef
```
## Undefining Variables
```makefile
foo := bar
undefine foo
# $(foo) is now empty
```
## Variable Scope
### Global Variables
Default scope - accessible everywhere:
```makefile
CC := gcc
```
### Target-Specific Variables
Set for one target and its prerequisites:
```makefile
debug: CFLAGS += -g -DDEBUG
debug: all
release: CFLAGS += -O3
release: all
```
### Pattern-Specific Variables
Set for targets matching pattern:
```makefile
%.o: CFLAGS += -MMD
```
### Private Variables
Don't inherit to prerequisites:
```makefile
all: private CFLAGS := -Wall
all: prog
# prog does NOT see CFLAGS from all
```
## Environment Variables
Make reads environment variables as defaults:
```makefile
# $HOME from environment if not set in Makefile
homedir := $(HOME)
# Override environment (use -e to reverse)
PATH := /custom/bin:$(PATH)
```
Command-line overrides everything:
```bash
make CFLAGS="-O3" # Overrides Makefile and environment
```
## Exporting Variables
Pass to sub-makes and shell commands:
```makefile
export PYTHONPATH := $(PWD)/src
export DATABASE_URL
# Export all variables
.EXPORT_ALL_VARIABLES:
```
Unexport:
```makefile
unexport CDPATH
```
## Override Directive
Allow Makefile to override command-line:
```makefile
override CFLAGS += -Wall # Add even if CFLAGS set on command line
```
## Substitution References
Replace suffix in variable:
```makefile
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:
```makefile
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.c
```
## Special 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](syntax.md#automatic-variables) 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`
```
### references/functions.md
```markdown
# Makefile Functions Reference
> Source: [GNU Make Manual - Functions for Transforming Text](https://www.gnu.org/software/make/manual/make.html#Functions)
## Function Call Syntax
```makefile
$(function arguments)
${function arguments}
```
Arguments separated by commas. Spaces after commas are significant.
## String Functions
### subst - Simple Substitution
```makefile
$(subst from,to,text)
# Example
$(subst ee,EE,feet on the street)
# Result: "fEEt on the strEEt"
```
### patsubst - Pattern Substitution
```makefile
$(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
```makefile
$(strip string)
# Example
$(strip a b c )
# Result: "a b c"
```
### findstring - Find Substring
```makefile
$(findstring find,in)
# Returns find if found, empty otherwise
$(findstring a,abc) # "a"
$(findstring x,abc) # ""
```
### filter - Select Matching Words
```makefile
$(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
```makefile
$(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
```makefile
$(sort list)
# Example
$(sort foo bar baz foo bar)
# Result: "bar baz foo"
```
### word - Select Nth Word
```makefile
$(word n,text)
# Example
$(word 2,foo bar baz)
# Result: "bar"
```
### wordlist - Select Word Range
```makefile
$(wordlist start,end,text)
# Example
$(wordlist 2,4,a b c d e f)
# Result: "b c d"
```
### words - Count Words
```makefile
$(words text)
# Example
$(words foo bar baz)
# Result: "3"
```
### firstword / lastword
```makefile
$(firstword text)
$(lastword text)
# Example
$(firstword foo bar baz) # "foo"
$(lastword foo bar baz) # "baz"
```
## File Name Functions
### dir - Directory Part
```makefile
$(dir names...)
# Example
$(dir src/foo.c hacks)
# Result: "src/ ./"
```
### notdir - File Name Part
```makefile
$(notdir names...)
# Example
$(notdir src/foo.c hacks)
# Result: "foo.c hacks"
```
### suffix - File Suffix
```makefile
$(suffix names...)
# Example
$(suffix src/foo.c src-1.0/bar.c hacks)
# Result: ".c .c"
```
### basename - Remove Suffix
```makefile
$(basename names...)
# Example
$(basename src/foo.c src-1.0/bar.c hacks)
# Result: "src/foo src-1.0/bar hacks"
```
### addsuffix - Add Suffix
```makefile
$(addsuffix suffix,names...)
# Example
$(addsuffix .c,foo bar)
# Result: "foo.c bar.c"
```
### addprefix - Add Prefix
```makefile
$(addprefix prefix,names...)
# Example
$(addprefix src/,foo bar)
# Result: "src/foo src/bar"
```
### join - Pairwise Join
```makefile
$(join list1,list2)
# Example
$(join a b,1 2 3)
# Result: "a1 b2 3"
```
### wildcard - Glob Expansion
```makefile
$(wildcard pattern)
# Example
$(wildcard *.c) # All .c files
$(wildcard src/*.c) # All .c in src/
$(wildcard src/*/*.c) # .c in src subdirs
```
### realpath / abspath
```makefile
$(realpath names...) # Canonical path (resolves symlinks)
$(abspath names...) # Absolute path (no symlink resolution)
```
## Conditional Functions
### if - Conditional
```makefile
$(if condition,then-part)
$(if condition,then-part,else-part)
# Example
$(if $(DEBUG),debug-flags,release-flags)
```
### or - First Non-Empty
```makefile
$(or condition1,condition2,...)
# Example
CC := $(or $(CC),gcc) # Use CC if set, else gcc
```
### and - All Non-Empty
```makefile
$(and condition1,condition2,...)
# Example
$(and $(CC),$(CFLAGS),ready)
# "ready" only if both CC and CFLAGS set
```
## Loop Functions
### foreach - Iterate Over List
```makefile
$(foreach var,list,text)
# Example
dirs := a b c
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*.c))
```
### call - User-Defined Functions
```makefile
$(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+)
```makefile
$(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
```makefile
$(shell command)
# Example
git_hash := $(shell git rev-parse --short HEAD)
today := $(shell date +%Y-%m-%d)
```
### eval - Parse as Makefile
```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
```makefile
$(value variable)
# Example
FOO = $$PATH
# $(FOO) expands $PATH
# $(value FOO) returns "$$PATH"
```
### origin - Variable Origin
```makefile
$(origin variable)
# Returns: undefined, default, environment, environment override,
# file, command line, override, automatic
```
### flavor - Variable Type
```makefile
$(flavor variable)
# Returns: undefined, recursive, simple
```
## Control Functions
### error - Abort with Message
```makefile
$(error text)
# Example
ifndef JAVA_HOME
$(error JAVA_HOME is not set)
endif
```
### warning - Print Warning
```makefile
$(warning text)
# Example
ifeq ($(DEBUG),1)
$(warning Building in debug mode)
endif
```
### info - Print Message
```makefile
$(info text)
# Example
$(info Building version $(VERSION))
```
## File Function
Read/write files directly:
```makefile
# Read file
$(file <filename)
# Write to file (overwrite)
$(file >filename,text)
# Append to file
$(file >>filename,text)
```
## Common Patterns
### Building file lists
```makefile
sources := $(wildcard src/*.c)
objects := $(patsubst src/%.c,build/%.o,$(sources))
```
### Conditional compilation flags
```makefile
CFLAGS := -Wall
CFLAGS += $(if $(DEBUG),-g -O0,-O2)
```
### Dynamic rule generation
```makefile
PROGRAMS := prog1 prog2
define make-program
$(1): $($(1)_OBJS)
$$(CC) -o $$@ $$^
endef
$(foreach prog,$(PROGRAMS),$(eval $(call make-program,$(prog))))
```
### Safe file existence check
```makefile
ifneq ($(wildcard config.mk),)
include config.mk
endif
```
```
### references/targets.md
```markdown
# Makefile Special Targets Reference
> Source: [GNU Make Manual - Special Built-in Target Names](https://www.gnu.org/software/make/manual/make.html#Special-Targets)
## Phony Targets
Targets that don't represent files:
```makefile
.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
```makefile
# Without .PHONY, if file named "clean" exists,
# "make clean" says "clean is up to date"
# With .PHONY: clean, recipe always runs
```
### Phony Target Dependencies
```makefile
.PHONY: all clean test
# Phony with dependencies
all: prog1 prog2 prog3
# Chained phony targets
cleanall: cleanobj cleandiff
rm program
cleanobj:
rm *.o
cleandiff:
rm *.diff
```
**Note:** Phoniness is NOT inherited. Prerequisites of phony targets are not automatically phony.
## Special Built-in Targets
### .PHONY
```makefile
.PHONY: target1 target2 ...
```
Declare targets that are not files.
### .SUFFIXES
```makefile
.SUFFIXES: # Clear default suffixes
.SUFFIXES: .c .o .h # Set custom suffix list
```
Controls old-style suffix rules.
### .DEFAULT
```makefile
.DEFAULT:
@echo "No rule for $@"
```
Recipe for targets with no rules.
### .PRECIOUS
```makefile
.PRECIOUS: %.o intermediate.txt
```
Don't delete these targets on interrupt or as intermediates.
### .INTERMEDIATE
```makefile
.INTERMEDIATE: intermediate.o
```
Mark as intermediate (delete after use).
### .NOTINTERMEDIATE
```makefile
.NOTINTERMEDIATE: important.o
.NOTINTERMEDIATE: # All targets not intermediate
```
Prevent intermediate file deletion.
### .SECONDARY
```makefile
.SECONDARY: generated.c
.SECONDARY: # All targets are secondary
```
Like intermediate but never auto-deleted.
### .SECONDEXPANSION
```makefile
.SECONDEXPANSION:
prog: $$(prog_OBJS)
```
Enable secondary expansion of prerequisites.
### .DELETE_ON_ERROR
```makefile
.DELETE_ON_ERROR:
```
Delete target if recipe fails.
### .IGNORE
```makefile
.IGNORE: cleanup # Ignore errors in cleanup target
.IGNORE: # Ignore all errors (discouraged)
```
### .LOW_RESOLUTION_TIME
```makefile
.LOW_RESOLUTION_TIME: dst
dst: src
cp -p src dst
```
For commands that can't preserve sub-second timestamps.
### .SILENT
```makefile
.SILENT: install # Don't echo install recipe
.SILENT: # Silent mode for all (discouraged)
```
### .EXPORT_ALL_VARIABLES
```makefile
.EXPORT_ALL_VARIABLES:
```
Export all variables to sub-processes.
### .NOTPARALLEL
```makefile
.NOTPARALLEL: # Disable parallel for entire make
.NOTPARALLEL: target # Disable parallel for target's prereqs
```
### .ONESHELL
```makefile
.ONESHELL:
target:
cd subdir
pwd # Still in subdir!
make
```
Run entire recipe in single shell invocation.
### .POSIX
```makefile
.POSIX:
```
Enable POSIX-conforming behavior.
### .WAIT
```makefile
# Force sequential execution in parallel builds
all: dep1 .WAIT dep2 .WAIT dep3
```
## Pattern Rules
Define how to build file types:
```makefile
# 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
```makefile
# Cancel built-in .c.o rule
%.o: %.c
```
Empty recipe with pattern cancels that implicit rule.
## Static Pattern Rules
Apply pattern to explicit target list:
```makefile
objects := foo.o bar.o baz.o
$(objects): %.o: %.c
$(CC) -c $< -o $@
```
## Double-Colon Rules
Multiple independent rules for same target:
```makefile
# 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.txt
```
## Empty Targets (Timestamps)
Record when action was last performed:
```makefile
print: *.c
lpr -p $?
touch print
```
## Force Targets
Always out-of-date:
```makefile
clean: FORCE
rm *.o
FORCE:
# Equivalent to:
.PHONY: clean
clean:
rm *.o
```
## Target-Specific Variables
```makefile
debug: CFLAGS += -g -DDEBUG
debug: all
release: CFLAGS += -O3 -DNDEBUG
release: all
# Pattern-specific
%.debug.o: CFLAGS += -g
```
## Common 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
```makefile
.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}'
```
```
### references/recipes.md
```markdown
# Makefile Recipe Execution Reference
> Source: [GNU Make Manual - Writing Recipes in Rules](https://www.gnu.org/software/make/manual/make.html#Recipes)
## Recipe Basics
Recipes are shell commands that update targets.
```makefile
target: prerequisites
command1
command2
```
**Critical:** Recipe lines MUST begin with TAB character (not spaces).
Alternative prefix:
```makefile
.RECIPEPREFIX := >
target:
> echo "Using > as prefix"
```
## Recipe Execution Model
Each line runs in a **separate shell**:
```makefile
# 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:
```makefile
.ONESHELL:
target:
cd subdir
pwd # Now in subdir!
for f in *.c; do
echo $$f
done
```
## Recipe Echoing
By default, commands are printed before execution.
```makefile
# @ 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:
```makefile
target:
command1 # If fails, stops here
command2 # Never runs if command1 fails
```
### Ignore Errors
```makefile
# - 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-target
```
### Continue on Error
```makefile
# -k / --keep-going: continue other targets
# make -k all
```
## Shell Selection
Default shell is `/bin/sh`:
```makefile
SHELL := /bin/bash
# Enable bash features
target:
[[ -f file.txt ]] && echo "exists"
```
### Shell Flags
```makefile
.SHELLFLAGS := -ec
# -e: exit on first error
# -c: execute string (required)
```
## Parallel Execution
```makefile
# 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 test
```
### Parallel Output
```makefile
# Output synchronization (GNU Make 4.0+)
# make -j4 -O # Group output by target
# make -j4 -Oline # Group by line
```
## Recursive Make
Call make from make:
```makefile
# 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
```makefile
export CFLAGS
export CC
# Or export all
.EXPORT_ALL_VARIABLES:
subsystem:
$(MAKE) -C subdir
```
### MAKEFLAGS
Flags automatically passed to sub-makes:
```makefile
# -j, -k, -s, etc. are inherited
# Add flags
MAKEFLAGS += --no-print-directory
```
## Canned Recipes
Reusable recipe blocks:
```makefile
define compile-cmd
$(CC) $(CFLAGS) -c $< -o $@
endef
%.o: %.c
$(compile-cmd)
```
With multiple commands:
```makefile
define run-tests =
echo "Running tests..."
pytest tests/
echo "Done"
endef
test:
$(run-tests)
```
## Variables in Recipes
Make variables vs shell variables:
```makefile
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; \
done
```
## Empty Recipes
Explicitly do nothing:
```makefile
target: ;
# Or
target:
@: # : is shell no-op
```
Use to:
- Prevent implicit rule search
- Override inherited recipes
- Mark file as always up-to-date
## Common Recipe Patterns
### Check command exists
```makefile
require-docker:
@command -v docker >/dev/null 2>&1 || \
(echo "Error: docker not found" && exit 1)
```
### Conditional execution
```makefile
target:
@if [ -f config.mk ]; then \
echo "Config found"; \
else \
echo "Using defaults"; \
fi
```
### Loop over files
```makefile
process-all:
@for f in $(SOURCES); do \
echo "Processing $$f"; \
process $$f; \
done
```
### Create directory if missing
```makefile
$(BUILDDIR)/%.o: %.c | $(BUILDDIR)
$(CC) -c $< -o $@
$(BUILDDIR):
mkdir -p $@
```
### Atomic file creation
```makefile
# Write to temp, then move (safe update)
output.txt: input.txt
process $< > [email protected]
mv [email protected] $@
```
## Recipe Debugging
```makefile
# Dry run - show commands without executing
# make -n target
# Print database
# make -p
# Debug mode
# make -d target
# make --debug=all target
```
## Best 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
```
### references/implicit.md
```markdown
# Makefile Implicit Rules Reference
> Source: [GNU Make Manual - Using Implicit Rules](https://www.gnu.org/software/make/manual/make.html#Implicit-Rules)
## What Are Implicit Rules?
Built-in rules that make knows how to build common file types:
```makefile
# 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 -c
```
## Common Built-in Rules
### C Compilation
```makefile
# .c → .o
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# .c → executable (single file)
%: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@
```
### C++ Compilation
```makefile
# .cc/.cpp/.C → .o
%.o: %.cc
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
```
### Other Languages
```makefile
# Fortran: .f → .o
# Pascal: .p → .o
# Assembly: .s → .o
# Yacc: .y → .c
# Lex: .l → .c
```
## Variables 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
```makefile
CC := gcc
CFLAGS := -Wall -O2
CPPFLAGS := -I./include -DDEBUG
LDFLAGS := -L./lib
LDLIBS := -lm -lpthread
```
## Pattern Rules
Define custom implicit rules:
```makefile
# 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
```makefile
# Rule: %.o: %.c
# Target: src/foo.o
# $* = src/foo (the stem)
# Looking for: src/foo.c
```
## Static Pattern Rules
Apply pattern to explicit list of targets:
```makefile
objects := foo.o bar.o baz.o
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
```
Advantage: More control, faster matching, no accidental matches.
### With Filter
```makefile
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:
```makefile
# Double-suffix: .c → .o
.c.o:
$(CC) -c $<
# Single-suffix: anything → .c
.c:
$(CC) $< -o $@
# Define suffixes
.SUFFIXES: .c .o .h
```
## Implicit 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
```makefile
# Cancel specific implicit rule
%.o: %.c
# Cancel all built-in rules
.SUFFIXES:
MAKEFLAGS += -r
```
## Chains of Implicit Rules
Make can chain rules:
```makefile
# 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
```makefile
# Explicitly mark as intermediate
.INTERMEDIATE: generated.c
# Keep intermediate (don't delete)
.SECONDARY: generated.c
.SECONDARY: # Keep all
```
## Match-Anything Rules
Pattern with just `%`:
```makefile
# Last-resort rule
%:
@echo "Don't know how to make $@"
# Terminal rule (can't chain further)
%:: default-recipe
```
## Automatic Prerequisites
Generate dependencies automatically:
```makefile
# Generate .d files with gcc
%.d: %.c
$(CC) -MM $(CPPFLAGS) $< > $@
# Include generated dependencies
-include $(sources:.c=.d)
```
Modern approach:
```makefile
CFLAGS += -MMD -MP
-include $(objects:.o=.d)
```
## Common Custom Rules
### Documentation
```makefile
%.html: %.md
pandoc -o $@ $<
%.pdf: %.tex
pdflatex $<
```
### Archive Files
```makefile
%.tar.gz: %
tar czf $@ $<
%.zip: %
zip -r $@ $<
```
### Code Generation
```makefile
%.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
```makefile
# 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)
```
```
### references/patterns.md
```markdown
# Common Makefile Patterns
> Practical patterns for modern development workflows.
## Project Setup Pattern
```makefile
# 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
```makefile
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
endif
```
## Tool Detection
```makefile
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
```makefile
# 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
```makefile
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 .coverage
```
## Node.js Project
```makefile
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 dist
```
## Docker Patterns
```makefile
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):latest
```
## Build Directory Pattern
```makefile
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
```makefile
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=production
```
## Verbose/Quiet Mode
```makefile
V ?= 0
ifeq ($(V),0)
Q := @
VFLAG :=
else
Q :=
VFLAG := -v
endif
compile:
$(Q)$(CC) $(CFLAGS) -c $< -o $@
# Usage: make compile V=1
```
## Parallel Quality Checks
```makefile
.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
```makefile
.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
```makefile
# 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
```makefile
# 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.sh
```
## File Generation Pattern
```makefile
# Generate file if missing
.env:
cp .env.example .env
@echo "Created .env - please configure"
# Always regenerate
.PHONY: config
config:
envsubst < config.template > config.yaml
```
## Include Pattern
```makefile
# 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
endif
```
## Archive/Release Pattern
```makefile
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)
```makefile
.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 test
```
## Output Discipline
```makefile
# ❌ 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"
```
```