Best practices for creating self-documenting Makefiles with auto-generated help. Use when creating or modifying Makefiles to ensure they follow conventions for discoverability and maintainability.
When creating or modifying Makefiles, follow these principles to ensure they are self-documenting and user-friendly.
helpAlways set help as the default target so users can discover available commands:
.DEFAULT_GOAL := help
This ensures running make without arguments shows available targets instead of executing an arbitrary first target.
The help target should automatically build itself from comments in the Makefile. Use the ## comment pattern:
target-name: ## Description of what this target does
@command here
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
target: ## Description - The ## marks the description for that target##@ Section - Creates optional section headers in the help outputawk command parses these patterns and formats the outputWhen asked to audit a Makefile, check for the following issues and report findings:
.DEFAULT_GOAL := help is sethelp target existshelp target uses the self-documenting awk pattern## Description comments##).PHONY declarations for non-file targetshelp target is not using $(MAKEFILE_LIST) (won't work with includes)Makefile Audit: <path>
PASS: .DEFAULT_GOAL set to help
PASS: help target exists
FAIL: 3 targets missing ## descriptions: build, test, deploy
WARN: Missing .PHONY for: build, test, clean
Summary: 2 passed, 1 failed, 1 warning
Provide specific line numbers and suggested fixes for each issue found.
For one-off shell scripts or binaries that should be available in ~/bin, use this symlink pattern:
SCRIPT_NAME := my-script.sh
INSTALL_DIR := $(HOME)/bin
INSTALL_PATH := $(INSTALL_DIR)/$(SCRIPT_NAME)
SOURCE_PATH := $(CURDIR)/$(SCRIPT_NAME)
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Removing existing symlink..."; \
rm -f "$(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists and is not a symlink"; \
exit 1; \
fi
@ln -s "$(SOURCE_PATH)" "$(INSTALL_PATH)"
@echo "Installed: $(INSTALL_PATH) -> $(SOURCE_PATH)"
uninstall: ## Remove symlink from ~/bin
@if [ -L "$(INSTALL_PATH)" ]; then \
rm -f "$(INSTALL_PATH)"; \
echo "Removed: $(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists but is not a symlink (not removing)"; \
exit 1; \
else \
echo "Nothing to remove: $(INSTALL_PATH) does not exist"; \
fi
check: ## Check installation status
@echo "Source: $(SOURCE_PATH)"
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Status: Installed (symlink)"; \
echo "Target: $$(readlink "$(INSTALL_PATH)")"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Status: Exists but NOT a symlink"; \
else \
echo "Status: Not installed"; \
fi
mkdir -p ensures the directory existsIf the script needs to be sourced (not executed), add post-install instructions:
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
# ... symlink creation ...
@echo ""
@echo "Add to your shell config:"
@echo " source ~/bin/$(SCRIPT_NAME)"
# my-tool Makefile
SCRIPT_NAME := my-tool.sh
INSTALL_DIR := $(HOME)/bin
INSTALL_PATH := $(INSTALL_DIR)/$(SCRIPT_NAME)
SOURCE_PATH := $(CURDIR)/$(SCRIPT_NAME)
.PHONY: help install uninstall check lint test all clean
.DEFAULT_GOAL := help
##@ General
help: ## Show this help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Installation
install: ## Install symlink to ~/bin
@mkdir -p $(INSTALL_DIR)
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Removing existing symlink..."; \
rm -f "$(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists and is not a symlink"; \
exit 1; \
fi
@ln -s "$(SOURCE_PATH)" "$(INSTALL_PATH)"
@echo "Installed: $(INSTALL_PATH) -> $(SOURCE_PATH)"
uninstall: ## Remove symlink from ~/bin
@if [ -L "$(INSTALL_PATH)" ]; then \
rm -f "$(INSTALL_PATH)"; \
echo "Removed: $(INSTALL_PATH)"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Error: $(INSTALL_PATH) exists but is not a symlink (not removing)"; \
exit 1; \
else \
echo "Nothing to remove: $(INSTALL_PATH) does not exist"; \
fi
check: ## Check installation status
@echo "Source: $(SOURCE_PATH)"
@if [ -L "$(INSTALL_PATH)" ]; then \
echo "Status: Installed (symlink)"; \
echo "Target: $$(readlink "$(INSTALL_PATH)")"; \
elif [ -e "$(INSTALL_PATH)" ]; then \
echo "Status: Exists but NOT a symlink"; \
else \
echo "Status: Not installed"; \
fi
##@ Development
lint: ## Run shellcheck on scripts
shellcheck $(SCRIPT_NAME)
test: ## Run tests
./test.sh
# Stubs to satisfy checkmake minphony rule
all: help
clean: uninstall
npx skills add plinde/makefile-best-practices下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer