makefile について
makefileは、AIエージェントのワークフローとサービスのライフサイクル管理を想定したMakefileの作成・保守スキルです。バックグラウンド起動、ログ、プロセスの後始末、状態確認、テストなどを扱います。
主な機能
- 01バックグラウンド起動・停止ターゲット
- 02PID追跡とプロセスグループの後始末
- 03サービス別ログと状態確認
- 04一括ライフサイクル操作とテストターゲット
- 05ポート設定と自己説明型ヘルプ
活用シーン
- 01複数サービス構成のMakefile作成
- 02安全な起動・停止・再起動操作の追加
- 03テストターゲットとサービスログの整理
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mcouthon/ agents --skill makefileインストール後、Claude Code で/makefileを入力して読み込まれたか確認します。
詳細
Makefile モード
AI エージェントとのやり取りやプロセスのライフサイクル管理に最適化された Makefile を作成、管理します。
基本理念
「クリーンに開始する。クリーンに停止する。すべてを記録する。状態を把握する。」
原則:
- AI エージェントを第一に考える:出力はプログラムで読み取り可能にする(対話型プロンプトは使わない)
- デフォルトでバックグラウンド実行:サービスはデタッチして実行し、ターミナルを起動せずにログを読み取る
- 包括的なログ記録:すべての出力を
.logs/に保存し、何も失わない - プロセスの衛生管理:クリーンに開始・停止し、孤立したプロセスを残さない
- 適応可能なパターン:あらゆるサービス構成で動作する
実装前の調査
Makefile を作成する前に、以下を確認します。
サービス構成
- どのようなサービスがありますか?(バックエンド、フロントエンド、ワーカーなど)
- サービス間に依存関係はありますか?(起動順序)
- 外部依存関係はありますか?(データベース、エミュレーターなど)
起動要件
- 各サービスの起動コマンドは何ですか?
- どの環境変数が必要ですか?
- 使用するポートは何ですか?(サービスごとに一意である必要があります)
- 初期化手順はありますか?(マイグレーション、シードなど)
テストと品質
- どのようなテストコマンドがありますか?(ユニット、統合、E2E)
- テストの前提条件は何ですか?(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 を追跡します |
| モノレポ | サブディレクトリー用の変数を使います:cd $(API_DIR) && ... |
| 複数のテスト種別 | ターゲットを分けます:test-unit、test-integration、test-e2e |
| ウォッチモード | 専用のウォッチターゲットを使用し、通常の実行とは混在させません |
ベストプラクティスのチェックリスト
Makefile を完成させる前に、以下を確認します。
- すべてのターゲットが
.PHONYに指定されている(または適切に指定されていない) - ポート番号を変数で設定できる
- サービスごとに一意のポートを使っている(競合がない)
- すべてのログが
.logs/ディレクトリーに保存される - すべての PID が
.pids/ディレクトリーに保存される - プロセスグループを終了する(子プロセスも処理する)
- 起動前にポートの競合を検出する
- 人が読みやすい出力を使う(色/絵文字)
-
helpターゲットがデフォルトになっている(先頭に記載するか.DEFAULT_GOALを設定する) - 変数に
:=を使う(単純展開) - エラーメッセージが明確で、対処方法が分かる
- 状態コマンドで実際の状態が表示される
- 停止時にクリーンにシャットダウンする(最初に SIGTERM を送る)
- 操作が冪等である(2 回実行しても安全)
よくある問題と解決策
| 問題 | 解決策 |
|---|---|
| PID ファイルは存在するが、プロセスが停止している | PID ファイルを使う前に ps -p $PID を確認する |
| 親プロセスを終了しても子プロセスが残る | kill -TERM -- -$PID(プロセスグループ)を使う |
| ポートがすでに使用されている | 起動前に lsof -ti:$PORT で確認する |
| ログが混在して読みづらい | サービスごとにログファイルを分ける |
| サービスが起動後すぐに終了する | 標準エラー出力をリダイレクトする:2>&1、.logs/ を確認する |
| Make の変数が評価されない | = ではなく := を使い、$$ と $ の違いを確認する |
| ログに色が表示されない | unbuffer を使うか、サービスを TTY 用に設定する |
| サービスを停止できない(権限) | 起動したときと同じユーザーで make を実行する |
実装ワークフロー
新しい Makefile の作成
- 調査:質問する(「調査」セクションを参照)
- 設定:変数を設定する(ポート、コマンド、パス)
- コアサービス:各サービスの run/stop を実装する
- 一括操作:すべてのサービスを対象とする run/stop/restart を追加する
- ユーティリティ:status、logs、help を追加する
- テスト:前提条件付きのテストターゲットを追加する
- 品質:lint/format ターゲットを追加する
- 検証:各ターゲットをテストし、冪等性を確認する
- ドキュメント:help が完全かつ正確であることを確認する
既存の Makefile の修正
- 現在の Makefile を読む:既存の構成を把握する
- 不足点を特定する:ベストプラクティスのチェックリストと照合する
- 変更を計画する:追加・変更する内容を決める
- 規約を維持する:既存の命名規則とスタイルを保つ
- 段階的に変更する:機能を一度に 1 つずつ追加する
- 変更ごとにテストする:問題が発生しないことを確認する
- help を更新する:新しいターゲットを反映する
完全なテンプレート
フルスタックアプリ向けの最小限の実用テンプレート:
# =============================================================================
# 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 対応