make

Guide for writing idiomatic, maintainable Makefiles following modern best practices. Enforces safety headers (SHELL, .SHELLFLAGS, .DELETE_ON_ERROR), self-documenting help, .PHONY declarations, modular includes. Triggers on "makefile", "Makefile", "make target", "make rule", "make pattern", "make help", "make template", ".mk file", "make include", "make variable", "make phony", "make dependency", "build system", "make clean", "make install", "make test", "make build", "make all", "make default", "task runner", "make automation", "GNU make", "make recipe", "make prerequisite". PROACTIVE: MUST invoke when writing ANY Makefile or *.mk file.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "make" with this command: npx skills add mauromedda/agent-toolkit/mauromedda-agent-toolkit-make

ABOUTME: Make skill for idiomatic, maintainable Makefiles with modular patterns

ABOUTME: Emphasizes safety, self-documentation, modularity, and orchestration focus

Make Skill

Quick Reference

RuleEnforcement
Safety FirstAlways set SHELL, .SHELLFLAGS, .DELETE_ON_ERROR
Self-DocumentingEvery target has ## comment; use ##@ for groups
.PHONYDeclare ALL non-file targets as phony
ExplicitNo magic; clear variable names and dependencies
ModularSplit into *.mk files; use include
OrchestrationMake orchestrates; scripts do complex logic
NamingHyphen-case; verb-noun prefixes (e.g., stack-up)
Help Default.DEFAULT_GOAL := help

🛑 FILE OPERATION CHECKPOINT (BLOCKING)

Before EVERY Write or Edit tool call on a Makefile or *.mk file:

╔══════════════════════════════════════════════════════════════════╗
║  🛑 STOP - MAKE SKILL CHECK                                      ║
║                                                                  ║
║  You are about to modify a Makefile.                             ║
║                                                                  ║
║  QUESTION: Is /make skill currently active?                      ║
║                                                                  ║
║  If YES → Proceed with the edit                                  ║
║  If NO  → STOP! Invoke /make FIRST, then edit                    ║
║                                                                  ║
║  This check applies to:                                          ║
║  ✗ Write tool with file_path containing "Makefile"               ║
║  ✗ Edit tool with file_path containing "Makefile"                ║
║  ✗ Write/Edit with file_path ending in .mk                       ║
║  ✗ ANY Makefile, regardless of conversation topic                ║
║                                                                  ║
║  Examples that REQUIRE this skill:                               ║
║  - "add a build target" (edits Makefile)                         ║
║  - "update the docker targets" (edits make/docker.mk)            ║
║  - "fix the help target" (edits any Makefile)                    ║
╚══════════════════════════════════════════════════════════════════╝

Why this matters: Makefiles without safety headers can fail silently or produce corrupt builds. The skill ensures .DELETE_ON_ERROR and proper .PHONY.

🔄 RESUMED SESSION CHECKPOINT

When a session is resumed from context compaction, verify Makefile development state:

┌─────────────────────────────────────────────────────────────┐
│  SESSION RESUMED - MAKE SKILL VERIFICATION                  │
│                                                             │
│  Before continuing Makefile implementation:                 │
│                                                             │
│  1. Was I in the middle of writing Makefiles?               │
│     → Check summary for "Makefile", "make target", ".mk"    │
│                                                             │
│  2. Did I follow all Make skill guidelines?                 │
│     → Safety headers (SHELL, .SHELLFLAGS, .DELETE_ON_ERROR) │
│     → .PHONY declarations for non-file targets              │
│     → Self-documenting help target                          │
│     → ABOUTME headers on new files                          │
│                                                             │
│  3. Check Makefile quality before continuing:               │
│     → Run: make -n <target> (dry run)                       │
│     → Verify help target works: make help                   │
│                                                             │
│  If implementation was in progress:                         │
│  → Review the partial Makefile for completeness             │
│  → Ensure safety headers are present                        │
│  → Verify no recipes exceed 5 lines (move to scripts)       │
│  → Re-invoke /make if skill context was lost                │
└─────────────────────────────────────────────────────────────┘

When to Use Make

Use Make for orchestration and build tasks:

  • Build automation (compile, link, bundle)
  • Development workflow commands (start, stop, test, lint)
  • Multi-service orchestration (Docker, Terraform)
  • CI/CD pipeline steps
  • Task runners with dependencies

Do NOT use Make for:

  • Complex business logic (use Python, Go, etc.)
  • Scripts requiring conditionals/loops (use Bash scripts)
  • Anything requiring error recovery
  • Configuration management (use dedicated tools)

Size guideline: If a Makefile exceeds ~300 lines, split into modular *.mk files.

Recipe rule: If a recipe exceeds 5 lines or contains if/else, move it to a script and invoke the /bash skill.

Core Principles

1. Safety Headers

Every Makefile starts with strict settings (like Bash's set -euo pipefail):

SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
SettingPurpose
SHELL := bashUse Bash instead of /bin/sh
.SHELLFLAGS-e exit on error, -u error on undefined, -o pipefail
.DELETE_ON_ERRORRemove target if recipe fails (prevents corrupt state)
--warn-undefined-variablesCatch typos in variable names
--no-builtin-rulesDisable implicit rules for clarity

2. Self-Documenting Help System

Every Makefile must have a help target as default:

.DEFAULT_GOAL := help

##@ General
.PHONY: help
help: ## Display this help
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} \
		/^[a-zA-Z_0-9-]+:.*?##/ { printf "  \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
		/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

Documentation syntax:

##@ Section Header        # Creates bold section in help output
target: ## Description    # Documents the target

3. Phony Targets

Declare ALL non-file-producing targets:

.PHONY: help build test clean deploy
.PHONY: docker-up docker-down
.PHONY: logs-%  # Pattern rules too

Why: Prevents conflicts with files named build, test, etc.

4. Explicit Over Implicit

  • Use $(VARIABLE) not $VARIABLE
  • Quote paths: "$(PATH_VAR)"
  • Name variables clearly: DOCKER_COMPOSE_FILES not DCF
  • Avoid automatic variables in complex contexts

5. Modular Design

Split large Makefiles into focused modules:

# Root Makefile
include make/common.mk
include make/docker.mk
include make/test.mk

# Optional includes (won't fail if missing)
-include make/local.mk

See resources/common.mk for a reusable library pattern.

6. Orchestration Focus

Make orchestrates; it doesn't implement:

# GOOD: Make orchestrates
test: ## Run tests
	@./scripts/run-tests.sh

# BAD: Complex logic in Make
test:
	@if [ -f .env ]; then \
		source .env && \
		for dir in $(TEST_DIRS); do \
			cd $$dir && npm test || exit 1; \
		done \
	fi

7. Consistent Naming

Prefixes for semantic grouping:

PrefixPurposeExample
stack-Full application stackstack-up, stack-down
infra-Infrastructure onlyinfra-network, infra-wait
docker-Docker operationsdocker-build, docker-ps
test-Test executiontest-unit, test-e2e
db- or migrate-Database operationsdb-migrate, db-seed

Verbs:

VerbMeaning
up / startCreate and run
down / stopStop and remove
restartStop then start
buildBuild only
rebuildBuild and start
logsStream logs
statusShow current state

Standard Template

Use resources/template.mk as a starter:

cp ~/.claude/skills/make/resources/template.mk ./Makefile

The template includes:

  • Safety headers
  • Color definitions
  • Logging macros
  • Help system
  • Example targets

Variable Patterns

Path Resolution (Portable)

# Get directory containing this Makefile
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))

# Project root (if Makefile is in project root)
PROJECT_ROOT := $(MAKEFILE_DIR)

# Relative paths from Makefile location
SRC_DIR := $(PROJECT_ROOT)src
BUILD_DIR := $(PROJECT_ROOT)build

Default Values

# Use ?= for overridable defaults
COMPOSE_PROJECT_NAME ?= myproject
PORT ?= 8080

# Use := for computed values
TIMESTAMP := $(shell date +%Y%m%d-%H%M%S)
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")

Environment Exports

# Export for child processes
export DOCKER_BUILDKIT := 1
export COMPOSE_DOCKER_CLI_BUILD := 1

# Pass through from environment
export API_KEY
export DATABASE_URL

Conditional Variables

# Makefile conditionals (evaluated at parse time)
ifeq ($(CI),true)
    VERBOSE := 1
endif

ifdef DEBUG
    BUILD_FLAGS += -v
endif

ifndef REQUIRED_VAR
    $(error REQUIRED_VAR is not set)
endif

Macros (define/endef)

Logging Macros

# Color codes
CYAN := \033[0;36m
GREEN := \033[0;32m
YELLOW := \033[1;33m
RED := \033[0;31m
BOLD := \033[1m
NC := \033[0m

define log_info
	@printf "$(CYAN)[INFO]$(NC) %s\n" "$(1)"
endef

define log_success
	@printf "$(GREEN)[OK]$(NC) %s\n" "$(1)"
endef

define log_warn
	@printf "$(YELLOW)[WARN]$(NC) %s\n" "$(1)"
endef

define log_error
	@printf "$(RED)[ERROR]$(NC) %s\n" "$(1)"
endef

define log_step
	@printf "$(BOLD)>>> %s$(NC)\n" "$(1)"
endef

# Usage
build:
	$(call log_step,Building project)
	@go build ./...
	$(call log_success,Build completed)

Utility Macros

# Check if command exists
define check_cmd
	@command -v $(1) >/dev/null 2>&1 || { \
		printf "$(RED)[ERROR]$(NC) $(1) is required but not installed\n"; \
		exit 1; \
	}
endef

# Ensure directory exists
define ensure_dir
	@mkdir -p $(1)
endef

# Confirm dangerous action
define confirm
	@read -p "Are you sure? [y/N] " ans && [ "$${ans:-N}" = "y" ]
endef

# Usage
deploy: ## Deploy to production
	$(call check_cmd,kubectl)
	$(call confirm)
	$(call log_step,Deploying to production)
	@kubectl apply -f manifests/

Pattern Rules

Dynamic Targets with %

# logs-<service> - tail logs for any service
.PHONY: logs-%
logs-%: ## Tail logs for a specific service
	docker compose logs -f $*

# docker-exec-<service> - exec into any container
.PHONY: exec-%
exec-%: ## Execute shell in a container
	docker compose exec $* sh

# build-<component> - build specific component
.PHONY: build-%
build-%:
	$(call log_step,Building $*)
	@cd $* && make build

Automatic variables:

VariableMeaning
$@Target name
$<First prerequisite
$^All prerequisites
$*Stem matched by %

No-op Targets for Arguments

# Allow: make logs backend
# These are filter arguments, not real targets
.PHONY: backend frontend api worker
backend frontend api worker:
	@:

.PHONY: logs
logs: ## Tail logs (usage: make logs <service>)
	@component="$(filter-out $@,$(MAKECMDGOALS))"; \
	if [ -n "$$component" ]; then \
		docker compose logs -f $$component; \
	else \
		docker compose logs -f; \
	fi

Modular Library Pattern

Structure

project/
├── Makefile              # Root: includes modules, defines high-level targets
└── make/
    ├── common.mk         # Shared: variables, colors, logging macros
    ├── docker.mk         # Docker Compose operations
    ├── test.mk           # Test orchestration
    └── app-python.mk     # Language-specific targets

Root Makefile

# Include order matters: common first, then specific
include make/common.mk
include make/docker.mk
include make/test.mk

# Optional local overrides
-include make/local.mk

##@ Stack Management
.PHONY: up
up: docker-up ## Start all services
	$(call log_success,Stack is running)

.PHONY: down
down: docker-down ## Stop all services
	$(call log_success,Stack stopped)

Consumer Pattern (Multi-Repo)

For projects that consume a shared Makefile library:

# Define path to shared infra
INFRA_DIR ?= $(HOME)/projects/shared-infra

# Clone if missing
$(if $(wildcard $(INFRA_DIR)),,$(shell git clone git@github.com:org/shared-infra.git $(INFRA_DIR)))

# Include shared modules
-include $(INFRA_DIR)/make/common.mk
-include $(INFRA_DIR)/make/app.mk
-include $(INFRA_DIR)/make/app-python.mk

See resources/common.mk for a complete reusable library.

Dependency Management

Prerequisite Chains

# Direct dependencies
deploy: build test ## Deploy (requires build and test)
	@./scripts/deploy.sh

# Chain dependencies
clean-all: clean-build clean-test clean-docker

# Order-only prerequisites (directory must exist, but changes don't trigger rebuild)
$(BUILD_DIR)/output: source.c | $(BUILD_DIR)
	gcc -o $@ $<

$(BUILD_DIR):
	mkdir -p $@

Conditional Dependencies

.PHONY: start
start: ## Start services (optionally with migrations)
ifeq ($(MIGRATE),1)
	$(call log_info,Running migrations first)
	@$(MAKE) db-migrate
endif
	@docker compose up -d

Sentinel Files (Complex Dependencies)

For dependencies that don't produce predictable files (like npm install):

# Sentinel file tracks when install was last run
.stamps:
	@mkdir -p .stamps

.stamps/npm-install: package.json package-lock.json | .stamps
	npm ci
	@touch $@

.stamps/pip-install: requirements.txt | .stamps
	pip install -r requirements.txt
	@touch $@

# Depend on sentinel, not directory
build: .stamps/npm-install
	npm run build

clean:
	rm -rf .stamps node_modules

Integration Patterns

Docker Compose

# Compose file configuration
COMPOSE_FILES := -f docker-compose.yml
ifdef CI
    COMPOSE_FILES += -f docker-compose.ci.yml
endif

COMPOSE := docker compose $(COMPOSE_FILES)

.PHONY: docker-up
docker-up: ## Start Docker services
	$(COMPOSE) up -d

.PHONY: docker-down
docker-down: ## Stop Docker services
	$(COMPOSE) down

Terraform

TF_DIR := terraform

.PHONY: tf-init
tf-init: ## Initialize Terraform
	cd $(TF_DIR) && terraform init

.PHONY: tf-plan
tf-plan: ## Plan Terraform changes
	cd $(TF_DIR) && terraform plan -out=tfplan

.PHONY: tf-apply
tf-apply: ## Apply Terraform changes
	$(call confirm)
	cd $(TF_DIR) && terraform apply tfplan

Anti-Patterns

Anti-PatternProblemFix
Spaces instead of tabsRecipes won't runUse tabs for recipe lines
Missing .PHONYTarget skipped if file existsDeclare all non-file targets
Bare rm without -fFails if file missingUse rm -f or rm -rf
Ignoring errors silently@command || true hides failuresHandle errors explicitly
Complex shell logicUnmaintainable, error-proneMove to script; invoke /bash
Recursive make without $(MAKE)Breaks parallelism, optionsUse $(MAKE) -C subdir
Hardcoded pathsNot portableUse variables with defaults
No help targetUsers don't know available commandsAlways provide help
Excessive @ silencingCan't debug issuesOnly silence noise, not errors

Validation

Run the validation script to check Makefile quality:

~/.claude/skills/make/scripts/validate_makefile.sh [Makefile]

The script checks:

  • Safety headers present
  • Help target exists
  • .PHONY declarations
  • Forbidden patterns (complex shell logic, missing error handling)
  • Uses checkmake if available

Checklist

Before committing a Makefile:

  • Safety headers present (SHELL, .SHELLFLAGS, .DELETE_ON_ERROR)
  • .DEFAULT_GOAL := help set
  • help target with awk-based documentation parser
  • All non-file targets declared .PHONY
  • Variables use $(VAR) syntax (not $VAR)
  • Paths are quoted where needed
  • No recipes exceed 5 lines (moved to scripts)
  • No complex shell logic (if/else, loops) in recipes
  • Logging macros used for user feedback
  • Dependencies declared correctly
  • Runs validate_makefile.sh without errors
  • Tested with make -n <target> (dry run)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

web-automation

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

golang

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

terraform

No summary provided by upstream source.

Repository SourceNeeds Review