パッケージとモジュール
パッケージ
「ソースファイル (.go) 」の集合体。
同じディレクトリ内のソースファイルが、パッケージの構成要素になる。
同じパッケージのソースファイルは一緒にコンパイルされ、関数・変数も共有される(つまり、1 ファイルに書いたのと同じ状態でコンパイルされる)。
モジュール
標準ライブラリでない「パッケージ」の集合体。「リポジトリから公開する単位」であり、「利用するパッケージ間の依存関係管理をいい感じにやってくれる仕組み」でもある。
go.modファイルがあるディレクトリ以下のすべてのパッケージがモジュールの構成要素になる。
バージョン管理システム (github など) の 1 リポジトリに 1 モジュールを登録する。
バージョンが違えば、異なるモジュールとみなされる。
go のバージョン 1.11 から モジュール機能が使えるようになった。
モジュール機能を使っているかどうかはGoの環境変数「GO111MODULE」で確認できる。
開発する Go プロジェクトについて、
モジュール機能を利用しない
= go mod tidy が使えない
= 外部パッケージのインストール、依存関係管理が容易にできない
ということになるので、現バージョンにおいて使わない選択肢はまずないはず。
# 確認
go env | grep GO111MODULE
=>
GO111MODULE=""
# on: モジュール機能ON
# off: モジュール機能OFF
# goのバージョンが1.17以上の場合、""ならば「on」扱いになる
設定値を更新したい場合は、
go env -w GO111MODULE=off
などと打てばよい。
go.modファイル  と go.sumファイル
go.modファイル
自モジュールに関する以下の内容が記述されているファイル。
- 公開場所
- 動作する Go のバージョン
- 直接的な依存先モジュール(自モジュールが動作するために必要な他モジュール)とそのバージョン
- 間接的な依存先モジュール(依存先モジュールの依存先モジュール)とそのバージョン
// A. 自モジュールの公開場所
module github.com/<オーナー名>/<モジュール名(=リポジトリ名)>
// A. 自モジュールが動作する Go のバージョン
go 1.19
// B. 直接的な依存先モジュール
require github.com/<オーナー名>/<モジュール名> <バージョン>
// B. 間接的な依存先モジュール
// 「indirect」とついているものは、依存先モジュールの依存先モジュールであることを示している。
require (
  github.com/<オーナー名>/<モジュール名> <バージョン> // indirect
  github.com/<オーナー名>/<モジュール名> <バージョン> // indirect
)
go.sumファイル
「依存先モジュール」のSHA-256チェックサム値を記録するファイル。
go.modファイルのrequireディレクティブに基づいてダウンロードした依存先モジュールが改ざんされていないかをチェックできる。
カレントモジュールのディレクトリで、go mod tidyを実行したときに生成される。
github.com/<オーナー名>/<モジュール名> <バージョン> h1:xxxxxxxxxxxxxxx
github.com/<オーナー名>/<モジュール名> <バージョン>/go.mod h1:xxxxxxxxxxxxxxx
実際の開発現場での go.mod, go.sumファイル作成,更新
①goプロジェクトを新規開発する際、最初にgo mod init github.com/<オーナー名>/<リポジトリ名>を実行する
→go.modが新規作成される(A. の内容のみ記述される)
②goプロジェクトの実装を進める中で、import ディレクティブに外部パッケージを記述する(タイミングがある)
※自分が使っているIDE GoLandだと、まだインストールされていない外部パッケージが記述された場合、破線が表示されて知らせてくれる
③go mod tidy実行
→
- 
go.modのB.の内容が追加・更新される
- 
go.sumが追加・更新される
- モジュールキャッシュのディレクトリ($GOMODCACHE)配下に外部パッケージのモジュールがダウンロードされる(こちらを参照)
動作のイメージ図 (ちょっと上の工程と前後してますが)
実際にモジュール化して公開してみる
で書いたサンプルコードを以下のようなディレクトリ構成でモジュールにする場合を例に説明する
go_debug_tool/
    print_type_and_value.go      # 対象の型と値を出力する関数 (パッケージ: debug_tool)
    print_type_and_value_test.go # 対象の型と値を出力する関数のテストコード(パッケージ: debug_tool)
    go.mod                       # go_debug_toolモジュールの置き場、対象Goバージョン、依存先モジュールの定義
    README.txt                   # readme
前提
- go v1.19インストール済み、goコマンドのパス通し済み
- SSHキーペア作成済み & GitHubに公開鍵登録済みで疎通確認済み
であるものとする。
1. GitHub にプロジェクトルートと同名の外部公開リポジトリを作成
GitHubへログイン > 右上アイコンから「New repository」押下
→「Create a new repository」ページへ遷移
以下を設定して「Create repository」を押下
例.
- Repository name: go_debug_tool
- Description: なし
- Public or Private: Public
- Add a README file: チェック
- Add .gitignore: Go
- Choose a license: MIT
2. リモートリポジトリをクローンしてローカルリポジトリを作成
# 作業用ディレクトリを作っていい場所まで移動
cd <適当な場所>
# リモートリポジトリ名をクローン(リモートリポジトリ名と同名のディレクトリ名)
git clone <GitHubのCodeタブ押下して出てくるgit@...>
3. 本処理のファイルを作成
go_debug_toolディレクトリ配下にprint_type_and_value.goを作成する。
package go_debug_tool
import "fmt"
/* 
 * [概要] 引数の型と値を出力する
 * [引数] 
 *   prefix: 前に挿入する接頭辞(string)
 *   printTarget:  型と値を出力する対象([T any])
 * [詳細]
 *   以下のような文字列がターミナルに出力される
 *   [<prefix>] 型: <printTargetの型>, 値: <printTargetの値>
 */
func PrintTypeAndValue[T any](prefix string, printTarget T) {
  fmt.Printf("[%s] 型: %T, 値: %+v\n", prefix, printTarget, printTarget)
}
4. テストコードのファイルを作成
※テストコードについては、こちらを参考に
go_debug_toolディレクトリ配下にprint_type_and_value_test.goを作成
package go_debug_tool
func ExamplePrintTypeAndValue() {
  stringVar := "test string"
  PrintTypeAndValue("stringVar", stringVar)
  // Output:
  // [stringVar] 型: string, 値: test string
}
5. go.modを作成
go mod init github.com/<オーナー名>/go_debug_tool
=>
go: creating new go.mod: module github.com/<オーナー名>/go_debug_tool
go: to add module requirements and sums:
        go mod tidy
module <モジュールのURL>
go 1.19
6. go.modを更新 go.sumを作成
go mod tidy
=>
何も起こらない(go_debug_toolは依存先モジュールが存在しないため正常)
7. テストを実行して通ることを確認
go test
=>
PASS
ok      <モジュールのURL> X.XXXs
8. リモートリポジトリとローカルリポジトリを同期 & リモートへPUSH
# 初期化
git init
# 全ファイルをステージングへ移動、初回コミット
git add .
git commit -m "First Commit"
外部公開したモジュールを利用してみる
先に作成した「go_debug_tool」モジュールを利用する場合を例に、手順を説明する。
1. モジュールを使うサンプルプロジェクトの作成
# サンプルプロジェクトのディレクトリを作成 & 移動
mkdir sample1
cd sample1
# main.goを作成
touch main.go
# main.goに以下の内容を記述して保存
package main
import (
	"fmt"
	"github.com/<オーナー名>/go_debug_tool"
)
func main () {
	fmt.Println("テスト")
	testVar := 2.3
	go_debug_tool.PrintTypeAndValue("testVar", testVar)
}
2. go.modファイルの作成
go mod tidyで外部モジュールを楽に扱えるようにする
go mod init github.com/<オーナー名>/sample1
=>
go: creating new go.mod: module github.com/<オーナー名>/sample1
go: to add module requirements and sums:
        go mod tidy
3. 依存先モジュールのDLとgo.sumファイルの作成、go.modファイルへの追記
go mod tidy
=>
go: finding module for package github.com/<オーナー名>/go_debug_tool
go: found github.com/<オーナー名>/go_debug_tool in github.com/<オーナー名>/go_debug_tool <バージョン>
4. サンプルプロジェクトの実行
go run main.go
=>
テスト
[testVar] 型: float64, 値: 2.3
ちゃんと外部公開したモジュールの関数を実行できた!
エラー集
本記事に関連して、過去に遭遇したエラーを記録しておく
- カレントモジュールのディレクトリにgo.modファイルがすでに存在する状態でgo mod initすると以下のようなエラーが発生する
go: /<カレントモジュールのパス>/go.mod already exists
- 
go.modファイルがない状態でgo testを実行すると以下のエラーが発生する
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
- 
go.modファイルが存在しない & 依存先モジュールをインストールしていない状態で、go run main.goを実行すると、以下のようなエラーが出力される。
main.go:5:2: no required module provides package github.com/<オーナー名>/<依存先モジュール名>: 
go.mod file not found in current directory or any parent directory; see 'go help modules'
[和訳]
パッケージ「github.com/<オーナー名>/<依存先モジュール名>」を提供している必要モジュールがない:
go.mod ファイルがカレントディレクトリおよびいくつかの親ディレクトリ内に存在しない
- 依存先モジュールをインストールしていない状態で、go run main.goを実行してみると、以下のようなエラーが出力される。
main.go:5:2: no required module provides package github.com/<オーナー名>/<依存先モジュール名>;
to add it:
        go get github.com/<オーナー名>/<依存先モジュール名>
[和訳]
パッケージ「github.com/<オーナー名>/<依存先モジュール名>」を提供している必要モジュールがない:
以下を実行しろ
        go get github.com/<オーナー名>/<依存先モジュール名>
参考
パッケージ・モジュールの説明
モジュールの作成
モジュールの公開
標準出力テストコードの記述
go.mod, go.sum

