Golang のパッケージがよくわかってなかったんで自分なりにまとめてみました。
この記事は Go7 Advent Calendar 2019 3日目の記事です。
この記事中で使われる環境は以下の docker-compose.yaml で用意しました。 使わなくても検証できますが、実行環境がない人やホスト環境を汚したくない人は使ってください。
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ファイルはすべて同じパッケージ名が指定されていなければいけません。
備考
パッケージ名はディレクトリ名と同じにすることが強く推奨されており、短くわかりやすい名前にすべきです。 別名として参照できるので外部パッケージと名前の衝突を気にする必要はありません。
Target directory
import文がパッケージを探すディレクトリは 標準パッケージを置くための $GOROOT と、ユーザが任意でダウンロードするパッケージを配置するディレクトリに分かれます。 前者は固定ですが後者については後述する「GOPATHモード」か「モジュールモード」で異なります。
モジュールモードについては後述するとして、GOPATHモード、つまり通常では 「 $GOPATH/src」、「 $GOPATH/src/ 配下のプロジェクトに置かれた vendor/ 」のいずれかから該当モジュールを探します.
備考
ユーザが任意でダウンロードするパッケージを大別すると、 golang.org/x/ で始まる準標準パッケージと、その他のサードパーティパッケージとがあります。
go get in GOPATH mode
go get はGo においてパッケージをダウンロード(インストール)する最も一般的な方法です。
これはとても便利ですが、バージョン管理の仕組みを持っていませんでした。 最新バージョンを取得してしまうし、プロジェクトごとに異なるバージョンのパッケージを利用することもできません。
たとえば 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 は非推奨になっていくのではないかと思っています。
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 から自動的に消えることはなく使用が再開されたときにもチェックサムの検証が可能となります。
公式には go.sum は go.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 というディレクティブが含まれます。 これは自身が置かれているディレクトリ配下のファイル郡を識別するためのモジュール名を自称します。
module root
go 1.13
require golang.org/x/text v0.3.2
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 init で go.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の内容
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 を使っていきましょう。
間違いがあったら教えて下さい(自信ない箇所が何箇所かある)
明日も書きます。よろしくー。