Gemfileと依存Gemの状態を点検する gemxray というGemを作りました。
github.com
RubyやRailsのアプリケーションを長く運用していると、Gemfileには少しずつ依存が増えていきます。
もちろん、それぞれの依存には追加された当時の理由があります。しかし、機能追加やリファクタリング、RubyやRailsのバージョンアップを重ねていくうちに、今も本当に必要なのか判断しづらいgemが残っていることがあります。
たとえば、以前は直接使っていたけれど今は参照されていないgemや、他のgemの依存としてすでに入っているgem、RubyやRails本体に含まれるようになって不要になったgemなどです。
また、依存しているgemのライセンスがプロジェクトのポリシーに合っているか、ソースリポジトリがアーカイブされていてメンテナンスが止まっていないか、といったことも継続的に見ていく必要があります。
こうした依存の状態は、すぐに大きな問題になるとは限りません。
しかし、Gemfileが大きくなるほど、不要な依存の見直し、ライセンス確認、メンテナンス状況の把握、脆弱性対応の範囲も広がっていきます。
そこで、GemfileやGemfile.lockに含まれる依存を複数の観点から点検するCLIとしてgemxrayを作りました。
gemxrayとは
gemxrayは、RubyプロジェクトのGemfileと依存関係を解析して、削除できる可能性のあるgem、ライセンスポリシーに合っていないgem、アーカイブされている依存などを見つけるCLIです。
大きくは以下のことができます。
- 使われていなさそうなgemを検出する
- 他のgemの依存としてすでに入っていそうなgemを検出する
- RubyやRailsのバージョンによって不要になっていそうなgemを検出する
- 許可していないライセンスやライセンス不明のgemを検出する
- GitHub上でアーカイブされている依存を検出する
Gemfileから依存を消す判断は、最終的には人間が行うべきだと思っています。
また、ライセンスやメンテナンス状況についても、プロジェクトや組織ごとに判断基準が異なります。
gemxrayは自動で正解を決めるためのものというより、レビューすべき依存を機械的に集めて、依存関係の整理やコンプライアンス確認を始めやすくするための道具です。
インストール
インストールは通常のgemと同じです。
プロジェクトの開発用ツールとして入れる場合は、以下のように追加できます。
bundle add gemxray --group development
グローバルにインストールして使うこともできます。
gem install gemxray
プロジェクトに設定ファイルを作成する場合は、まず init を実行します。
bundle exec gemxray init
これで .gemxray.yml が作成されます。
まずscanする
基本的な使い方は scan です。
bundle exec gemxray scan
gemxray は、コマンドを指定しなかった場合も scan として動作します。
scan はGemfileと依存関係を解析して結果を表示するだけで、ファイルの変更は行いません。
CIやスクリプトで扱いやすいように、JSONやYAMLで出力することもできます。
bundle exec gemxray scan --format json
bundle exec gemxray scan --format yaml
また、CIで使う場合は --ci と --fail-on を指定することで、指定したseverity以上の検出結果がある場合に終了ステータスを 1 にできます。
bundle exec gemxray scan --format json --ci --fail-on danger
これによって、たとえば「dangerに分類される依存が増えたらCIで気づけるようにする」といった使い方ができます。
5つのanalyzer
gemxrayは、Gemfileと依存関係に対して5つの観点から解析を行います。
unused
unused は、プロジェクト内で使われていなさそうなgemを検出します。
具体的には、require、定数参照、gemspecの依存、Railsのautoloadの兆候などを見て、Gemfileに書かれているgemが使われているかを判断します。
もちろんRubyでは動的に定数を参照したり、文字列から require したりすることもできるため、完全に判定できるわけではありません。
そのため、これは「消してよいgemを断定する」というより、「今も必要か確認する価値のあるgemを見つける」ためのanalyzerです。
redundant
redundant は、他のトップレベルのgemが依存としてすでに持っているgemを検出します。
たとえばGemfileに直接書かれているgemが、実は別のgemの依存として Gemfile.lock にも含まれている場合、その直接依存は冗長かもしれません。
ただし、直接依存として明示しておくこと自体に意味がある場合もあります。
たとえば、そのgemのAPIをアプリケーションコードから直接使っている場合や、依存の意図をGemfile上に残しておきたい場合です。
そのため、ここも機械的に削除するというより、依存の持ち方を見直すきっかけにするためのものです。
version
version は、利用しているRubyやRailsのバージョンによって、すでにカバーされている可能性があるgemを検出します。
Rubyにはdefault gemやbundled gemがあります。また、Railsのバージョンアップによって、以前はGemfileに書いていたものが不要になることもあります。
こうした「昔は必要だったが、今はRubyやRails側で提供されているかもしれないもの」を見つけるためのanalyzerです。
バージョンアップを重ねてきたアプリケーションでは、この手の依存が残っていることがあります。
人間がGemfileを眺めていても見落としやすいところなので、機械的に候補を出せると便利だと思っています。
license
license は、GemfileやGemfile.lockから把握できる依存gemのライセンスを確認します。
.gemxray.yml には許可するライセンスの一覧を設定できます。
許可リストに含まれていないライセンスや、ライセンス情報が取得できないgemを検出できます。
license:
enabled: true
allowed:
- MIT
- Apache-2.0
- BSD-2-Clause
- BSD-3-Clause
- ISC
- Ruby
deny_unknown: false
ライセンスの情報は、ローカルにインストールされているgemspecを見て、必要に応じてRubyGems APIも参照します。
deny_unknown を true にすると、ライセンス不明のgemを danger として扱えます。
使いどころとしては、以下のようなケースを想定しています。
- プロジェクトで利用可能なライセンスを制限したいとき
- 新しいgemを追加した際に、ライセンス違反がないかCIで確認したいとき
- 企業のコンプライアンス要件に合わせて、依存gemのライセンスを継続的に管理したいとき
たとえば、MITとApache-2.0のみを許可するようなポリシーを設定しておけば、新しいgemを追加したときに、意図せず許可していないライセンスのgemが入っていないかを確認できます。
archive
archive は、依存しているRubyGemのGitHubリポジトリがアーカイブされているかを確認します。
アーカイブされている依存が即座に悪いというわけではありません。
しかし、メンテナンスが止まっている可能性を知っておくことは、依存管理の上で重要です。
GitHub APIを使うため、必要に応じて GITHUB_TOKEN を設定できます。
また、gemのメタデータから正しいGitHubリポジトリを推定できない場合に備えて、手動でgem名とリポジトリの対応を設定することもできます。
archive:
enabled: true
github_token_env: GITHUB_TOKEN
overrides:
some_gem: owner/repo
使いどころとしては、以下のようなケースを想定しています。
- 依存gemのメンテナンスが止まっていないか確認したいとき
- アーカイブ済みのgemに依存し続けるリスクを把握したいとき
- 代替gemへの移行を検討するきっかけを早めに得たいとき
アーカイブ済みのgemに依存し続けると、将来的にセキュリティパッチが出なかったり、新しいRubyやRailsへの追従が難しくなったりする可能性があります。
そのため、すぐに置き換えるかどうかは別として、依存のリスクを把握するための情報として扱えます。
severity
gemxrayの検出結果には info、warning、danger の3つのseverityがあります。
danger: 削除候補として信頼度が高いもの、ライセンス違反、設定によってはライセンス不明のもの
warning: 削除できる可能性があるもの、アーカイブされたリポジトリ、ライセンス不明のもの
info: 補足的なヒントや信頼度の低い検出結果
--severity を指定すると、指定したseverity以上の結果だけを表示できます。
bundle exec gemxray scan --severity warning
clean --auto-fix で自動削除の対象になるのは danger のみです。
warning や info は自動削除されません。
このあたりは意図的に保守的にしています。
Gemfileの整理は便利である一方で、誤って必要な依存を消してしまうとアプリケーションの動作に影響します。
また、ライセンス違反やアーカイブ済みリポジトリの検出は、必ずしも「削除すればよい」という話ではありません。
そのため、自動化できるところは自動化しつつ、レビューすべきところはレビューに残すという方針にしています。
Gemfileを整理する
検出結果を見て、実際にGemfileを整理したい場合は clean を使います。
bundle exec gemxray clean
clean は、scan と同じ解析を行った上で、検出されたgemごとに削除するかどうかを確認します。
y または yes を入力した場合のみ削除され、それ以外はスキップされます。
まず変更内容を確認したい場合は、--dry-run を使います。
bundle exec gemxray clean --dry-run
実際にはGemfileを書き換えず、削除候補と差分のプレビューだけを表示します。
また、削除するのではなくコメントとして残したい場合は --comment を使えます。
bundle exec gemxray clean --dry-run --comment
実際にGemfileを書き換える場合には、保存前に Gemfile.bak が作成されます。
また、--bundle を指定すると、編集後に bundle install を実行できます。
Pull Requestを作る
gemxrayには、検出からPull Request作成まで行う pr コマンドもあります。
bundle exec gemxray pr
pr は、解析結果をもとにGemfileを編集し、新しいブランチを作成してコミットし、GitHubにPull Requestを作成します。
内部的にはまず gh pr create を試し、gh が使えない場合は GH_TOKEN または GITHUB_TOKEN を使ってGitHub API経由でPRを作成します。
1つのPRにまとめることもできますし、--per-gem を指定するとgemごとにPRを作成できます。
bundle exec gemxray pr --per-gem --no-bundle
依存の整理は、1つずつ小さくレビューできる方が安心な場合があります。
とくに大きなRailsアプリケーションでは、gemごとにCIを通しながら確認した方が進めやすいこともあると思います。
そのような場合に --per-gem が使えます。
ライセンス一覧を出す
削除候補の検出とは別に、Gemfileに含まれるgemのライセンス一覧を出す licenses コマンドもあります。
bundle exec gemxray licenses
これにより、gem名、バージョン、ライセンスを一覧できます。
こちらも --format json や --format yaml を指定できるため、CIや社内の確認フローに組み込みやすいと思います。
bundle exec gemxray licenses --format json
設定
設定ファイルは .gemxray.yml です。
設定は、組み込みのデフォルト、.gemxray.yml、CLIオプションの順に適用されます。
たとえば、以下のように設定できます。
version: 1
ci: false
ci_fail_on: warning
whitelist:
- bootsnap
- tzinfo-data
scan_dirs:
- engines/billing/app
- engines/billing/lib
redundant_depth: 2
overrides:
puma:
severity: ignore
github:
base_branch: main
labels:
- dependencies
- cleanup
reviewers: []
per_gem: false
bundle_install: true
license:
enabled: true
allowed:
- MIT
- Apache-2.0
- BSD-2-Clause
- BSD-3-Clause
- ISC
- Ruby
deny_unknown: false
archive:
enabled: true
github_token_env: GITHUB_TOKEN
whitelist には、解析対象から外したいgemを設定できます。
scan_dirs には、標準の探索対象に加えて見に行きたいディレクトリを追加できます。
overrides では、gemごとにseverityを上書きしたり、ignore にして検出結果から外したりできます。
実際のプロジェクトでは、機械的な検出だけでは判断しづらい依存が必ず出てくると思います。
そのため、チームやプロジェクトごとの判断を設定ファイルに残せるようにしています。
おわりに
gemxrayは、GemfileとGemfile.lockの依存を機械的に眺めて、削除できる可能性があるもの、ライセンスポリシーに合っていないもの、メンテナンス状況を確認した方がよいものを見つけるためのCLIです。
Gemfileの整理は、地味ですがプロジェクトを長く運用していく上では大切な作業だと思っています。
不要な依存を減らすことは、アップデートの見通しを良くします。
同時に、ライセンス確認やメンテナンス状況の把握を継続的に行うことは、プロジェクトを安全に運用していく上で重要です。
一方で、依存の削除やライセンスの判断、アーカイブ済みリポジトリへの対応は雑にやると危険です。
だからこそ、候補を集めるところは機械に任せつつ、最終的な判断は人間がレビューできる形にするのが良いのではないかと思っています。
まだ改善できるところは多いですが、Gemfileの整理や依存gemの点検を始めるきっかけとして使ってもらえると嬉しいです。
引き続き、少しずつ改善していきたいと思います。