Golangのパッケージ完全に理解した←わかってない

2019-12-03

Golang のパッケージがよくわかってなかったんで自分なりにまとめてみました。

この記事は Go7 Advent Calendar 2019 3日目の記事です。

目次

この記事中で使われる環境は以下のファイルを使用しました。 使わなくても検証できますが、実行環境がない人やホスト環境を汚したくない人は使ってください。

docker-compose.yml
version: '3.7'
services:
  go113:
    image: golang:1.13-stretch
    container_name: go113
    tty: true
    volumes:
      - .:/root/
    working_dir: /root/

  go112:
    image: golang:1.12-stretch
    container_name: go112
    tty: true
    volumes:
      - .:/root/
    working_dir: /root/

  go111:
    image: golang:1.11-stretch
    container_name: go111
    tty: true
    volumes:
      - .:/root/
    working_dir: /root/

備考

当該Docker環境での $GOPATH/go に設定されています。

import 文

基本的な構文であるimport文についても軽く整理しておきます。

Golangでは自身とは異なるパッケージを利用できるようにするために import文を使います。

パッケージはディレクトリ単位で区切られ import もこの単位で行われます。つまり同一パッケージ内のGoファイルはすべて同じパッケージ名が指定されていなければいけません。

備考

パッケージ名はディレクトリ名と同じにすることが強く推奨されており、短くわかりやすい名前にすべきです。 別名として参照できるので外部パッケージと名前の衝突を気にする必要はありません。

http://golang.jp/effective_go#package-names

Target directory

import文がパッケージを探すディレクトリは 標準パッケージを置くための $GOROOT と、ユーザが任意でダウンロードするパッケージを配置するディレクトリに分かれます。 前者は固定ですが後者については後述する「GOPATHモード」か「モジュールモード」で異なります。

モジュールモードについては後述するとして、GOPATHモード、つまり通常では 「 $GOPATH/src」、「 $GOPATH/src/ 配下のプロジェクトに置かれた vendor/ 」のいずれかから該当モジュールを探します.

備考

ユーザが任意でダウンロードするパッケージを大別すると、 golang.org/x/ で始まる準標準パッケージと、その他のサードパーティパッケージとがあります。

go get in GOPATH mode

go get はGo においてパッケージをダウンロード(インストール)する最も一般的な方法です。

備考

このセクションでは GOPATH モードの話をします。

モードに関する話は #Module mode のセクションで後述します。

これはとても便利ですが、バージョン管理の仕組みを持っていませんでした。 最新バージョンを取得してしまうし、プロジェクトごとに異なるバージョンのパッケージを利用することもできません。

たとえば ProjectA と ProjectB というプロジェクトがあり、ともに golang.org/x/text という準標準パッケージを利用していたとします。

しかし、 ProjectA では 0.3.2 を、 ProjectB では 0.3.1 を使いたいとしたらどうでしょうか。 $GOPATH/src にダウンロードされるパッケージのパスはバージョン情報を含まないため、どちらか片方のバージョンしか使うことが出来ないのです。 多くの場合最新版を使うのに越したことはありませんが、動作確認が取れていなければ簡単にアップグレードすることはできません。

docker とか使うとその辺はカバーできそうですが、それはまた別の話です

dep

dep は Golang が提供するパッケージマネージャです

備考

パッケージについて広く浅く知ることを目的としているため、 dep についても軽く触れますが、 現在では後述する Go modules が使われる流れになってきているので、 dep に興味ない方は各自読み飛ばしてください。 (#go modules)

確証はありませんが、おそらく今後 dep は非推奨になっていくのではないかと思っています。

https://github.com/golang/dep/blob/1066608457fa1d1f8f2e1417098914f88d0ca777/website/blog/2018-07-25-announce-v0.5.0.md#dep-vgomodules-and-beyond

Go1.9以上のバージョンで利用可能です。 Mac では Homebrew で、Linuxでは go get でインストールしましょう。

$ go get -u github.com/golang/dep/cmd/dep

dep はプロジェクト毎に vendor というディレクトリを作りその中でパッケージ管理を行うことで、従来の go get におけるバージョン重複問題を解決しています。

dep では go get は使わずに dep ensure コマンドでパッケージを追加していきます。

# 初期化
root@0c287973b452:/go/src/test# dep init

root@5b464397924b:/go/src/test# dep ensure -add golang.org/x/text
Fetching sources...

"golang.org/x/text" is not imported by your project, and has been temporarily added to Gopkg.lock and vendor/.
If you run "dep ensure" again before actually importing it, it will disappear from Gopkg.lock and vendor/.

root@5b464397924b:/go/src/test# ls vendor/golang.org/x/
text

# $GOPATH/pkg/dep 配下にもある(キャッシュかな?)
root@5b464397924b:/go/src/test# ls /go/pkg/dep/sources/
https---go.googlesource.com-text

# $GOPATH/src には保存されない
root@5b464397924b:/go/src/test# ls /go/src/
github.com  test

作られたファイル:

Gopkg.toml
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
#   name = "github.com/user/project"
#   version = "1.0.0"
#
# [[constraint]]
#   name = "github.com/user/project2"
#   branch = "dev"
#   source = "github.com/myfork/project2"
#
# [[override]]
#   name = "github.com/x/y"
#   version = "2.4.0"
#
# [prune]
#   non-go = false
#   go-tests = true
#   unused-packages = true


[prune]
  go-tests = true
  unused-packages = true

[[constraint]]
  name = "golang.org/x/text"
  version = "0.3.2"
Gopkg.lock
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.


[[projects]]
  digest = "1:88f793426b704818112a477c640b1c3419b3b525f30b96b6d65b297df4ec8568"
  name = "golang.org/x/text"
  packages = [
    ".",
    "collate",
    "collate/build",
    "internal/colltab",
    "internal/gen",
    "internal/language",
    "internal/language/compact",
    "internal/tag",
    "internal/triegen",
    "internal/ucd",
    "language",
    "transform",
    "unicode/cldr",
    "unicode/norm",
  ]
  pruneopts = "UT"
  revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
  version = "v0.3.2"

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

警告

$GOPATH/src 配下のプロジェクトであること、Goのプログラムファイルが存在することが dep を使うための条件です。これを満たさないとエラーになります。

# $GOPATH/src 配下じゃないとだめ
root@5b464397924b:~# dep init
init failed: unable to detect the containing GOPATH: /root is not within a known GOPATH/src

# $GOPATH/src 直下だとだめなので
root@5b464397924b:~# cd /go/src/
root@5b464397924b:/go/src# dep init
init failed: unable to determine the import path for the root project /go/src: dep does not currently support using GOPATH/src as the project root

# 移動して test ディレクトリを作って移動
root@5b464397924b:/go/src# mkdir test
root@5b464397924b:/go/src# cd test/

# dep init はできた
root@5b464397924b:/go/src/test# dep init

# goのファイルがないと dep ensure できないそうだ
root@5b464397924b:/go/src/test# dep ensure -add golang.org/x/text
no dirs contained any Go code

# 空のファイルじゃだめぽ..
root@5b464397924b:/go/src/test# touch main.go
root@5b464397924b:/go/src/test# dep ensure -add golang.org/x/text
all dirs contained build errors

# package文があればGoファイルとして認識される
root@5b464397924b:/go/src/test# echo package main > main.go

検証めんどくさくて画面グーパンしそうになった

Go modules

Go modules は 1.11 から使えるようになった公式のパッケージマネージャです。前は vgo とも呼ばれていたそうです。

1.13 からはデフォルトで利用可能ですが、それ未満では環境変数で GO111MODULE=on もしくは GO111MODULE=auto のようにすると使えるようになります。1.13 以上と それ未満の Go modules では Go modules が有効になる要件が異なるようですが、ここでは 1.13を対象とした話をします。

Go modules は dep のようにパッケージの依存関係やバージョンを保存することができますが、 dep で使っていた Gopkg.toml, Gopkg.lock は使わず、 go.mod で管理します。また、 別途インストールする必要はありません。

備考

Go modules によるパッケージ管理では go.mod と同じ階層に go.sum と呼ばれるファイルが作られますが、これはパッケージごとのチェックサムを記録したファイルであって、ロックファイルではありません。( go.mod だけで依存バージョンまで再現可能です)

go.mod からパッケージが削除されても go.sum から自動的に消えることはなく使用が再開されたときにもチェックサムの検証が可能となります。

https://github.com/golang/go/wiki/Modules#is-gosum-a-lock-file-why-does-gosum-include-information-for-module-versions-i-am-no-longer-using

公式には go.sumgo.mod と同様にリポジトリにコミットすることが推奨されていますが、チェックサムの計算方法がこれまでに何度か変わっているらしく、これにより checksum mismatch が発生するため、 go.sum をコミットしないという対応をしているところもあるようです。 (今の事情はよくわからないのでキャッチアップしたら追記します)

Go 1.11.4 以上で Go Modules を使って依存関係解決しようとしたら checksum mismatch

概要はこのくらいにして動作を見ていきましょう。

root@039e0fbdc156:~# go mod init
go: cannot determine module path for source directory /root (outside GOPATH, module path must be specified)

Example usage:
        'go mod init example.com/m' to initialize a v0 or v1 module
        'go mod init example.com/m/v2' to initialize a v2 module

Run 'go help mod init' for more information.


root@039e0fbdc156:~# go mod init root
go: creating new go.mod: module root

root@039e0fbdc156:~# ls
docker-compose.yml  go.mod  index.rst  main.go

root@039e0fbdc156:~# go get golang.org/x/text
go: finding golang.org/x/text v0.3.2
go: downloading golang.org/x/text v0.3.2
go: extracting golang.org/x/text v0.3.2

root@039e0fbdc156:~# ls /go/src/

root@039e0fbdc156:~# ls /go/pkg/mod/golang.org/x
text@v0.3.2

go mod init コマンドで go.mod ファイルが作成されます。

備考

  • $GOPATH/src の中では モジュール名の指定が不要ですが、それ以外の場所で go mod init する場合は モジュール名の指定が必須です。
    • この例では /root ディレクトリでモジュール名の指定なしに実行したため初回実行でエラーになっています。
  • dep からの移行措置として Gopkg.lock があるとそれをもとに go.mod を作成するようです。

Module mode

Go modules が有効な状態を モジュールモード と表現します。英語だと module-aware mode ともいうそうです。 これに対して従来の $GOPATH/src に保存する方式を GOPATHモード といいます。

Go1.13 では go.mod があるとモジュールモードになります。環境変数 GO111MODULE でいうところの auto のようですが、1.12以前とは auto の定義が違い go.mod ファイルの存在だけがモジュールモードの有効/無効を決めるようになっています。 なお、 GO111MODULE の設定は Go1.13 でも有効で GO111MODULE=off にセットしておけば go.mod があっても GOPATHモードとして動作します。

モジュールモードでは既存の go get の挙動が変わり、ダウンロードしたパッケージを $GOPATH/src 配下ではなく $GOPATH/pkg/mod 配下にバージョンを含めて配置するようになります。

そして import も $GOPATH/pkg/mod から探します。(プロジェクト自身のモジュールは除く)

また、GOPATH モードの go get では最新のパッケージを取得しましたが、モジュールモードでは go get golang.org/x/text@v0.3.1 のようにバージョンを指定してインストールできるようになりました。

go.mod

go.mod ファイルの中身を見てみると、依存パッケージを指定する require ディレクティブの他に、 module というディレクティブが含まれます。 これは自身が置かれているディレクトリ配下のファイル郡を識別するためのモジュール名を自称します。

go.mod
module root

go 1.13

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

GOPATHモードでは $GOPATH/src 配下にプロジェクトを配置しなければ import で検出できずビルドできなかったのですが、モジュールモードでは go.mod で指定したモジュール名がインポート対象になるためどこにおいてもビルドできるようになります。やったね。

認識されているモジュール一覧は以下のように取得できます。

go list -m all
root
golang.org/x/text v0.3.1
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e

これはコマンドを実行したプロジェクトディレクトリ(が対応するgo.mod)ごとに違う結果を返すようになっており、単に $GOPATH/pkg/mod 以下を一覧しているというわけではありません。 また、自身のモジュールも認識できているというのも注目するポイントですね。

警告

go mod initgo.mod を作成すると言いましたが、モジュール名を指定しないとモジュール名は自動的にディレクトリ名になります。

github.com/username/package のようにホスト名を含み import する場合は、モジュール名もこれに合わせる必要があります

備考

モジュールモードで go get を実行すると $GOPATH/bin に実行ファイルが作られなくなるパッケージが一部存在するようです。(go install が失敗してるっぽい?)

少し変な例ですがモジュールモード で dep をインストールしてみます

root@e437ad44b37e:/go/src/test# go get github.com/golang/dep/cmd/dep
go: finding github.com/golang/dep v0.5.4
go: downloading github.com/golang/dep v0.5.4
go: extracting github.com/golang/dep v0.5.4
go: downloading golang.org/x/sync v0.0.0-20190423024810-112230192c58
go: finding github.com/pelletier/go-toml v1.6.0
go: finding github.com/armon/go-radix v1.0.0
go: finding github.com/pkg/errors v0.8.1
go: finding github.com/Masterminds/vcs v1.13.1
go: finding gopkg.in/yaml.v2 v2.2.7
go: downloading gopkg.in/yaml.v2 v2.2.7
go: downloading github.com/pelletier/go-toml v1.6.0
go: downloading github.com/armon/go-radix v1.0.0
go: downloading github.com/pkg/errors v0.8.1
go: downloading github.com/Masterminds/vcs v1.13.1
go: finding github.com/golang/protobuf v1.3.2
go: finding github.com/Masterminds/semver v1.5.0
go: finding github.com/sdboyer/constext latest
go: finding github.com/boltdb/bolt v1.3.1
go: downloading github.com/boltdb/bolt v1.3.1
go: extracting golang.org/x/sync v0.0.0-20190423024810-112230192c58
go: downloading github.com/Masterminds/semver v1.5.0
go: downloading github.com/golang/protobuf v1.3.2
go: downloading github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353
go: extracting gopkg.in/yaml.v2 v2.2.7
go: extracting github.com/armon/go-radix v1.0.0
go: extracting github.com/pkg/errors v0.8.1
go: downloading golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
go: extracting github.com/pelletier/go-toml v1.6.0
go: extracting github.com/Masterminds/vcs v1.13.1
go: finding github.com/jmank88/nuts v0.4.0
go: finding github.com/nightlyone/lockfile latest
go: extracting github.com/boltdb/bolt v1.3.1
go: downloading github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443
go: extracting github.com/Masterminds/semver v1.5.0
go: downloading github.com/jmank88/nuts v0.4.0
go: extracting github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353
go: extracting github.com/golang/protobuf v1.3.2
go: extracting github.com/jmank88/nuts v0.4.0
go: extracting github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443
go: extracting golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
go: downloading golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
go: extracting golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
go: finding golang.org/x/sync v0.0.0-20190423024810-112230192c58
go: finding golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
# github.com/golang/dep/gps
/go/pkg/mod/github.com/golang/dep@v0.5.4/gps/constraint.go:149:4: undefined: semver.Constraint

# 実行ファイルが見つからない
root@e437ad44b37e:/go/src/test# dep
bash: dep: command not found

# bin にもない
root@e437ad44b37e:/go/src/test# ls /go/bin

# 適当にgo.mod が無いディレクトリに移動して
root@e437ad44b37e:/go/src/test# cd /root/
root@e437ad44b37e:~# go get github.com/golang/dep/cmd/dep

# できた
root@e437ad44b37e:~# dep
Dep is a tool for managing dependencies for Go projects

Usage: "dep [command]"

Commands:

  init     Set up a new Go project, or migrate an existing one
  status   Report the status of the project's dependencies
  ensure   Ensure a dependency is safely vendored in the project
  version  Show the dep version information
  check    Check if imports, Gopkg.toml, and Gopkg.lock are in sync

Examples:
  dep init                               set up a new project
  dep ensure                             install the project's dependencies
  dep ensure -update                     update the locked versions of all dependencies
  dep ensure -add github.com/pkg/errors  add a dependency to the project

Use "dep help [command]" for more information about a command.

# 当然 bin にもある
root@e437ad44b37e:~# ls /go/bin
dep

このような場合にはGOPATHモードで go get するとうまくいきます。つまり go.mod がない(モジュールモード が有効になっていない)ディレクトリで、もしくは 環境変数を GO111MODULE=off で実行すればOKです。 (他に良い対応あれば教えて下さい)

そして Go modules がさらに賢いのは、ビルドに必要なパッケージが不足していると自動的に取得してくれて、 go.mod に追加してくれるのです。神かな?

root@13c6011d163e:/go/src/test# go run main.go
main.go:6:2: cannot find package "golang.org/x/text/width" in any of:
        /usr/local/go/src/golang.org/x/text/width (from $GOROOT)
        /go/src/golang.org/x/text/width (from $GOPATH)

# go.mod を作る
root@13c6011d163e:/go/src/test# go mod init
go: creating new go.mod: module test

# ビルドする
root@13c6011d163e:/go/src/test# go run main.go
go: finding golang.org/x/text v0.3.2
go: downloading golang.org/x/text v0.3.2
go: extracting golang.org/x/text v0.3.2
24歳,学生です

# 追記までされてる!
root@13c6011d163e:/go/src/test# cat go.mod
module test

go 1.13

require golang.org/x/text v0.3.2
main.goの内容
main.go
package main

import (
	"fmt"

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

func main() {
	zen := width.Widen.String("24歳,学生です")
	fmt.Println(zen)

}

利用パッケージをアップグレードしたければ go.mod のバージョンを書き換えればビルド時に自動的に記載されたパッケージがダウンロードされます。

Go modules には他にもたくさんの特徴や機能がありますが、とてもじゃないですがこの記事内ですべてを伝えることはできません。 また得るものがあったら記事にするかもしれません。

最後に Go modules と dep のトレンドを見てみます。

備考

  • 記載揺れを考慮して go modules + go mod vs go dep という構図にしてみました
  • Pokemon go のノイズが結構あったので、カテゴリを コンピュータ、電化製品 にしぼりました

ノイズも割と入ってると思いますが、 go modules だけでも go dep を上回ってますね。

これからは積極的に Go modules を使っていきましょう。

間違いがあったら教えて下さい(自信ない箇所が何箇所かある)

明日も書きます。よろしくー。

参考