これは、なにをしたくて書いたもの?
Goを使ったプログラムを書いていこうと思うのですが、モジュールに関する情報を全然知らないので、試しながら学んで
みようと。
プログラムを書いていたら、パッケージくらいは使うでしょう、と。
なお、ここで試す条件は、以下の通りです。
要するに、ローカルでとりあえず作って動かしたいというケースを想定しています。
作成するソースコードを$GOPATH
配下にまとめていると、もしかして今回のreplace
を使う話って発生しないんですかね…。
※現時点で$GOPATH
は見ないまま書いています
モジュールに関する情報を見る
なにはともあれ、ドキュメントです。
Modules · golang/go Wiki · GitHub
リファレンスもあります。ただ、開発中だとは書かれていますが。
Go Modules Reference - The Go Programming Language
言語仕様には、モジュールのことは書いてなさそうなんですよね…。
The Go Programming Language Specification - The Go Programming Language
環境
今回の環境は、こちら。
$ go version go version go1.15.6 linux/amd64
お題
以下のようなディレクトリツリーを作ります。
├── calc │ ├── func.go │ └── go.mod ├── format │ ├── func.go │ └── go.mod ├── go.mod └── main.go
最上位がmain
モジュールで、以下の依存関係で呼び出しができるようにしてみます。
main
→calc
main
→format
→calc
※ あとで気づきましたが、入れ子にしない方が例として適切でしたね
※ というか、この構成なら単純にパッケージにしてimport
すれば良かったと後で知る…
では、始めていきましょう。
ローカルのモジュール(パッケージ)を呼び出す
最初に、モジュール用のディレクトリを作って移動。
$ mkdir import-local-package-example $ cd import-local-package-example
go mod init
。
$ go mod init example.com/my/import-local-package-example go: creating new go.mod: module example.com/my/import-local-package-example
モジュール名には、慣習的に(公開時を踏まえると)ホスト名を入れた方が良さそうなので、今回はexample.com
と
しておきます。ふだんはgithub.com/[ユーザー名]
あたりが使われることが多いのでしょう。
簡単なソースコードを書いて
main.go
package main import ( "fmt" ) func main() { fmt.Println("Hello World!!") }
確認。
$ go run main.go Hello World!!
この時の、go.mod
はこうです。
go.mod
module example.com/my/import-local-package-example go 1.15
続いて、呼び出し先のモジュールを作ります。
$ mkdir calc $ cd calc
go mod init
。
$ go mod init example.com/my/import-local-package-example/calc go: creating new go.mod: module example.com/my/import-local-package-example/calc
こんな感じのファイルを作成。
func.go
package calc func Plus(a int, b int) int { return a + b } func Minus(a int, b int) int { return a - b }
main
側に戻ります。
$ cd ..
現時点での構成は、こんな感じです。
$ tree . ├── calc │ ├── func.go │ └── go.mod ├── go.mod └── main.go
次に、main.go
を雰囲気で先ほど作ったcalc
モジュールを使うように変更してみます。
main.go
package main import ( "./calc" "fmt" ) func main() { //fmt.Println("Hello World!!") fmt.Printf("1 + 3 = %d\n", calc.Plus(1, 3)) fmt.Printf("5 - 2 = %d\n", calc.Minus(5, 2)) }
実行。
$ go run main.go build command-line-arguments: cannot find module for path _/path/to/import-local-package-example/calc
そんなモジュールはないよ、と言われます。
importを、go mod init
時に指定したものに変えてみましょう。
import ( //"./calc" "example.com/my/import-local-package-example/calc" "fmt" )
実行。
$ go run main.go go: finding module for package example.com/my/import-local-package-example/calc main.go:5:2: cannot find module providing package example.com/my/import-local-package-example/calc: unrecognized import path "example.com/my/import-local-package-example/calc": reading https://example.com/my/import-local-package-example/calc?go-get=1: 404 Not Found
これだと公開されているモジュールを期待することになってしまうので、当然ですが世の中にそんなモジュールはありません。
ここで、go.mod
をこちらから
go.mod
module import-local-package-example/main go 1.15
以下のように変更します。
go.mod
module example.com/my/import-local-package-example go 1.15 require example.com/my/import-local-package-example/calc v0.0.1
バージョンは、適当に振りました。
これだけだと、依存関係をgo.mod
に明記しただけなので、go mod edit -replace
を行ってローカルパスで解決するようにします。
# Add a replace directive. $ go mod edit -replace example.com/a@v1.0.0=./a
Go Modules Reference / go mod edit
実行。
$ go mod edit -replace example.com/my/import-local-package-example/calc@v0.0.1=./calc
すると、go.mod
に1行追加されます。今回はgo mod edit -replace
を使いましたが、手でgo.mod
を書き換えてもOKです。
go.mod
module example.com/my/import-local-package-example go 1.15 require example.com/my/import-local-package-example/calc v0.0.1 replace example.com/my/import-local-package-example/calc v0.0.1 => ./calc
このやり方は、以下に記載があります。
Go Modules Reference / replace directive
Go Modules / Can I work entirely outside of VCS on my local filesystem?
これで、やっと実行できるようになります。
$ go run main.go 1 + 3 = 4 5 - 2 = 3
めでたし、めでたし。
疑似バージョン
ところで、モジュールに対する依存関係を書く時にバージョンが必要になるのですが、ここで疑似バージョン(Pseudo Version)を
使うことでVCS環境内で依存関係を解決できるようです。
Go Modules Reference / Pseudo-versions
こうすると、replace
は要らなくなりそうですね。
疑似バージョンは、「ベースバージョン-タイムスタンプ-コミットハッシュ」で構成されます。
こんな感じですね。
- vX.0.0-yyyymmddhhmmss-abcdefabcdef
- v1.2.4-0.20191109021931-daa7c04131f5
もうひとつパッケージを増やす
続いて、もうひとつパッケージを増やしてみましょう。先ほどmain
内に書いていた処理を移すことにします。
ディレクトリの作成。
$ mkdir format $ cd format
go mod init
。
$ go mod init example.com/my/import-local-package-example/format go: creating new go.mod: module example.com/my/import-local-package-example/format
ソースコードを作成。先ほど作った、calc
パッケージを使います。
func.go
package format import ( "example.com/my/import-local-package-example/calc" "fmt" ) func FormatPlus(a int, b int) string { return fmt.Sprintf("%d + %d = %d", a, b, calc.Plus(1, 3)) } func FormatMinus(a int, b int) string { return fmt.Sprintf("%d - %d - %d", a, b, calc.Minus(5, 2)) }
go.mod
の中身は、このままです。
go.mod
module example.com/my/import-local-package-example/format go 1.15
main
側に戻って
$ cd ..
ソースコードを修正。calc
パッケージへの依存は削除します。
main.go
package main import ( //"./calc" //"example.com/my/import-local-package-example/calc" "example.com/my/import-local-package-example/format" "fmt" ) func main() { //fmt.Println("Hello World!!") //fmt.Printf("1 + 3 = %d\n", calc.Plus(1, 3)) //fmt.Printf("5 - 2 = %d\n", calc.Minus(5, 2)) fmt.Println(format.FormatPlus(1, 3)) fmt.Println(format.FormatMinus(5, 2)) }
go.mod
ですが、2つのモジュールへの依存とreplace
を書きます。
go.mod
module example.com/my/import-local-package-example go 1.15 //require example.com/my/import-local-package-example/calc v0.0.1 //replace example.com/my/import-local-package-example/calc v0.0.1 => ./calc require ( example.com/my/import-local-package-example/format v0.0.1 example.com/my/import-local-package-example/calc v0.0.1 ) replace ( example.com/my/import-local-package-example/format v0.0.1 => ./format example.com/my/import-local-package-example/calc v0.0.1 => ./calc )
format
モジュールのgo.mod
に、calc
モジュールへの依存関係を書いても意味がありません。エントリポイントとなる位置にある
go.mod
に書いてあることが重要みたいです。
推移的依存関係まで、全部書く必要があるということですね。
最終的に、ディレクトリ構成はこうなりました。
$ tree . ├── calc │ ├── func.go │ └── go.mod ├── format │ ├── func.go │ └── go.mod ├── go.mod └── main.go
動作確認。
$ go run main.go 1 + 3 = 4 5 - 2 - 3
OKですね。
ビルド&実行
最後に、ビルドして
$ go build -o app main.go
実行。
$ ./app 1 + 3 = 4 5 - 2 - 3
こちらもOKです。