Skip to main content

makefile-workflow

Makefile best practices for project automation and build systems. Covers command usage, target organization (PHONY vs file targets), variable management (:=, ?=, =), platform detection, common development targets (clean, test, lint, format, run), DevContainer integration, version management with semantic versioning, Docker integration, output control, error handling, and advanced patterns. Activate when working with Makefiles, make commands, .PHONY targets, build automation, or development workflows.

Source facts

Repository
ilude/claude-code-config
Last source activity
November 24, 2025 at 00:24
Detected SKILL.md language
English
Stars
8
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
makefile-workflow
description
Makefile best practices for project automation and build systems. Covers command usage, target organization (PHONY vs file targets), variable management (:=, ?=, =), platform detection, common development targets (clean, test, lint, format, run), DevContainer integration, version management with semantic versioning, Docker integration, output control, error handling, and advanced patterns. Activate when working with Makefiles, make commands, .PHONY targets, build automation, or development workflows.
# Makefile Workflow Guidelines Makefiles provide consistent command interfaces across development, testing, and deployment. They abstract complex commands into simple, memorable targets and enable reproducible builds across platforms. --- ## Overview and Purpose Makefiles serve as a unified interface for project automation: - **Consistency**: Same commands work across different environments - **Documentation**: Help text via grep parsing of `##` comments - **Dependency Management**: Automatic rebuild only when dependencies change - **Platform Abstraction**: Handle OS-specific commands in one place - **Workflow Orchestration**: Chain targets for complex processes (test → lint → deploy) A well-designed Makefile replaces scattered shell scripts and tribal knowledge with explicit, executable documentation. --- ## Basic Structure ### Template Makefile ```makefile # Project Makefile .DEFAULT_GOAL := help # Variables PYTHON := python UV := uv PROJECT_DIR := $(shell pwd) # Phony targets (not files) .PHONY: help install test lint format clean run help: ## Show this help message @echo "Available targets:" @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' install: ## Install dependencies $(UV) sync --extra dev test: ## Run tests $(UV) run pytest -v lint: ## Run linters $(UV) run ruff check . $(UV) run mypy src/ format: ## Format code $(UV) run ruff format . clean: ## Remove generated files find . -type d -name __pycache__ -exec rm -rf {} + find . -type f -name "*.pyc" -delete rm -rf .pytest_cache htmlcov .coverage build dist *.egg-info run: ## Run application $(UV) run python run.py ``` --- ## Command Usage Principles ### Avoid Unnecessary Flags **Only include flags that change behavior or add information:** ```makefile # ✅ Good: Minimal, clear flags test: $(UV) run pytest tests/ -v # ❌ Poor: Redundant flags that add no value test: $(UV) run pytest tests/ -v --tb=short --strict-markers --durations=0 # ✅ Good: Flag only when needed for specific use case test-verbose: ## Run tests with detailed output $(UV) run pytest tests/ -vv ``` **Principle:** Add flags only when they provide value. Keep default commands simple. --- ## Target Organization ### PHONY Targets (Commands) Use `.PHONY` for targets that don't produce files. These are action targets, not build targets: ```makefile .PHONY: test clean install deploy help # PHONY targets don't create files - they run commands test: pytest tests/ clean: rm -rf build/ install: pip install -r requirements.txt help: @echo "Available commands" ``` **Why `.PHONY` matters:** Without it, `make test` fails if a file named `test` exists in your directory. ### File Targets (Dependencies) Use real file targets when building artifacts. Make automatically tracks dependencies and rebuilds only when sources change: ```makefile # Real file target - only rebuilds if source changes build/app: src/main.py src/utils.py mkdir -p build python -m PyInstaller src/main.py -o build/ # Dependency chain dist/app.tar.gz: build/app tar -czf dist/app.tar.gz -C build app # Declaration order: prerequisites first, then target docs/index.html: docs/source/*.md mkdocs build -d docs/index.html ``` **Best Practice:** Use file targets for actual build artifacts, PHONY for development workflows. --- ## Variable Management ### Three Assignment Types Makefiles support three variable assignment styles with different evaluation models: #### Immediate Assignment (`:=`) Evaluated **once**, at definition time. Use for values that never change: ```makefile # Evaluated immediately PYTHON := python UV := uv SRC_DIR := $(shell pwd)/src BUILD_DATE := $(shell date +%Y-%m-%d) # Variables available before next line FULL_PATH := $(SRC_DIR)/main.py ``` **When to use:** Constants, paths that won't change, one-time shell evaluations. #### Conditional Assignment (`?=`) Set value **only if not already set**. Enables environment variable overrides: ```makefile # Default values - can override via CLI or environment ENVIRONMENT ?= development LOG_LEVEL ?= info DATABASE_URL ?= postgresql://localhost/testdb # Usage: # make test ENVIRONMENT=production # Override at CLI # ENVIRONMENT=staging make test # Override via environment ``` **When to use:** Configuration that users might override, sensible defaults. #### Recursive Assignment (`=`) Evaluated **on every use** (late binding). Creates dynamic variables: ```makefile # Evaluated each time it's referenced TIMESTAMP = $(shell date +%Y%m%d-%H%M%S) # This evaluates to current time each time it's used log-target: ## Create timestamped log file @echo "Creating log-$(TIMESTAMP).log" @touch log-$(TIMESTAMP).log ``` **When to use:** Values that change over time, dynamic computations. ### Environment Variables Export variables to subprocesses for tools to access: ```makefile # Export specific variables export DATABASE_URL := postgresql://localhost/testdb export FLASK_ENV := development export PYTHONPATH := $(SRC_DIR) # Or export all variables at once .EXPORT_ALL_VARIABLES: # Now all Make variables available to commands test: $(UV) run pytest tests/ # pytest sees DATABASE_URL, etc. ``` --- ## Platform and Runtime Detection ### Detecting Operating System Make scripts must work across Linux, macOS, and Windows. Use conditional logic: ```makefile # Detect OS UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) PLATFORM := linux OPEN := xdg-open RM := rm -rf endif ifeq ($(UNAME_S),Darwin) PLATFORM := macos OPEN := open RM := rm -rf endif ifeq ($(OS),Windows_NT) PLATFORM := windows OPEN := start RM := rmdir /s /q endif # Use platform-specific variables show-docs: ## Open documentation in browser $(OPEN) htmlcov/index.html ``` ### Detecting Tool Availability Check if required tools exist before using them: ```makefile # Check if command exists HAS_UV := $(shell command -v uv 2>/dev/null) HAS_POETRY := $(shell command -v poetry 2>/dev/null) HAS_DOCKER := $(shell command -v docker 2>/dev/null) install: ifdef HAS_UV uv sync --extra dev else ifdef HAS_POETRY poetry install --with dev else pip install -r requirements-dev.txt endif ``` --- ## Common Development Targets ### Installation and Setup ```makefile .PHONY: install install-dev initialize install: ## Install production dependencies $(UV) sync install-dev: ## Install development dependencies $(UV) sync --extra dev initialize: install-dev ## Initialize development environment mkdir -p logs tmp test -f .env || cp .env.example .env @echo "Development environment initialized!" ``` ### Testing Targets ```makefile .PHONY: test test-unit test-integration test-coverage test: ## Run all tests $(UV) run pytest tests/ -v test-unit: ## Run unit tests only $(UV) run pytest tests/unit/ -v test-integration: ## Run integration tests only $(UV) run pytest tests/integration/ -v test-coverage: ## Run tests with coverage report $(UV) run pytest --cov=app --cov-report=html --cov-report=term @echo "Coverage report: htmlcov/index.html" ``` ### Code Quality Targets ```makefile .PHONY: lint format check lint: ## Run linters $(UV) run ruff check . $(UV) run mypy src/ format: ## Format code $(UV) run ruff format . $(UV) run ruff check --fix . check: format lint test ## Run all quality checks @echo "All checks passed!" ``` ### Cleanup Targets ```makefile .PHONY: clean clean-pyc clean-test clean-build clean: clean-pyc clean-test clean-build ## Remove all generated files clean-pyc: ## Remove Python file artifacts find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true find . -type f -name "*.pyc" -delete find . -type f -name "*.pyo" -delete find . -type f -name "*~" -delete clean-test: ## Remove test and coverage artifacts rm -rf .pytest_cache rm -rf htmlcov rm -rf .coverage rm -rf .mypy_cache rm -rf .ruff_cache clean-build: ## Remove build artifacts rm -rf build/ rm -rf dist/ rm -rf *.egg-info ``` --- ## DevContainer Integration ### Separate DevContainer Makefile Keep DevContainer-specific targets separate in `.devcontainer/Makefile`: ```makefile # Main Makefile -include .devcontainer/Makefile ``` ```makefile # .devcontainer/Makefile .PHONY: dev-setup dev-test dev-lint dev-setup: ## DevContainer-specific setup @echo "Running devcontainer setup..." uv sync --extra dev pre-commit install || true dev-test: ## Run tests in devcontainer context uv run pytest tests/ -v --log-cli-level=DEBUG dev-lint: ## Run linters with devcontainer-specific settings uv run ruff check . uv run mypy src/ --strict ``` **Pattern:** Use `-include` to optionally include environment-specific makefiles without errors if missing. --- ## Version Management with Semantic Versioning ### Automatic Version Detection Extract version from canonical source (package metadata, git tags, or VERSION file): ```makefile # Option 1: From pyproject.toml VERSION := $(shell grep -m1 version pyproject.toml | cut -d'"' -f2) # Option 2: From VERSION file VERSION := $(shell cat VERSION) # Option 3: From git tags VERSION := $(shell git describe --tags --always) # Use version in targets build: ## Build version-stamped release @echo "Building version $(VERSION)" python -m build docker-build: ## Build Docker image with version tag docker build -t myapp:$(VERSION) . ``` ### Version Bumping Create targets to increment semantic versions: ```makefile .PHONY: version-patch version-minor version-major version-patch: ## Increment patch version (x.y.Z) @bash -c ' \ VERSION=$$(grep -m1 version pyproject.toml | cut -d'"' -f2); \ PARTS=($${VERSION//./ }); \ PARTS[2]=$$(($${PARTS[2]} + 1)); \ NEW_VERSION=$${PARTS[0]}.$${PARTS[1]}.$${PARTS[2]}; \ sed -i "s/version = \"$$VERSION\"/version = \"$$NEW_VERSION\"/" pyproject.toml; \ git add pyproject.toml; \ git commit -m "Bump version to $$NEW_VERSION"; \ git tag -a v$$NEW_VERSION -m "Release $$NEW_VERSION"; \ ' version-minor: ## Increment minor version (x.Y.0) @bash -c ' \ VERSION=$$(grep -m1 version pyproject.toml | cut -d'"' -f2); \ PARTS=($${VERSION//./ }); \ PARTS[1]=$$(($${PARTS[1]} + 1)); \ PARTS[2]=0; \ NEW_VERSION=$${PARTS[0]}.$${PARTS[1]}.$${PARTS[2]}; \ sed -i "s/version = \"$$VERSION\"/version = \"$$NEW_VERSION\"/" pyproject.toml; \ git add pyproject.toml; \ git commit -m "Bump version to $$NEW_VERSION"; \ git tag -a v$$NEW_VERSION -m "Release $$NEW_VERSION"; \ ' version-major: ## Increment major version (X.0.0) @bash -c ' \ VERSION=$$(grep -m1 version pyproject.toml | cut -d'"' -f2); \ PARTS=($${VERSION//./ }); \ PARTS[0]=$$(($${PARTS[0]} + 1)); \ PARTS[1]=0; \ PARTS[2]=0; \ NEW_VERSION=$${PARTS[0]}.$${PARTS[1]}.$${PARTS[2]}; \
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub