お久しぶりです
ちゃんと元気に生きています。 1ヶ月ほど禁酒していて体の調子がすごく良いです。その代わりアイスの消費量が増えました。毎日ハーゲンダッツとかガリガリ君とか食べてます。
独自Linterをgolangci-lintと統合する手順
まずこの記事でカバーする範囲について補足します。
- Linter のコードの書き方については一切触れません
- 書いたあと、それを golangci-lint に組み込む方法についてがメインです
- 手順の説明のため、GitHub - golangci/example-plugin-linter: example linter that can be used as a plugin for github.com/golangci/golangci-lint をサンプルプラグインとして使用します。自前のプラグインコードがすでに手元にあればそれを使って応用しても良いと思います。
- プロジェクト独自の Linter を、プロジェクト内部でプライベートに使用する方法の説明です。
概要
プロジェクトで Linter として golangci-lint
を使用している場合、他の Linter と合わせてプロジェクト独自の Linter を追加し適用させたい、という機会があるかもしれない。
ここでは golangci-lint
用の独自Linter を書いたあと、それを golangci-lint 上で使用できるようにするための手順をまとめた。
環境
本記事は以下の環境で動作確認した。なお golangci-lint
のソースインストールが必要なため Windows 環境では動作しない可能性がある(手元に環境がないため検証できなくてすみません)。
ハマりどころ
- Linter プラグインが依存するパッケージのバージョンをすべて
golangci-lint
と同一にしなければならない。golangci-lint
本体のバージョンが変わると、プラグイン側の依存パッケージのバージョンも要変更になる可能性がある
大まかな説明の流れ
golangci-lint
インストール- 独自 Linter パッケージを作成
- プラグインファイルを生成
- Linter を適用するプロジェクトの作成
- Linter を適用
ディレクトリ構成
ホームディレクトリ以下に work
という作業用ディレクトリを作成し、それ以下で作業を進めた。本記事の手順を試すにはまず任意の場所に空の work
ディレクトリ(名前は何でも良い)を作成してから始めれば良い。最終的には以下のようなディレクトリ構成になる。
example-plugin-linter
: プラグインのコードhello
: Linter の適用対象となるサンプルプロジェクトplugins
:golangci-lint
に組み込み可能なプラグインを配置する場所
work ├── example-plugin-linter │ ├── README.md │ ├── example.go │ ├── go.mod │ ├── go.sum │ ├── lint_test.go │ ├── plugin │ │ └── example.go │ ├── testdata │ │ └── src │ │ └── testlintdata │ │ └── todo │ │ └── todo.go │ └── tools.go ├── hello │ ├── .golangci.yml │ ├── go.mod │ └── main.go └── plugins └── example.so
準備
PATH
に $GOPATH/bin
を追加しておく。
$ export PATH=$PATH:$GOPATH/bin
golangci-lint インストール
今回は v1.42.0
を使用する。
カスタム Linter プラグインを golangci-lint
に組み込むには、本体をソースからインストールする必要がある(といっても go install
を使ってインストールするだけ)。バイナリインストールでは Plugin の組み込みができないようだ。
インストール方法の具体的な解説は Install | golangci-lint に掲載されているが、以下の通りに実行すれば良い。
なお、gcc
を必要に応じてインストールする。
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.0 $ golangci-lint --version golangci-lint has version v1.42.0 built from (unknown, mod sum: "h1:hqf1zo6zY3GKGjjBk3ttdH22tGwF6ZRpk6j6xyJmE8I=") on (unknown)
Linter プラグインの作成
今回は golangci/example-plugin-linter: example linter that can be used as a plugin for github.com/golangci/golangci-lint のサンプル Linter を使用する。
ちなみにこの LInter は、author の指定がない TODO:
コメントを検知するためのもの。以下のような挙動をする。
// TODO: author がないので NG // TODO(): author がないので NG // TODO(dareka): これは author を指定しているからOK
main.go:5:1: todo: TODO comment has no author (example) // TODO: author がないので NG ^ main.go:6:1: todo: TODO comment has no author (example) // TODO(): author がないので NG ^
というわけで、Linter の構築を進める。
$ git clone https://github.com/golangci/example-plugin-linter.git
$ cd example-plugin-linter
このサンプルを元に、プラグインファイル(*.so
)を生成する。
生成するコマンド自体は簡単。
$ go build -buildmode=plugin -o (出力先) (AnalyzerPlugin が定義された .go ファイル)
ただハマりどころなのは、このプラグインが依存するパッケージのバージョンを、golangci-lint
本体のそれと完全に合わせておく必要があるというだ。少しでもバージョンが異なると Linter を適用する段階でエラーとなる。
例えば、Linter のプラグインを書く時に不可欠な go/analysis
は golang.org/x/tools
パッケージに含まれるが、golangci-lint
が依存している golang.org/x/tools
のバージョンは、本体のバージョンによって変わる。
(golangci-lint v.1.41.1 の場合)
$ go version -m /home/egawata/go/bin/golangci-lint | grep golang.org/x/tools dep golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
(golangcl-lint v1.42.0 の場合)
$ go version -m /home/egawata/go/bin/golangci-lint | grep golang.org/x/tools dep golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
そして、プラグイン側も golang.org/x/tools
のバージョンが上記のものに合うよう、go.mod
を調整しておく必要がある。
とはいえ、 go version -m $GOPATH/bin/golangci-lint
を実行してすべてのパッケージのバージョンを確認し、go.mod
側を1つずつ書き換えていくのはあまり現実的ではない。そこで以下の方法で、確実にバージョンを合わせていく方法を取る。
(なお、この手順の意味については 参考1 が分かりすい)
golangci-lint
パッケージをブランクインポート
tools.go
というファイルをプラグインプロジェクトのルートに作成し、以下の内容を記述する。これにより、このプラグインプロジェクトが golangci-lint
に依存することを宣言できる。もちろん実際に参照している箇所はないのでブランクインポートで良い。
(tools.go
というファイル名や tools
というパッケージ名は何でも良いのだが、慣習的に tools
を使用するらしい)
// +build tools package tools import ( _ "github.com/golangci/golangci-lint/cmd/golangci-lint" )
golangci-lint の依存パッケージのバージョンを go.mod に反映させる
いったん go.mod
を捨てて作りなおす。
4行目の golangci-lint
のバージョンには、インストールしたバージョンと同一のものを指定するところがポイント。
また go mod tidy
と順番を逆にすると正しいバージョンのパッケージが入らないので注意。
$ rm go.* $ go mod init example $ go clean -modcache $ go get -d github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.0 $ go mod tidy
プラグインファイルの出力
$ mkdir ../plugins # なければ作る $ go build -buildmode=plugin -o ../plugins/example.so plugins/example.go
-o
に出力先を指定する。このパスはあとで設定に使用する。
Linter 適用対象のプロジェクトを作る
ここではサンプルプロジェクトとして hello
というものを新規に作成する。もし既存のプロジェクトがあるならそれを利用してみても良い。
$ cd ~/work/ $ mkdir hello $ cd hello $ go mod init hello
この下に、チェック対象のソースコード(main.go
)を以下のように作成する。
example
Linter の検出対象となるよう、main()
の前に問題のあるコメントを入れておく。
package main import "fmt" // TODO: implement here func main() { fmt.Println("Hello") }
Linter が example
プラグインを使用するよう、 .golangci.yml
に設定を追加する(なければ新規作成)
linters-settings: custom: example: path: ../plugins/example.so description: Check TODO without author original-url: github.com/golangci/example-linter linters: enable: - example
linters-settings.custom
以下に Plugin 情報を設定する。path
にプラグインの位置(絶対パス、もしくはhello
プロジェクトからの相対パス)を設定する。
また linters.enable
に、使用するプラグイン名を追加する。
Linter を適用
hello
プロジェクトのルートディレクトリで golangci-lint
実行。正しく適用されていることが分かる。
$ golangci-lint run ./... main.go:5:1: todo: TODO comment has no author (example) // TODO: implement here ^
補足
ここでは、作成した独自 Linter を golangci-lint
のプラグインとして統合する方法を説明した。しかし今回の方法は、やや手順が煩雑だということのほかに懸念点が一つある。
それは golangci-lint
をソースインストールする必要があるという点で、この方法はいくつか問題を抱えているようだ。
Note: such go get installation aren't guaranteed to work. We recommend using binary installation.
公式ドキュメントではその理由をいくつか挙げた上で、ソースインストールではなくバイナリインストールを推奨しているようだ。公式が推奨しない方法で利用を続けていくというのはいささか不安を拭えない。
独自 Linter を利用する方法がこれしかない、ということであれば素直に従う必要があるのだろうが、go vet
から起動する など他のお手軽な方法もある。今回取り上げた方法はあくまで選択肢の一つとしてとらえるのが丁度良いのかもしれない。
参考サイト
- 参考1 Developer tools as module dependencies
- 異なるモジュール間のバージョンを合わせる手法についての解説
- 参考2 Custom linter plugins for golangci-lint | by Adam Baratz | Jul, 2021 | Devoted Health + Tech
- golangci-lint でカスタムLinterプラグインを作成する際の注意点をまとめている
- 参考3 New linters | golangci-lint
- 公開/非公開 Linter の作り方について概要を説明している
- 参考4 Writing Useful go/analysis Linter
- カスタム Linter を作成し、golangci-lint に統合するまでの流れ。Linter コード自体の作成方法についてわかりやすい