home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Groovy/Maven/Examples (includes "Java Joint Compile")

Groovy/Maven/Examples (includes "Java Joint Compile")

Groovy / Maven / Examples (includes "Java Joint Compile")
id: 1188 所有者: msakamoto-sf    作成日: 2013-04-22 00:04:15
カテゴリ: Groovy Java Maven 

Groovy/Gradle/Mixing Java and Groovy でGradleを使ったJava/Groovy混在プロジェクトでのポイントをまとめました。
しかしながら、既存のMaven資産や(ようやく安定し始めた感のある)m2e + Eclipse IDEとの統合開発環境を活用したいいために、引き続きJavaを中心でビルドシステムはMaven、開発効率化としてGroovyで脇を固めたい、というニーズもあるはずです。・・・あるんですってば。

そうした場合にも、2013-04現在では大きく2種類の方式で、Java/Groovy混在プロジェクトをMavenで統合管理出来ます。

  1. "groovyc" Antタスクを、maven-antrun-pluginから起動する。
  2. GMaven Mavenプラグインを利用する。

Groovy/Maven/GMaven + Eclipse memo ではGMavenをごく簡単に紹介しましたが、今回はより深く突っ込んで、以下のパターンそれぞれで、"groovyc" AntタスクによるビルドとGMavenによるビルドを構成してみます。

  • GroovyのみのMavenプロジェクト
  • Java + GroovyのMavenプロジェクト(Java -> Groovy参照:無し、Groovy -> Java参照:有り)
  • Java + GroovyのMavenプロジェクト(Java -> Groovy参照:有り、Groovy -> Java参照:有り)

というわけで作成したのが以下になります。

細かい解説は、README.mdおよび実際のコードを参照してください。

むしろ本記事では、これらの作成で思いっきり地雷を踏み抜いいたり嵌ってしまった部分を紹介して行きたいと思います。
さらに、おまけとして一番複雑な「Java + GroovyのMavenプロジェクト(Java -> Groovy参照:有り、Groovy -> Java参照:有り)」を題材に、Eclipse + m2e環境でインポートして "mvn tomcat6:run" でJava/Groovyそれぞれをデバッグ実行するためのポイントも簡単に紹介します。


参考にしたサイト

最初に参考サイトです。

GMaven参考

GroovyのAntタスクを、maven-antrun-pluginから呼び出す:

"Java Joint Compile" で嵌った箇所

嵌った箇所・・・ではありませんが、そもそも"Java Joint Compile"とはどういったものかについてです。

まず、基本的に xxxx.groovy が yyyy.java ソースのクラスを参照するだけであればどのビルドシステムを使っても大して問題は起きません。

  1. yyyy.java がコンパイルされる。
  2. xxxx.groovy がコンパイルされる。 -> yyyy.classが存在するのでコンパイル成功。

ところが、yyyy.java が zzzz.groovy ソース内のクラスを参照しているとなると、問題が起こります。
大抵のビルドシステムでは・・・というかGradle/Maven/Antでは、特に意識しない限りは、「Javaコンパイル」→「Groovyコンパイル」の順に処理されます。教科書(=公式サイト)の解説にしたがってGroovyコンパイルを追加しただけだと。
すると、yyyy.java をコンパイルするには zzzz.groovy をコンパイルしたクラスが必要ですが、 zzzz.groovy をコンパイルするにはJavaコンパイルフェーズが完了していなければならず、鶏が先か卵が先か、という状況になってしまいます。

これを解決するのが "Java Joint Compile" (念のため:本記事で便宜的につけてる名前で、プラグインや記事によって表記ゆれがあるっぽいです) です。
これは、最初に xxxx.groovy, zzzz.groovy を、クラスやメソッドのシグネチャだけJavaに変換した "Stub" の java ファイルを生成します。これらは戻り値のあるメソッドであればnullを返すだけなど、本当に、単にそれらを参照してる java ファイルのコンパイルを成功させるためだけの内容です。
続いて、それを含めて本来のyyyy.javaがビルドシステムのJavaコンパイルフェーズでコンパイルされます。
最後に、本来の xxxx.groovy や zzzz.groovy が改めてGroovyでコンパイルされます。↑でコンパイルされたStubの同名classファイルは、このステップにより本来のGroovyソースがコンパイルされた正しい内容で上書きされます。

・・・一応上記説明はイメージです。今回色々試行錯誤して、失敗時や成功時のエラーメッセージを見たり途中過程を追ううちに、多分こうなってるんじゃないかな~程度で。

ということで、「まずStubを生成する」という"Java Joint Compile"の特性を理解していないと、GMavenやgroovyc AntTask + maven-antrun-pluginをうまく構成出来ません。

GMavenで嵌った箇所

2013-04-27追記:何を間違えていたのか、今日改めて確認してみたら次の設定で問題なく、GMavenのstub生成とcompileが適切なタイミングで実行されました。

        <executions>
          <execution>
            <goals>
              <goal>generateStubs</goal>
              <goal>compile</goal>
              <goal>generateTestStubs</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>

GitHubの方には既に上記のようなシンプルな設定に直したpom.xmlを上げてあります。
https://github.com/msakamoto-sf/maven-java-groovy-conjunction-demo1/commit/09109628f6ddd4f4b9c2b7df7e295d25bf7f10a5

以下の手法は、もし上記の設定でうまく動かなかった場合の、手動調整用として参照してください。


Groovy/Maven/Java + TestNG Example で既にGMavenを使ってMavenプロジェクトを構成していましたが、その時点では

mvn groovy:compile groovy:testCompile

というふうに手動でGMavenのゴールを指定する必要がありますよ・・・と書いてました。(2013-04-27:書いてませんでしたね。何を勘違いしてたんだろう・・・)
pom.xmlはこんな感じです:

<plugin>
  <groupId>org.codehaus.gmaven</groupId>
  <artifactId>gmaven-plugin</artifactId>
  <version>${gmavenVersion}</version>
  <configuration>
    <providerSelection>${gmavenProviderSelection}</providerSelection>
    <sourceEncoding>UTF-8</sourceEncoding>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>generateStubs</goal>
        <goal>compile</goal>
        <goal>generateTestStubs</goal>
        <goal>testCompile</goal>
      </goals>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>${groovyVersion}</version>
    </dependency>
  </dependencies>
</plugin>

今だから言えますが、これ、単にGMavenの公式Web上のコピペしただけですので、本来のGMavenのゴールを正しく、対応するフェーズで実行する設定にはなってません。2013-04-27に改めて検証したところ、上記設定で問題ないようです。(何か別のところでミスってたか、勘違いしてたか。)

本来は、各ゴールは以下のフェーズで実行されるのが・・・多分、今日試してた限りではgoodぽい。

  • groovy:generateStubs -> "generate-sources" phase
  • groovy:compile -> "compile" phase
  • groovy:generateTestStubs -> "generate-test-sources" phase
  • groovy:testCompile -> "test-compile" phase

ということで、上記のマッピングでGMavenのゴールとphaseが連動するように調整したのが、以下になります。

  <plugin>
    <groupId>org.codehaus.gmaven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>${gmavenVersion}</version>
    <configuration>
      <providerSelection>${gmavenProviderSelection}</providerSelection>
      <sourceEncoding>UTF-8</sourceEncoding>
    </configuration>
    <executions>
      <execution>
        <id>gmaven-generate-stubs</id>
        <phase>generate-sources</phase>
        <goals>
          <goal>generateStubs</goal>
        </goals>
      </execution>
      <execution>
        <id>gmaven-compile</id>
        <phase>compile</phase>
        <goals>
          <goal>compile</goal>
        </goals>
      </execution>
      <execution>
        <id>gmaven-generate-test-stubs</id>
        <phase>generate-test-sources</phase>
        <goals>
          <goal>generateTestStubs</goal>
        </goals>
      </execution>
      <execution>
        <id>gmaven-generate-test-compile</id>
        <phase>test-compile</phase>
        <goals>
          <goal>testCompile</goal>
        </goals>
      </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>${groovyVersion}</version>
      </dependency>
    </dependencies>
  </plugin>

いや~、これに辿り着くまで、改めてMavenのphaseとgoalの関係を勉強しなおしたり、ちょっとした嵌りどころでした。

2013-04-27追記:改めて、本来は前述のような簡略化されたgoal設定で問題ないようです。うまく動かなかったりした時の、手動設定として上記のmappingを参照してください。

groovyc Antタスク + maven-antrun-plugin で "Java Joint Compile" の嵌りどころ

groovyc Antタスクでは"<javac>"タスクをネストすることで自動的に"Java Joint Compile"を処理してくれます。
が・・・Mavenに組み込もうとするとちょっとした・・・どころじゃなくて、ドエライ嵌りどころが出てきます。
maven-compiler-pluginのデフォルト挙動をストップする必要があるのが嵌りポイントです。

これ、解決までに5時間を要しました。

groovyc Antタスクは、compileフェーズで起動するようにmaven-antrun-pluginを使って設定されます。
ところが、そうなるとデフォルト状態では"maven-compiler-plugin"によるデフォルトのJavaコンパイルも発動してしまいます。このプラグインによるコンパイルは、Groovyの存在など全く知らないため、 Groovyソースを参照しているJavaソースをコンパイルしようとしたら当然エラーになります。

groovyc Antタスクがサポートしている"Joint Compile"を使わずに、"maven-compiler-plugin"によるコンパイルでJavaソースをコンパイルしようとすると、Stubファイルを予め生成する必要が出てくるわけです。
が・・・「この辺手動で生成すればうまくいけるんじゃねーかなー」と、そう考えていた時代が僕にもありました。

手動でstubを生成して・・・

↑↑この辺使って動的にaddSourceして・・・
その後、maven-compiler-plugin によるデフォルトのJavaコンパイラ起動、さらにその後、groovycのAntタスクを起動でどうにかならね?
・・・と思ったのですが。実験してみればすぐわかるんですが、

  1. maven-compiler-pluginによって、Stubのjavaソースもコンパイルされて、classファイルになる。
  2. maven-antrun-pluginによって、groovycのAntタスクが起動。
  3. Stubファイルのコンパイルにより、既に同名のクラスファイルが最新のタイムスタンプで生成されているため、Groovyコンパイラがスルーしてしまう。
  4. Stubファイルはシグネチャ合わせてnullをreturnするだけの中身空っぽのソースなので、当然、実行するとNullPointerExceptionだらけになる。
  5. でもGroovyコンパイラがスルーしてしまうため、本来のGroovyコードはコンパイルされない。
  6. 手詰まり状態\(^o^)/オワタ

という流れで、にっちもさっちも行かなくなります。(ここでさらに独自のantタスクとか準備して、Groovyソースと同名のクラスファイルが生成されていれば一旦それらを削除してからgroovycのAntタスク起動・・・とかやるのもアリなんですが、あまりにもなんというか、力技過ぎる気がするので却下しました。)

そうなると、最終的な回答としてはStubファイルの手動生成 + maven-compiler-pluginではなく、groovyc Antタスクがサポートしているネストされた "<javac>" による "Java Joint Compile" が残された手段になります。
その場合もネックになるのが maven-compiler-plugin の無効化です。というわけで今回最も時間を取られた嵌りどころが・・・

How to disable maven-compiler-plugin ?

そこでようやく本題ですが、Googleで調べた "disable maven-compiler-plugin" の尽くが全く期待通りに動いてくれなかったんですよ!!!

先に答えを書くと、以下のように"<id>"で "default-compile", "default-testCompile" を指定した上で"<phase>none</phase>"を指定すると、compile/test-compileの各フェーズでmaven-compiler-pluginが無効化されます。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.0</version>
        <executions>
          <execution>
            <id>default-compile</id>
            <phase>none</phase>
          </execution>
          <execution>
            <id>default-testCompile</id>
            <phase>none</phase>
          </execution>
        </executions>
      </plugin>

Googleで調べて見つけた記事では、この <id> 指定をしてる記事が見当たらなかったんですよ。(いや、そりゃヒットした上位数件を見ただけなので、細かく見てけばもっとちゃんとしたのもあったとは思うんですが。)

実は、"<id>"指定を省略するとデフォルトの <id> の設定が残ってしまうので、どんなに頑張ってもそれが残ってるので maven-compiler-plugin のデフォルトコンパイルを無効化出来ないというとんでもない罠が潜んでます。

これ、ようやく半信半疑で "mvn help:effective-pom" で見つけるまでに、試行錯誤で5時間費やしました・・・。

なお、環境は

JDK 1.7
Maven 3.4
maven-compiler-plugin 3.0

です。もしかしたら古いバージョンでは"<id>"指定なしでも無効化出来たのかもしれませんね。

e4.2 + m2e 1.3 にインポートするときの注意点

では最後のトピックです。
https://github.com/msakamoto-sf/maven-java-groovy-conjunction-demo1
の、"sample6 : 02-java-refer-groovy-antrun" をEclipseにインポートするときの注意点です。

環境:

JDK 1.7
Eclipse 4.2 (Juno SR-2)
m2e 1.3.1
Groovy-Eclipse Plugin 2.8.0 (SpringSourceのGGTSでまとめて導入)

そのまま "Existing Maven Projects" でインポートしようとすると、

Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-antrun-plugin...

とエラーが発生します。このエラー自体は後で修正出来るタイプのエラーらしく、インポート自体は完了します。
また、その後プロジェクト右クリック -> "Configure" -> "Convert to Groovy Project" でGroovyサポートを組み込み、ソースディレクトリを手動で調整してあげれば、コンパイルも成功します。

あとは上記のエラーだけですが、この内容は端的に言うと、「pom.xml中で指定されたmaven-antrun-pluginを、Eclipse上でいつ実行すれば良いのか分かりません。」というエラーです。
詳細は以下で解説されてます。

対処法としては、m2e 1.3であれば pom.xml にm2eのlifecycle設定用のmapping情報を埋め込みます。これにより、どのプラグインをEclipse上でどのタイミングで実行すればよいのか、mapping情報が埋め込まれることで、m2eは上記エラーを解決出来ます。

今回のmaven-antrun-plugin設定については、結局のところEclipse上ではEclipseのJavaコンパイラとGroovy-Eclipse PluginによるGroovyプラグインが、自動的に Java Joint Compile も処理してくれるため、Eclipse上では不要となります。そのため、以下のように単に"<ignore />"を"<action>"に設定すればOKです。

<project>
...

  <build>
  ...
    <plugins>
      ...
      <plugin>
        <groupId>org.eclipse.m2e</groupId>
        <artifactId>lifecycle-mapping</artifactId>
        <version>1.0.0</version>
        <configuration>
          <lifecycleMappingMetadata>
            <pluginExecutions>
              <pluginExecution>
                <pluginExecutionFilter>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-antrun-plugin</artifactId>
                  <versionRange>[1.7,)</versionRange>
                  <goals>
                    <goal>run</goal>
                  </goals>
                </pluginExecutionFilter>
                <action>
                  <ignore />
                </action>
              </pluginExecution>
            </pluginExecutions>
          </lifecycleMappingMetadata>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

以上のテクニックを用いて、
https://github.com/msakamoto-sf/groovyservlet-sample-template
に上記を適用したpom.xmlを用い、"mvn tomcat6:run"をデバッグ実行、見事Eclipse上で対話的にブレークポイント設定して、ブレークで止められたのが以下にupした画像になります。
http://twitpic.com/ckyezv


Groovy/Java/Maven/EclipseのIntegrationネタは大体、以上でひと通り出揃ったと思います。
というかこれ以上地雷が潜んでるとか勘弁して欲しいので、ホント、これで終わりにしたいです。

Groovy・・・というか、JVM上で動作する言語(Groovy, Scala, JRuby, Jython, Clojure)は言語それ自体だけでなく、JVMをとりまくエコシステム(ビルドシステム, IDE)をトータルで扱えて初めて、Javaを超えることが出来るという感じがします。
教科書(=公式サイト)には載っていないようなイレギュラーなケースに対応出来て、初めて実際の開発現場でその威力を発揮できるはずです。

ということで、本記事が「言語自体はスゴイのだけれど、既存のMavenで組み上げられたJava中心のプロジェクトに追加するのはちょっと・・・」と感じているエンジニアの方に、その壁をぶち壊す突破口を切り開くお手伝いになることを祈ります。

・・・いやホント、自分が嵌ってプライベート時間を無駄遣いしたのは、他で困ってるエンジニアを救ったという事実が救済してくれますので、何卒どこかで役立つことを祈ってます。



プレーンテキスト形式でダウンロード
現在のバージョン : 2
更新者: msakamoto-sf
更新日: 2013-04-28 00:11:47
md5:a6cf2d4b527713cb5e6a436382acab9c
sha1:cc291bf0203c6f2dc11c0fdf655f421f69c22787
コメント
コメントを投稿するにはログインして下さい。