go の自作モジュールを github で公開して import するまで

LuneScript 向けの別ツールを作ろうと思い、 LuneScript の go 向けランタイムを単独モジュールとして分割して管理すべく 奮闘した際の備忘録。

go のモジュール

go は、 github に公開されているモジュールを取得して使用できる。

では、自作モジュールを github に公開して使用するにはどうすれば良いか?

ここでは、その方法について順を追って説明する。

go のモジュール管理のおさらい

go にはモジュール管理機能が内包されており、 基本的には次の手順でコマンドを実行するだけで、 使用している依存モジュールを管理できる。

$ go mod init
$ go mod tidy

依存モジュール情報は go.mod に記録される。

自作モジュールを github へ公開する

モジュールを github へ公開しないと始まらないので、 まずは github に公開する。

この時ディレクトリ構成は何でも良い。

最も重要な点は、次の 1 点。

タグを付ける

このタグは v0.0.0 (数値はモジュールのバージョン)で付ける。

これがないと、 意図したリビジョンのモジュールを go が取ってきてくれない。

タグは必須ではない。

タグはバージョン管理する際に必要だが、 タグがなくても go でモジュールを利用できる。

go mod init した際に、 プログラムで import しているモジュール情報を収集し、 そのモジュールの最新のタグを拾ってきて go.mod に記録している。

このときに依存モジュールの git に一つもタグがないと、 依存モジュールの初回コミットが利用される。 一つでもタグがある場合は、そのタグが利用される。

go のバージョンアップで go mod init , go mod tidy の動きが変っている。

go 1.16 では、以下の動作になる。

  • go mod init

    • go のコードは解析せず、 module 名と go のバージョンだけ記載した go.mod を作成する。
  • go mod tidy

    • go のコードを解析し、 import しているモジュール情報を取得し go.mod に反映する。
    • このとき、 import しているモジュールにタグが付いていれば、 最新タグのモジュールを取得する。

      • タグ付けした後に更新があったとしても、 それにタグが無ければその更新は無視される。
    • import しているモジュールにタグが付いていなければ、最新のコミットを取得する
    • go.mod に、既にモジュール情報、バージョン情報が記載されていれば、 その情報は更新しない。
    • バージョン情報にブランチ名を書いていると、そのブランチの最新コミットを取得する

      • これは、モジュールにタグが付いている場合でも、最新コミットを取得する。

自作モジュールの更新

自作モジュールを更新した場合、 git への push はもちろんのこと、 タグを付けなければならない。

前述した通り、go の依存モジュール管理は、あくまでもタグで制御しているため、 その修正を有効にするにはタグ付けが必須である。

自作モジュールを更新した場合、git への push するだけで良い。

なお、ローカルで試す際は git への push せずに確認できる。

go.mod の更新

モジュールを使用している側の go.mod は、 import しているモジュールと、そのバージョン(タグ)を紐付けて管理している。

一度 go.mod にバージョン情報が記録されると、 go mod tidy を実行しても依存モジュールのバージョンが自動で更新されることはない。

使用する依存モジュールのバージョンを更新するには、 go.mod で指定されているバージョンを書き換える必要がある。

最新に変更するだけなら、バージョン情報にブランチ名を書けば良い。 たいていは master を指定するだけでよい。

環境変数の GOPATH 以下にソースを置いていれば、基本的にはこれだけで良い。

環境変数の GOPATH 以下にソースを置いていない場合は、 幾つかの対応が必要となる。

なお、 go module を利用するのであれば、 GOPATH 以下にソースを置くのが結局は間違いのない方法になる。

ただし、GOPATH 以下にソースを置くとディレクトリが深くなってしまうので、 それを嫌うのであれば、ソースを管理するディレクトリは別に作成し、 GOPATH 以下にはシンボリックリンクを作成することで回避できる。 但し、シンボリックリンクでは正常に動作しない可能性も考えられるので、 その辺りは自己責任で。

replace

以上のように、 依存モジュールはバージョン情報で管理されている。

これは、依存モジュールの再現性を担保するには必要な機能である。 一方でバージョン毎に管理するのが面倒なこともある。

replace 機能は、 require しているモジュールを他の場所から取得できるように置き換える機能である。

たとえば、 github.com/ifritJP/lnssqlite3 のモジュールを ../ のローカルディレクトリから取得したい場合は、 次のように書く。

require github.com/ifritJP/lnssqlite3 v0.0.0
replace github.com/ifritJP/lnssqlite3 => ../

これにより github.com/ifritJP/lnssqlite3 は、 どのバージョンに限らずに ../ ディレクトリのものを利用する。

ブランチ名

前述の通り go.mod は依存モジュールをバージョンと紐付けて管理している。

module hoge

go 1.14

require github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
require github.com/ifritJP/LuneScript v1.1.12-0.20201216131727-df4ec0979d4d

ここで、次のようにバージョンの代わりにブランチ名を指定し、 go mod tidy することで、そのブランチの最新を取得できる。

module hoge

go 1.14

require github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
require github.com/ifritJP/LuneScript master

ただし、go mod tidy すると、 上記の master の部分が v1.1.12-0.20201216131727-df4ec0979d4d のように 最新のバージョンに置き変わるので、 依存ライブラリを再度更新した場合、 go.mod を master に書き直す必要がある。

外部ライブラリを利用している場合

LuneScript は、外部ラリブラリとして lua を利用している。

go は cgo を使うことで C 言語のライブラリを利用できるが、 cgo では外部ライブラリの include パスやリンクオプションを .go のソースファイル内にコメントとして指定する必要がある。

外部ライブラリのパスは環境によって異なるため、 全ての環境に合せて include パスやリンクオプションを指定しておくことは出来ない。

そこで pkg-config を利用する。

cgo で pkg-config を利用するには、次のように指定する。

// #cgo pkg-config: package1 package2 package3

LuneScript では、次のように指定している。

// #include <string.h>
// #include <stdlib.h>
// #cgo pkg-config: lua-5.3
// #include <lauxlib.h>
// #include <lualib.h>
import "C"