朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

Go modulesの話

朝日ネット開発部のmuratamです。 先日Go 1.12もリリースされたことなので、今回はGo modulesについて書きます。

これまでのGoのパッケージ管理

これまでは環境変数GOPATHの下にソースコード等を配置し、そこから使用するパッケージを探して使用するようにしていました。

GitHubなどからgo getしたパッケージは$GOPATH/srcの下にダウンロードされ、 Goのソースの中でimport文を書いて使います。 自分で作るライブラリもGOPATHの下に作っておけば同じように$GOPATH/srcからの相対パスでimportができます。

例えばgo get github.com/foo/bar とすれば $GOPATH/src/github.com/foo/bar にダウンロードされ、import "github.com/foo/bar"で使うことができます。

dep

depはGoでパッケージの依存関係を自動で解決して管理するソフトウェアのひとつです。 Goでは<パッケージのルート>/vendor/をimport pathとみなすことができるので、 vendor/ にパッケージを保存して特定のバージョンを固定して使うことができます(vendoring)。

Modulesの導入

これまでのGOPATHに代わってパッケージ管理する仕組みとしてGo 1.11からmodulesが追加されました。 今後はこちらに移っていく意向で、modulesは1.13 (2019年8月リリース予定)でデフォルトになる模様です。

moduleを使うときは、go.modファイルをGoプログラムのルートディレクトリに置きます。 go.modファイルではモジュール名の宣言と、依存モジュールを記述することができます。

go buildなどモジュールのimportが必要になれば$GOPATH/pkg/mod/以下にバージョン別に整理されてダウンロードされ、参照できるようになります。

自分のプログラム内でimportするときもGOPATHの下で作業をしなくてもモジュール名を使ってimportができます。

go.modには依存パッケージのバージョンを書いて使用するライブラリのバージョンを指定することができます。 これによりdepなど既存のパッケージ管理ツールを置き換えることになりそうです。 実際、go.modを使うときはvendor/ 以下にあるパッケージを見ない挙動になっているようです。

使ってみる

ここではgo 1.11.5を使います。

$ go version
go version go1.11.5 linux/amd64

GOPATHの外でディレクトリを作ります。 1

$ mkdir newmod
$ cd newmod

go mod init <module名>でmodule名が設定されたgo.modファイルを作ることができます。

$ go mod init newmod
go: creating new go.mod: module newmod
$ cat go.mod
module newmod

golang.org/x/text/width を使って全角文字を半角文字に変換するプログラムを作ってみます。 別のパッケージ libに、変換を行う関数を置きます。

main.go

package main

import (
    "fmt"
    "newmod/lib"
)

func main() {
    s1 := "abc123-~アイウ|"
    s2 := "abc123-~アイウ|"
    fmt.Println(s1)
    ns := lib.Convert(s2)
    fmt.Println(ns)
}

lib/lib.go

package lib

import "golang.org/x/text/width"

func Convert(s string) string {
    return width.Narrow.String(s)
}

main.goから、newmod/lib という名前でimportしています。 これまでは$GOPATH/src/newmod/libにあるものをimportしていましたが、 go.modファイルのある場所からlibを探してimportしています。

go buildすると自動でimport文を解決して必要なパッケージをダウンロードします。

$ go build newmod
go: finding golang.org/x/text/width latest
go: finding golang.org/x/text v0.3.0
go: downloading golang.org/x/text v0.3.0

go.modに依存モジュールを要求するrequireが書き込まれています。

$ cat go.mod
module newmod

require golang.org/x/text v0.3.0 // indirect

ファイルそのものはGOPATHの下にダウンロードされています。

ls $GOPATH/pkg/mod/golang.org/x/text@v0.3.0/

確認のためビルドしたバイナリを実行すると期待通りに動いています。

$ ./newmod
abc123-~アイウ|
abc123-~アイウ|

depからの移行

go.modファイルを生成するgo mod initコマンドではdepで使われるGopkg.lockファイルがあればそれを解釈してgo.modに反映させる機能があるので使ってみます。

先ほどと同じソースコードをGOPATHの下に用意してdep initします。

$ dep init
  Using ^0.3.0 as constraint for direct dep golang.org/x/text
  Locking in v0.3.0 (f21a4df) for direct dep golang.org/x/text

Gopkg.lockはこのような感じ

[[projects]]
  digest = "1:fc3e61b10647ddcee206a5431707878c043faeb5a1cef6de1e0f1d3a2147bcfa"
  name = "golang.org/x/text"
  packages = [
    "internal/gen",
    "internal/triegen",
    "internal/ucd",
    "transform",
    "unicode/cldr",
    "width",
  ]
  pruneopts = "UT"
  revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
  version = "v0.3.0"

[solve-meta]
  analyzer-name = "dep"
  analyzer-version = 1
  input-imports = ["golang.org/x/text/width"]
  solver-name = "gps-cdcl"
  solver-version = 1

GO111MODULE=onでmodulesを有効にしてgo mod initをしてみます。

$ GO111MODULE=on go mod init newmod
go: creating new go.mod: module newmod
go: copying requirements from Gopkg.lock
$ cat go.mod
module newmod

require golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38

バージョン指定がv0.0.0(未指定)ですが末尾のcommit hashは同じように見えます。2

おわりに

まだmodulesを実運用するには早そうですが備えてはおきたいですね。

参考

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。


  1. まだmodulesはデフォルトでないためgo.modがない環境ではGOPATHが優先されます。環境変数でGO111MODULE=onを指定するとmodulesが使われます。

  2. 1.12でやってもrequireは同じ結果でした。