Go Modulesでマルチモジュールにする方法がわからなくて調べました。発端は単にgo.mod
がある別モジュールのパッケージをインポートしようとしても出来なかったことです。そこで、Go Modulesでマルチモジュールを実現するためのシナリオを説明してみたいと思います。
(本記事は自分のブログからの転載記事です。)
TL;DR
- Go Modulesは便利なので使っていこう
- Go Modulesでマルチモジュール構成にする場合はgo.modファイルで
replace
ディレクティブを使おう - マルチモジュール構成の採用には慎重になろう
Go Modulesとは
とりあえず、Go Modules is 何?
という方の為に簡単に説明します。ご存知の方はこの節を飛ばしてください。
Go ModulesはGo 1.11から試験的に導入され、Go 1.13からデフォルトで有効になる予定の新しいパッケージ依存関係の管理方法です。使ってみた実感としてはすでに充分実用的なので新規にプロジェクトを作成する場合はGo Modulesを使って作成することをオススメします。Go 1.12でGo Modulesを有効にするためには、GOPATH以外のパスで作業をするか以下で環境変数を設定します。
export GO111MODULE=on
この記事ではGo 1.12の前提で解説します。
Go Modulesで管理を始める
基本はディレクトリを作成してgo mod init <モジュール名>
で始められます。
$ mkdir go-multi-modules
$ cd go-multi-modules
$ go mod init go-multi-modules
go.mod
というファイルが作成されています。これが依存関係を管理するファイルになります。
module go-multi-modules
go 1.12
まずは基本のおはようの挨拶から
早速ですが、基本どおりHello World
から初めて見ます。ただし、依存関係を入れるために go-figureを利用して挨拶をしてみます。ソースコードは以下の通りです。
package main
import(
"github.com/common-nighthawk/go-figure"
)
func main() {
myFigure := figure.NewFigure("Hello World", "", true)
myFigure.Print()
}
go build
をすると依存関係があるパッケージがダウンロードされて、ビルドされます。事前にgo get
する必要がないので、これだけでもGo Modulesの良さがわかります。./go-multi-modules
で実行して無事挨拶ができれば成功です。
$ go build
go: finding github.com/common-nighthawk/go-figure latest
$ ./go-multi-modules
_ _ _ _ __ __ _ _
| | | | ___ | | | | ___ \ \ / / ___ _ __ | | __| |
| |_| | / _ \ | | | | / _ \ \ \ /\ / / / _ \ | '__| | | / _` |
| _ | | __/ | | | | | (_) | \ V V / | (_) | | | | | | (_| |
|_| |_| \___| |_| |_| \___/ \_/\_/ \___/ |_| |_| \__,_|
go.mod
ファイルを見てみるとrequire
の行が追加されて依存関係が追跡されているのが分かります。
module go-multi-modules
go 1.12
require github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a
また、go.sumというファイルも生成されます。依存関係の管理はgo.modだけでもできますが、go.sum
は検査用に必要なようです。詳しくは ここを参照してください。
github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a h1:kTv7wPomOuRf17BKQKO5Y6GrKsYC52XHrjf26H6FdQU=
github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
おはようの挨拶をパッケージにしてみる
さて、挨拶は毎日するものです。せっかくなので再利用可能なようにパッケージとして分離してみます。pkg
ディレクトリを作成し1、その下にhello-world
ディレクトリを作成して、その下にhello-world.go
ファイルを作成します。ディレクトリ構成は以下の通りです。今回はhelloworld
というパッケージを作成します。
.
├── go-multi-modules // `go build`で生成された実行ファイル
├── go.mod // `go mod init` で生成されたモジュール管理ファイル
├── go.sum // `go build`で生成されたモジュール管理ファイル(検査用)
├── main.go // メインファイル
└── pkg
└── hello-world
└── hello-world.go // 新規追加
hello-world.go
ファイルの中身は以下の通りです。
package helloworld
import(
"github.com/common-nighthawk/go-figure"
)
func HelloWorld() {
myFigure := figure.NewFigure("Hello World", "", true)
myFigure.Print()
}
main.go
ファイルは以下のように書き換えます。
package main
import (
"go-multi-modules/pkg/hello-world"
)
func main() {
helloworld.HelloWorld()
}
go build
でビルドして./go-multi-modules
で実行して同じように挨拶ができたら成功です。
Go Homeしようとして失敗する
さて、挨拶も済んだのでもう用はありません。帰宅したくなってきたとします。ただし、帰宅時間まで細かく管理されたくないので別モジュールで管理することを考えます。この場合、pkg
ディレクトリ配下にgo-home
ディレクトリを作成して、go-home
ディレクトリに移動してからgo mod init gohome
を実行します。
ディレクトリ構成は以下のようになります。
.
├── go-multi-modules // `go build`で生成された実行ファイル
├── go.mod // `go mod init` で生成されたモジュール管理ファイル
├── go.sum // `go build`で生成されたモジュール管理ファイル(検査用)
├── main.go // メインファイル
└── pkg
├── go-home // このディレクトリ配下は別モジュールになる
│ ├── go.mod // `go mod init`で生成される
│ └── home.go // 新規追加
└── hello-world
└── hello-world.go // 挨拶パッケージ
go-home
配下のgo.mod
は以下のようになります。初期化しただけなのでrequire
はありません。
module gohome
go 1.12
home.go
は以下のようになります。
package gohome
import("github.com/common-nighthawk/go-figure")
func GoHome() {
figure.NewFigure("Go Home!", "basic", true).Scroll(30000, 400, "right")
}
main.go
は以下のように書き換えます。
package main
import (
"go-multi-modules/pkg/hello-world"
"go-multi-modules/pkg/go-home"
)
func main() {
helloworld.HelloWorld()
gohome.GoHome()
}
これをトップディレクトリ(go-multi-modules
ディレクトリ)でgo build
でビルドしようとしたところ以下のようなエラーが出てうまくいきませんでした。どうやらモジュールの読み込みに失敗したようです。
$ go build
build go-multi-modules: cannot load go-multi-modules/pkg/go-home: cannot find module providing package go-multi-modules/pkg/go-home
replace
のおかげでGo Homeに成功する
解決方法は簡単で親のgo.mod
に以下のreplace
ディレクティブを記述することでした。
replace go-multi-modules/pkg/go-home => ./pkg/go-home
replace
ディレクティブを記述してgo build
をするとビルドが成功します。
以下はgo build
後のgo.mod
です。依存関係(require
)が追加されています。
module go-multi-modules
go 1.12
require (
github.com/common-nighthawk/go-figure v0.0.0-20190529165535-67e0ed34491a
go-multi-modules/pkg/go-home v0.0.0-00010101000000-000000000000
)
replace go-multi-modules/pkg/go-home => ./pkg/go-home //追加
さて、ビルドできたら./go-multi-modules
で実行してみましょう。一瞬Hello Worldが表示されてその後Go Home!が実行されます。
我々はようやく成し遂げたのです(笑)。
なぜ、マルチモジュール化したかったのか?
さて、ここまででマルチモジュール化の方法が分かったわけですが、問題の発端のなぜ自分がマルチプロジェクトにしたかったのかをまだ説明していませんでした。
理由としてはC言語のライブラリをビルドしてcgoで呼び出すモジュールを書いたのですが、makeでビルドする必要があったのでgitのサブモジュールでローカルに取り込もうとして、必然的にマルチモジュール構成になりました。ただ本家のFAQでは一つのリポジトリに一つのモジュールをススメているので、一般的にはマルチモジュールの採用には慎重になったほうがいいと思われます。
まとめ
本記事ではGo Modulesにおける「マルチモジュール構成」に焦点を当てて、以下についてストーリ仕立てで解説しました。
- Go Modulesを使って依存関係を管理する方法
- パッケージに分割して呼び出す方法
- マルチモジュール構成にする方法
- ただし安易にマルチモジュール構成にしないほうがよい
- Go Home! する方法2
またこの記事を書くために作成したコードは以下に置きました。
この記事がGo Modulesを使ってモジュール管理を始めようという方、マルチモジュールで躓いた方の参考になれば幸いです。
参考文献
- Using Go Modules - The Go Blog (和訳)
- Modules · golang/go Wiki
- Go Modulesの概要とGo1.12に含まれるModulesに関する変更 #golangjp #go112party - My External Storage
- Go Modules
-
pkg
ディレクトリはGo Modulesを使う上で必須ではありませんが、ここではGoの標準レイアウトを採用しています。 ↩ -
Go Homeはネタなので優しくスルーして頂けると幸いです。 ↩