はじめに
JavaのコーディングをVS Codeでしていて、Dependencyの解決で見事にハマりました。
備忘録として(たぶん、またハマると思うんで)、調査の過程を記します。
それは、興が乗った私がGitHubからクローンしたSpring Bootのサンプルコードを魔改造しようとして、調子に乗ってpom.xmlを書き換えた後に発生しました。
「追加したdependencyがVSCodeに反映されない…だと?」
そこから、調査がはじまったのです。
調査
VS Codeに導入しているJava関係の拡張機能はJava Extension Packのみですので、もしJava Extension Packの問題であれば世間様(※Google)に聞けばわかるはずです。
ところが、世間様(※Stack Overflow)のご意見を伺っても、pom.xmlを更新したら「更新を反映しますか?」的な確認ダイアログが出てきちんと反映されるよ、というご意見ばかり。
唯一可能性がありそうなのは、Java Language Serverのキャッシュに問題があって、それを消せば良いのではという意見でした。
やってみましょう。
対処1(失敗)
問題のVS Code環境は、Remote-WLSを使用してWLS2のUbuntu上で動作させています。VS Codeのキャッシュデータは、以下の場所に配置されます。
~/.vscode-server/data/User/workspaceStorage/
一旦VS Codeを終了して、Windows TerminalからUbuntuのターミナルにアクセスし、キャッシュデータを削除します。それから、VS Codeを再起動する。キャッシュの問題であればこれで問題は解決です。正直、勝ったと思いました。まあ、そう上手く行くなら我々はツールの挙動にハマることもなく、私もこんな記事を書くことはないので、そんな思いは幻想に過ぎなかったわけですが。
当然のごとく、再起動されたVS Code上で依存関係は更新されていません。
無駄に mvn compile などもしてみましたが、全くの無反応です。手詰まりか、と思った時点で、ふとあることを思い出しました。そう、pom.xmlを編集したときに表示されるはずの例のダイアログ、アレを見た記憶が一切無いのです。
もしや、という思いが頭を駆け巡ります。このサンプルコードはmavenだけではなく、gradleでもビルドできるのです。自分が普段gradle派ではないので、完全に見(え)ない振りをしていたbundle.gradleが、このサンプルコードにも付属しているのです…pom.xmlと同じ階層に。
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/main" path="src/main/java">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin/main" path="src/main/resources">
<attributes>
<attribute name="gradle_scope" value="main"/>
<attribute name="gradle_used_by_scope" value="main,test"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
思わず、半笑いになりました。
じつはここまで2時間弱、この問題にハマってたのです。痛恨の一撃です。このやり場のない気持ちをぶつける先がありません。
この.classpathはVS Code側で依存関係解決のために対象に含めるディレクトリやライブラリを指定するためのファイルで、自動生成されるものです。そりゃ、いくらmavenのpom.xmlを書き換えても依存関係が反映されるわけないですね。最初っから参照されてないんですから…はあ(´・ω・`)
この現象を解決する手段は2択です。
・素直にgradleにビルドツールを切り替える。
・強引にmavenで依存関係が参照されるように.classpathを生成し直す。
対処2(また失敗)
gradleの軍門に降るのは癪だったので、迷わず後者を選択します。
一旦VS Codeを終了し、ターミナルから.classpathとbuild.gradleをmvしました。これで再起動した時にmavenを元に.classpathが…作成されませんね?うーん?
ちょっと悩みましたが、犯人は.projectでした。
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spring-boot</name>
<comment>Project complete created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
思いっきりgradleって書いてありますね。これもmvしましょう。VS Codeを再起動します。今度はきちんとmavenを元に依存関係が反映されました。
しかし、.projectと.classpathが生成されず…コーディングをするには問題ないのですが、とても気持ち悪いですね。またハマる原因にもなりかねませんし。
それに、.projectと.classpathがどのタイミングで生成されるのかも気になります。検証してみましょう。
検証
別のディレクトリを作って、元にしたGitHubのリポジトリから再びcloneしてきます。それをVS Codeで開くと…生成されましたね。
こちらがVS Code起動後の状態です。この時点で既に.projectが生成され、内部はgradleを使用するように書かれていました。.classpathもあります。当然のごとくgradleを使用するようになっていました。どうやら、mavenよりgradleのほうが優先されるようです。
増えたのは、以下のファイルとディレクトリです。
・.classpath
・.gradle
・.project
・.settings
・bin
つまり、VS Codeとしてはディレクトリを開いた際に、(おそらく)build.gradleがあればそちらをビルドツールとして使用するように選択して、.projectファイルを生成し、gradleベースでプロジェクトの環境が自動的に設定される、ということなのだろうと思います。
では、このプロジェクトをもういちどcloneしなおして、今度はbuild.gradleを削除した場合の挙動を確認してみます。VS Codeを開く前のディレクトリはこんな感じです。
VS Codeで開いたら、.projectも.classpathも生成されました。
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spring-boot</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
どちらもmavenベースに置き換わっています。私の目指すところはこれなのです。
対処3(成功)
それでは、一度VS Codeでgradleプロジェクトとして認識されてしまったものを、mavenプロジェクトとして再認識させるためには、なにが足りなかったのでしょうか。
先ほどgradleプロジェクトからbuild.gradleと.classpath、.projectをmvしましたが、他にも自動生成されたディレクトリや、gradle関連のリソースが含まれています。が、怪しいのは自動生成されたリソースです。
そのため、それらをやはり全てmvしてみましょう。…あれ、変更されませんね?
そこでピンと来ました。原因は.projectや.classpathをプロジェクトルート配下のbackupというディレクトリにmvしたことでした。どうも、.projectがルートディレクトリの配下になくても、サブディレクトリに存在していればそちらを読み込む、という仕様が存在するようです。
.projectと.classpathを削除することで、新しい.projectと.classpathが生成され、プロジェクトのビルドツールがmavenに切り替わりました。
まとめ
- VS CodeのJava Extension Packでは、pom.xmlかbuild.gradleが存在しているディレクトリを初めて開いた際に、.projectが生成されJavaプロジェクトとして認識される。
- build.gradleとpom.xmlの両方が存在していた場合には、gradleが優先される。
- 一度ビルドツールが決定してしまった後は、VS Code側では変更する手段はない。(そのため、pom.xmlだけ修正するなどというおバカなことをやるとハマる)
- ビルドツールを移行したい場合には、build.gradle以外にも、.projectと.classpathを削除してから再度VS Codeでディレクトリを開く必要がある。
- mvとremoveは違う。
ということですね。ずいぶん遠回りしましたが、無事ビルドツールをmavenに切り替えることができました。
めでたし、めでたし。
とっぴんぱらりのぷう。