[GO言語]GOPATH以外の場所でのimportの挙動について整理

`$GOPATH`以外の場所でimportをするとハマることが多い。
それは以下のパターンが有るからだ。

* GOPATHモードとモジュールモードの違い
* GitHub上にあるような外部パッケージと、ディレクトリの中に有る内部パッケージの違い

これらそれぞれについて、importがどう違うか整理する。
(GOのバージョンは1.14を想定している)

# ざっくりまとめ

結論の概要を書くと以下のような感じ

* 外部ライブラリ
  * GOPATHモード→探す先は`$GOPATH/src`
  * モジュールモード→探す先は`$GOPATH/pkg/mod`
* 内部ライブラリ
  * GOPATHモード→探す先は相対パス
  * モジュールモード→探す先はgo.modにかかれたmodule名+go.modがあるディレクトリからの相対パス

# はじめに

### 環境

* OS: MacOS
* GO言語: go version go1.14.3 darwin/amd64

### GOPATHモードとモジュールモードの切り替えについて

GOPATHモードとモジュールモードは、`go.mod`ファイルの有無によって切り替わる

* GOPATHモード：`go.mod` ファイルが無い
* モジュールモード：`go.mod` ファイルが有る(**実行するディレクトリよりも上位の階層にgo.modが有っても有効になる**）

# 外部パッケージのimport

## GOPATHモードでの外部パッケージのimport

ディレクトリ構成

```
.
└── main.go
```

`main.go`

```go
package main

import (
	"fmt"
	"github.com/antonholmquist/jason" // 外部パッケージのインポート
)

func main() {
	v, _ := jason.NewObjectFromBytes([]byte(`{"Name": "fetaro"}`))
	fmt.Println(v)
}
```

#### 探しに行く場所はどこか？

`$GOPATH/src/`

具体的には、`$GOPATH/src/github.com/antonholmquist/jason`

#### ここにパッケージがないとどうなるか 

`cannot find package`エラーになる

```
main.go:5:2: cannot find package "github.com/antonholmquist/jason" in any of:
	/usr/local/Cellar/go/1.14.3/libexec/src/github.com/antonholmquist/jason (from $GOROOT)
	/Users/tetsutaro.watanabe/go/src/github.com/antonholmquist/jason (from $GOPATH)
```

#### どうやってパッケージを配置するか？ 

`go get`

具体的には `go get github.com/antonholmquist/jason`

## モジュールモードでの外部パッケージのimport

ディレクトリ構成

```
.
├── go.mod ←go.modファイルを置いたのでモジュールモードになる
└── main.go
```

`main.go`

```go
package main

import (
	"fmt"
	"github.com/antonholmquist/jason" // 外部パッケージのインポート
)

func main() {
	v, _ := jason.NewObjectFromBytes([]byte(`{"Name": "fetaro"}`))
	fmt.Println(v)
}
```

`go.mod`

```go
module hoge

go 1.14
```

#### 探しに行く場所はどこか？

`$GOPATH/pkg/mod`

具体的には、`/Users/tetsutaro.watanabe/go/pkg/mod/github.com/antonholmquist/jason\@v1.0.0/`

#### ここにパッケージがないとどうなるか

勝手にダウンロードされる。

まだダウンロードされていない状態で `go run main.go`をすると以下のようにダウンロードされてから実行される

```
$ go run main.go
go: finding module for package github.com/antonholmquist/jason
go: found github.com/antonholmquist/jason in github.com/antonholmquist/jason v1.0.0 ←ダウンロード結果
{"Name":"fetaro"} ←実行結果
```

一度ダウンロードされると `go.mod` が書き換わる

書き換わった`go.mod`

```go
module hoge

go 1.14

require github.com/antonholmquist/jason v1.0.0 // indirect
```

# 内部パッケージのimport

## GOPATHモードでの内部パッケージのimport

ディレクトリ構成

```
.
├── main.go
└── mypkg  ←内部パッケージ
    └── bar.go
```

`main.go`

```go
package main

import (
	"./mypkg" // 内部パッケージのインポート
)

func main() {
	mypkg.PrintBar()
}
```

`bar.go`

```go
package mypkg

import "fmt"

func PrintBar(){
	fmt.Println("bar")
}
```

#### 探しに行く場所はどこか

main.goからの相対パスで `./mypkg`

#### ここにパッケージがないとどうなるか

`cannot find package`エラーになる

```
main.go:4:2: cannot find package "." in:
	/Users/tetsutaro.watanabe/git/golab3/mypkg
```

## モジュールモードでの内部パッケージのimport

ディレクトリ構成

```
.
├── go.mod ←go.modがあるからモジュールモード
├── main.go
└── mypkg ←内部ライブラリ
    └── bar.go
```

`main.go`

```go
package main

import (
	"mymodule/mypkg" //←ここをgo.modに書いたモジュール名 + パッケージ名にする
)

func main() {
	mypkg.PrintBar()
}
```

`go.mod`

```go
module mymodule

go 1.14
```

`bar.go`

```go
package mypkg

import "fmt"

func PrintBar(){
	fmt.Println("bar")
}
```

#### 探しに行く場所はどこか

ここが難しい。
モジュールモードではimportに指定した `"mymodule/mypkg"` はモジュールとパッケージを連結したものになる。

具体的には、 (go.modに記載したモジュール名) + (go.modのある場所からのパッケージの相対パス)を探しに行くことになる。

今回の例では、`mymodule`がモジュールで、 `/mypkg` は、「module mymoduleを指定したgo.mod」のあるディレクトリからの相対パスになる。

#### ここにパッケージがないとどうなるか

エラーになる

```
main.go:4:2: package mymodule/mypkg is not in GOROOT (/usr/local/Cellar/go/1.14.3/libexec/src/mymodule/mypkg)
```

#### 探しに行く場所の実験1

go.modがmain.goよりも上位に有るケース

例えば以下のようなディレクトリ構成の場合

```
.
├── c_pkg
│   ├── d_pkg
│   │   └── bar.go
│   └── main.go
└── go.mod
```

go.modに `module a_module/b_module` を指定すると、
main.goでは `import "a_module/b_module/c_pkg/d_pkg"` で読み込める

#### 探しに行く場所の実験2

よくあるcmdとinternalの構成

```
.
├── cmd
│   └── main.go
├── go.mod
└── internal
    └── lib.go
```

go.mod

```go
module github.com/fetaro/my_module

go 1.14
```

main.go

```go
package main

import (
    "github.com/fetaro/my_module/internal"
)

func main() {
    internal.PrintHoge()
}
```

lib.go

```go
package internal

import "fmt"

func PrintHoge(){
    fmt.Println("hoge")
}
```