🔨

Webアプリ開発者に薦めたいMakefileの世界

2024/02/15に公開

はじめに

Makefileというものすごく便利なツールを発見しました。

Makefileはなんぞや?ということをChatGPTに質問してみました。

◎僕
Makefileについて説明してください。

◎ChatGPT
Makefileは、プログラムのビルドプロセスを自動化するためのファイルであり、makeというツールによって使用されます。
プログラムのコンパイルやリンクなどのビルド手順を簡素化し、効率化する目的で設計されています。
Makefileの使用は、ソフトウェア開発において時間を節約し、繰り返し発生するタスクを一貫して正確に実行するために非常に役立ちます。

主な機能と目的
・自動化: 複雑なビルド手順を自動化し、単一のコマンドで多数のコンパイルとリンク手順を実行できます。
・依存性管理: ファイル間の依存関係を追跡し、変更されたファイルのみを再コンパイルすることでビルド時間を短縮します。
・再現性: 開発者間で一貫したビルド環境を提供し、プロジェクトの再現性を高めます。

以上のことからも分かる通り、基本的にビルド(コンパイル)といった作業の自動化関係で使用されることが多く、C, C++といった言語でよく使用されているそうです。

そのため、Webアプリ開発者である僕には縁がないものだと思ってました。(そもそも知らなかった)

しかし「再現性」という点で、このMakefileはC, C++に限らずいろんな開発シーンで応用できるものであることを知りました。

その便利さについて、これから紹介させてください。

前提

  • MacOSユーザー(XCodeのコマンドラインツールインストール済)
    • もし未インストールの場合は、ターミナルから xcode-select --install でインストール可能です。
  • Dockerを使用したプロジェクト

結論

Makefileを使うと、プロジェクト単位でよく使用する便利コマンドを簡単に共有/利用できるようになります。

こういったコマンドを便利にまとめる行為は、.zshrc 等で定義して利用することが多いと思いますが、プロジェクト単位で共有出来ないという欠点があります。

ナレッジとして共有し、各々が設定するしかないので少し不便があります。

そんな不便さを解消してくれるのがこの Makefile です。

今回紹介するのは以下のコマンドです。

  • よく使用する docker compose コマンドを簡単に呼び出す
  • 差分ファイルのみを抽出して静的解析/自動整形をかける

準備

Makefileを作成

とても簡単です。

Makefile というファイルを作成するだけです。

Webアプリを開発する想定なので、そのWebアプリプロジェクトのルートディレクトリに作成することをお薦めします。

プロジェクト内に Makefile を作成することで Gitの監視対象になり、チーム全体で共有することが可能になります。

console
$ touch {プロジェクトパス}/Makefile

Makefileの書き方

作成したファイルに定義していきます。

Makefileは以下の基本的な構成から成り立っています。

  • ターゲット: 実行するタスクや生成するファイルの名前。ターゲットは依存関係のリストの前に記述されます。
  • 依存関係: ターゲットをビルドまたは実行する前に最新状態にしておく必要があるファイルや他のターゲット。
  • レシピ: ターゲットを生成するために実行されるコマンドのシーケンス。レシピはタブ文字で始まる行に記述されます。

更に、オプション的な機能もあります。

  • .PHONY: 実際のファイル名ではなく、ターゲット名を指定することをmakeに伝えます。これにより、ファイル名とターゲット名が衝突することを防ぎます。
  • .SILENT: 指定されたターゲットのコマンド実行時にコマンド自体を出力しないようにします。つまり、実行されるコマンドの詳細を非表示にします。

.PHONY の説明が難しいですが、基本的に付けておけばOKです。

ビルド/コンパイルが関わる場合は、このオプションを付けるか付けないかを適切に判断する必要があるようです。
しかし、今回のような便利コマンドをまとめるだけの場合(実際にファイルを生成しないタスクの場合)は、むしろ付けることが推奨されるようなので、取りあえず付けちゃいましょう。

以下のイメージです。

Makefile
.PHONY: {ターゲット1}
.SILENT: {ターゲット1}
{ターゲット1}: {依存関係1} {依存関係2}
	{レシピ1}

.PHONY: {ターゲット1}
.SILENT: {ターゲット1}
{ターゲット2}: {依存関係 3}
	{レシピ2}

# ...続く

また今回、便利なコマンドをまとめるだけでビルドに関連することは一切書きません。
そのため、「依存関係」の部分は今回使用しませんのでご了承ください。

よく使用する docker compose コマンドを登録

Makefile
# イメージビルド&コンテナ起動
.PHONY: up
.SILENT: up
up:
	docker compose up -d --build

# コンテナ停止&削除
.PHONY: down
.SILENT: down
down:
	docker compose down

# コンテナ再起動
.PHONY: restart
.SILENT: restart
down:
	docker compose restart

# コンテナ停止
.PHONY: stop
.SILENT: stop
down:
	docker compose stop

# イメージ、コンテナ、ボリュームの完全削除
.PHONY: clear
.SILENT: clear
clear:
	docker compose down --rmi all --volumes --remove-orphans

# コンテナ接続
.PHONY: {任意のコンテナ名}
.SILENT: {任意のコンテナ名}
	docker compose exec {任意のコンテナ名} bash

差分ファイルのみを抽出して静的解析/自動整形をかける

私がPHPエンジニアということもあり、静的解析にPHPStan、自動整形にLaravel Pintを使用することを想定して定義します。

呼び出しコマンド部分等は、各々が利用するものに合わせてご変更ください。

また、以下のようなディレクトリ構造を想定したプロジェクトですので、その辺も合わせてご調整ください。

ディレクトリ構成
- laravel(Laravelプロジェクト)
  - vendor
  - artisan
  - composer.json
  - composer.lock
  - phpstan.neon
  - phpunit.xml
  - pint.json
- Makefile
- docker-compose.yml
Makefile
.PHONY: pint
.SILENT: pint
pint:
	# git diffを使用して差分の.phpファイルを抽出。
	# ファイルパスの先頭に `laravel/` が付いているので、その部分のみを削除したファイル名一覧を生成。
	# それを CHANGED_FILES という変数に追加。
	CHANGED_FILES=$$(git diff --name-status HEAD | grep -E '^[AM].*\.php$$' | cut -f2 | sed 's/^laravel\///') && \
	# CHANGED_FILES 内にファイル名が存在するかどうか?
	if [ -n "$$CHANGED_FILES" ]; then \
		# 存在する場合、そのファイル名を対象として pint を実行。以下のコマンドのイメージになる。
		#   php artisan ./vendor/bin/pint -v {ファイル名1} {ファイル名2} {...}
		docker compose exec -e CHANGED_FILES="$$CHANGED_FILES" php bash -c 'php artisan ./vendor/bin/pint -v $$CHANGED_FILES'; \
	else \
		# 存在しない場合は、スキップさせる
		echo "No PHP files changed in specified paths. Skipping pint."; \
	fi

.PHONY: stan
.SILENT: stan
stan:
	# git diffを使用して差分の.phpファイルを抽出。
	# ファイルパスの先頭に `laravel/` が付いているので、その部分のみを削除したファイル名一覧を生成。
	# それを CHANGED_FILES という変数に追加。
	CHANGED_FILES=$$(git diff --name-status HEAD | grep -E '^[AM].*\.php$$' | cut -f2 | sed 's/^laravel\///') && \
	# CHANGED_FILES 内にファイル名が存在するかどうか?
	if [ -n "$$CHANGED_FILES" ]; then \
		# 存在する場合、そのファイル名を対象として pint を実行。以下のコマンドのイメージになる。
		#   ./vendor/bin/phpstan analyze -c phpstan.neon --memory-limit=1G {ファイル名1} {ファイル名2} {...}
		docker-compose exec -e CHANGED_FILES="$$CHANGED_FILES" php bash -c 'php artisan ./vendor/bin/phpstan analyze -c phpstan.neon --memory-limit=1G $$CHANGED_FILES'; \
	else \
		# 存在しない場合は、スキップさせる
		echo "No PHP files changed in specified paths. Skipping PHPStan."; \
	fi

使用方法

ここまででMakefileの定義が完了したら、実際に使ってみましょう。

使い方は、Makefileが配置されているディレクトリまで移動し、以下のように実行します。

console
$ make {ターゲット名}

実例で行くと、

console
$ make pint

のように利用します。

終わりに

今回は MacOS 環境を想定しましたが、 Linux/Unix系OSには make がデフォルトでインストールされていますし、Windowsユーザーでも開発時はWSL(Linux)を利用すると思うので、基本的にどのユーザーでも利用できると思います。

また、Docker環境を想定しましたが、これも同じくコマンド部分を docker compose exec -e ではなく、直接ローカルのパスを指定してコマンド実行すればOKなので、こちらも含めてどのユーザーでも利用できると思います。

特定の言語に囚われず、広く活躍できる便利ツールなので、是非これを機にご活用ください🙏

GitHubで編集を提案

Discussion