フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

【Go】パッケージ/モジュールやgo modコマンドについてまとめ

f:id:lirlia:20210321183729p:plain

Goを操作する上で基本となるPackageやModule、またそれに関連するコマンドなどについていろいろとまとめていきます。

※前提としてGO111MODULEが設定されていないGo 1.13〜を基本としているので、それ以前のバージョンの場合は対象外となります ※今回の前提の環境はGo 1.16です

How to Write Go Code - The Go Programming Language

Packageとは

  • 同じディレクトリに存在するソースコードファイル群のことです
  • ソースコードファイル群は一緒にコンパイルされます
  • 同じPackageに所属するソースファイル間では、関数や変数などで共有されます

例えば以下のような2つのファイルがあるとしましょう。

package main

import "fmt"

func main() {
    fmt.Println("test1")
    test2()
}
package main

import "fmt"

func test2() {
    fmt.Println("test2")
}

これらのファイルをコンパイルして実行するとこのようになります。同じpackageなので1つのファイルに収まりましたね。

$ go build .
$ ./test 
test1
test2

ちなみに$ go run test1.goと実行するとエラーになります。

$ go run test1.go 
# command-line-arguments
./test1.go:9:2: undefined: test2

これは必要なファイルのうち片方しかコンパイル→実行の処理を行っていないためです。なので以下のようにして両方とも読み込むようにしましょう。

$ go run test1.go test2.go 
test1
test2

または

$ go run .
test1
test2

Module(モジュール)とは

一緒にリリースされた関連するGoパッケージの集合体のことです。簡単いうと複数のパッケージの集合体ですね。

一般的にGoリポジトリには1つのGoモジュールがリポジトリの直下に格納されています。例えばgithubにaaaというリポジトリがあったら、そのルートディレクトリにモジュールが格納されているということです。github.com/aaa/hogehoge_moduleといった感じですね。

go.modとは

go.modGoモジュールのパスを書いておくファイルです。モジュールの中に含まれる全てのパッケージインポート用のパスプレフィックスが書かれています。

以下は3つのモジュールを使用しているgo.modの中身です。

root@7fb8580f21c0:/go/src/app# cat go.mod 
module app

go 1.16

require (
        github.com/Wing924/ltsv v0.3.1
        gorm.io/driver/mysql v1.0.5
        gorm.io/gorm v1.21.7
)

モジュールの中にはgo.modファイルやパッケージが含まれており、さらにサブディレクトリにいくとまた別のgo.modファイルが含まれていることもあります。

gorm@v1.21.7モジュールの中を確認するとこうなっています。go.modファイルがありますね。

root@7fb8580f21c0:/go/src/app/pkg/mod/gorm.io/gorm@v1.21.7# ls
License           finisher_api.go  model.go
README.md         go.mod           prepare_stmt.go
association.go    go.sum           scan.go
callbacks         gorm.go          schema
callbacks.go      interfaces.go    soft_delete.go
chainable_api.go  logger           statement.go
clause            migrator         statement_test.go
errors.go         migrator.go      utils

各モジュールのパスは、パッケージインポート用のパスプレフィックスとして機能するだけでなく、goコマンドがダウンロードする場所を示しています。

たとえば、モジュール(golang.org/x/tools) をダウンロードするために、goコマンドはhttps://golang.org/x/tools で示されるリポジトリを参照します。

go.sumとは

中を見てみるとこんな感じになっており依存モジュールのチェックサムが記録されています。(sumchecksumのことですかね)

※チェックサムとはあるファイルやデータの一意性を示すハッシュのことです。簡単にいうとファイルの中身やデータごとに全く違うハッシュを生成することができるので、ファイルが改竄されていないことを証明されるときに用いられます

$ cat go.sum 
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

このチェックサムを利用してGoでは使っているモジュールの内容に変更があったかどうかを検出しています。

コマンドの意味

go get [import path]

  • モジュールのダウンロード及びgo.mod / go.sumの修正
    • go get golang.org/x/xerrors

go mod tidyはソースコード内のimportを宣言的なものとして捉えそこに近づけるように追加/削除を行いますが、go getはユーザが特定のもの指定してインストールします。

参考: Go1.16からの go get と go install について - Qiita

go install [import path]

  • インターネット上のツールを$GOPATH/binにインストールする機能
    • go install golang.org/x/tools/gopls@latest
  • 作業ディレクトリをビルドして$GOPATH/binに配置する機能
    • go get localhost/xxx/xxx

参考: Go1.16からの go get と go install について - Qiita

go mod init [import path]

現在のディレクトリにおけるモジュールを外部からインポートする時のパスを設定します。基本的にはGitHubや自サイトなどでの公開が一般的となるため、https://[import path]となるようなimport pathを設定しましょう。

$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello

go 1.16

go mod tidy

以下の機能を持っています。

  • コード内でimportしているがgo getされていないモジュールをダウンロード
  • ダウンロードされているがコード内でimportされていないモジュールを削除
  • 上記2つを実施したあとにgo.modgo.sumを修正 または 削除

試してみよう

まずは準備します。外部モジュールを使う簡単なコードを用意します。

package main

import (
    "fmt"

    "github.com/google/go-cmp/cmp"
)

func main() {
    fmt.Println("hello")
    fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

必要なパッケージをインストールします。

$ go get github.com/google/go-cmp/cmp
go: downloading github.com/google/go-cmp v0.5.5
go get: added github.com/google/go-cmp v0.5.5
$ cat go.mod 
module example.com/user/hello

go 1.16

require github.com/google/go-cmp v0.5.5 // indirect
$ go run hello.go hello
  string(
-       "Hello World",
+       "Hello Go",
  )

先ほどのhello.goから外部モジュールを外してみます。先ほどgo getしたモジュールが不要になりますね。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello")
}

ここでgo mod tidyを実行してみましょう。これによって使われていない不要なモジュールが削除されます。便利!

$ go mod tidy
$ cat go.mod 
module example.com/user/hello

go 1.16
$ cat go.
go.mod  go.sum  
$ cat go.sum 

また元に戻してみます。要するにモジュールが足りていない状態です。

package main

import (
    "fmt"

    "github.com/google/go-cmp/cmp"
)

func main() {
    fmt.Println("hello")
    fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

ここでgo mod tidyを叩くとこのように必要なモジュールが勝手にインストールされます。(pythonにおけるpip install -r requirement.txtに近いですね)

$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.5
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543

Moduleを作ってインストールしてみる

$ mkdir hello # Alternatively, clone it if it already exists in version control.
$ cd hello
$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello

go 1.16

続いてhello.goを作成します。

package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

そしたら以下のコマンドを実行して、example.com/user/helloモジュール使ってhelloコマンドを作成しましょう。

$ go install example.com/user/hello
$~/go/bin/hello 
hello

この時インストール先のディレクトリはGOPATHGOBINによって管理されるので注意してください。

  • GOBINが定義されている場合:$GOBIN直下に保存されます
  • GOPATHが定義されている場合:GOPATHリストの最初のディレクトリのbin配下に保存されます→$GOPATH/bin