【Go】gomvpkgを使ってgoのpackageを安全にrenameする
はじめに
こんにちは、kenです。お仕事では Go をよく書きます。
Go言語で開発を進めていると、プロジェクトの成長に伴ってパッケージ名を変更したくなることってありますよね。
ただ手動でパッケージ名を変更すると、依存関係の修正漏れや置換ミスによるコンパイルエラーなどが発生することがあります。
そこで今回は、Goの公式ツールであるgomvpkgを使って、安全かつ効率的にパッケージ名を変更する方法を紹介します。
gomvpkgとは
gomvpkgは、Goのパッケージ名を安全に変更するためのツールです。
このツールは、パッケージ名の変更に伴う影響をプロジェクト全体にわたって自動的に反映してくれます。これにより手動での修正作業は大幅に削減でき、エラーのリスクを軽減できます。
gomvpkgのインストール
まずはgomvpkgをインストールしましょう。Goの公式ツールなので、go installコマンドを使って簡単にインストールできます。
go install golang.org/x/tools/cmd/gomvpkg@latest
パッケージ名の変更手順
では、実際にgomvpkgを使ってパッケージ名を変更してみましょう。
ここでは、oldpackageというパッケージ名をnewpackageに変更する例を示します。
1. 現在のプロジェクト構成
まずは、現在のプロジェクト構成を確認します。
myproject/
├── main.go
└── oldpackage/
└── sample.go
main.goでは、oldpackage内のsample.goの中で定義した変数などを参照しています。
2. GO111MODULE環境変数をoffに設定
コマンドの実行前にGO111MODULE環境変数をoffに設定しておきます。
「おや?」と思った方もいるかもしれませんが、理由は後述するので今はスルーしてください。
export GO111MODULE=off
また、gomvpkgを実行したいプロジェクトはGOPATH配下に置く必要があります。これも理由は後述します。
3. gomvpkgを使ってパッケージ名を変更
次に、gomvpkgを使ってパッケージ名を変更します。
gomvpkg -from github.com/TechBlogSamples/go-mvpkg/oldpackage -to github.com/TechBlogSamples/go-mvpkg/newpackage
このコマンドを実行すると、oldpackageがnewpackageに変更され、プロジェクト内のすべての参照が自動的に更新されます。
変更後のプロジェクト構成は以下のようになります。
myproject/
├── main.go
└── newpackage/
└── sample.go
sample.go内のパッケージ宣言や、main.goでのimportパスも自動的に更新されています。
次に貼るGIFをご覧いただければその様子がわかると思います。

変更先のpackage名が他の変数名等と競合する場合
変更先のpackage名が、コード内の変数名などと競合する場合、単純な置き換えではうまくいかないはずです。そのときの挙動がどうなるのか試してみたところ、どうやら処理をスキップするようです。
さすがにこの場合は手動で対応してねということなんですかね。
❯ gomvpkg -from github.com/TechBlogSamples/go-mvpkg/oldpackage -to github.com/TechBlogSamples/go-mvpkg/newpackage
/Users/ken/go/src/github.com/TechBlogSamples/go-mvpkg/main.go:6:2: renaming this imported package name "oldpackage" to "newpackage"
/Users/ken/go/src/github.com/TechBlogSamples/go-mvpkg/main.go:14:9: would cause this reference to become shadowed
/Users/ken/go/src/github.com/TechBlogSamples/go-mvpkg/main.go:12:2: by this intervening var definition
/Users/ken/go/src/github.com/TechBlogSamples/go-mvpkg/main.go:4:2: skipping update of this file

注意点
この gomvpkg には一つ注意点があります。
それは GOPATHモード(GO111MODULE=off)でないと importパスの自動更新が行われない ということです。
具体的な説明
どうやら、モジュール対応モード(GO111MODULE=on)の場合にgomvpkg を使ってパッケージ名を変更すると、対象のディレクトリ名やパッケージ名は変更されますが、そのパッケージを参照している他のファイルのimportパスは 自動で更新されないようです。
例えば、先ほど使ったgomvpkgを実行してもに変更しても、モジュール対応モードの場合import "github.com/TechBlogSamples/go-mvpkg/oldpackage" の部分は import "github.com/TechBlogSamples/go-mvpkg/newpackage" に書き換えられません。(一方でpackage宣言文の書き換えや、ディレクトリ名の変更は行われます)
つまり、モジュール対応モードでgomvpkg を実行した場合は手動で import パスを修正する必要があるということです。(ちょっと不便ですね)[1]
理由
このgomvpkgの実装コード内ではgolang.org/x/tools/refactor/renameパッケージのMove関数を呼び出しているのですが、この関数がモジュール対応モードに対応してないようです。
Package rename contains the obsolete implementation of the deleted golang.org/x/tools/cmd/gorename. This logic has not worked properly since the advent of Go modules, and should be deleted too.
パッケージの整理は頻繁でないにしろ、行う機会はまあまああるのでモジュール対応モードでも使えるようにしてほしいところではありますが、今は難しそうですね。
さいごに
gomvpkgを使うことで、Goのパッケージ名を安全かつ効率的に変更することができました。
ただ、GOPATHモードでないと動かせないのが少し不便かつ気をつけないといけない部分でしたね...。
最後まで読んでいただきありがとうございました。間違いなどありましたらコメントにてご指摘ください。
参考
他の方の記事ですが、GOPATHモードを理解するにあたって下の記事を読ませていただきました。ありがとうございました。
-
また
export GO111MODULE=offと設定していても、モジュール名がGOPATHモードと互換性のある書き方(GOPATH環境変数からプロジェクトルートへの相対パス)でない場合もおそらく動かないと思います。 ↩︎
Discussion