Maven plugin throw some exception

eclipseでincubator-parquet-mrのコードをビルドする為に、mvn eclipse:eclipseを実行したところ、以下のエラーが出ました。

[ERROR] Failed to execute goal com.twitter:scrooge-maven-plugin:3.9.0:compile (thrift-sources) on project parquet-scrooge: Execution thrift-sources of goal com.twitter:scrooge-maven-plugin:3.9.0:compile failed. NullPointerException -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal com.twitter:scrooge-maven-plugin:3.9.0:compile (thrift-sources) on project parquet-scrooge: Execution thrift-sources of goal com.twitter:scrooge-maven-plugin:3.9.0:compile failed.
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:224)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
	at org.apache.maven.lifecycle.internal.MojoExecutor.executeForkedExecutions(MojoExecutor.java:364)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:198)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:116)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:80)
	at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:120)
	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:347)
	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:154)
	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:582)
	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:214)
	at org.apache.maven.cli.MavenCli.main(MavenCli.java:158)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
Caused by: org.apache.maven.plugin.PluginExecutionException: Execution thrift-sources of goal com.twitter:scrooge-maven-plugin:3.9.0:compile failed.
       at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:143)
       at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
       ... 23 more
Caused by: java.lang.NullPointerException
       at com.twitter.AbstractMavenScroogeMojo.findThriftDependencies(AbstractMavenScroogeMojo.java:324)
       at com.twitter.AbstractMavenScroogeMojo.findThriftFiles(AbstractMavenScroogeMojo.java:292)
       at com.twitter.AbstractMavenScroogeMojo.execute(AbstractMavenScroogeMojo.java:191)
       at com.twitter.MavenScroogeCompileMojo.execute(MavenScroogeCompileMojo.java:22)
       at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:132)

com.twitter:scrooge-maven-plugin:3.9.0AbstractMavenScroogeMojo.java:324NullPointerExceptionが出ているようです。原因を調べる為、このMavenプラグインのデバッグにトライしました。

mvnDebug

Mavenプラグインのデバッグについてググったところ、mvnDebugを使うとできるようなので、早速試してみました。このmvnDebugはMavenにバンドルされているので、事前に何かをインストールする必要はありません。

$ mvnDebug eclipse:eclipse
Preparing to Execute Maven in Debug Mode
Listening for transport dt_socket at address: 8000

なるほど、mvnの実行プロセスにアタッチできるようです。そうなるとこのプラグインのソースコードが欲しいので、https://oss.sonatype.org/からcom.twitter:scrooge-maven-plugin:3.9.0のソースコードをダウンロードし、jar xvfで展開しておきます。

jdb

次にjdbでmvnのプロセスにアタッチします。

$ jdb -sourcepath . -attach 8000
uncaught java.lang.Throwable を設定しました
保留した uncaught java.lang.Throwable を設定しました
jdb の初期化中です...
>
VM が起動しました: 現行の呼び出しスタックにはフレームがありません

この状態ではまだプロセスの実行が止まっている状態なので、NPEが発生している箇所にブレークポイントを設定します。

main[1] stop at com.twitter.AbstractMavenScroogeMojo:324
ブレークポイント com.twitter.AbstractMavenScroogeMojo:324 を保留しています。
クラスがロードされた後に設定されます。

そしてプロセスを実行します。

main[1] run

すると、ブレークポイントを設定した問題箇所で停止します。

> 保留した ブレークポイント com.twitter.AbstractMavenScroogeMojo:324 を設定しました

ブレークポイントのヒット: "スレッド=main", com.twitter.AbstractMavenScroogeMojo.findThriftDependencies(), line=324 bci=170
324            for (String name : depTrail) {

ここでNPEが発生するということは、depTrailがnullということになります。depTrailに何が設定されているかprint depTrailで確認します。

main[1] print depTrail
 depTrail = "[com.twitter:parquet-scrooge:jar:1.6.1-SNAPSHOT, com.twitter:parquet-column:jar:1.6.1-SNAPSHOT, commons-codec:commons-codec:jar:1.5]"

問題なさそうです。contで先に進めます。

main[1] cont
>
ブレークポイントのヒット: "スレッド=main", com.twitter.AbstractMavenScroogeMojo.findThriftDependencies(), line=324 bci=170
324            for (String name : depTrail) {

またprint depTrailsで値をチェックします。

main[1] print depTrail
 depTrail = "[com.twitter:parquet-scrooge:jar:1.6.1-SNAPSHOT, com.twitter:parquet-thrift:jar:1.6.1-SNAPSHOT, com.twitter.elephantbird:elephant-bird-core:jar:4.4, com.google.guava:guava:jar:11.0.1, com.google.code.findbugs:jsr305:jar:1.3.9]"

問題ないです。contで先に進めます。

main[1] cont
>
ブレークポイントのヒット: "スレッド=main", com.twitter.AbstractMavenScroogeMojo.findThriftDependencies(), line=324 bci=170
324            for (String name : depTrail) {

main[1] print depTrail
 depTrail = null

depTrailがnullになりました。この箇所でNPEが発生することになります。localsで変数の状態を確認します。

main[1] locals
メソッド引数:
whitelist = instance of java.util.HashSet(id=2365)
ローカル変数:
thriftDependencies = instance of java.util.HashSet(id=2366)
deps = instance of java.util.HashSet(id=2367)
depsMap = instance of java.util.HashMap(id=2368)
i$ = instance of java.util.HashMap$KeyIterator(id=2369)
artifact = instance of org.apache.maven.artifact.DefaultArtifact(id=2370)
depTrail = null

depTrailは多分pom.xmlのdependencyに関する情報だと考えられます。そこでdumpコマンドを使ってartifactという変数の内容を見てみます。

main[1] dump artifact
 artifact = {
    groupId: "commons-httpclient"
    artifactId: "commons-httpclient"
    baseVersion: "3.0.1"
    type: "jar"
    classifier: null
    scope: "test"
    file: null
    repository: null
    downloadUrl: null
    dependencyFilter: null
    artifactHandler: instance of org.apache.maven.artifact.handler.DefaultArtifactHandler$__sisu12(id=3036)
    dependencyTrail: null
    version: "3.0.1"
    versionRange: instance of org.apache.maven.artifact.versioning.VersionRange(id=3037)
    resolved: false
    release: false
    availableVersions: null
    metadataMap: null
    optional: false
}

commons-httpclientのdependencyが取得できなかったように見えます。このcommons-httpclientのjarファイルを見てみるとpom.xmlではなくproject.xmlが入っており、かなり古いモジュールであることから、dependencyがうまく取得できなかったと考えられます。

こういったartifactがあることを考えると、depTrailがnullであるケースに対応する必要があります。scrooge-maven-pluginのgithubのコードを見ると、この問題はすでに修正されていました。

scrooge-maven-plugin/src/main/java/com/twitter/AbstractMavenScroogeMojo.java:

 1        // depTrail can be null sometimes, which seems like a maven bug
 2        if (depTrail != null) {
 3          for (String name : depTrail) {
 4            Artifact dep = depsMap.get(name);
 5            if (dep != null && "idl".equals(dep.getClassifier()) && whitelist.contains(dep.getArtifactId())) {
 6              thriftDependencies.add(artifact);
 7              break;
 8            }
 9          }
10        }

実際にscrooge-maven-pluginのバージョンを上げてみたところ、エラーは発生せず正常に完了しました。

Conclusion

mvnDebugを使ってMaven Pluginをデバッグしました。

mvnまわりでエラーがでると調べるのが面倒ですが、今回のようにコードを修正しなくても回避できる手段が見つかることもあるので、取りあえずパッと見てみるのも良いかな、と思います。