AIエージェントを使うようになってから、古くからある make コマンドの便利さを改めて感じている。

make は C 言語のビルドで使う古い道具、という印象が強かった。自分も昔から知っていたが、仕組みをきちんと理解して使っていたわけではない。

ところが、Claude Code や Codex のような CLI 型 AI エージェントを相棒に開発すると、make はかなり相性がいい。

理由は単純で、プロジェクトでよく使う操作を make testmake build-releasemake device-debug のような短いコマンドにまとめられるからだ。人間と AI エージェントが同じコマンドを見て、同じように実行できる。

特に iOS アプリ開発では効果が大きい。make device-debug でビルド、実機インストール、起動までできるようにしておくと、Xcode を開かずにターミナルだけで開発を進められる場面が増える。Xcode は他の IDE と操作感がかなり違い、毎回開くのが負担に感じることもある。Xcode への依存を半分でも減らせると思うと、iOS アプリ開発の心理的なハードルも下がる。左半分のターミナルで Claude Code などの CLI AI と対話し、右半分や別タブで make を叩く。あるいは AI エージェント自身に make test を実行してもらう。

左側のターミナルでCLI AIに依頼し、右側のターミナルでmakeコマンドを実行する流れ

GitHub Issue とターミナルがあれば、基本的な開発作業がかなり済んでしまう。

makeは何をする道具なのか

make は、Makefile に書かれたルールを読んで、必要なコマンドを実行する道具である。

GNU Make のマニュアル では、make は大きなプログラムのどの部分を再コンパイルする必要があるかを自動判定し、そのためのコマンドを実行するユーティリティとして説明されている。

もともとの発想は、ファイル同士の依存関係を見て、変更があった部分だけを更新することだ。

たとえば C の世界なら、main.c から main.o を作り、複数の .o をリンクして実行ファイルを作る。ヘッダーファイルが変わったら、それに依存するソースを再コンパイルする。こういう関係を毎回人間が覚えて実行するのは面倒だし、間違いやすい。

そこで Makefile に「このファイルを作るには、このファイルが必要で、このコマンドを実行する」と書いておく。

基本形はこうなる。

target: prerequisites
	command

target は作りたいものや実行したい名前、prerequisites は前提になるファイル、command は実際に実行するコマンドである。

ただし、現代の個人開発で便利なのは、ファイル更新の差分ビルドだけではない。testcleandeployscreenshots のような「名前付きコマンド集」としても使えるところだ。

.PHONY: test clean deploy

test:
	npm test

clean:
	rm -rf dist

deploy:
	./deploy.sh

このくらいなら、シェルスクリプトを並べるよりも見通しがいい。make help を用意しておけば、プロジェクトで使える操作一覧にもなる。

makeの歴史をざっくり知る

make はかなり古い道具である。

Stuart I. Feldman による論文「Make – A Program for Maintaining Computer Programs」は、Software: Practice and Experience の 1979 年 4 月号に掲載されている。CiNii Research の書誌情報 では、Make は UNIX システム上で 1975 年から使われていたと説明されている。

つまり、make は最近登場した自動化ツールではない。半世紀近く前から、プログラムを複数の部品に分け、それぞれの依存関係を追い、必要な処理を実行するために使われてきた道具である。

今の感覚で見ると、Makefile のタブ必須ルールや独特の変数展開は古くさく感じる。実際、もっと分かりやすいタスクランナーやビルドツールはたくさんある。

それでも make が残っているのは、かなり低いレイヤーで、雑に強いからだと思う。

  • 多くの Unix 系環境で使いやすい
  • シェルコマンドをそのまま呼べる
  • 言語やフレームワークに依存しない
  • make target という入口が短い
  • AI エージェントにも説明しやすい

特に最後が、AI 時代になって急に輝く理由になっている。

AI時代にmakeが便利な理由

AI エージェントに作業を頼むとき、毎回長い手順を自然言語で説明するのはあまりよくない。

「このプロジェクトでは、このスキームで、このデバイス向けに、このスクリプトを使って、必要なら CocoaPods を入れて、最後にこのテストを走らせて」と説明していると、どこかでずれる。

一方で、Makefile に作業の入口をまとめておけば話が早い。

  • テストして:make test
  • デバッグビルドして:make build-debug
  • 実機へ入れて:make device-debug
  • App Store 用画像を作って:make screenshots

こうなると、AI エージェントへの指示がかなり短くなる。

修正後に make test を実行して、失敗したら原因を見て直してください。

実機確認用に make device-debug が通る状態まで直してください。

このように書ける。

重要なのは、人間が確認するときも同じコマンドを使えることだ。AI が実行した make test と、自分が実行する make test が同じなら、認識のズレが減る。

もうひとつ地味にうれしいのが、シェルの補完だ。環境にもよるが、make まで入力して Tab キーを押すと、Makefile に書いた testdevice-debugscreenshots などのターゲット名を補完できる。npm にも npm completion はあるので補完自体が不可能なわけではないが、プロジェクト固有の作業入口を make のターゲットとして並べておくと、コマンドを思い出す負担がかなり減る。

iOS開発ではXcodeを開かない入口になる

iOS アプリ開発では、普通に考えると Xcode を開いて、スキームを選び、デバイスを選び、ビルドボタンを押す。

もちろん Xcode は必要な場面がある。Storyboard を見る、署名設定を確認する、クラッシュログを追う、GUI で調整する。そういう作業では Xcode が強い。

ただ、日々の開発で毎回 Xcode を操作したいかというと、そうではない。むしろ開発の腰を重くすることがある。

AI エージェントとターミナル中心で開発していると、Xcode の画面操作は文脈の切り替えになる。さっきまで GitHub Issue を見て、ターミナルで AI と対話していたのに、ビルドだけ Xcode へ移動する。この切り替えが地味に重い。

そこで、iOS アプリの Makefile に次のようなターゲットを用意した。

% make help
Usage: make <target> [VARIABLE=value]

build            Build Debug for the iOS Simulator
test             Run unit and UI tests
test-unit        Run unit tests only
test-ui          Run UI tests only
screenshots      Auto-capture screenshots and generate App Store images
build-debug      Build Debug for device without deploying
build-release    Build Release for device without deploying
device-debug     Build, install, and launch Debug on device
device-release   Build and install Release on device
clean            Clean the selected simulator scheme
list-devices     List available iOS Simulators
list-schemes     List workspace schemes
pods             Install CocoaPods dependencies

Defaults:
  WORKSPACE=memo.xcworkspace
  SCHEME=memo-Debug
  CONFIGURATION=Debug
  SIMULATOR_NAME=iPhone 17
  SIMULATOR_OS=latest
  DESTINATION=platform=iOS Simulator,name=iPhone 17,OS=latest

これだけあると、ターミナルからほぼ一通りの作業ができる。

make build-debugmake build-release で、デバッグモードとリリースモードを一発で切り替えられる。Xcode でスキームや設定を毎回選び直すより、手元の作業としてはかなり軽い。

make device-debug なら、ビルドして、実機に入れて、起動するところまで進められる。make device-release なら、リリース構成で実機インストールできる。

make testmake test-unitmake test-ui は、人間が確認したいときにも便利だし、AI エージェントに自動実行してもらう入口としても使いやすい。

make screenshots では、App Store に投稿するスクリーンショットの作成まで自動化した。これも自分で毎回やると面倒だが、Makefile に入っていればコマンド一発で済む。

実際のMakefile例

この Makefile は、自分が開発している iOS アプリ「スーパーメモ」で使っている。

スーパーメモは、買い物リストや短いメモをタグで整理し、必要なメモを通知センターやロック画面から確認できる iPhone アプリである。このアプリの開発で、ビルド、実機デプロイ、テスト、App Store 用スクリーンショット生成までを make にまとめた。

スーパーメモ タグと通知センターで短いメモを整理するiPhoneアプリ 開く

実際には、次のような Makefile にしている。

WORKSPACE ?= memo.xcworkspace
SCHEME ?= memo-Debug
CONFIGURATION ?= Debug
SIMULATOR_NAME ?= iPhone 17
SIMULATOR_OS ?= latest
DESTINATION ?= platform=iOS Simulator,name=$(SIMULATOR_NAME),OS=$(SIMULATOR_OS)
DERIVED_DATA ?= build/DerivedData

XCODEBUILD = xcodebuild \
	-workspace "$(WORKSPACE)" \
	-scheme "$(SCHEME)" \
	-destination "$(DESTINATION)" \
	-derivedDataPath "$(DERIVED_DATA)"

.DEFAULT_GOAL := help

.PHONY: help build build-debug build-release device-debug device-release test test-unit test-ui screenshots clean list-devices list-schemes pods

help:
	@printf "%s\n" "Usage: make <target> [VARIABLE=value]"
	@printf "\n"
	@printf "%-16s %s\n" "build" "Build Debug for the iOS Simulator"
	@printf "%-16s %s\n" "test" "Run unit and UI tests"
	@printf "%-16s %s\n" "test-unit" "Run unit tests only"
	@printf "%-16s %s\n" "test-ui" "Run UI tests only"
	@printf "%-16s %s\n" "screenshots" "Auto-capture screenshots and generate App Store images"
	@printf "%-16s %s\n" "build-debug" "Build Debug for device without deploying"
	@printf "%-16s %s\n" "build-release" "Build Release for device without deploying"
	@printf "%-16s %s\n" "device-debug" "Build, install, and launch Debug on device"
	@printf "%-16s %s\n" "device-release" "Build and install Release on device"
	@printf "%-16s %s\n" "clean" "Clean the selected simulator scheme"
	@printf "%-16s %s\n" "list-devices" "List available iOS Simulators"
	@printf "%-16s %s\n" "list-schemes" "List workspace schemes"
	@printf "%-16s %s\n" "pods" "Install CocoaPods dependencies"
	@printf "\n"
	@printf "%s\n" "Defaults:"
	@printf "  WORKSPACE=%s\n" "$(WORKSPACE)"
	@printf "  SCHEME=%s\n" "$(SCHEME)"
	@printf "  CONFIGURATION=%s\n" "$(CONFIGURATION)"
	@printf "  SIMULATOR_NAME=%s\n" "$(SIMULATOR_NAME)"
	@printf "  SIMULATOR_OS=%s\n" "$(SIMULATOR_OS)"
	@printf "  DESTINATION=%s\n" "$(DESTINATION)"

build:
	$(XCODEBUILD) -configuration "$(CONFIGURATION)" build

test:
	$(XCODEBUILD) test

test-unit:
	$(XCODEBUILD) test -only-testing:memoTests

test-ui:
	$(XCODEBUILD) test -only-testing:memoUITests

screenshots:
	./scripts/generate-app-store-screenshots.sh

screenshots-themes:
	./scripts/generate-app-store-screenshots.sh --only-themes

screenshots-composite:
	./scripts/generate-app-store-screenshots.sh --skip-snapshot

build-debug:
	DEPLOY=0 ./scripts/build-debug.sh

build-release:
	DEPLOY=0 ./scripts/build-release.sh

device-debug:
	./scripts/build-debug.sh

device-release:
	./scripts/build-release.sh

clean:
	$(XCODEBUILD) clean

list-devices:
	xcrun simctl list devices available

list-schemes:
	xcodebuild -list -workspace "$(WORKSPACE)"

pods:
	pod install

ポイントは、難しい Makefile 技法を使っていないことだ。

WORKSPACE ?= memo.xcworkspace のようにデフォルト値を置き、必要なら実行時に上書きできる。

make build SCHEME=memo-Release CONFIGURATION=Release

このように、環境ごとの差し替えもできる。

そして .DEFAULT_GOAL := help にしておくと、単に make と打ったときにヘルプが出る。Makefile は便利だが、ターゲット名は忘れやすい。だから、忘れる前提で make help を最初から作っておく。

AIエージェントに作ってもらうと現実的になる

正直、この Makefile を自分で一項目ずつ調べながら作ると思うと、かなり大変で、あまりやりたくない。

xcodebuild の引数、実機デプロイ用スクリプト、スクリーンショット生成、help の整形、デバッグとリリースの切り替え。ひとつずつ詰めると、地味に時間がかかる。

しかし AI エージェントに任せると、かなりサクッと作れてしまう。

ここで面白いのは、AI エージェントが Makefile を作る側にも、Makefile を使う側にも回れることだ。

最初はこう頼む。

この iOS プロジェクトで、よく使う xcodebuild と実機デプロイのコマンドを Makefile にまとめてください。make help で一覧が出るようにしてください。

すると、AI エージェントが既存のワークスペース名、スキーム名、スクリプト構成を読みながら Makefile を作ってくれる。

その後はこう頼める。

変更後に make test を実行してください。

実機向けに make build-release が通るか確認してください。

実際には、make test などといちいち言わずとも「テストして」で通じることが多い。

AI が作った作業入口を、次の AI 作業でそのまま使えるようになる。これによって、開発作業に統一感が出るようになった。

これは小さいようで大きい。プロジェクト固有の作業手順が、自然言語のプロンプトではなく、リポジトリ内の実行可能な知識として残る。

MakefileはプロジェクトのREADMEより強い場合がある

README に「テストはこのコマンドで実行します」と書くのも大事だ。

ただ、README は読まれないことがある。管理を忘れて古くなることもある。書いてあるだけでは実行されない。

Makefile は、説明であると同時に実行入口でもある。

make help を見れば何ができるか分かる。make test を叩けば実際に走る。間違っていれば、その場で失敗する。

AI エージェントにとってもこれは扱いやすい。README を読んで手順を推測するより、Makefile のターゲットを見て実行するほうがブレにくい。

個人的には、AI 時代の Makefile は「人間と AI の共通操作盤」だと思っている。

書きすぎないほうが使いやすい

もちろん便利だからといって、Makefile に何でも詰め込むと逆に分かりにくくなる。

最初は、よく使うものだけでいい。

  • make help
  • make build
  • make test
  • make clean
  • make device-debug
  • make build-release

このくらいで十分役に立つ。

また、Makefile の中に長い処理を全部書くより、複雑な処理は scripts/ 配下のシェルスクリプトへ逃がし、Makefile は入口に徹するほうが読みやすい。

今回の例でも、実機ビルドやスクリーンショット生成は ./scripts/build-debug.sh./scripts/generate-app-store-screenshots.sh に任せている。Makefile は、それらを覚えやすい名前で呼び出しているだけだ。

このくらいの薄さがちょうどいい。

関連記事と参考リンク

まとめ

make は古い道具だが、AI エージェント時代になってむしろ便利さが増している。

理由は、プロジェクト固有の作業を短いコマンドにまとめ、人間と AI エージェントが同じ入口から実行できるようにするからだ。

iOS 開発でも、make device-debugmake device-releasemake testmake screenshots のようなターゲットを用意しておけば、Xcode を開かずに進められる作業がかなり増える。

AI エージェントに Makefile を作ってもらい、その Makefile を次の AI 作業で使う。これが回り始めると、GitHub Issue とターミナルだけで進む範囲が一気に広がる。

昔からある make を、AI 時代の開発操作盤として再発見した感じがある。