2019-12-03

Golangのパッケージ完全に理解した

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

この記事は Go7 Advent Calendar 2019 3日目の記事です。 https://qiita.com/advent-calendar/2019/go7

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

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/

info
  • 当該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/ 」のいずれかから該当モジュールを探します.

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

go get in GOPATH mode

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

info
  • このセクションでは GOPATH モードの話をします。
  • モードに関する話はモジュールモードのセクションで後述します。

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

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

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

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

このような問題を解決すべく後述する dep や go.mod などの パッケージマネージャが登場していくわけです。

dep

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

depは Golang が提供するパッケージマネージャです https://github.com/golang/dep

確証はありませんが、おそらく今後 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
      
      
warning
  • $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 をコミットしないという対応をしているところもあるようです。 (今の事情はよくわからないのでキャッチアップしたら追記します)

https://medium.com/@ponde_m/go-1-11-4-%E4%BB%A5%E4%B8%8A%E3%81%A7-go-modules-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E8%A7%A3%E6%B1%BA%E3%81%97%E3%82%88%E3%81%86%E3%81%A8%E3%81%97%E3%81%9F%E3%82%89-checksum-mismatch-c5528eaebd72 概要はこのくらいにして動作を見ていきましょう。

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 [email protected]

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

$GOPATH/src の中ではモジュール名の指定が不要ですが、それ以外の場所で go mod init する場合は モジュール名の指定が必須です。

この例では /root ディレクトリでモジュール名の指定なしに実行したため初回実行でエラーになっています。 dep からの移行措置として Gopkg.lock があるとそれをもとに go.mod を作成するようです。

モジュールモード

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/[email protected] のようにバージョンを指定してインストールできるようになりました。

go.mod

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

go.mod
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 以下を一覧しているというわけではありません。 また、自身のモジュールも認識できているというのも注目するポイントですね。

warning
  • go mod initgo.mod を作成すると言いましたが、モジュール名を指定しないとモジュール名は自動的にディレクトリ名になります。
  • github.com/username/package のようにホスト名を含み import する場合は、モジュール名もこれに合わせる必要があります
info
  • モジュールモードで 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/[email protected]/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 を使っていきましょう。

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

参考

https://github.com/golang/go/wiki/Modules https://text.baldanders.info/golang/go-module-aware-mode/ https://qiita.com/yoshinori_hisakawa/items/268ba201611401ca7935 https://tech.opst.co.jp/2019/07/09/go-modules%E3%82%82%E8%A7%A6%E3%82%8C%E3%81%A6%E3%81%BF%E3%82%8Bgo%E5%85%A5%E9%96%80/