以前、rails routes を便利に扱う IntelliJ Platform Plugin の Railroads を作りました。日頃の開発でも使っていますが、ほぼノーメンテナンスで使い続けてきました。
- RubyMine 2023.3系から、rails routes を便利に扱える Railways プラグインが動かなくなったので、代替プラグイン Railroads を作りました
- https://plugins.jetbrains.com/plugin/24076-railroads
そんな中、JetBrains IDE 2025.1 で使うとExceptionが出る旨のバグレポートをいただきました。
https://github.com/thinkAmi/railroads/issues/54
ためしに手元のRubyMineで動かしてみたところ、状況としては次のような感じで再現性があるものでした。日頃の開発にも影響しそうなので対応することにしました。
- 2024.3系
- 発生しない
- 2025.1
- 発生する
Rails Routesボタンをクリックしたときにエラーが発生する- ボタンを押すとき以外にも発生している
rails routesの結果は取得できている模様
- 発生する
自分で調査して対応することも考えましたが、4/17に公開されたJetBrainsの強化されたAIのことを思い出しました。
JetBrains AI | Intelligent Coding Assistance, AI Solutions, and More
今までJetBrainsのAI AssistantやJunieを使ったことがない自分としては、JetBrains AIを使うちょうどよい機会のように感じました。そこで、今回AIとともに修正してみたことから、内容をメモとして残しておきます。
目次
- 環境
- IntelliJ Platform Gradle Plugin 2.x 化
- AI Assistantを使ったエラー修正
- 今回の修正の影響により、サポートするIDEのバーションを変更
- ソースコード
環境
IntelliJ Platform Gradle Plugin 2.x 化
RailroadsプラグインはIntelliJ Platform Plugin Templateをベースに作っています。
https://github.com/JetBrains/intellij-platform-plugin-template
Railroadsプラグインを作った当時はこのテンプレートに含まれるGradle系のプラグインは1.x系でした。しかし、テンプレートのバージョンアップが進んだ結果、
など、Railroadsとテンプレートの間で開発環境の乖離が発生していました。
テンプレートに追随してプラグインを修正する必要はないかもしれません。しかし、今後の開発効率を考えるとテンプレートに追随したほうが良さそうと感じました。また、IDEのバージョン2025.1で発生したエラーがテンプレートを使った環境では解消できているかも...という思いもありました。
そこで、今回のタイミングでGradle系をはじめ、利用しているライブラリをバージョンアップすることにしました。次に、バージョンアップについては以下のどちらかで進めようと考えました。
- テンプレートをupstreamとして、Railroadsリポジトリにmergeし、コンフリクトを解消する
- テンプレートの修正を確認し、必要な部分だけRailroadsへ取り込む
前者を試してみたところ、コンフリクトがかなり発生し修正するのが大変そうでした。そこで今回は後者の方法を取ることにしました。
作業については、テンプレートの内容を正として、そこにRailroadsの設定を追加する流れで進めました。
困ったのが、「Railroadsプラグインの動作確認をする際、ローカルに該当バージョンのIDEがあればそちらを起動する」と独自の設定をしていたことです。新しいGradleプラグインではどのように実現すればよいか調べたところ、 local を使えば良さそうでした。
https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#custom-target-platforms
テンプレートでは create を使っていますが、 local とは併用できないようでした。そこで、開発環境にある設定ファイルに定義があればローカルのIDEを、そうでなければ create を使うようにしました。
val localPropertiesFileExists = File(rootProject.rootDir, "local.properties").exists() val prop = if (localPropertiesFileExists) Properties().apply { load(FileInputStream(File(rootProject.rootDir, "local.properties"))) } else null // ... dependencies { intellijPlatform { if (prop != null) { prop.getProperty("ideDir")?.let { ideDirValue -> if (ideDirValue.isNotEmpty()) { local(file(ideDirValue)) } } } else { create(providers.gradleProperty("platformType"), providers.gradleProperty("platformVersion")) } // ...
修正が終わったので動作確認したところ、まだ2025.1ではエラーが発生していました。
AI Assistantを使ったエラー修正
コーディングで使えそうなAIは AI Assistant と Junie でした。まずは AI Assistant を使ってみて、それでダメそうならJunieを使う方向で進めることにしました。
AI Assistantでは Codebase をオン、モデルを Claude 3.7 Sonnet を選択し、他は何も設定しないで進めることとしました。また、AI Assistantの性能を確認するため、AI Assistantにはエラーメッセージを丸投げすることにしました。
Read access is allowed from inside read-action only への対応
AI Assistantに次のような質問をしました。
RubyMine 2025.1 で実行すると以下のエラーがでます。なにか考えられることはありますか? com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments: Read access is allowed from inside read-action only (see Application.runReadAction()); If you access or modify model on EDT consider wrapping your code in WriteIntentReadAction ; see https://jb.gg/ij-platform-threading for details ...
すると、次のような回答と提案がありました。
## 問題の内容 スタックトレースを確認すると、`RailsAction.getMethodVisibility()`メソッドで、PSIファイル(プログラム構造情報)にアクセスしようとしているときに、適切な読み取りアクションでラップされていないことがわかります。 IntelliJ Platformでは、モデルデータへのアクセス(特にPSI要素へのアクセス)は、以下のルールに従う必要があります: 1. 読み取り操作 → `runReadAction()` 内で行う 2. 書き込み操作 → `runWriteAction()` 内で行う ## 解決方法 ### 1. 問題のコードを特定する エラースタックトレースの主な問題は以下の部分で発生しています `RailsAction.getMethodVisibility()`メソッドはPSI要素にアクセスしていますが、適切な読み取りアクションでラップされていません。 ### 2. ReadActionでコードをラップする 修正するには、PSI要素にアクセスするコードを`ReadAction`でラップします: ## 2025.1の変更点について IntelliJ Platform 2025.1では、スレッディングモデルが強化され、以前よりもスレッドセーフティに関するチェックが厳格になっています。これにより、以前のバージョンでは問題なく動作していたコードが、新しいバージョンではエラーになることがあります。 特に以下の点に注意する必要があります: 1. EDTスレッド(UIスレッド)からのPSI要素へのアクセスは、適切な読み取りアクションでラップする必要があります 2. 読み取り/書き込みアクションは、特定のコンテキスト内でのみ許可されます 3. 長時間の操作はバックグラウンドスレッドで実行すべきです ## 具体的な修正案 問題のコードは `RailsAction.getMethodVisibility()` メソッドと思われます。以下のように修正を行ってください:
もらった修正案はこちら。
// RailsAction.kt import com.intellij.openapi.application.ReadAction class RailsAction { // ... fun getMethodVisibility(): String { return ReadAction.compute<String, Throwable> { // 元のメソッドの内容 // 例: rMethod?.visibility?.toString() ?: "unknown" } } fun getIcon(): Icon { return ReadAction.compute<Icon, Throwable> { // 元のメソッドの内容 // 例: IconLoader.getIcon("icons/${getMethodVisibility()}.png", this.javaClass) } } }
修正案を元に自分で実装したところ、エラーは解消しました。
Class initialization must not depend on services への対応
ビルドしたところ別のエラーが出たため、またAI Assistantへ質問しました。
今の状態でビルドすると、プラグインを実行したときに以下のエラーになります。何が原因かわかりますか
java.lang.Throwable: com.github.thinkami.railroads.ui.RailroadColor <clinit> requests com.intellij.openapi.editor.colors.TextAttributesKey$TextAttributeKeyDefaultsProvider instance. Class initialization must not depend on services. Consider using instance of the service on-demand instead.
at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
at com.intellij.serviceContainer.ComponentManagerImplKt.checkOutsideClassInitializer(ComponentManagerImpl.kt:1588)
...
次のような回答がありました(文中のソースコードは (略) のように省略)。
## 問題の原因 ファイルを確認すると、以下のコードに問題があります: `RailroadColor.kt` (略) このコードは`companion object`内でクラスの初期化時に実行されており、のサービスに依存しています。IntelliJプラットフォームでは、クラスの初期化(static initializer / `<clinit>`)でサービスに依存することは禁止されています。これは、サービスの初期化順序によって予期しない問題が発生する可能性があるためです。 `TextAttributesKey` ## 解決方法 この問題を解決するためには、以下のいずれかの方法を使用できます: ### 1. 遅延初期化を使用する方法 RailroadColor.ktを以下のように修正します: `RailroadColor.kt` (略) この方法では、`by lazy`を使用しての取得をクラス初期化時ではなく、実際に値が必要になった時点まで遅延させています。 `TextAttributesKey` ### 2. インスタンスメソッドとして実装する方法 もう一つの方法は、`companion object`ではなくインスタンスメソッドとして実装することです: (略) この場合、ActionCellRenderer.kt の修正は必要ありません。 `ActionCellRenderer.kt` ### 3. 必要なときにその場で生成する方法 もう一つのアプローチは、を単なるユーティリティクラスとして扱い、必要なときに属性を生成するようにする方法です: `RailroadColor` ## 推奨される解決方法 最も推奨される方法は、1番目の「遅延初期化を使用する方法」です。この方法により、`companion object`の構造を維持しながら、サービスへの依存を遅延初期化することができます。また、既存のコードに最小限の変更で対応できるメリットがあります。 エラーの根本的な問題は、IntelliJプラットフォームのサービスへの依存をクラス初期化時に行うと、依存関係の循環や初期化の順序によって問題が発生する可能性があることです。遅延初期化を使用することで、必要になった時点でサービスにアクセスするため、この問題を回避できます。
提案通りに修正したところ、このエラーは解消しました。
Not enough memory to run compilation への対応
再びビルドすると、別のエラーメッセージが出ました。Gradleのどこに設定をすればよいか分からなかったため、これも質問してみました。
現在の状態でプラグインをビルドすると以下のメッセージが表示されます。どのような設定をすればよいですか Not enough memory to run compilation. Try to increase it via 'gradle.properties': kotlin.daemon.jvmargs=-Xmx<size>
回答は以下でした。
# Kotlin コンパイラのメモリ設定方法 エラーメッセージ「Not enough memory to run compilation」は、Kotlinコンパイルデーモンに割り当てられているメモリが不足していることを示しています。このエラーを解決するために、ファイルにKotlinデーモンのJVMヒープサイズを増やす設定を追加する必要があります。 `gradle.properties` ## 解決方法 ファイルに以下の行を追加してください: `gradle.properties` (略) この設定は、Kotlinコンパイラデーモンに最大2GBのメモリを割り当てます。ご使用のマシンのメモリ容量に応じて、この値を調整できます。一般的には以下のような値が使用されます: - `-Xmx1536m`: 1.5GB(中小規模のプロジェクト向け) - : 2GB(中規模プロジェクト向け) `-Xmx2048m` - `-Xmx3072m`: 3GB(大規模プロジェクト向け) - `-Xmx4096m`: 4GB(非常に大規模なプロジェクト向け) ## 追加の設定オプション メモリ不足の問題が続く場合は、以下の追加設定も検討してください: ### Gradleのメモリ設定 Gradle自体のメモリも増やすことができます: > Gradleデーモンのメモリ設定 > org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError ### 並列実行の調整 Gradleの並列実行ワーカー数を調整することで、リソースの使用を最適化できます: > # Gradleの並列実行の設定 > org.gradle.parallel=true > org.gradle.workers.max=4 # CPUコア数に合わせて調整 ### インクリメンタルコンパイルの有効化 Kotlinのインクリメンタルコンパイルを有効にすると、ビルド時間とメモリ使用量を削減できます: > # インクリメンタルコンパイルの有効化 > kotlin.incremental=true
Kotlinコンパイラのメモリ設定を変更したところエラーを解消できたため、他の設定はそのままにしました。
その他の改善提案への対応
ここまでの修正で JetBrains IDE 2025.1 でエラーなしで動作するようになりました。そこで、最後によりよい修正方法がないかを聞いてみました。
ここまでの対応でビルドが通り、2025.1バージョンのIDEで動くようになりました。修正したファイルのうち、RailsAction.ktやRailroadColor.ktでより良い実装方法があれば提案してください。
回答は以下でした。
# RailsAction.ktとRailroadColor.ktの改善提案 ビルドが通り、IntelliJ IDE 2025.1で動作するようになったのは素晴らしいですね。現在の実装を確認し、より良い実装方法を提案します。 ## 1. RailsAction.ktの改善提案 ### 1.1 ReadActionのよりシンプルな使用方法 現在の実装ではReadActionを使用していますが、さらに改善できる点があります: ### 1.2 nullセーフティの改善 現在のコードではnull確認が多数あります。Kotlinの機能を活用して、よりエレガントにできます: ### 1.3 キャッシュと非同期処理の導入 PSI要素へのアクセスは頻繁に必要になる可能性があるため、キャッシュ機構を導入すると効率的です: ## 2. RailroadColor.ktの改善提案 ### 2.1 シングルトンへの変更 現在の実装は事実上シングルトンですが、Kotlinのobjectを使うとより明確になります: ### 2.2 色の定義とテーマ対応の改善 IntelliJ IDEのダークテーマやハイコントラストテーマなど、さまざまなテーマに対応するために、JBColorを積極的に使用します: ### 2.3 色の一元管理 アプリケーション全体で使用する色を一元管理することで、一貫性を保ちやすくなります: ## 3. 共通の改善点 ### 3.1 ReadAction.computeのより簡潔な使用方法 現在のコードでは`return@compute`を使用していますが、Kotlinではもっと簡潔に書けます: ### 3.2 スレッドセーフティの強化 IntelliJ Platform 2025.1では、スレッディングモデルがさらに強化されています。スレッドセーフティを向上させるために、AtomicReferenceなどの並行処理プリミティブの使用を検討できます:
RailsAction.ktの修正については、次のようにIntelliJ Platform PluginのThreading Modelまわりに変更が入るようでした。
- Changes in threading model in IntelliJ Platform 2025.1 - Announcements - JetBrains Platform
- Kotlin Coroutines | IntelliJ Platform Plugin SDK
そのため、RailsAction向けの対応はしないことにしました。また、色まわりの改善もまだいいかな...ということで見送ることにしました。
そこで、共通の改善点のうちの 3.1 だけが実装できそうでした。対応したところエラーになったため、また質問を重ねました。
3.1の対応をすると、次のエラーが出ます。解決できるコードは? Cannot infer type for this parameter. Specify it explicitly. Not enough information to infer type argument for 'E'.
回答は以下でした。
# ReadAction.computeの型推論エラーの解決方法 `ReadAction.compute`を使用する際に「Cannot infer type for this parameter. Specify it explicitly.」というエラーが発生していますね。これは、Kotlinの型推論が`ReadAction.compute`の戻り値の型を自動的に判断できないことを示しています。 ## 解決方法 `ReadAction.compute`の使用時に型パラメータを明示的に指定することで解決できます。以下に解決策を示します。 ### 1. 型パラメータを明示的に指定する方法 ### 2. computeの代わりにrunReadActionを使用する方法 もう一つの方法として、IntelliJ Platformが提供する別のAPI `ApplicationManager.getApplication().runReadAction` を使用することもできます。 ### 3. ReadActionのコンテキスト関数を使用する方法 Kotlinのコンテキスト関数を使用して、より簡潔に記述することもできます: ### 4. Type-Safe Builderパターンを使用する方法 IntelliJ Platformの最新バージョンでは、Type-Safe Builderパターンを使用したAPIも提供されています: ## 最も推奨される解決方法 最も単純で確実な解決策は、1番目の方法である「型パラメータを明示的に指定する方法」です。以下のコードを使用することを推奨します:
最も推奨される解決方法を採用して動作確認したところ、エラーが発生することなくRailroadsプラグインを利用できました。
消費クレジットについて
AI Assistantとの会話ではクレジットを消費します。タスクが終わったところで確認したところ、残りのクレジットはスクリーンショットの状態でした。ちなみに、タスクの実行前のクレジットは満タンでした。
感想
AIエディタまわりの自分の状況は以下です。
- 今まで JetBrains の AI Assistant は使っていない
- 今回初めての体験
- Cline + Claude 3.7 Sonnet の利用経験あり
- 他のAIエディタの利用経験はない
今回AI Assistantを使ってみて、以下のように感じました。
- 今回程度の修正であれば、AI Assistantで十分
- デフォルトのままでも、AI Assistant はいい感じに動いてくれた
- 特にルールファイルを作成せずとも、リポジトリの中から関係しそうなファイルを読み込んでいるような挙動をしていた
- Clineに比べると、回答をもらうまでにやや時間がかかる
- ただし、的ハズレな回答ではなかった
- 今回の作業に対するクレジット消費量を考えると、すべてAIに任せてしまうと早い段階で月間のクレジットが不足してしまいそう
- 消費が激しい月だけ AI Ultimate (4,200円、今なら2,800円)にしてもいいかもだが、それでも足りるかどうか...
- AIモデルのAPIキーを持ち込みできたら嬉しいかも
正直もうちょっと使い込んでみないとなんとも言えない感じなので、引き続きAI AssistantやJunieを使ってみようと思います。
今回の修正の影響により、サポートするIDEのバーションを変更
JetBrains IDE 2025.1 でプラグインを動かす場合、Java 21 が必要そうでした。一方、今までターゲットとしてきたミニマムのバージョン 2023.3 は Java 17 でした。
https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html#platformVersions
ためしに Java 21 でビルドしたRailroadsを 2023.3系で使ってみたところ、エラーとなってプラグインを起動できませんでした。
対応をどうするか迷いましたが、
と考えました。そこで、サポートするIDEのバージョンをJava 21で動く 2024.2 以降へと変更しました。
ソースコード
GitHubに上げました。
https://github.com/thinkAmi/railroads
今回のプルリクはこちら。
https://github.com/thinkAmi/railroads/pull/55
ちなみに、今回の修正が反映されたバージョン 0.3.0 は現在JetBrainsによる審査中です。審査が通れば公開となります。
