关于 makefile
makefile 是用于创建和维护 Makefile 的技能,侧重 AI Agent 工作流与服务生命周期管理。它涵盖后台启动、日志、进程清理、状态检查、测试目标及按项目调整。
主要功能
- 01后台服务启动与停止目标
- 02PID 跟踪和进程组清理
- 03按服务记录日志并检查状态
- 04组合生命周期与测试目标
- 05可配置端口和自说明帮助
适用场景
- 01为多服务应用创建 Makefile
- 02添加安全的启动、停止和重启操作
- 03整理测试目标与服务日志
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mcouthon/ agents --skill makefile安装后在 Claude Code 中输入/makefile确认已加载。
详细介绍
Makefile 模式
创建和管理针对 AI Agent 交互和进程生命周期管理优化的 Makefile。
核心理念
“干净启动。干净停止。记录一切。了解状态。”
原则:
- AI Agent 优先:输出可通过程序读取(无交互式提示)
- 默认在后台运行:服务以分离模式运行;读取日志,而不是启动终端
- 全面记录日志:所有输出都写入
.logs/文件,不遗漏任何内容 - 进程管理规范:干净启动、干净停止,不遗留孤立进程
- 模式灵活适配:适用于任何服务拓扑
实施前调研
创建 Makefile 前,确定以下事项:
服务拓扑
- 有哪些服务?(后端、前端、工作进程等)
- 是否有服务依赖其他服务?(启动顺序)
- 是否有外部依赖?(数据库、模拟器等)
启动要求
- 启动各项服务的命令是什么?
- 需要哪些环境变量?
- 使用哪些端口?(每项服务必须使用唯一端口)
- 是否有任何初始化步骤?(迁移、种子数据等)
测试与质量
- 有哪些测试命令?(单元测试、集成测试、端到端测试)
- 测试需要哪些前置条件?(Docker、模拟器等)
- 使用哪些代码检查/格式化工具?(eslint、ruff、mypy 等)
项目背景
- 使用什么语言/框架?(影响惯例)
- 开发环境与生产环境的行为有何不同?
- 团队有哪些惯例?(需要保留的现有做法)
Makefile 架构
标准结构(按顺序):
# 1. Configuration Variables
# 2. Directory Setup
# 3. Service Lifecycle Targets (run-*, stop-*)
# 4. Combined Operations (run, stop, restart)
# 5. Testing & Quality (test, lint)
# 6. Utility Targets (logs, status, help)
# 7. .PHONY declarations
核心模式库
A. 启动服务(后台运行并跟踪 PID)
run-backend:
@mkdir -p .pids .logs
@if lsof -ti:$(BACKEND_PORT) > /dev/null 2>&1; then \
echo "❌ Backend already running on port $(BACKEND_PORT)"; \
exit 1; \
fi
@echo "🚀 Starting backend on port $(BACKEND_PORT)..."
@nohup $(BACKEND_CMD) > .logs/backend.log 2>&1 & echo $$! > .pids/backend.pid
@echo "✅ Backend started (PID: $$(cat .pids/backend.pid))"
B. 停止服务(清理进程组)
stop-backend:
@if [ -f .pids/backend.pid ]; then \
PID=$$(cat .pids/backend.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "🛑 Stopping backend (PID: $$PID)..."; \
kill -TERM -- -$$PID 2>/dev/null || kill $$PID; \
rm .pids/backend.pid; \
echo "✅ Backend stopped"; \
else \
echo "⚠️ Backend process not found, cleaning up PID file"; \
rm .pids/backend.pid; \
fi \
else \
echo "ℹ️ Backend not running"; \
fi
C. 检查状态
status:
@echo "📊 Service Status:"
@echo ""
@for service in backend frontend; do \
if [ -f .pids/$$service.pid ]; then \
PID=$$(cat .pids/$$service.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "✅ $$service: running (PID: $$PID)"; \
else \
echo "❌ $$service: stopped (stale PID file)"; \
fi \
else \
echo "⚪ $$service: not running"; \
fi; \
done
D. 跟踪日志
logs:
@if [ -f .logs/backend.log ] || [ -f .logs/frontend.log ]; then \
tail -n 50 .logs/*.log 2>/dev/null; \
else \
echo "No logs found"; \
fi
logs-follow:
@tail -f .logs/*.log 2>/dev/null
E. 组合操作
run: run-backend run-frontend
stop: stop-frontend stop-backend # Reverse order for clean shutdown
restart: stop run
F. 带前置条件的测试
test: test-setup
@echo "🧪 Running tests..."
@$(TEST_CMD)
test-setup:
@if [ -n "$(DOCKER_COMPOSE_FILE)" ] && [ -f "$(DOCKER_COMPOSE_FILE)" ]; then \
docker-compose -f $(DOCKER_COMPOSE_FILE) up -d; \
fi
G. 帮助目标(自文档化)
.DEFAULT_GOAL := help
help:
@echo "Available targets:"
@echo ""
@echo " make run Start all services"
@echo " make stop Stop all services"
@echo " make restart Restart all services"
@echo " make status Show service status"
@echo " make logs Show recent logs"
@echo " make logs-follow Follow logs in real-time"
@echo " make test Run all tests"
@echo " make lint Run linters and formatters"
@echo ""
@echo "Individual services:"
@echo " make run-backend Start backend only"
@echo " make run-frontend Start frontend only"
@echo " make stop-backend Stop backend only"
@echo " make stop-frontend Stop frontend only"
适配模式
| 场景 | 适配方式 |
|---|---|
| 多个后端 | 使用后缀命名:run-api、run-worker 等。 |
| 数据库迁移 | 添加 migrate 目标,并让 run-backend 依赖它 |
| 模拟器 | 与其他服务一样处理,并跟踪 PID |
| Docker Compose | 封装 docker-compose 命令,并跟踪容器 ID |
| Monorepo | 使用子目录变量:cd $(API_DIR) && ... |
| 多种测试类型 | 分设目标:test-unit、test-integration、test-e2e |
| 监视模式 | 单独设置监视目标,不要与常规运行混用 |
最佳实践检查清单
完成 Makefile 前,请确认:
- 所有目标都标记为
.PHONY(或根据情况不标记) - 端口号可通过变量配置
- 每项服务使用唯一端口(无冲突)
- 所有日志都写入
.logs/目录 - 所有 PID 都写入
.pids/目录 - 按进程组终止进程(处理子进程)
- 启动前检测端口冲突
- 输出便于人类阅读(颜色/表情符号)
-
help目标为默认目标(列在首位或设置.DEFAULT_GOAL) - 变量使用
:=(简单展开) - 错误消息清晰且可执行
- 状态命令显示实际状态
- 停止时干净关闭(先发送 SIGTERM)
- 操作具备幂等性(可安全地重复运行)
常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| PID 文件存在,但进程已终止 | 使用 PID 文件前,先检查 ps -p $PID |
| 父进程终止后,子进程仍在运行 | 使用 kill -TERM -- -$PID(进程组) |
| 端口已被占用 | 启动前使用 lsof -ti:$PORT 检查 |
| 日志交错,难以阅读 | 为每个服务分别创建日志文件 |
| 服务启动后立即退出 | 重定向 stderr:2>&1,检查 .logs/ |
| Make 变量未求值 | 使用 := 而非 =,检查 $$ 与 $ |
| 日志中不显示颜色 | 使用 unbuffer,或将服务配置为使用 TTY |
| 无法停止服务(权限问题) | 使用启动服务时的同一用户运行 make |
实施工作流
创建新的 Makefile
- 了解需求:提出问题(参见“需求了解”部分)
- 配置:设置变量(端口、命令、路径)
- 核心服务:为每个服务实现运行和停止操作
- 组合操作:为所有服务添加运行、停止和重启操作
- 实用工具:添加状态、日志和帮助功能
- 测试:添加带前置条件的测试目标
- 质量:添加 lint 和格式化目标
- 验证:测试每个目标,并验证幂等性
- 文档:确保帮助内容完整且准确
修改现有 Makefile
- 阅读当前 Makefile:了解现有结构
- 找出差距:对照最佳实践检查清单
- 规划更改:确定要添加或修改的内容
- 保留约定:沿用现有的命名和风格
- 逐步更改:每次添加一项功能
- 测试每项更改:确认没有破坏任何功能
- 更新帮助:反映新增目标
完整模板
适用于全栈应用的最小可用模板:
# =============================================================================
# Configuration
# =============================================================================
BACKEND_PORT := 3001
FRONTEND_PORT := 3000
BACKEND_CMD := npm run dev --prefix backend
FRONTEND_CMD := npm run dev --prefix frontend
TEST_CMD := npm test
# =============================================================================
# Directory Setup
# =============================================================================
$(shell mkdir -p .pids .logs)
# =============================================================================
# Service Lifecycle
# =============================================================================
run-backend:
@if lsof -ti:$(BACKEND_PORT) > /dev/null 2>&1; then \
echo "❌ Backend already running on port $(BACKEND_PORT)"; \
exit 1; \
fi
@echo "🚀 Starting backend on port $(BACKEND_PORT)..."
@nohup $(BACKEND_CMD) > .logs/backend.log 2>&1 & echo $$! > .pids/backend.pid
@echo "✅ Backend started (PID: $$(cat .pids/backend.pid))"
run-frontend:
@if lsof -ti:$(FRONTEND_PORT) > /dev/null 2>&1; then \
echo "❌ Frontend already running on port $(FRONTEND_PORT)"; \
exit 1; \
fi
@echo "🚀 Starting frontend on port $(FRONTEND_PORT)..."
@nohup $(FRONTEND_CMD) > .logs/frontend.log 2>&1 & echo $$! > .pids/frontend.pid
@echo "✅ Frontend started (PID: $$(cat .pids/frontend.pid))"
stop-backend:
@if [ -f .pids/backend.pid ]; then \
PID=$$(cat .pids/backend.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "🛑 Stopping backend (PID: $$PID)..."; \
kill -TERM -- -$$PID 2>/dev/null || kill $$PID; \
rm .pids/backend.pid; \
echo "✅ Backend stopped"; \
else \
echo "⚠️ Backend not found, cleaning up PID file"; \
rm .pids/backend.pid; \
fi \
else \
echo "ℹ️ Backend not running"; \
fi
stop-frontend:
@if [ -f .pids/frontend.pid ]; then \
PID=$$(cat .pids/frontend.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "🛑 Stopping frontend (PID: $$PID)..."; \
kill -TERM -- -$$PID 2>/dev/null || kill $$PID; \
rm .pids/frontend.pid; \
echo "✅ Frontend stopped"; \
else \
echo "⚠️ Frontend not found, cleaning up PID file"; \
rm .pids/frontend.pid; \
fi \
else \
echo "ℹ️ Frontend not running"; \
fi
# =============================================================================
# Combined Operations
# =============================================================================
run: run-backend run-frontend
stop: stop-frontend stop-backend
restart: stop run
# =============================================================================
# Testing & Quality
# =============================================================================
test:
@echo "🧪 Running tests..."
@$(TEST_CMD)
lint:
@echo "🔍 Running linters..."
@npm run lint 2>&1 || true
# =============================================================================
# Utilities
# =============================================================================
status:
@echo "📊 Service Status:"
@echo ""
@for service in backend frontend; do \
if [ -f .pids/$$service.pid ]; then \
PID=$$(cat .pids/$$service.pid); \
if ps -p $$PID > /dev/null 2>&1; then \
echo "✅ $$service: running (PID: $$PID)"; \
else \
echo "❌ $$service: stopped (stale PID file)"; \
fi \
else \
echo "⚪ $$service: not running"; \
fi; \
done
logs:
@tail -n 50 .logs/*.log 2>/dev/null || echo "No logs found"
logs-follow:
@tail -f .logs/*.log 2>/dev/null
clean:
@rm -rf .pids .logs
@echo "🧹 Cleaned up PID and log files"
# =============================================================================
# Help
# =============================================================================
.DEFAULT_GOAL := help
help:
@echo "Available targets:"
@echo ""
@echo " make run Start all services"
@echo " make stop Stop all services"
@echo " make restart Restart all services"
@echo " make status Show service status"
@echo " make logs Show recent logs (last 50 lines)"
@echo " make logs-follow Follow logs in real-time"
@echo " make test Run tests"
@echo " make lint Run linters"
@echo " make clean Remove PID and log files"
@echo ""
@echo "Individual services:"
@echo " make run-backend Start backend only"
@echo " make run-frontend Start frontend only"
@echo " make stop-backend Stop backend only"
@echo " make stop-frontend Stop frontend only"
# =============================================================================
# .PHONY
# =============================================================================
.PHONY: run run-backend run-frontend stop stop-backend stop-frontend \
restart status logs logs-follow test lint clean help
Gitignore 补充项
提醒用户将以下内容添加到 .gitignore:
.pids/
.logs/
评论
支持 Markdown