CircleCI | GO111MODULE=off go getかGo Modulesを使うか

thumnail
2020-04-16

Circle CIのテストのみで使用するGoのpackageをGo Moduleに含めておくか、CircleCIの中でgo getを実行するかの2つの選択肢が存在し、どっちが良いのか迷った。これを備忘録としてまとめておく。

Go Modulesの基本

go get とは

go get とはgoで書かれた実行ファイル、すなわちバイナリコードを$GOPATH/binに配置するコマンド。次のコマンドを叩いてみる。

$ go get -v golang.org/x/tools/cmd/goimports
get "golang.org/x/tools/cmd/goimports": found meta tag get.metaImport{Prefix:"golang.org/x/tools", VCS:"git", RepoRoot:"https://go.googlesource.com/tools"} at //golang.org/x/tools/cmd/goimports?go-get=1 ...

$GOPATH/binに実行ファイルが生成されたか確認してみる。

$ ls $GOPATH
bin  src
$ cd bin/
$ ls
goimports

想定通り、実行ファイル, goimportsが生成されていた。

go mod とは

golang 1.11からはModulesと呼ばれる依存モジュール管理ツールが標準で使えるようになった。主に次の2つの機能を持っている。

・依存モジュールの事前検知
・依存モジュールのバージョンアップ検知、バージョン固定

依存モジュールの情報はgo.mod, go.sumというファイルに記載される。これらのファイルをGitHubなどで管理することでチーム内の開発者で同様のversionのパッケージを使って開発を進めることができる。

go.modファイルは次のコマンドを叩くことで生成することができる。

$ go mod init 
go: creating new go.mod: module tmp

中身はこんな感じ。

$ cat go.mod 
module tmp

go 1.14

module tmpと記載されているのは現在いるディレクトリ、プロジェクトフォルダがtmpだからだ。

$ pwd
/go/src/tmp

ここでmain.goを生成して、次のコードを記述してみる。

package main

import (
  "github.com/ant0ine/go-json-rest/rest"
  "log"
  "net/http"
)

func main() {
  api := rest.NewApi()
  api.Use(rest.DefaultDevStack...)
  api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
    w.WriteJson(map[string]string{"Body": "Hello World!"})
  }))
  log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

package github.com/ant0ine/go-json-rest/restをimportする処理を書いた。ここでgo.sumの中身を確認してみると次のようになっていた。

$ cat go.sum 
github.com/ant0ine/go-json-rest v3.3.2+incompatible h1:nBixrkLFiDNAW0hauKDLc8yJI6XfrQumWvytE1Hk14E=
github.com/ant0ine/go-json-rest v3.3.2+incompatible/go.mod h1:q6aCt0GfU6LhpBsnZ/2U+mwe+0XB5WStbmwyoPfc+sk=

必要パッケージを追加するとファイルが生成・パッケージの依存関係が追加されていた。また、go.modにもimportするパッケージの情報が追加されていた。

$ cat go.mod 
module tmp

go 1.14

require github.com/ant0ine/go-json-rest v3.3.2+incompatible

go.modに書かれているパッケージをインストールするにはgo buildを叩けば良いらしい。

$ go build
go: downloading github.com/ant0ine/go-json-rest v3.3.2+incompatible

go getを叩いてパッケージのダウンロードを行った場合でもgo.mod, go.sumは更新される。適当にlintというパッケージをインストールしてみる。

$ go get golang.org/x/lint            
go: downloading golang.org/x/lint v0.0.0-20200302205851-738671d3881b
go: golang.org/x/lint upgrade => v0.0.0-20200302205851-738671d3881b
go: downloading golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7

go.mod, go.sumを確認してみる。

$ cat go.mod
module tmp

go 1.14

require (
  github.com/ant0ine/go-json-rest v3.3.2+incompatible
  golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
)
$ cat go.sum
github.com/ant0ine/go-json-rest v3.3.2+incompatible h1:nBixrkLFiDNAW0hauKDLc8yJI6XfrQumWvytE1Hk14E=
github.com/ant0ine/go-json-rest v3.3.2+incompatible/go.mod h1:q6aCt0GfU6LhpBsnZ/2U+mwe+0XB5WStbmwyoPfc+sk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

golang.org/x/lint というパッケージはざまざまなパッケージに依存していることがわかる。

使用していないpackageを削除する

既存のコードで呼ばれていないパッケージを削除する場合はgo mod tidyを叩けば良い。先ほど適当にインストールしたlintパッケージが削除されるかを確認してみる。

$ go mod tidy
$ cat go.sum 
github.com/ant0ine/go-json-rest v3.3.2+incompatible h1:nBixrkLFiDNAW0hauKDLc8yJI6XfrQumWvytE1Hk14E=
github.com/ant0ine/go-json-rest v3.3.2+incompatible/go.mod h1:q6aCt0GfU6LhpBsnZ/2U+mwe+0XB5WStbmwyoPfc+sk=
$ cat go.mod
module tmp

go 1.14

require github.com/ant0ine/go-json-rest v3.3.2+incompatible

main.goから呼ばれていない golang.org/x/lint はgo.mod, go.sumから削除されていることがわかる。

予めパッケージを追加しておく方法

今回の本題に戻る。CIの中でしか使用しないパッケージを予め.modにパッケージを追加した状態でCircleCIを実行する場合、次のメリットがある。

・バージョンが固定されるのでどの環境でも同じものを使っていると保証できる
・リポジトリが何に依存しているのか一覧で把握できる

go.sumにバージョンが指定されるので、誰がパッケージをインストールしても同じ状態になることが保証される。また

$ go list -m all

を叩くことでパッケージの依存関係を確認できる。しかし以下のデメリットもある。

・ソースコード中でそのパッケージを使用していない場合 go mod tidy したとき削除されるので、cliだけで使うツールの場合は工夫が必要

今回はまさにそれで、使うのがCIiだけで使うパッケージである。さらにローカルでは使わず、CircleCI内でしか使わない。なのでわざわざ前もってgo.modに登録して使うこの方法は適さないと感じた。

Circle CIの中でインストールする方法

予めパッケージを追加しておく方法ではなくてCI内でgetして使えば良いのではないのかと思った。この場合、次のメリットがある。

・コマンド1つでバイナリ生成できる

しかしここで一点ハマった点があった。僕のチームの設定では意図せず依存関係が変更されないようにデフォルトではgo getを叩くとエラーが起きるように設定されていたのだ。これを回避するためには

GO111MODULE=off go get xxx

とgo getの前にGO111MODULE=off を付与する必要があった。

KATUBLO

Copyright since 2018 KATUO All Rights Reserved.