Edited at

Go言語開発を便利にするMakefileの書き方


Go言語開発での makeコマンド と Makefile

Go言語の開発ではmakeコマンドをタスク自動化ツールとしてよく使います。

よく使うコマンド、自動化したいタスクをMakefileに記述しておくと、開発に使う複雑なコマンドをすぐに実行したり、チームで共有出来ます。

Makefileに対して、難しいイメージを持っているかもしれませんが、超基本のMakefileの書き方はとてもシンプルなものです。


この記事の目的


  • Makefileの超基本がわかる

  • Go言語開発のタスク自動化ツールとしてのMakefileの書き方がわかる


前提知識


  • シェルスクリプト についての知識


書き方


書き始める前の準備

EditorConfigを設定して、タブ / スペース によるインデントのトラブルに会わないようにしましょう。

公式サイトにあなたのエディタが、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というファイルを作成します。

拡張子は不要です。


コメントの書き方

コメントは行頭に # を書きます。


Makefile

# これはコメントです



タスクの実行方法

makeコマンドの引数にタスク名を渡します。

以下は task という名前のタスクを実行します。


make実行

$ make task



タスクを定義する

タスク定義のインデントは必ずタブで行います。


一番シンプルなタスク定義


Makefile

task:

command


実行したコマンドを表示したくない場合


Makefile

task:

@command


タスク定義が複数のコマンドの場合


Makefile

task:

command1
command2


ワンライナーで書く

Makefileの記法というより、シェルスクリプトの記法です。


Makefile

task:

command1 && command2


Makefile

task:

command1 ; command2 ;\
command3


タスク定義が複数ある場合


Makefile

task1:

command1
task2:
command2

実行する場合は、タスク定義の名前を指定します。


make実行

$ make task2



タスク定義名が、プロジェクトのファイル名やディレクトリ名と同じ場合

makeの仕様でタスク定義名と同じファイルが存在している場合はタスクが実行されません。

これを回避するためには、.PHONY: task をタスク定義に付けます。


Makefile

.PHONY: task

task:
command


あるタスク定義実行の前に別のタスク定義を依存タスクとして実行したい場合

go build の前に go getgo test を実行するなどが出来ます。


Makefile

task1:

echo "task1実行"

task2: task1
echo "task2実行"

task3: task1 task2
echo "task3実行"



make実行結果

$ make task3

echo "task1実行"
task1実行
echo "task2実行"
task2実行
echo "task3実行"
task3実行

なお、task3の依存タスク定義を辿ると、 task1 が2回登場しますが、無駄な実行はされません。


自身のMakefileに定義してある別のタスク定義を実行したい場合


Makefile

task1:

echo "task1実行"

task2:
$(MAKE) task1
echo "task2実行"



make実行結果

$ make task2

/Library/Developer/CommandLineTools/usr/bin/make task1
echo "task1実行"
task1実行
echo "task2実行"
task2実行

$(MAKE) ではなく、 make で書いてもこのケースでは動作しますが、

make task2 に付けたオプションも引き継がせるためには$(MAKE)を使います。


変数を使う

通常のシェルスクリプト変数(環境変数も含む)に加え、Makefile変数があります。


Makefile変数


固定値を変数に設定する

Makefile変数名=値 の形式でMakefile変数を定義します。


Makefile

VAR=hello make


Makefile変数をタスク定義で使うには、変数名を$()で囲みます。2


Makefile

VAR=hello make

hello:
echo $(VAR)



make実行結果

$ make hello

echo hello make
hello make


シェル実行結果をMakefile変数に入れたい場合

$(shell command) を使用します。


Makefile

NOW=$(shell date)

task:
echo $(NOW)


make実行結果

$ make task

echo Wed Oct 10 20:24:20 JST 2018
Wed Oct 10 20:24:20 JST 2018


シェルスクリプト変数・環境変数の扱い


シェルスクリプト変数・環境変数の参照

通常、シェルスクリプト変数・環境変数は $変数名 または ${変数名} で参照出来ますが、MakefileではMakefile変数としての解釈が優先されるため、

$${変数名} などのように$$を重ねて、 $をエスケープする必要があります。


Makefile

task:

VAR="hello make" && echo $$VAR


make実行結果

$ make task

VAR="hello make" && echo $VAR
hello make


シェルスクリプト変数・環境変数の書き換え

タスク実行中に環境変数を書き換えたい場合は、コマンド実行が1行終わるごとに環境変数がmakeコマンド実行時に戻ることに注意します。

例えば、exportで一時的に環境変数を書き換えて何かをするタスクを複数コマンドに分けて記述したい場合は、 ;\ で改行してコマンドを複数書きます。


Makefile

task1:

export VAR="この値は消えてしまいます"
echo VAR=$${VAR}

task2:
export VAR="この値は残ります" ;\
echo VAR=$${VAR}



make実行結果

$ make task1

export VAR="この値は消えてしまいます"
echo VAR=${VAR}
VAR=

$ make task2
export VAR="この値は残ります" ;\
echo VAR=${VAR}
VAR=この値は残ります



サブディレクトリのMakefileのタスク定義を実行する


サブディレクトリ./subdir/Makefile

sub-task:

echo "hello"


ルートディレクトリMakefile

task:

make -C subdir sub-task


make実行時に変数の値を渡す

make task 変数名=値 の形で変数を渡すことができます。

Makefile変数とシェルスクリプト変数の両方が上書きされます。


Makefile

VAR="これは上書きされる"

task:
echo $(VAR) $$VAR


make実行結果

$ make task VAR=hello

echo hello $VAR
hello hello


応用パターンの紹介


シェルスクリプトのifを使う

golintがインストールされていなければインストールする例です。


Makefile

install-golint:

@if ! type golint; then go get -u golang.org/x/lint/golint ; fi

複数行で書くこともできます。


Makefile

install-golint:

@if ! type golint; \
then go get -v -u golang.org/x/lint/golint ; \
fi


Go言語情報の取得

go version, GOOS, GOARCHを取得してMakefile変数に入れる例です。


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 コマンドを開発環境用と本番環境用のパラメータ切り替えして使い回すことを想定した例です。

ビルドタグも切り替えしています。


Makefile

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タスクを定義する

シェルスクリプトのコマンド置換$()を使っていますが、 $ をエスケープして、$$()とする必要があります。


Makefile

lint:

golint -set_exit_status $$(go list ./...)
go vet ./...


終わりに

makeコマンド、 Makefileの仕様はここで紹介したもの以外にもたくさんあり、

私自身も使いこなせていない・よくわかっていない機能があります。

ですが、Go言語開発のお供のタスクランナーとして、よく使うコマンドを登録するくらいの用途であればMakefileは怖いものではありません。

どんどんタスクを登録して開発を便利にしていきましょう。





  1. インデントにタブ文字を使うこと。タブの代わりにスペースを2個、または4個入れてインデントすることを「ソフトタブ」と言います。 



  2. ${} で囲んでも$()と同じ動きになりますが、シェルスクリプトの変数と紛らわしくなるため、$()のみを使用します。