golangci-lintのModule Plugin Systemが良さそうなので使ってみた
はじめに
この記事では、golangci-lint
のv1.57.0
でリリースされたModule Plugin System
について既存のPlugin機構を交えて解説します。
弊社サービスSNKRDUNKのバックエンドはGoで実装されておりLinterはgolangci-lintを使っています。
Pluginで動かしているLinterもあり、その影響でローカル環境でgolangci-lintを実行するのがやや手間になっており、何か良い方法がないかと調べていたらModule Plugin Systemというものがリリースされていたので、自分自身の理解のためにも今回の記事を書くに至りました。
golangci-lintのPlugin Systemについて
まずはじめに、golangci-lintにlinterを追加する方法は以下の2通りあります。
1. public linterとしてgolangci-lint本体に実装する
2. private linterとして実装して、golangci-lintのPluginとして動かす
1のケースではgolangci-lintの公式ドキュメントに沿って実装してPRを出すフローです。
label: new
のラベルでフィルタリングするとlinterに関するPR一覧を確認できます。
declinedされてるlinterも結構ありますが、この辺のPRを読むのも勉強になりそうですね。
Plugin Systemは、2のprivate linterをgolangci-lintの一部として動作することを可能にする仕組みです。
Plugin SystemにはGo Plugin System
とModule Plugin System
の2つが存在します。
Go Plugin System
Go Plugin SystemはModule Plugin Systemがリリースされる前から存在しているPlugin機構で、Go標準パッケージで用意されているPlugin機構を利用して実現されています。
仕組としては任意のパスに配置したPluginの実行ファイル(*.so)をgolanci-lintが実行時に標準Pluginパッケージの仕組みを使ってロードして実行するという感じです。
追加手順
1. Linterを実装する
Linterではgolang.org/x/tools/go/analysis
のAnalyzer
を提供する必要があります。
さらにPluginとして動かすためにはplugin/example.goのようファイルを作成して、([]*analysis.Analyzer, error)
を返すNew関数を実装する必要があります。
↓が公式ドキュメントに載っている例です。
*.so
実行ファイルを生成する
2. Linterをビルドして実行ファイルを生成するには以下のようにgo build
コマンドでオプション-buildmode=plugin
を指定して、手順1で追加したplugin/exmaple.goファイルパスを指定します。
go build -buildmode=plugin plugin/example.go
.golanci.yml
でlinters-settings.custom.{linter_name}.path
でビルドした*.so
ファイルへのパスを設定して、linters.enable
で有効かする
3. 以下が具体的な指定方法です
linters-settings:
custom:
example:
path: /example.so
description: The description of the linter
linters:
disable-all: true
enable:
- example
Go Plugin Systemのつらみ
1. golangci-lintをCGO_ENABLED=1でビルドする必要がある
これはGo標準のPluginでCGO_ENABLED=1じゃないと動かないのが影響してると思われます。
2. golangci-lintと同じスタックでPluginをビルドする必要がある
Goのバージョン, 依存するライブラリのバージョンなどは全て揃えないと動かないです。つらい。
以下のようにgolangci-lintをblank importすることで、明示的にgolangci-lintに依存させてライブラリのバージョンを揃えるということをしていました。
3. ビルドしたPluginの実行ファイル(*.so)を任意のディレクトリに配置する必要がある
1人でローカル開発する分にはあまり気にする問題ではないかも知れませんが、チームで開発する場合にそれぞれの手元でPluginをビルドしてもらうのは結構大変です。
そしてローカルのgolangci-lintのバージョンがそれぞれ異なることが原因で、ローカルでPluginが動かないこともあります。
Module Plugin System
Module Plugin Systemはv1.57.0でリリースされた新しいPlugin機構です。
この新しく追加されたPlugin機構では、Go Plugin Systemのつらみを解消するアプローチをいくつかとっています。
-
PluginのLinterパッケージをシンプルなGoの依存関係として、golangci-lintの
cmd/golanci-lint/plugins.go
にブランクインポートする -
PluginのLinterパッケージのinit関数で
github.com/golangci/plugin-module-register/register
パッケージのPlugin関数を使って、レジスタに自身を登録する -
golangci-lint内部では、レジスタからプラグインをロードする
上記のようなアプローチをすることで、GoのPlugin機構を使うことがなくなるのでCGO_ENABLED=1でgolangci-lintをビルドする必要がなくなります。
さらに、シンプルなGoの依存関係としてPluginをブランクインポートするので、golanci-lintビルド時にPluginが依存パッケージとして同梱されてる状態なので、Plugin側でバージョン差分などを気にする必要がなくなります。
そして、Pluginはgolangci-lintのバイナリに含まれているので、別途Pluginの実行ファイルを任意のディレクトリに配置する必要もありません。
Go Plugin Systemのつらみを解消していてすごい!!
さらに、この変更に伴ってgolanci-lint custom
コマンドもリリースされ、設定ファイルを追加することで、このコマンド一発でPluginを同梱したgolanci-lintバイナリをビルドしてくれます。便利すぎる
追加手順
.custom-gcl.yml
に設定を記述する
1. version: v1.59.0 # golangci-lintのバージョン
name: custom-golangci-lint # バイナリの名前。デフォルトはcustom-gcl
destination: ./.plugins/ # バイナリが格納されるディクトりを指定
plugins:
- module: 'github.com/snkrdunk/empty_err_checker' # Pluginのモジュール名
path: ../empty_err_checker/ # Pluginへのパス
golangci-lint custom
でカスタムバイナリをビルドする
2. -v
オプションをつけるとログを確認できます。ビルドが成功すると以下のような表示になります。
ログからcmd/golangci-lint/plugins.go
にPluginパッケージをブランクインポートしていることが分かります。
$ golangci-lint custom -v
INFO Cloning golangci-lint repository
INFO Adding plugin imports
INFO generated imports info /var/folders/d_/j7r2nk5x74dblhw7p5v_rgc80000gn/T/custom-gcl649156304/golangci-lint/cmd/golangci-lint/plugins.go:
package main
import (
_ "github.com/snkrdunk/empty_err_checker"
)
INFO Adding replace directives
INFO run: go mod edit -replace github.com/snkrdunk/empty_err_checker=/Users/otaka.toshiki/go/src/github.com/snkrdunk/empty_err_checker
INFO Running go mod tidy
INFO Building golangci-lint binary
INFO Moving golangci-lint binary
.golangci.yml
に設定を記述
3. 以下が設定例です。
Go Plugin Systemと違うのは、typeで"module"を指定していることです。
linters-settings:
custom:
custom_linters:
type: "module"
description: custom_linters is checking terms of snkrdunk.com.
linters:
disable-all: true
enable:
- custom_linters
4. カスタムビルドしたgolangci-lintを実行
今回の例だと、.plugins
ディレクトリにcustom-golangci-lint
という名前でカスタムビルドしたバイナリを格納したので以下のように実行すると、Pluginを含んだ状態でgolangci-lintが走ります。
$./.plugins/custom-golangci-lint run
最後に
Module Plugin System
が追加されたPRでGo Plugin System
が廃止されるか、今後も共存するのかという会話があり、2つのPlugin Systemは共存できメンテナンスもそこまで必要ないが、これが将来必ずGo Plugin Systemを廃止しないということではないとコメントされています。
なので、すぐにGo Plugin Systemが廃止されることはない思いますが、今後廃止される可能性はあるので早めにModule Plugin Systemを使うようにした方が良いかもしれません。
株式会社SODAの開発組織がお届けするZenn Publicationです。 是非Entrance Bookもご覧ください! → recruit.soda-inc.jp/engineer
Discussion