Go言語開発での makeコマンド と Makefile
Go言語の開発ではmake
コマンドをタスク自動化ツールとしてよく使います。
よく使うコマンド、自動化したいタスクをMakefile
に記述しておくと、開発に使う複雑なコマンドをすぐに実行したり、チームで共有出来ます。
Makefileに対して、難しいイメージを持っているかもしれませんが、超基本のMakefileの書き方はとてもシンプルなものです。
この記事の目的
- Makefileの超基本がわかる
- Go言語開発のタスク自動化ツールとしてのMakefileの書き方がわかる
前提知識
- シェルスクリプト についての知識
書き方
書き始める前の準備
EditorConfigを設定して、タブ / スペース によるインデントのトラブルに会わないようにしましょう。
公式サイトにあなたのエディタが、EditorConfigをサポートしているか、プラグインの追加が必要かの一覧があります。
エディタのアイコンをクリックすればそれぞれのエディタ用のインストールページに飛びます。
.editorconfig
ファイルを次のように設定して、プロジェクトのルートディレクトリに置きます。
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
[*.go]
indent_style = tab
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 4
この設定で、Go言語とMakefileのどちらもハードタブ 1 が適用されます。
Makefileを作成する
プロジェクトのルートディレクトリにMakefile
というファイルを作成します。
拡張子は不要です。
コメントの書き方
コメントは行頭に #
を書きます。
# これはコメントです
タスクの実行方法
make
コマンドの引数にタスク名を渡します。
以下は task
という名前のタスクを実行します。
$ make task
タスクを定義する
タスク定義のインデントは必ずタブで行います。
一番シンプルなタスク定義
task:
command
実行したコマンドを表示したくない場合
task:
@command
タスク定義が複数のコマンドの場合
task:
command1
command2
ワンライナーで書く
Makefileの記法というより、シェルスクリプトの記法です。
task:
command1 && command2
task:
command1 ; command2 ;\
command3
タスク定義が複数ある場合
task1:
command1
task2:
command2
実行する場合は、タスク定義の名前を指定します。
$ make task2
タスク定義名が、プロジェクトのファイル名やディレクトリ名と同じ場合
makeの仕様でタスク定義名と同じファイルが存在している場合はタスクが実行されません。
これを回避するためには、.PHONY: task
をタスク定義に付けます。
.PHONY: task
task:
command
あるタスク定義実行の前に別のタスク定義を依存タスクとして実行したい場合
go build
の前に go get
や go test
を実行するなどが出来ます。
task1:
echo "task1実行"
task2: task1
echo "task2実行"
task3: task1 task2
echo "task3実行"
$ make task3
echo "task1実行"
task1実行
echo "task2実行"
task2実行
echo "task3実行"
task3実行
なお、task3の依存タスク定義を辿ると、 task1 が2回登場しますが、無駄な実行はされません。
自身のMakefileに定義してある別のタスク定義を実行したい場合
task1:
echo "task1実行"
task2:
$(MAKE) task1
echo "task2実行"
$ make task2
/Library/Developer/CommandLineTools/usr/bin/make task1
echo "task1実行"
task1実行
echo "task2実行"
task2実行
$(MAKE)
ではなく、 make
で書いてもこのケースでは動作しますが、
make task2
に付けたオプションも引き継がせるためには$(MAKE)
を使います。
変数を使う
通常のシェルスクリプト変数(環境変数も含む)に加え、Makefile変数があります。
Makefile変数
固定値を変数に設定する
Makefile
に 変数名=値
の形式でMakefile変数を定義します。
VAR=hello make
Makefile変数をタスク定義で使うには、変数名を$()
で囲みます。2
VAR=hello make
hello:
echo $(VAR)
$ make hello
echo hello make
hello make
シェル実行結果をMakefile変数に入れたい場合
$(shell command)
を使用します。
NOW=$(shell date)
task:
echo $(NOW)
$ make task
echo Wed Oct 10 20:24:20 JST 2018
Wed Oct 10 20:24:20 JST 2018
シェルスクリプト変数・環境変数の扱い
シェルスクリプト変数・環境変数の参照
通常、シェルスクリプト変数・環境変数は $変数名
または ${変数名}
で参照出来ますが、MakefileではMakefile変数としての解釈が優先されるため、
$${変数名}
などのように$$
を重ねて、 $
をエスケープする必要があります。
task:
VAR="hello make" && echo $$VAR
$ make task
VAR="hello make" && echo $VAR
hello make
シェルスクリプト変数・環境変数の書き換え
タスク実行中に環境変数を書き換えたい場合は、コマンド実行が1行終わるごとに環境変数がmakeコマンド実行時に戻ることに注意します。
例えば、export
で一時的に環境変数を書き換えて何かをするタスクを複数コマンドに分けて記述したい場合は、 ;\
で改行してコマンドを複数書きます。
task1:
export VAR="この値は消えてしまいます"
echo VAR=$${VAR}
task2:
export VAR="この値は残ります" ;\
echo VAR=$${VAR}
$ make task1
export VAR="この値は消えてしまいます"
echo VAR=${VAR}
VAR=
$ make task2
export VAR="この値は残ります" ;\
echo VAR=${VAR}
VAR=この値は残ります
サブディレクトリのMakefileのタスク定義を実行する
sub-task:
echo "hello"
task:
make -C subdir sub-task
make実行時に変数の値を渡す
make task 変数名=値
の形で変数を渡すことができます。
Makefile変数とシェルスクリプト変数の両方が上書きされます。
VAR="これは上書きされる"
task:
echo $(VAR) $$VAR
$ make task VAR=hello
echo hello $VAR
hello hello
応用パターンの紹介
シェルスクリプトのifを使う
golintがインストールされていなければインストールする例です。
install-golint:
@if ! type golint; then go get -u golang.org/x/lint/golint ; fi
複数行で書くこともできます。
install-golint:
@if ! type golint; \
then go get -v -u golang.org/x/lint/golint ; \
fi
Go言語情報の取得
go version, GOOS, GOARCHを取得してMakefile変数に入れる例です。
GOVERSION=$(shell go version)
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
go test -v の結果に色をつける
go test -v
の結果は色気がないですが、sed
とANSIカラーで無理やり色をつけてみる例です。
# ANSI color
RED=\033[31m
GREEN=\033[32m
RESET=\033[0m
COLORIZE_PASS=sed ''/PASS/s//$$(printf "$(GREEN)PASS$(RESET)")/''
COLORIZE_FAIL=sed ''/FAIL/s//$$(printf "$(RED)FAIL$(RESET)")/''
test:
go test -v ./... | $(COLORIZE_PASS) | $(COLORIZE_FAIL)
コマンドをテンプレート化して使い回す
go build コマンドを開発環境用と本番環境用のパラメータ切り替えして使い回すことを想定した例です。
ビルドタグも切り替えしています。
BUILD_TAGS_PRODUCTION='production'
BUILD_TAGS_DEVELOPMENT='development unittest'
build-base:
go build -o $(BIN_NAME) -tags '$(BUILD_TAGS) netgo' -installsuffix netgo -ldflags '-s -w' main.go
build-development:
$(MAKE) build-base BUILD_TAGS=$(BUILD_TAGS_DEVELOPMENT) BIN_NAME=dev
build-production:
$(MAKE) build-base BUILD_TAGS=$(BUILD_TAGS_PRODUCTION) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 BIN_NAME=prod
lintタスクを定義する
シェルスクリプトのコマンド置換$()
を使っていますが、 $
をエスケープして、$$()
とする必要があります。
lint:
golint -set_exit_status $$(go list ./...)
go vet ./...
終わりに
makeコマンド、 Makefileの仕様はここで紹介したもの以外にもたくさんあり、
私自身も使いこなせていない・よくわかっていない機能があります。
ですが、Go言語開発のお供のタスクランナーとして、よく使うコマンドを登録するくらいの用途であればMakefileは怖いものではありません。
どんどんタスクを登録して開発を便利にしていきましょう。