基本操作

Makefile入門|コマンドを効率的にまとめて実行する

Linux Makefile 自動化

Makefile入門|コマンドを効率的にまとめて実行する

Makefileの基本構文から、変数、PHONYターゲット、パターンルールまで、プロジェクトのビルドやタスクを自動化する方法を解説します。

こんな人向けの記事です

  • Makefileの書き方を学びたい
  • よく使うコマンドをまとめて管理したい
  • ビルドやテストを自動化したい

環境

項目詳細
OSLinux / macOS(Windowsの場合はWSL推奨)
ツールGNU Make 4.x
makeのインストール確認
ターミナルで 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つで構成されます。

Makefile(基本構文)
ターゲット: 依存関係
	コマンド
重要: コマンドの行頭は必ずタブ文字で始める必要があります。スペースではエラーになります。エディタの設定でタブがスペースに変換されていないか確認してください。

具体例:C言語のコンパイル

Makefile
# hello という実行ファイルを作る
hello: hello.c
	gcc -o hello hello.c

# 掃除用のルール
clean:
	rm -f hello

この例では:

  • hello がターゲット(作りたいファイル)
  • hello.c が依存関係(必要なソースファイル)
  • gcc -o hello hello.c がコマンド(実行するビルド処理)

依存関係の仕組み

makeは、ターゲットファイルと依存ファイルの更新日時を比較します。依存ファイルの方が新しい(=変更された)場合のみコマンドを実行します。

Makefile
# 複数の依存関係
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の実行順序
make app を実行すると、makeはまず依存関係を解析し、main.outils.oconfig.o を先にビルドしてから app をビルドします。変更されていないファイルのビルドはスキップされます。

複数ターゲットの実行

ターミナル
# 最初のターゲットがデフォルト
make

# 特定のターゲットを指定
make clean

# 複数のターゲットを一度に実行
make clean app

Step 3変数の定義と使用

Makefileでは変数を使って、繰り返し使う値を一箇所にまとめて管理できます。コンパイラの指定やフラグの変更が容易になります。

変数の基本

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)

代入演算子の種類

演算子名前動作
=再帰的代入参照されるたびに値を展開する(遅延評価)
:=単純代入定義した時点で値を展開する(即時評価)
?=条件付き代入変数が未定義の場合のみ代入する
+=追加代入既存の値に追加する
Makefile
# = (再帰的代入): 参照時に展開
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"
変数の優先順位(高い順)
  1. コマンドラインで指定した変数(make CC=clang
  2. Makefile内で定義した変数
  3. 環境変数
?= は環境変数やコマンドライン指定がない場合のデフォルト値として便利です。

Step 4PHONYターゲット

.PHONY は「このターゲットはファイル名ではなくコマンド名である」と宣言するための特別なターゲットです。

PHONYが必要な理由

makeは通常、ターゲット名と同じ名前のファイルが存在するかチェックします。もし clean という名前のファイルがディレクトリに存在すると、makeは「cleanは最新です」と判断してコマンドを実行しません。

Makefile(問題のある例)
# cleanという名前のファイルがあると実行されない
clean:
	rm -f *.o myapp
ターミナル
# cleanという名前のファイルを作ってみる
touch clean

# makeを実行
make clean
# => make: 'clean' is up to date.  (実行されない!)

.PHONYの宣言

Makefile(正しい例)
# .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    - アプリを実行"
ベストプラクティス: ファイルを生成しないターゲット(clean、test、run、help、install、deployなど)には必ず .PHONY を付けましょう。意図しない動作を防ぎ、常に確実にコマンドが実行されます。
コマンドの先頭の @ について
通常、makeはコマンドを実行する前にそのコマンド自体を表示します。@ を先頭に付けると、コマンドの表示を抑制できます。echo のように出力そのものが目的のコマンドで便利です。

Step 5パターンルールと自動変数

パターンルールと自動変数を組み合わせると、繰り返しのルールをシンプルに記述できます。

パターンルール(%)

% はワイルドカードとして機能し、同じパターンのルールをまとめて定義できます。

Makefile
# 個別にルールを書く場合(冗長)
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
Makefile
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 に置換します。オブジェクトファイルのリストを自動生成するときによく使われるテクニックです。

関数の活用

Makefile
# ワイルドカードでソースファイルを自動取得
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)

Makefile
.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)

Makefile
.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ターゲットの依存関係
deploy: lint test build と書くことで、デプロイ前にリント→テスト→ビルドが順番に実行されます。テストが失敗すればデプロイは実行されないため、安全にデプロイできます。

条件分岐とデバッグ

Makefile
# 条件分岐
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)"
注意: Makefile内の条件分岐(ifeq など)はmakeの解析時に評価されます。シェルの if 文とは異なり、コマンド実行時ではなくMakefile読み込み時に分岐が決まります。

Makefile学習チェックリスト

  • ターゲット・依存関係・コマンドの基本構文を理解した
  • コマンド行はタブ文字で始めることを把握した
  • 変数の定義と4種類の代入演算子を使い分けられる
  • .PHONYの役割と必要な場面を理解した
  • パターンルール(%)で繰り返しルールを簡潔に書ける
  • 自動変数($@, $<, $^)を活用できる
  • 実際のプロジェクトでタスクランナーとしてMakefileを活用できる