Makefileをタスクランナーとして使う:make dev・make test・make lint の書き方

スポンサーリンク

Makefileをタスクランナーとして使う:make dev・make test・make lint の書き方

はじめに

MakefileはもともとC言語などのビルドを自動化するツールですが、現在では言語に関係なくタスクランナーとして広く使われています

make dev    # 開発サーバーを起動
make test   # テストを実行
make lint   # Lintを実行
make build  # ビルドを実行

プロジェクトのルートに Makefile を置くことで、チームメンバーが長いコマンドを覚えなくても make だけで操作できるようになります。


基本的な書き方

ターゲット名: 依存関係
    実行するコマンド

重要:コマンドの先頭はタブ文字(スペースではなく)です。

hello:
    echo "Hello, World!"
make hello
# echo "Hello, World!"
# Hello, World!

デフォルトでは実行するコマンド自体も表示されます。コマンドの先頭に @ をつけると非表示になります。

hello:
    @echo "Hello, World!"
make hello
# Hello, World!

.PHONY の意味と必要な理由

make は本来ファイルのビルドツールなので、ターゲット名と同じ名前のファイルが存在すると「すでに最新」と判断してコマンドを実行しません。

# testというファイルが存在する場合
touch test

make test
# make: 'test' is up to date.  ← コマンドが実行されない!

.PHONY に登録することで、ファイルの存在に関係なく常にコマンドを実行するようになります。

.PHONY: test dev lint build

test:
    @go test ./...

dev:
    @go run main.go

タスクランナーとして使う場合は、すべてのターゲットを .PHONY に登録するのが基本です。


よく使うタスクの例

開発サーバー起動

.PHONY: dev

dev:
    @npm run dev
# Railsの場合
dev:
    @bin/rails server
# Pythonの場合
dev:
    @uvicorn main:app --reload

テスト実行

.PHONY: test

test:
    @npm test
# RSpecの場合
test:
    @bundle exec rspec
# Goの場合
test:
    @go test ./...

Lint・フォーマット

.PHONY: lint fmt

lint:
    @npm run lint

fmt:
    @npm run format
# Go(複数コマンドをまとめる)
lint:
    @golangci-lint run

fmt:
    @gofmt -w .

ビルド

.PHONY: build

build:
    @npm run build

変数を使う

変数を定義して複数の場所で使い回せます。

APP_NAME = myapp
PORT = 8080

.PHONY: dev build

dev:
    @PORT=$(PORT) go run main.go

build:
    @go build -o $(APP_NAME) .

環境変数を上書きして実行することもできます。

make dev PORT=3000

引数を渡す

make の引数として変数を渡せます。

.PHONY: migrate

migrate:
    @bundle exec rails db:migrate RAILS_ENV=$(ENV)
make migrate ENV=production

引数のデフォルト値は ?= で設定できます。

ENV ?= development

migrate:
    @bundle exec rails db:migrate RAILS_ENV=$(ENV)
make migrate           # RAILS_ENV=development
make migrate ENV=test  # RAILS_ENV=test

依存関係:他のターゲットを先に実行する

ターゲット名の後ろに依存するターゲットを書くと、先にそちらが実行されます。

.PHONY: build test lint

lint:
    @npm run lint

test: lint
    @npm test

build: test
    @npm run build
make build
# まず lint が実行される
# 次に test が実行される
# 最後に build が実行される

CIでよく使うパターンです。


まとめて実行するターゲット

all という名前のターゲットを作ると make だけで実行できます(all は引数なしの make のデフォルトターゲット)。

.PHONY: all lint test build

all: lint test build

lint:
    @npm run lint

test:
    @npm test

build:
    @npm run build
make        # lint → test → build が順番に実行される
make all    # 同じ

ヘルプを表示する

各ターゲットの説明を ## コメントで書いておき、help ターゲットで一覧表示できます。

.PHONY: help dev test lint build

help: ## ヘルプを表示する
    @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
        awk 'BEGIN {FS = ":.*?## "}; {printf "  %-10s %s\n", $$1, $$2}'

dev: ## 開発サーバーを起動する
    @npm run dev

test: ## テストを実行する
    @npm test

lint: ## Lintを実行する
    @npm run lint

build: ## ビルドする
    @npm run build
make help
#   help       ヘルプを表示する
#   dev        開発サーバーを起動する
#   test       テストを実行する
#   lint       Lintを実行する
#   build      ビルドする

まとめ

機能 書き方
基本のタスク ターゲット名: + タブ + コマンド
コマンドを非表示 コマンド先頭に @
ファイル判定を無効化 .PHONY に登録(必須)
変数 変数名 = 値 / $(変数名)
デフォルト値付き変数 変数名 ?= デフォルト値
依存関係 ターゲット: 依存ターゲット
デフォルト実行 all: ターゲットを定義
  • タスクランナーとして使う場合は .PHONY を忘れずに
  • コマンドに @ をつけると出力がすっきりする
  • help ターゲットを作っておくとチームで使いやすくなる