Golang を使うなら Makefile を恐れるな

Golang: Don’t afraid of makefiles

translated on

最近 Golang を使っています。開発中、私は go buildgo test を繰り返し手入力で実行するのに慣れてしまいました。これはついついやってしまう、私の悪い癖でした。引数がないようなシンプルなコマンドであれば、それほど辛くありません。しかし、タスクが複雑になってくれば、当然苦痛になってきます。逃げ道となりえる選択肢はほとんどありません。bash スクリプトは、あなたの仕事の役に立つでしょう。しかし、私としては makefile が役に立つと言いたいです。make ツールはこういった理由から生まれたものであり、 makefile には普段行う作業をまとめておくことができるからです。私は make の教祖になって、うまい書き方を教えたりすることはできませんが、今回の記事では、私のプロジェクトでよく使っている makefile をまとめておきました。では、やってみましょう。

# Go パラメータ
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
BINARY_NAME=mybinary
BINARY_UNIX=$(BINARY_NAME)_unix

all: test build
build:
    $(GOBUILD) -o $(BINARY_NAME) -v
test:
    $(GOTEST) -v ./...
clean:
    $(GOCLEAN)
    rm -f $(BINARY_NAME)
    rm -f $(BINARY_UNIX)
run:
    $(GOBUILD) -o $(BINARY_NAME) -v ./...
    ./$(BINARY_NAME)
deps:
    $(GOGET) github.com/markbates/goth
    $(GOGET) github.com/markbates/pop


# クロスコンパイル
build-linux:
    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v
docker-build:
    docker run --rm -it -v "$(GOPATH)":/go -w /go/src/bitbucket.org/rsohlich/makepost golang:latest go build -o "$(BINARY_UNIX)" -v

いつでも DRY 原則を胸に留めておきましょう。よく使うコマンドと変数を、ファイルの先頭で宣言しておくと便利です。そして、ファイル内でそれを参照してください。

# 基本的な Go コマンド
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get

# バイナリの名前
BINARY_NAME=mybinary
BINARY_UNIX=$(BINARY_NAME)_unix

makefile のターゲットは、: の前に定義されています。つまり、上記の例で言うと、allbuildtestrun などがターゲットにあたります。コマンドラインから実行時、make の後にパラメータとしてターゲットが与えられていれば、 make ツールによりそれが実行されます。パラメータを指定しない場合、最初のタスクが実行されます。この例では、all が実行されます。

> make run # 特定のタスク実行
> make # make が "all" を実行

基本コマンド

makefile の中で最も重要なのが build ステップです。変数 $(GOBUILD) を使うことで、go build コマンドが実行され、バイナリの名前は -o $(BINARY_NAME) によって定義されます。また -v で verbose モードに切り替えると便利です。verbose モードでは、現在ビルド中のパッケージを見ることができます。

build:
    $(GOBUILD) -o $(BINARY_NAME) -v # "go build -o mybinary -v" と同じ

早い話、私たちは怠け者ですよね。run ターゲットはこのようになります。このターゲットはバイナリをビルドし、アプリケーションを実行します。

run:
    $(GOBUILD) -o $(BINARY_NAME) -v ./...
    ./$(BINARY_NAME)

当然、テストコマンドも makefile に含まれているべきでしょう。個人的には、常に verbose モードにしておき、テストの実行がうまく動いているか見ておくようにしています。

test:
    $(GOTEST) -v ./...

プロジェクトで CI / CD を使っている場合や、または一貫性を保つためにも、パッケージで使う依存関係のリストを持っておくと良いです。これは deps タスクによって行われ、go get コマンドで、必要な依存関係をすべて取得してくれます。

deps:
    $(GOGET) github.com/markbates/goth
    $(GOGET) github.com/markbates/pop

一連の便利なコマンドをひとまとめにするために、clean コマンドが makefile に入っています。rm -f コマンドはバイナリを削除するためのものです。$(BINARY_XXX) 変数の中身に一致するバイナリを削除しています。別のクリーンアップコマンドが、この make セクションの一部である場合が多いです。

clean:
    $(GOCLEAN)
    rm -f $(BINARY_NAME)
    rm -f $(BINARY_UNIX)

クロスコンパイル用のコマンド

もしプロジェクト開発時のプラットフォーム以外の場所で実行する場合、クロスコンパイル用のコマンドを組み込んでファイルを作成しておくと便利です。私は普段はコンテナ内の Linux 上でバイナリを実行するので、 makefile には Linux ビルドが含まれています。

build-linux:
    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v

もし C バインディングを使っている場合、少し詰まってしまうかもしれません。CGO の問題点は、特定のプラットフォームで gcc との互換性が必要だということです。OSX や Windows 上で開発が行われた場合、Linux 用にコンパイルできるよう、gcc をビルドする必要があります。少なくとも私にとって、C 言語のコードをクロスコンパイルするための設定は、OSX においてあまり簡単だとは思えません。CGO が必要な場合は、Docker イメージが、Linux ビルドの作成をするのに最適な方法でしょう。唯一求められるのは、Docker のインストールだけです。

docker-build:
    docker run --rm -it -v "$(GOPATH)":/go -w /go/src/bitbucket.org/rsohlich/makepost golang:latest go build -o "$(BINARY_UNIX)" -v

まとめ

このようにすることで、開発プロセスをより効果的に、サクサク進めていくことができます。今回の記事で使われている makefile は、gist から手に入ります。コメントや質問は、遠慮なくどうぞ。喜んでお返事します。ではやってみてください!