Go Modulesとマルチモジュール構成でGo Homeする方法

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以外のパスで作業をするか以下で環境変数を設定します。

1
export GO111MODULE=on

この記事ではGo 1.12の前提で解説します。

Go Modulesで管理を始める

基本はディレクトリを作成してgo mod init <モジュール名>で始められます。

1
2
3
$ mkdir go-multi-modules
$ cd go-multi-modules
$ go mod init go-multi-modules

go.modというファイルが作成されています。これが依存関係を管理するファイルになります。

go.mod
1
2
3
module go-multi-modules

go 1.12

まずは基本のおはようの挨拶から

早速ですが、基本どおりHello Worldから初めて見ます。ただし、依存関係を入れるために go-figureを利用して挨拶をしてみます。ソースコードは以下の通りです。

main.go
1
2
3
4
5
6
7
8
9
10
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で実行して無事挨拶ができれば成功です。

1
2
3
4
5
6
7
8
$ go build
go: finding github.com/common-nighthawk/go-figure latest
$ ./go-multi-modules
_ _ _ _ __ __ _ _
| | | | ___ | | | | ___ \ \ / / ___ _ __ | | __| |
| |_| | / _ \ | | | | / _ \ \ \ /\ / / / _ \ | '__| | | / _` |
| _ | | __/ | | | | | (_) | \ V V / | (_) | | | | | | (_| |
|_| |_| \___| |_| |_| \___/ \_/\_/ \___/ |_| |_| \__,_|

go.modファイルを見てみるとrequireの行が追加されて依存関係が追跡されているのが分かります。

go.mod
1
2
3
4
5
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は検査用に必要なようです。詳しくは ここを参照してください。

go.sum
1
2
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というパッケージを作成します。

ディレクトリ構成
1
2
3
4
5
6
7
8
.
├── 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ファイルの中身は以下の通りです。

hello-world.go
1
2
3
4
5
6
7
8
9
10
package helloworld

import(
"github.com/common-nighthawk/go-figure"
)

func HelloWorld() {
myFigure := figure.NewFigure("Hello World", "", true)
myFigure.Print()
}

main.goファイルは以下のように書き換えます。

hello-world.go
1
2
3
4
5
6
7
8
9
package main

import (
"go-multi-modules/pkg/hello-world"
)

func main() {
helloworld.HelloWorld()
}

go buildでビルドして./go-multi-modulesで実行して同じように挨拶ができたら成功です。

[^1]: pkgディレクトリはGo Modulesを使う上で必須ではありませんが、ここではGoの標準レイアウトを採用しています。

Go Homeしようとして失敗する

さて、挨拶も済んだのでもう用はありません。帰宅したくなってきたとします。ただし、帰宅時間まで細かく管理されたくないので別モジュールで管理することを考えます。この場合、pkgディレクトリ配下にgo-homeディレクトリを作成して、go-homeディレクトリに移動してからgo mod init gohomeを実行します。
ディレクトリ構成は以下のようになります。

ディレクトリ構成
1
2
3
4
5
6
7
8
9
10
11
.
├── 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はありません。

go.mod
1
2
3
module gohome

go 1.12

home.goは以下のようになります。

home.go
1
2
3
4
5
6
7
package gohome

import("github.com/common-nighthawk/go-figure")

func GoHome() {
figure.NewFigure("Go Home!", "basic", true).Scroll(30000, 400, "right")
}

main.goは以下のように書き換えます。

main.go
1
2
3
4
5
6
7
8
9
10
11
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でビルドしようとしたところ以下のようなエラーが出てうまくいきませんでした。どうやらモジュールの読み込みに失敗したようです。

1
2
$ 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)が追加されています。

go.mod
1
2
3
4
5
6
7
8
9
10
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を使ってモジュール管理を始めようという方、マルチモジュールで躓いた方の参考になれば幸いです。

[^2]: Go Homeはネタなので優しくスルーして頂けると幸いです。

参考文献

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×