Makefile patterns for development workflows. Use when creating or understanding Makefiles for build automation, release workflows, and development iteration.
Install
npx skillscat add thrashr888/thrashr888-agent-kit/makefile-patterns Install via the SkillsCat registry.
SKILL.md
Makefile Patterns
Common Makefile patterns for development, building, testing, and releasing.
Basic Structure
# Variables at top
PROJECT := myproject
VERSION := $(shell cat VERSION 2>/dev/null || echo "0.0.0")
BUILD_DIR := ./build
# Default target (first target)
.PHONY: help
help:
@echo "Available targets:"
@echo " build - Build the project"
@echo " test - Run tests"
@echo " clean - Clean build artifacts"
# Actual targets
.PHONY: build test clean
build:
@echo "Building $(PROJECT) $(VERSION)..."
test:
@echo "Running tests..."
clean:
rm -rf $(BUILD_DIR)Variable Patterns
Environment with Defaults
# Use env var if set, otherwise default
CONFIG ?= Release
PORT ?= 8080
# Computed from shell
VERSION := $(shell git describe --tags --always 2>/dev/null || echo "dev")
GIT_SHA := $(shell git rev-parse --short HEAD)
BUILD_TIME := $(shell date -u +%Y%m%dT%H%M%SZ)Conditional Variables
# Platform-specific
ifeq ($(shell uname),Darwin)
OS := macos
SED := sed -i ''
else
OS := linux
SED := sed -i
endifHelp Target
Self-Documenting Help
.PHONY: help
help:
@echo "Development:"
@echo " dev - Start development server"
@echo " build - Build for production"
@echo " test - Run tests"
@echo ""
@echo "Release:"
@echo " release - Full release workflow"
@echo " tag - Create git tag"
@echo ""
@echo "Utilities:"
@echo " clean - Clean build artifacts"
@echo " version - Show current version"Grep-Based Help (auto-generates from comments)
.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
build: ## Build the project
...
test: ## Run tests
...Development Patterns
Dev Loop (Kill, Build, Run)
APP_NAME := MyApp
APP := $(BUILD_DIR)/$(APP_NAME).app
.PHONY: dev kill run
dev: kill build run ## Quick dev iteration
kill: ## Stop running app
@echo "Stopping $(APP_NAME)..."
@pkill -x "$(APP_NAME)" 2>/dev/null || true
@sleep 0.5
run: ## Run the app
@echo "Starting $(APP_NAME)..."
@open $(APP)Watch Mode
.PHONY: watch
watch: ## Watch for changes and rebuild
@echo "Watching for changes..."
@fswatch -o src/ | xargs -n1 -I{} make buildBuild Patterns
Rust Build
.PHONY: build build-release
build: ## Debug build
cargo build
build-release: ## Release build
cargo build --release
check: ## Fast syntax check
cargo check
fmt: ## Format code
cargo fmt
lint: ## Lint with clippy
cargo clippy -- -D warnings
test: ## Run tests
cargo test
# Quality gate (run before commits)
.PHONY: qa
qa: fmt lint test ## Run all quality checksXcode Build
DEVELOPER_DIR ?= /Applications/Xcode.app/Contents/Developer
PROJECT := ./MyApp.xcodeproj
SCHEME := MyApp
CONFIG ?= Debug
BUILD_DIR := ./build
DEST := platform=macOS
.PHONY: build
build: deps ## Build the app
DEVELOPER_DIR=$(DEVELOPER_DIR) xcodebuild -project $(PROJECT) \
-scheme $(SCHEME) -configuration $(CONFIG) \
-derivedDataPath $(BUILD_DIR) -destination '$(DEST)' \
build | cat
deps: ## Resolve dependencies
DEVELOPER_DIR=$(DEVELOPER_DIR) xcodebuild -resolvePackageDependencies \
-project $(PROJECT) | cat
archive: ## Create release archive
DEVELOPER_DIR=$(DEVELOPER_DIR) xcodebuild -project $(PROJECT) \
-scheme $(SCHEME) -configuration Release \
-archivePath $(BUILD_DIR)/$(SCHEME).xcarchive \
archive | catPython/uv Build
.PHONY: install test lint fmt
install: ## Install dependencies
uv sync --dev
test: ## Run tests
uv run pytest
lint: ## Run linter
uv run ruff check .
fmt: ## Format code
uv run black .
uv run isort .Release Patterns
DMG Creation (macOS)
DMG_NAME := MyApp
DMG_DIR := $(BUILD_DIR)/dmg
DMG_FILE := $(BUILD_DIR)/$(DMG_NAME).dmg
EXPORTED_APP := $(HOME)/Downloads/MyApp.app
.PHONY: dmg
dmg: ## Create DMG installer
@test -d "$(EXPORTED_APP)" || \
(echo "Error: Export app from Xcode first" && exit 1)
rm -rf $(DMG_DIR) $(DMG_FILE)
mkdir -p $(DMG_DIR)
cp -R $(EXPORTED_APP) $(DMG_DIR)/
create-dmg \
--volname "$(DMG_NAME)" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "MyApp.app" 150 190 \
--app-drop-link 450 190 \
$(DMG_FILE) $(DMG_DIR)SHA256 and Upload
SHA256_FILE := $(BUILD_DIR)/MyApp.dmg.sha256
S3_BUCKET := my-bucket
.PHONY: sha256 upload
sha256: dmg ## Generate SHA256
shasum -a 256 $(DMG_FILE) | awk '{print $$1}' > $(SHA256_FILE)
@echo "SHA256: $$(cat $(SHA256_FILE))"
upload: sha256 ## Upload to S3
aws s3 cp $(DMG_FILE) s3://$(S3_BUCKET)/downloads/$(VERSION)/MyApp.dmg --acl public-read
aws s3 cp $(SHA256_FILE) s3://$(S3_BUCKET)/downloads/$(VERSION)/MyApp.dmg.sha256 --acl public-readHomebrew Update
HOMEBREW_TAP := $(HOME)/Workspace/homebrew-myapp
.PHONY: brew-update
brew-update: sha256 ## Update Homebrew tap
@test -d "$(HOMEBREW_TAP)" || (echo "Error: Tap not found" && exit 1)
@SHA=$$(cat $(SHA256_FILE)) && \
$(SED) "s/version \".*\"/version \"$(VERSION)\"/" $(HOMEBREW_TAP)/Casks/myapp.rb && \
$(SED) "s/sha256 \".*\"/sha256 \"$$SHA\"/" $(HOMEBREW_TAP)/Casks/myapp.rb
@echo "Updated tap to $(VERSION)"Git Tag
.PHONY: tag
tag: ## Create git tag
@if git rev-parse "v$(VERSION)" >/dev/null 2>&1; then \
echo "Tag v$(VERSION) already exists"; \
else \
git tag -a "v$(VERSION)" -m "Release $(VERSION)" && \
echo "Created tag v$(VERSION)"; \
fiFull Release
.PHONY: release
release: upload brew-update ## Full release workflow
@echo ""
@echo "=== Release $(VERSION) complete ==="
@echo "Next steps:"
@echo " 1. git commit -m 'Release $(VERSION)' && git push"
@echo " 2. make tag && git push --tags"
@echo " 3. cd $(HOMEBREW_TAP) && git commit -m 'Update to $(VERSION)' && git push"Utility Patterns
Check Prerequisites
.PHONY: check-aws
check-aws: ## Verify AWS credentials
@aws sts get-caller-identity > /dev/null 2>&1 || \
(echo "Error: AWS not configured. Run 'aws configure'" && exit 1)
@echo "AWS credentials OK"Version Display
.PHONY: version
version: ## Show current version
@echo "Version: $(VERSION)"
@echo "Git SHA: $(GIT_SHA)"
@echo "Build: $(BUILD_TIME)"Clean
.PHONY: clean clean-all
clean: ## Clean build artifacts
rm -rf $(BUILD_DIR)
clean-all: clean ## Clean everything including dependencies
rm -rf node_modules .venv __pycache__Anti-Patterns
DON'T:
- Use tabs inconsistently (Makefiles require tabs for recipes)
- Forget
.PHONYfor non-file targets - Use
cdwithout&&(each line runs in new shell) - Hardcode paths that vary by machine
DO:
- Use
?=for overridable defaults - Add
| catto Xcode commands (prevents xcpretty issues) - Quote variables that might have spaces
- Use
@prefix to hide command echo when appropriate