AntでEclipseのECJ(Eclipse Compiler for Java)を使う

はじめに

知っている人にはよく知られていることですが、EclipseJava開発環境、JDTがJavaソースのコンパイルに使っているコンパイラは、OracleJDKなどとは違い、Eclipseが独自実装しているコンパイラ ECJ (Eclipse Compiler for Java)です。

ECJとOracle JDK(OpenJDK)のjavacと何が違うのかというと、

  • コンパイラの許容ルールが異なる。(おそらくOracle JDKより緩いケースが多そう)
    • これのせいでEclipseコンパイルできたとしても、javacでコンパイルできないソースが出るので、そういうのやめたい人はきちんとJenkinsとかで常時チェックかかるようにしましょう。
  • コンパイルが一部失敗しても成功したclassファイルは生成される。
    • それどころか失敗したクラスでさえ仮のclassファイルを生成させることができる。
    • ある程度のコンパイルエラーを許容しなければならない荒っぽいプロジェクトでは使える性質でしょう。
  • コンパイラオプションが異なる。
  • 警告などの出力フォーマットが異なる。
    • JenkinsでWarnings Pluginを使っている場合は、ECJ用のルールがデフォルトで存在しています。

ECJはEclipseを立ちあげなくてもAntから使えます。(batch compiler)

んじゃあ、AntでEclipseJavaソースコンパイラ使ってみよう

プレーンなbuild.xml

たとえば、下記のようなフォルダ構成だったとします。

project-root/
  build.xml
  src/
    Hoge.java
  compiled/
    (Hoge.classがここにできればいいな)

この場合、build.xmlはこうなるでしょう。(わざとシンプルにしてます。Ant1.8以上)

<project default="build">
       <target name="build">
             <delete dir="compiled" />
             <mkdir dir="compiled" />
             <javac srcdir="src" destdir="compiled" />
       </target>
</project>

上記例だとパスの通ってるjavacを叩いているので、これをECJに切り替えましょう。

ECJを手に入れよう

ECJを構成するJARを手に入れましょう。

入手方法1. 直接ダウンロード

下記のMavenリポジトリ

http://mvnrepository.com/artifact/org.eclipse.jdt.core.compiler/ecj/

で見つかりました。 (groupId=org.eclipse.jdt.core.compiler, artifactId=ecj)

どっか公式DLページあるんですかね?(検索あきらめた)

ecj-..jar をダウンロードしましょう。JARの中にAnt用のアダプタとコンパイラコア本体が入っています。

ここでは project-root/ant-lib/ にそのjarをおいておきます。

入手方法2. Eclipseから取得

Eclipse本体からでも取得できます。

まずは下記の場所からJDTのコアのプラグインJARを見つけましょう。

eclipseインストールパス/plugins/
  org.eclipse.jdt.core_3.10.0.v20140604-1726.jar みたいなの

んで、上記jarをzipファイルとして解凍してAntへのアダプタとなる jdtCompilerAdapter.jar をとっておきましょう。

ここでは下記2つのJARを project-root/ant-lib/ にいれておきます。

  • org.eclipse.jdt.core_3.10.0.v20140604-1726.jar
  • jdtCompilerAdapter.jar

Ant関係なくECJを起動してみよう

ECJはスタンドアロンでjavacのように扱うことが可能です。javacと基本ほとんど同じですね。

java -jar ECJJARファイル コンパイルしたいソース.java

※ECJJARファイルはそれぞれ下記を使用。
 入手方法1の場合はecj-*.*.jar
 入手方法2の場合はorg.eclipse.jdt.core_3.10.0.v20140604-1726.jar
※マニフェストで起動したくない場合は起動クラスとして org.eclipse.jdt.internal.compiler.batch.Main を指定すればよい。
※依存するライブラリがある場合は-classpath "依存.jar"みたいな感じでjavaに渡してあげる。
※細かいオプションみたい人は -? 引数を指定してみてね。

ECJをbuild.xmlに組み込んでAntから呼んでみよう

ECJをtypedefし、それをjavacで参照、javacのcompilerでorg.eclipse.jdt.core.JDTCompilerAdapterを使うように指定すればOKです。

<project default="build">
     <typedef name="ecj" classname="org.eclipse.jdt.core.JDTCompilerAdapter">
          <classpath>
               <fileset dir="ant-lib" includes="*.jar" />
          </classpath>
     </typedef>
     <target name="build">
          <delete dir="compiled" />
          <mkdir dir="compiled" />
          <javac srcdir="src" destdir="compiled" compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
               <ecj />
          </javac>
     </target>
</project>

typedef+<ecj />が面倒な人は、下記のいずれかのようにすれば、compiler属性の指定だけで使えます。

  • Ant起動時に-lib ECJのjarを指定する。
  • ${ANT_HOME}/lib~/.ant/lib にそのままECJ関連のjar放り込む。
  • typedefを作らず下記のようにcompilerclasspathをインラインで書く。
          <javac srcdir="src" destdir="compiled">
               <compilerclasspath>
                    <fileset dir="ant-lib" includes="*.jar" />
               </compilerclasspath>
          </javac>

compiler属性の指定が面倒な人は、下記のいずれかをすればいいでしょう。

  • <property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter" />をどこかに入れておいたり、
  • Antの起動引数でプロパティ指定しておけば

まあ、macrodefとか使ってもいいと思うし、お好きに。

動作させてみよう

さっそくこれを動かしてみると、(わざとエラー出させています)

Buildfile: F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml
build :
   [ delete] Deleting directory F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ mkdir ] Created dir: F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ javac ] F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml:6: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [ javac ] Compiling 1 source file to F:\eclipse-luna\workspace\CompileWithEclipseCompiler\compiled
    [ javac ] ----------
    [ javac ] 1. ERROR in F:\eclipse-luna\workspace\CompileWithEclipseCompiler\src\Hoge.java (at  line 5)
    [ javac ]       String missing_semicolon
    [ javac ]              ^^^^^^^^^^^^^^^^^
    [ javac ] Syntax error, insert ";" to complete BlockStatements
    [ javac ] ----------
    [ javac ] 1 problem (1 error)

BUILD FAILED
F:\eclipse-luna\workspace\CompileWithEclipseCompiler\build.xml:6: Compile failed; see the compiler error output for details.

Total time: 752 milliseconds

ちょっと出力ログ形式がOracle JDK(下記)と違いますね。

    [ javac ] F:\eclipse-luna\workspace\CompileWithEclipseCompiler\src\Hoge.java :5: エラー: ';'がありません
    [ javac ]             String missing_semicolon
    [ javac ]                                     ^
    [ javac ] エラー1個

もちろんコンパイルが成功した場合はclassファイルがcompiledフォルダに出力されます。

独自オプション

コンパイラのオプションは http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm とかにまとまっています。

たとえば-proceedOnErrorというのを下記のように指定すれば、(compilearg要素@line属性)

          <javac srcdir="src" destdir="compiled" compiler="org.eclipse.jdt.core.JDTCompilerAdapter">
               <ecj />
               <compilerarg line="-proceedOnError" />
          </javac>

エラーがでているクラスでも仮のclassファイルを生成してくれます。

もちろんコンパイルエラーが発生しているメソッドを呼んだりすると Exception in thread "main" java.lang.Error: Unresolved compilation problem: などと怒られます。

まとめ

Eclipseコンパイラを使うことでしか動作できないプロジェクト作るのやめてほしいです…(切実)