$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

Goで書いたツールの依存管理をdepからGo Modulesに移行した

昔作った notify-issues-to-slackの依存モジュールはdepのままで管理していたが、勉強がてらGo Modulesに移行することにした。

参考にした資料

やったこと

Pull Requestは https://github.com/shibayu36/notify-issues-to-slack/pull/10

  • コード上で使っている依存モジュールをgo.modで管理するように
  • ツール郡をgo.modで管理するように
  • Makefileなどを修正する

の三段階で行った。

コード上で使っている依存モジュールをgo.modで管理するように

まずはコード上で使っている依存モジュールをgo.modで管理するように変更する。golintなどのツール群は後回し。

go mod initする。

$ go mod init github.com/shibayu36/notify-issues-to-slack
go: creating new go.mod: module github.com/shibayu36/notify-issues-to-slack
go: copying requirements from Gopkg.lock
go: converting Gopkg.lock: stat github.com/karrick/tparse@v2.6.1: github.com/karrick/tparse@v2.6.1: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2
go: converting Gopkg.lock: stat github.com/google/go-github@v21.0.1: github.com/google/go-github@v21.0.1: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v21

するとtparseとgo-githubinvalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v21 というエラーが出たので、次にこれを解決する。

Go Modulesの仕様として、v2以上のモジュールをimportする時はimport path自体を変えるという仕様があるようだ。Go ModulesドキュメントのSemantic Import Versioningという章が参考になる。

これに従いつつ、バージョンはGopkg.lockに載っていたものを使うようにimport pathを変えていく。変更はこちら

ここまで出来たらdep用のファイルを全て削除し、go buildすることでgo.modとgo.sumが更新される

$ rm Gopkg.toml Gopkg.lock
$ rm -fr vendor/
$ go build

これでコード上で使っている依存モジュールをgo.modで管理できるようになった。

ツール郡をgo.modで管理する

さらにgolintなどのツール郡もgo.modに載せていく。Goモジュールでツールもバージョン管理する - Plan 9とGo言語のブログを参考にする。変更点はこのcommit

まずツール郡はtools.goにimportだけするというのがベストプラクティスのようだ。これをしておかないと、go getでツールをインストールしてgo.modに載せても、go mod tidyなどを実行した時に消されてしまう。

tools.go

// +build tools

package main

import (
    _ "github.com/Songmu/ghch/cmd/ghch"
    _ "github.com/Songmu/goxz/cmd/goxz"
    _ "github.com/mattn/goveralls"
    _ "github.com/tcnksm/ghr"
    _ "github.com/x-motemen/gobump/cmd/gobump"
    _ "golang.org/x/lint/golint"
)

この状態でまたgo buildすると最新版がインストールされ、ツール郡もgo.modで管理できるようになった。

Makefileを修正する

最後にMakefiledepを使っていたところを修正する。変更点は https://github.com/shibayu36/notify-issues-to-slack/pull/10/files#diff-b67911656ef5d18c4ae36cb6741b7965

  • GO111MODULE=onにしておく
  • go testやgo buildで勝手に依存モジュールを取得してくれるようになったので、depsのようなタスクは必要なくなった
  • ツール郡はgo installで入れることで、go.modで指定されたバージョンをインストールする

これでdepからgo.modへの移行が完了した。

学んだこと

  • ツール郡の管理はtools.goにimport & go installで行う
  • Go ModulesではMinimal version selection algorithmという考え方を取っている
  • v2以上の場合import pathを変える
  • go.modのindirectというのは直接依存でない、依存モジュールの依存という意味
  • go.modのincompatibleというのはSemantic Import Versioningに従ってない場合についている
  • go get (module名)@(バージョン名) みたいなのでうまく取れなかった時は、go proxyに登録されているモジュールのバージョンを確認してみると良い

go proxyに登録されているモジュールのバージョンを確認する方法はこんな感じ。

$ curl 'https://proxy.golang.org/github.com/google/go-github/v21/@v/list'
v21.0.1
v21.0.0
$ curl 'https://proxy.golang.org/github.com/karrick/tparse/v2/@v/list'
v2.3.0
v2.2.0
v2.6.0
v2.1.2
v2.3.4
v2.1.3
v2.4.0
v2.7.1
v2.8.0
v2.3.2
v2.4.1
v2.7.0
v2.8.1
v2.1.0
v2.3.3
v2.1.4
v2.3.1
v2.4.2
v2.6.1
v2.1.1
v2.1.5
v2.5.0

まとめ

depからGo Modulesへの移行を試したので、やったことを記録に残しておいた。Go Modulesについて知見が多少深まった気はする。