Goプログラム開発におけるモジュール管理


Goプログラムでのパッケージ探索方法やモジュール管理についてざっくりと調べてみたので解説する。

<br>

## GOPATHモードとモジュールモード

Goには、GOPATHモードとモジュールモードが存在しており、`go.mod`が存在するか否かでGoが自動的に判断する。

- `go.mod`が有る：モジュールモード
- `go.mod`が無い：GOPATHモード


モジュールモードは、Go 1.16以降で利用可能になったモードで、GOPATH変数を使用しないように設計されている。
GOPATH変数の代わりにGoモジュールという仕組みを使用して依存関係を管理している。
GOPATHモードでは、GoパッケージはGOPATHディレクトリ以下に配置され、importパスの一部として指定していたが、モジュールモードではモジュールごとにgo.mod（モジュールファイル）が配置されることで管理している。


<br><br>


## パッケージの探索手順

**モジュールモード**における、パッケージの探索は以下の手順で実行される。

1. カレントディレクトリのvendorディレクトリ内
2. モジュールキャッシュ
3. モジュールのダウンロード先

まず現在のディレクトリにvendorディレクトリがある場合、その中にパッケージが存在するかを探索する。
次に、モジュールキャッシュ内にパッケージがあるかを確認して、それでもなければモジュールのダウンロード先にあるかを探索する。
モジュールキャッシュとは、モジュールをダウンロードしてビルドした結果を保存する場所で、デフォルトで$GOPATH/pkg/modに保存される。

<br>

**GOPATHモード**の場合のパッケージの探索は以下の通り

1. カレントディレクトリのvendorディレクトリ内
2. $GOPATH/srcディレクトリ内
3. $GOROOT/srcディレクトリ内


GOROOT/srcはGoのインストールディレクトリを指定する環境変数のこと。GOROOTにGoの標準パッケージやコンパイラ、開発ツールが含まれる。

<br><br>

## 「パッケージ」について

Goコンパイラの処理単位を表している。ディレクトリ内の複数のGoファイルをパッケージとして処理している。
パッケージはそれぞれimportすることができ、絶対パスや相対パスを指定する。

```go
import "github.com/myuser/mypackage"
```

go getまたはgo mod tidy コマンドを使って外部パッケージをダウンロードすることで参照可能となる。
また、Goの標準ライブラリには、fmt、os、io、netなどのパッケージが含まれており、それらのパッケージをimportして使用可能。


<br><br>


## 「モジュール」について

モジュールモードでは、標準ライブラリを除くパッケージを「モジュール」として管理している。Go1.11 以降はモジュールという考え方が導入され、Go1.10以前のGOPATHによるコードの一元管理から移行が進み、プロジェクト単位でディレクトリを自由な場所に置けるようになっている。
パッケージが一つのディレクトリを指しているのに対して、モジュールはgo.modファイルのあるディレクトリ以下の全てのパッケージがモジュール配下となる。


<br><br>

## GOPATHが担う役割

GOPATHとはGoプログラムをビルドする際に、必要なパッケージやライブラリを保存する場所を指定する環境変数のこと。
goコマンドによってパッケージのソースコードをGOPATH/src配下から探し出し、パッケージをビルドして、バイナリをGOPATH/bin配下にインストールする。
このGOPATH配下には以下のようなディレクトリが存在する。

 
#### ディレクトリ構造
```zsh
$GOPATH/
  |-src/
  |-pkg/
  |-bin/
```

<br>

<dl>
  <dt>・$GOPATH/src:</dt>
  <dd>Goのソースコードが格納されるディレクトリ。Go modulesを無効としている場合は、importされたpackageの探索先として使用される。このディレクトリはGo modulesを使用することで不要になる。</dd>
　　　　<br>
  <dt>・$GOPATH/pkg:</dt>
  <dd>Goプログラムをビルドした際にソースコードからコンパイルされたオブジェクトが生成され、そのバイナリファイルが格納されるディレクトリ。</dd>
　　　　<br>
　　　　<dt>・$GOPATH/bin:</dt>
  <dd>Goコマンドによりインストールしたパッケージのバイナリが格納されるディレクトリ。</dd>
</dl>



<br>

### Go modulesについて

Go Modulesを使って依存関係管理が可能。
このGo Modulesを利用する為には、環境変数の`GO111MODULE`を設定する必要がある。

<table>
  <caption></caption>
  <thead>
    <tr>
      <th>Go Modules</th> <th>役割</th>
    </tr>
  </thead>
  <tr>
    <td> auto </td> <td>Goモジュールが有効になっている場合、モジュールモードでビルドが行われる。無効な場合は、GOPATHモードでビルドが行われる。</td>
  </tr>
  <tr>
    <td> on </td> <td>常にモジュールモードでビルドが行われる。</td>
  </tr>
  <tr>
    <td> off </td> <td>常にGOPATHモードでビルドが行われる。</td>
  </tr>
</table>

<br>


Go modulesを有効化しておくことで、packageの探索先として GOPATH/srcを使用する必要がなくどこでも開発が可能となる。

go modulesを有効にする為には.zshrcファイルを開いて、`GO111MODULE=on`にすれば設定可能。

```zsh
open ~/.zshrc
export GO111MODULE=on 
```

<br>

```zsh
export GO111MODULE=on
```
<br><br>


## go.modについて

既に何度も登場しているgo.modについて解説する。
go.modはモジュールを追跡し、依存関係を管理するためのもの。

> That module is defined by a go.mod file that tracks the modules that provide those packages. That go.mod file stays with your code, including in your source code repository.
[Tutorial: Get started with Go](https://go.dev/doc/tutorial/getting-started)

<br>

#### go.modの作成

go.mod init コマンドを使用してgo.modファイルを作成する。

```bash
go mod init example/hello
go: creating new go.mod: module example/hello
```

<br>

#### go mod tidy

モジュール管理を行う中で使用しなくなったパッケージを削除するためのコマンド。

> go mod tidy ensures that the go.mod file matches the source code in the module. It adds any missing module requirements necessary to build the current module’s packages and dependencies, and it removes requirements on modules that don’t provide any relevant packages. It also adds any missing entries to go.sum and removes unnecessary entries.
[Tutorial: Get started with Go](https://go.dev/doc/tutorial/getting-started)

```bash
go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2
```

go mod tidy -vで削除されたパッケージの情報が表示される。

```bash
go mod tidy -v
unused rsc.io/quote
```

<br><br>

## モジュールモードで外部パッケージをimportする場合

モジュールモードの場合、go.modファイルが置かれる。

```ディレクトリ構造
├── go.mod
└── main.go
```


```go:main.go
package main

import (
    "os"
    "fmt"
    "gopkg.in/ini.v1" // 外部パッケージをインポート
)
```

goは`$GOPATH/pkg/mod`内にパッケージがあるかどうかを確認しにいく。

<br>

```go:go.mod
module go-sample

go 1.19
require gopkg.in/ini.v1 v1.67.0 // indirect
```


<br><br>

## モジュールモードで内部パッケージをimportする場合

内部パッケージをimportは以下の通り

```ディレクトリ構造
├── go.mod
└── main.go
└── config.ini // 内部ライブラリ
└── utils
      └── logging.go // 内部ライブラリ
```


```go
package main

import (
	"fmt"
	"./config"
	"./utils"
)

func main() {
	utils.LoggingSettings(config.Config.LogFile)
	fmt.Println(config.Config.ApiKey)
}

```




<br><br><br>

■ 参考文献

https://qiita.com/fetaro/items/31b02b940ce9ec579baf

https://dev.classmethod.jp/articles/go-setup-and-sample/

https://zenn.dev/tennashi/articles/3b87a8d924bc9c43573e

https://zenn.dev/spiegel/articles/20210223-go-module-aware-mode#go-get-%E3%81%AF%E3%82%AA%E3%83%AF%E3%82%B3%E3%83%B3%EF%BC%9F

https://zenn.dev/optimisuke/articles/105feac3f8e726830f8c