mcpField
M
makefile

makefile

github.com/mcouthon开发7950 次安装今天更新
代码仓库

关于 makefile

makefile 是用于创建和维护 Makefile 的技能,侧重 AI Agent 工作流与服务生命周期管理。它涵盖后台启动、日志、进程清理、状态检查、测试目标及按项目调整。

主要功能

  1. 01后台服务启动与停止目标
  2. 02PID 跟踪和进程组清理
  3. 03按服务记录日志并检查状态
  4. 04组合生命周期与测试目标
  5. 05可配置端口和自说明帮助

适用场景

  1. 01为多服务应用创建 Makefile
  2. 02添加安全的启动、停止和重启操作
  3. 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
  1. 了解需求:提出问题(参见“需求了解”部分)
  2. 配置:设置变量(端口、命令、路径)
  3. 核心服务:为每个服务实现运行和停止操作
  4. 组合操作:为所有服务添加运行、停止和重启操作
  5. 实用工具:添加状态、日志和帮助功能
  6. 测试:添加带前置条件的测试目标
  7. 质量:添加 lint 和格式化目标
  8. 验证:测试每个目标,并验证幂等性
  9. 文档:确保帮助内容完整且准确
修改现有 Makefile
  1. 阅读当前 Makefile:了解现有结构
  2. 找出差距:对照最佳实践检查清单
  3. 规划更改:确定要添加或修改的内容
  4. 保留约定:沿用现有的命名和风格
  5. 逐步更改:每次添加一项功能
  6. 测试每项更改:确认没有破坏任何功能
  7. 更新帮助:反映新增目标

完整模板

适用于全栈应用的最小可用模板:

# =============================================================================
# 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