makefile

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Makefile Mode

Makefile 模式

Create and manage Makefiles optimized for AI agent interaction and process lifecycle management.
创建并优化Makefile,以适配AI Agent交互和进程生命周期管理。

Core Philosophy

核心理念

"Start clean. Stop clean. Log everything. Know your state."
Principles:
  • AI-agent first: Outputs readable programmatically (no interactive prompts)
  • Background by default: Services run detached; read logs, don't spawn terminals
  • Comprehensive logging: All output to files at
    .logs/
    - nothing lost
  • Process hygiene: Clean starts, clean stops, no orphan processes
  • Adaptable patterns: Works for any service topology
"启动要干净,停止要彻底,日志全留存,状态需清晰。"
原则:
  • 优先适配AI Agent: 输出内容便于程序读取(无交互式提示)
  • 默认后台运行: 服务以分离模式运行;通过日志查看状态,不生成终端窗口
  • 全面日志记录: 所有输出均写入
    .logs/
    目录下的文件,无内容丢失
  • 进程规范: 干净启动、彻底停止,无孤儿进程残留
  • 模式可适配: 适用于任意服务拓扑结构

Pre-Implementation Discovery

实现前的调研

Before creating a Makefile, determine:
在创建Makefile前,需明确以下信息:

Service Topology

服务拓扑

  • What services exist? (backend, frontend, workers, etc.)
  • Do any services depend on others? (start order)
  • Are there external dependencies? (databases, emulators, etc.)
  • 存在哪些服务?(后端、前端、工作进程等)
  • 服务间是否存在依赖关系?(启动顺序要求)
  • 是否有外部依赖?(数据库、模拟器等)

Startup Requirements

启动要求

  • What commands start each service?
  • What environment variables are needed?
  • What ports are used? (must be unique per-service)
  • Any initialization steps? (migrations, seeds, etc.)
  • 每个服务的启动命令是什么?
  • 需要哪些环境变量?
  • 使用哪些端口?(每个服务必须使用唯一端口)
  • 是否有初始化步骤?(数据迁移、种子数据等)

Testing & Quality

测试与质量保障

  • What test commands exist? (unit, integration, e2e)
  • What prerequisites for tests? (docker, emulators, etc.)
  • What linting/formatting tools? (eslint, ruff, mypy, etc.)
  • 有哪些测试命令?(单元测试、集成测试、端到端测试)
  • 测试有哪些前置条件?(Docker、模拟器等)
  • 使用哪些代码检查/格式化工具?(eslint、ruff、mypy等)

Project Context

项目上下文

  • Language/framework? (affects conventions)
  • Development vs Production behavior?
  • Team conventions? (existing practices to preserve)
  • 使用的语言/框架?(会影响约定规范)
  • 开发环境与生产环境的行为差异?
  • 团队已有约定?(需保留现有实践)

Makefile Architecture

Makefile 架构

Standard structure (in order):
makefile
undefined
标准结构(按顺序):
makefile
undefined

1. Configuration Variables

1. 配置变量

2. Directory Setup

2. 目录初始化

3. Service Lifecycle Targets (run-, stop-)

3. 服务生命周期目标(run-, stop-

4. Combined Operations (run, stop, restart)

4. 组合操作(run, stop, restart)

5. Testing & Quality (test, lint)

5. 测试与质量保障(test, lint)

6. Utility Targets (logs, status, help)

6. 工具类目标(logs, status, help)

7. .PHONY declarations

7. .PHONY 声明

undefined
undefined

Core Patterns Library

核心模式库

A. Starting a Service (Background with PID Tracking)

A. 启动服务(后台运行+PID跟踪)

makefile
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))"
makefile
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. Stopping a Service (Process Group Cleanup)

B. 停止服务(清理进程组)

makefile
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
makefile
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 Checking

C. 状态检查

makefile
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
makefile
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. Log Tailing

D. 查看日志

makefile
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
makefile
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. Combined Operations

E. 组合操作

makefile
run: run-backend run-frontend
stop: stop-frontend stop-backend  # Reverse order for clean shutdown
restart: stop run
makefile
run: run-backend run-frontend
stop: stop-frontend stop-backend  # 按逆序停止以实现干净关闭
restart: stop run

F. Testing with Prerequisites

F. 带前置条件的测试

makefile
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
makefile
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. Help Target (Self-Documenting)

G. 帮助目标(自文档化)

makefile
.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"
makefile
.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"

Adaptation Patterns

适配模式

ScenarioAdaptation
Multiple backendsUse suffix naming:
run-api
,
run-worker
, etc.
Database migrationsAdd
migrate
target, make
run-backend
depend on it
EmulatorsTreat like any other service with PID tracking
Docker ComposeWrap docker-compose commands, track container IDs
MonorepoUse subdirectory variables:
cd $(API_DIR) && ...
Multiple test typesSeparate targets:
test-unit
,
test-integration
,
test-e2e
Watch modesUse separate watch targets, don't mix with regular run
场景适配方案
多后端服务使用后缀命名:
run-api
run-worker
数据库迁移添加
migrate
目标,设置
run-backend
依赖该目标
模拟器视为普通服务,同样进行PID跟踪
Docker Compose封装docker-compose命令,跟踪容器ID
单体仓库使用子目录变量:
cd $(API_DIR) && ...
多类型测试拆分目标:
test-unit
test-integration
test-e2e
监听模式使用独立的watch目标,不与常规run目标混合

Best Practices Checklist

最佳实践检查清单

Before completing a Makefile, verify:
  • All targets are
    .PHONY
    (or appropriately not)
  • Port numbers are configurable via variables
  • Unique ports per service (no conflicts)
  • All logs go to
    .logs/
    directory
  • All PIDs go to
    .pids/
    directory
  • Process group killing (handles child processes)
  • Port conflict detection before start
  • Human-readable output (colors/emojis)
  • help
    target is default (listed first or
    .DEFAULT_GOAL
    )
  • Variables use
    :=
    (simple expansion)
  • Error messages are clear and actionable
  • Status command shows actual state
  • Clean shutdown on stop (SIGTERM first)
  • Idempotent operations (safe to run twice)
完成Makefile前,请验证以下内容:
  • 所有目标均已声明为
    .PHONY
    (或按需不声明)
  • 端口号可通过变量配置
  • 每个服务使用唯一端口(无冲突)
  • 所有日志均写入
    .logs/
    目录
  • 所有PID均写入
    .pids/
    目录
  • 支持进程组终止(可处理子进程)
  • 启动前检测端口冲突
  • 输出内容便于人类阅读(含颜色/表情符号)
  • help
    目标为默认目标(置于开头或设置
    .DEFAULT_GOAL
  • 变量使用
    :=
    (简单展开)
  • 错误信息清晰且可执行
  • 状态命令显示真实状态
  • 停止时执行干净关闭(优先使用SIGTERM)
  • 操作具有幂等性(重复执行无异常)

Common Issues & Solutions

常见问题与解决方案

ProblemSolution
PID file exists but process deadCheck
ps -p $PID
before using PID file
Child processes survive parent killUse
kill -TERM -- -$PID
(process group)
Port already in useCheck with
lsof -ti:$PORT
before start
Logs interleaved/unreadableSeparate log files per service
Service starts but immediately exitsRedirect stderr:
2>&1
, check
.logs/
Make variables not evaluatedUse
:=
not
=
, check
$$
vs
$
Colors don't show in logsUse
unbuffer
or configure service for TTY
Can't stop service (permission)Run make with same user that started it
问题解决方案
PID文件存在但进程已终止使用PID文件前先执行
ps -p $PID
检查
父进程终止后子进程仍存活使用
kill -TERM -- -$PID
(终止进程组)
端口已被占用启动前执行
lsof -ti:$PORT
检查
日志交错/难以阅读为每个服务设置独立日志文件
服务启动后立即退出重定向stderr:
2>&1
,查看
.logs/
目录下的日志
Make变量未被正确求值使用
:=
而非
=
,注意
$$
$
的区别
日志中不显示颜色使用
unbuffer
或配置服务支持TTY
无法停止服务(权限问题)使用启动服务的同一用户执行make命令

Implementation Workflow

实现流程

Creating a New Makefile

创建新Makefile

  1. Discovery: Ask questions (see Discovery section)
  2. Configuration: Set up variables (ports, commands, paths)
  3. Core services: Implement run/stop for each service
  4. Combined ops: Add run/stop/restart for all services
  5. Utilities: Add status, logs, help
  6. Testing: Add test targets with prerequisites
  7. Quality: Add lint/format targets
  8. Validation: Test each target, verify idempotency
  9. Documentation: Ensure help is complete and accurate
  1. 调研: 询问相关问题(参考调研部分)
  2. 配置: 设置变量(端口、命令、路径)
  3. 核心服务: 为每个服务实现run/stop目标
  4. 组合操作: 添加所有服务的run/stop/restart目标
  5. 工具类: 添加status、logs、help目标
  6. 测试: 添加带前置条件的测试目标
  7. 质量保障: 添加lint/format目标
  8. 验证: 测试每个目标,验证幂等性
  9. 文档: 确保help目标内容完整准确

Amending an Existing Makefile

修改现有Makefile

  1. Read current Makefile: Understand existing structure
  2. Identify gaps: Compare against best practices checklist
  3. Plan changes: Determine what to add/modify
  4. Preserve conventions: Keep existing naming/style
  5. Incremental changes: Add features one at a time
  6. Test each change: Verify nothing breaks
  7. Update help: Reflect new targets
  1. 阅读现有Makefile: 理解现有结构
  2. 识别差距: 与最佳实践检查清单对比
  3. 规划变更: 确定需要添加/修改的内容
  4. 保留约定: 遵循现有命名/风格
  5. 增量变更: 逐个添加功能
  6. 测试变更: 验证无功能损坏
  7. 更新帮助: 同步新目标到help中

Complete Template

完整模板

A minimal working template for a full-stack app:
makefile
undefined
适用于全栈应用的最小可用模板:
makefile
undefined

=============================================================================

=============================================================================

Configuration

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

Directory Setup

=============================================================================

=============================================================================

$(shell mkdir -p .pids .logs)
$(shell mkdir -p .pids .logs)

=============================================================================

=============================================================================

Service Lifecycle

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

Combined Operations

=============================================================================

=============================================================================

run: run-backend run-frontend stop: stop-frontend stop-backend restart: stop run
run: run-backend run-frontend stop: stop-frontend stop-backend restart: stop run

=============================================================================

=============================================================================

Testing & Quality

Testing & Quality

=============================================================================

=============================================================================

test: @echo "🧪 Running tests..." @$(TEST_CMD)
lint: @echo "🔍 Running linters..." @npm run lint 2>&1 || true
test: @echo "🧪 Running tests..." @$(TEST_CMD)
lint: @echo "🔍 Running linters..." @npm run lint 2>&1 || true

=============================================================================

=============================================================================

Utilities

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"
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

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"
.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

=============================================================================

=============================================================================

.PHONY: run run-backend run-frontend stop stop-backend stop-frontend
restart status logs logs-follow test lint clean help
undefined
.PHONY: run run-backend run-frontend stop stop-backend stop-frontend
restart status logs logs-follow test lint clean help
undefined

Gitignore Additions

Gitignore 补充内容

Remind users to add these to
.gitignore
:
.pids/
.logs/
提醒用户将以下内容添加到
.gitignore
.pids/
.logs/