Makefile入門|コマンドを効率的にまとめて実行する
Makefileの基本構文から、変数、PHONYターゲット、パターンルールまで、プロジェクトのビルドやタスクを自動化する方法を解説します。
こんな人向けの記事です
- Makefileの書き方を学びたい
- よく使うコマンドをまとめて管理したい
- ビルドやテストを自動化したい
環境
| 項目 | 詳細 |
|---|---|
| OS | Linux / macOS(Windowsの場合はWSL推奨) |
| ツール | GNU Make 4.x |
make --version を実行してバージョンが表示されればインストール済みです。表示されない場合は、Ubuntuなら sudo apt install build-essential、macOSなら xcode-select --install でインストールできます。
Step 1Makefileとは
make は、ファイルのビルド(コンパイル・リンクなど)を自動化するためのツールです。Makefile というファイルに「何をどう作るか」のルールを記述し、make コマンドで実行します。
もともとはC言語のコンパイルを自動化するために生まれましたが、現在では言語を問わず、プロジェクト内のさまざまなタスクをまとめて管理するツールとして広く使われています。
Makefileを使うメリット
| メリット | 説明 |
|---|---|
| コマンドの一元管理 | 長いコマンドを短い名前で呼び出せる |
| 依存関係の管理 | ファイルの変更を検知して必要な部分だけ再ビルド |
| チーム共有 | プロジェクトのビルド手順をファイルで共有できる |
| タスクランナー | テスト・デプロイ・クリーンアップなど日常タスクを自動化 |
# Makefileがあるディレクトリで実行 make # デフォルトターゲット(最初のルール)を実行 make build # buildターゲットを実行 make clean # cleanターゲットを実行
Step 2基本構文(ターゲット・依存関係・コマンド)
Makefileのルールは、ターゲット(作りたいもの)、依存関係(必要なもの)、コマンド(実行する処理)の3つで構成されます。
ターゲット: 依存関係 コマンド
具体例:C言語のコンパイル
# hello という実行ファイルを作る hello: hello.c gcc -o hello hello.c # 掃除用のルール clean: rm -f hello
この例では:
helloがターゲット(作りたいファイル)hello.cが依存関係(必要なソースファイル)gcc -o hello hello.cがコマンド(実行するビルド処理)
依存関係の仕組み
makeは、ターゲットファイルと依存ファイルの更新日時を比較します。依存ファイルの方が新しい(=変更された)場合のみコマンドを実行します。
# 複数の依存関係 app: main.o utils.o config.o gcc -o app main.o utils.o config.o # 各オブジェクトファイルのルール main.o: main.c header.h gcc -c main.c utils.o: utils.c header.h gcc -c utils.c config.o: config.c config.h gcc -c config.c clean: rm -f app *.o
make app を実行すると、makeはまず依存関係を解析し、main.o、utils.o、config.o を先にビルドしてから app をビルドします。変更されていないファイルのビルドはスキップされます。
複数ターゲットの実行
# 最初のターゲットがデフォルト make # 特定のターゲットを指定 make clean # 複数のターゲットを一度に実行 make clean app
Step 3変数の定義と使用
Makefileでは変数を使って、繰り返し使う値を一箇所にまとめて管理できます。コンパイラの指定やフラグの変更が容易になります。
変数の基本
# 変数の定義(= で定義、参照時に展開)
CC = gcc
CFLAGS = -Wall -Wextra -O2
TARGET = myapp
SRCS = main.c utils.c config.c
OBJS = main.o utils.o config.o
# 変数の参照($(変数名) または ${変数名})
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f $(TARGET) $(OBJS)
代入演算子の種類
| 演算子 | 名前 | 動作 |
|---|---|---|
= | 再帰的代入 | 参照されるたびに値を展開する(遅延評価) |
:= | 単純代入 | 定義した時点で値を展開する(即時評価) |
?= | 条件付き代入 | 変数が未定義の場合のみ代入する |
+= | 追加代入 | 既存の値に追加する |
# = (再帰的代入): 参照時に展開 A = hello B = $(A) world A = goodbye # $(B) は "goodbye world" になる # := (単純代入): 定義時に展開 A := hello B := $(A) world A := goodbye # $(B) は "hello world" のまま # ?= (条件付き代入): 未定義の場合のみ代入 CC ?= gcc # 環境変数やコマンドラインで指定されていなければ gcc を使用 # += (追加代入): 値を追加 CFLAGS = -Wall CFLAGS += -Wextra CFLAGS += -O2 # $(CFLAGS) は "-Wall -Wextra -O2"
コマンドラインからの変数上書き
# コマンドラインから変数を上書き make CC=clang CFLAGS="-O3 -march=native" # デバッグ用にフラグを追加 make CFLAGS="-Wall -g -DDEBUG"
- コマンドラインで指定した変数(
make CC=clang) - Makefile内で定義した変数
- 環境変数
?= は環境変数やコマンドライン指定がない場合のデフォルト値として便利です。
Step 4PHONYターゲット
.PHONY は「このターゲットはファイル名ではなくコマンド名である」と宣言するための特別なターゲットです。
PHONYが必要な理由
makeは通常、ターゲット名と同じ名前のファイルが存在するかチェックします。もし clean という名前のファイルがディレクトリに存在すると、makeは「cleanは最新です」と判断してコマンドを実行しません。
# cleanという名前のファイルがあると実行されない clean: rm -f *.o myapp
# cleanという名前のファイルを作ってみる touch clean # makeを実行 make clean # => make: 'clean' is up to date. (実行されない!)
.PHONYの宣言
# .PHONYを宣言すると、同名ファイルがあっても常に実行される .PHONY: clean test run help clean: rm -f *.o myapp test: ./run_tests.sh run: ./myapp help: @echo "使用可能なターゲット:" @echo " make build - ビルド" @echo " make test - テスト実行" @echo " make clean - 成果物を削除" @echo " make run - アプリを実行"
.PHONY を付けましょう。意図しない動作を防ぎ、常に確実にコマンドが実行されます。
@ を先頭に付けると、コマンドの表示を抑制できます。echo のように出力そのものが目的のコマンドで便利です。
Step 5パターンルールと自動変数
パターンルールと自動変数を組み合わせると、繰り返しのルールをシンプルに記述できます。
パターンルール(%)
% はワイルドカードとして機能し、同じパターンのルールをまとめて定義できます。
# 個別にルールを書く場合(冗長) main.o: main.c gcc -c main.c utils.o: utils.c gcc -c utils.c config.o: config.c gcc -c config.c # パターンルールで一括定義(簡潔) %.o: %.c gcc -c $<
%.o: %.c は「任意の .o ファイルは、同名の .c ファイルから作られる」というルールを表します。
自動変数
| 自動変数 | 意味 | 例(main.o: main.c header.h) |
|---|---|---|
$@ | ターゲット名 | main.o |
$< | 最初の依存関係 | main.c |
$^ | すべての依存関係 | main.c header.h |
$? | ターゲットより新しい依存関係 | (更新されたもののみ) |
$* | パターンの%に一致した部分 | main |
CC = gcc CFLAGS = -Wall -Wextra -O2 TARGET = myapp SRCS = main.c utils.c config.c OBJS = $(SRCS:.c=.o) # .c を .o に置換 → main.o utils.o config.o # $@ = myapp, $^ = main.o utils.o config.o $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ # $@ = *.o, $< = *.c %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY: clean clean: rm -f $(TARGET) $(OBJS)
$(SRCS:.c=.o) は変数 SRCS の値に含まれる .c をすべて .o に置換します。オブジェクトファイルのリストを自動生成するときによく使われるテクニックです。
関数の活用
# ワイルドカードでソースファイルを自動取得 SRCS = $(wildcard src/*.c) OBJS = $(patsubst src/%.c, build/%.o, $(SRCS)) # ディレクトリの作成 DIRS = build $(TARGET): $(DIRS) $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) $(DIRS): mkdir -p $@ build/%.o: src/%.c $(CC) $(CFLAGS) -c $< -o $@
| 関数 | 用途 | 例 |
|---|---|---|
$(wildcard パターン) | ファイルを検索 | $(wildcard src/*.c) |
$(patsubst 元,先,テキスト) | パターン置換 | $(patsubst %.c,%.o,$(SRCS)) |
$(notdir パス) | ファイル名のみ取得 | $(notdir src/main.c) → main.c |
$(dir パス) | ディレクトリ部分を取得 | $(dir src/main.c) → src/ |
$(shell コマンド) | シェルコマンド実行 | $(shell date +%Y%m%d) |
Step 6プロジェクト開発でのMakefile活用例
実際のプロジェクトでは、ビルドだけでなく、テスト・リント・デプロイなどさまざまなタスクをMakefileで管理します。
Webアプリプロジェクト(Docker + Python)
.PHONY: help build up down logs test lint migrate shell clean
# デフォルトターゲット
help:
@echo "=== プロジェクト管理コマンド ==="
@echo " make build - Dockerイメージのビルド"
@echo " make up - コンテナの起動"
@echo " make down - コンテナの停止"
@echo " make logs - ログの表示"
@echo " make test - テストの実行"
@echo " make lint - コードチェック"
@echo " make migrate - DBマイグレーション"
@echo " make shell - コンテナ内シェル"
@echo " make clean - 不要データの削除"
# Docker操作
build:
docker compose build
up:
docker compose up -d
down:
docker compose down
logs:
docker compose logs -f
# 開発タスク
test:
docker compose exec web python manage.py test
lint:
docker compose exec web flake8 .
docker compose exec web black --check .
migrate:
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
shell:
docker compose exec web python manage.py shell
# クリーンアップ
clean:
docker compose down -v --rmi local
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
フロントエンドプロジェクト(Node.js)
.PHONY: install dev build test lint format clean deploy # 変数 NODE_ENV ?= development PORT ?= 3000 install: npm ci dev: NODE_ENV=development npm run dev build: NODE_ENV=production npm run build test: npm run test lint: npm run lint format: npm run format clean: rm -rf node_modules dist .cache # 本番デプロイ(ビルドしてからデプロイ) deploy: lint test build rsync -avz dist/ server:/var/www/app/ @echo "デプロイ完了"
deploy: lint test build と書くことで、デプロイ前にリント→テスト→ビルドが順番に実行されます。テストが失敗すればデプロイは実行されないため、安全にデプロイできます。
条件分岐とデバッグ
# 条件分岐
DEBUG ?= 0
ifeq ($(DEBUG), 1)
CFLAGS += -g -DDEBUG
$(info デバッグモードでビルドします)
else
CFLAGS += -O2
endif
# OS判定
UNAME := $(shell uname)
ifeq ($(UNAME), Darwin)
# macOS
OPEN_CMD = open
else
# Linux
OPEN_CMD = xdg-open
endif
# Makefileのデバッグ
.PHONY: debug-vars
debug-vars:
@echo "CC = $(CC)"
@echo "CFLAGS = $(CFLAGS)"
@echo "SRCS = $(SRCS)"
@echo "OBJS = $(OBJS)"
@echo "UNAME = $(UNAME)"
ifeq など)はmakeの解析時に評価されます。シェルの if 文とは異なり、コマンド実行時ではなくMakefile読み込み時に分岐が決まります。
Makefile学習チェックリスト
- ターゲット・依存関係・コマンドの基本構文を理解した
- コマンド行はタブ文字で始めることを把握した
- 変数の定義と4種類の代入演算子を使い分けられる
- .PHONYの役割と必要な場面を理解した
- パターンルール(%)で繰り返しルールを簡潔に書ける
- 自動変数($@, $<, $^)を活用できる
- 実際のプロジェクトでタスクランナーとしてMakefileを活用できる