Antで学ぶJavaビルド

自分の会社のJavaプロジェクトでAntが使用されていたので、プロジェクトをビルドするためにAntをざっと勉強した際のメモ。 Javaの3大ビルドツール Ant, Maven, Gradleのうちの1つだが、最近では理由がない限り使われないと思う。 ただ、ビルドツールを理解するうえでは、Antを勉強するだけでも十分基本概念を勉強できると思う。 実務ではビルドツールをある程度理解していることは必須知識になるので、Javaの簡単なビルドと同じことをビルドツールで再現してみることで、一般的なビルドの流れを把握しておくことはとても有益だと思う。

ドキュメント

公式ドキュメント

Apache Ant™ User Manual

体系的なチュートリアル

Apache ANT Tutorial

参考記事

【超初心者向け】Ant超入門 - Qiita

Antについてメモ

  • makeファイルのようなもの

    Apache Ant is a Java-based build tool. In theory, it is kind of like make , without make 's wrinkles.

  • makeファイルとは異なりshellコマンドベースではなく、xmlベース
  • target treeを呼び出して実行される

インストール

  • 環境
  • JDKインストール
    1. sudo apt update
    2. sudo apt install openjdk-11-jdk
    3. java -version
    4. .zshrcにJAVA_HOMEを追加

        # Java
        export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"
      
    5. echo $JAVA_HOME

  • Antインストール

    前提:JDKをインストール済みで、JAVA_HOMEが設定済み

    Apache Ant™ User Manual

    1. Antのバイナリをインストールする

      Binary Distributions

    2. ダウンロードした圧縮フォルダを解凍し、任意のディレクトリに格納する

    3. 2.のディレクトリのパスでANT_HOMEを追加

        export ANT_HOME="${HOME_DIR}/apache-ant-1.10.12-bin/apache-ant-1.10.12/"
      
    4. さらにPATHに追加

        export PATH=$PATH:$ANT_HOME/bin
      
    5. Ant task実行に必要なライブラリをインストールしておく。ANT_HOME/lib配下に格納される。(systemを指定した場合)

        cd $ANT_HOME
        ant -f fetch.xml -Ddest=system
      

Buildファイル(build.xlm)

以下のUsing Apache Antのページ

Apache Ant™ User Manual

  • Projects

    • name default basedir の3つのプロパティを持つ
      • name :プロジェクト名
      • default :target指定ない時に実行されるデフォルトタスク
      • basedir相対パスのルートとなるディレクトリ。指定しない場合はbuildファイルがあるディレクト
  • Targets

    • tasksとdatatypesのコンテナ
    • taskは他のtaskに依存し、antは現在のtaskの前に依存taskが実行されることを保証する
    • 依存チェーンで重複で依存している場合でも、一回しか呼び出されないようになっている。
      • A → B → C → Dの場合は、Dを実行するためにCを実行し、その際にCの依存関係としてB,Aが実行されて、Dの依存関係に戻るが、さらにそこからB,Aを実行するということはない。

          A → B → C → D
          A → B → C
          A → B
          A
        
  • Tasks

    ↑のマニュアルの「Ant Tasks」のページ → 各タスクのドキュメント

    <task名 attribute1="*value1*" attribute2="*value2*" ... />

    ユニークな任意のidを割り当てて、参照することもできる

    <*taskname* id="*taskID*" ... />

  • Properties

    • カスタムフィールド

チュートリアル

Hello World with Apache Ant

まず手動でコンパイルしてクラスファイル作成し、実行してみる。この流れをantのbuild.xlmに定義してantのビルドで同じことを再現してみる。

Javaコマンドで実施

  1. 以下のディレクトリ構造を用意し、javaファイルを用意する

     .
     ├── build
     │   ├── classes
     │   └── jar
     └── src
         └── oata
             └── HelloWorld.java
    
  2. javaファイルを適当に用意

     package oata;
    
     public class HelloWorld {
         public static void main(String[] args) {
             System.out.println("Hello World");
         }
     }
    
  3. コンパイルしてclassファイルを作成

     javac -sourcepath src -d build/classes src/oata/HelloWorld.java
    
  4. 実行してみる

     java -cp build/classes oata.HelloWorld
    
     Hello World
    
  5. さらにjarファイルも作成してみる。まずはmanifestファイルを用意。

     echo Main-Class: oata.HelloWorld>myManifest
    
  6. jarファイルを作成

     jar cfm build/jar/HelloWorld.jar myManifest -C build/classes .
    
  7. jarファイル実行

     java -jar build/jar/HelloWorld.jar
    

    出力

     Hello World
    

ここまでのディレクトリ構成

.
├── build
│   ├── classes
│   │   └── oata
│   │       └── HelloWorld.class
│   └── jar
│       └── HelloWorld.jar
├── myManifest
└── src
    └── oata
        └── HelloWorld.java

Antで実施

  1. ルートにbuild.xlmを用意

     touch build.xml
    
  2. build.xlmにproject, targe, taskを定義

    target

    • clean
    • compile
    • jar
    • run
     <project>
    
         <target name="clean">
             <delete dir="build"/>
         </target>
    
         <target name="compile">
             <mkdir dir="build/classes"/>
             <javac srcdir="src" destdir="build/classes"/>
         </target>
    
         <target name="jar">
             <mkdir dir="build/jar"/>
             <jar destfile="build/jar/HelloWorld.jar" basedir="build/classes">
                 <manifest>
                     <attribute name="Main-Class" value="oata.HelloWorld"/>
                 </manifest>
             </jar>
         </target>
    
         <target name="run">
             <java jar="build/jar/HelloWorld.jar" fork="true"/>
         </target>
    
     </project>
    
  3. antのtaskを実行する

     ant compile
     ant jar
     ant run
    
  4. 一括で実行もできる

     ant compile jar run
    

build.xmlを修正する

いくつかの問題を改善する

  1. 同じディレクトリ名(src, build, build/classes)を複数回使っている

     <!-- 複数回参照するディレクトリ名をプロパティ化 -->
     <property name="src.dir" value="src"/>
     <property name="build.dir" value="build"/>
     <property name="classes.dir" value="${build.dir}/classes"/>
     <property name="jar.dir" value="${build.dir}/jar"/>
    
  2. main class, jarファイル名がハードコーディング

     <!-- project名をjarファイル名と合わせるようにすると便利 -->
     <project name="HelloWorld" basedir="." default="main">
    
     <!-- main class名、jarファイル名のハードコーディングを避けるためプロパティ化 -->
     <property name="main-class" value="oata.HelloWorld"/>
    
  3. ターゲットの実行順序を覚えていないといけない(ant compile jar run)

    main: clean → (compile → jar → run)

     <!-- ディレクトリ名をプロパティ参照に変更 -->
     <target name="compile">
         <mkdir dir="${classes.dir}"/>
         <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
     </target>
    
     <!-- ディレクトリ名をプロパティ参照に変更 -->
     <!-- dependsで依存関係を追加 -->
     <target name="jar" depends="compile">
         <mkdir dir="${jar.dir}"/>
         <jar destfile="${jar.dir}/${ant.project.name}" basedir="${classes.dir}">
             <manifest>
                 <attribute name="Main-Class" value="${main-class}"/>
             </manifest>
         </jar>
     </target>
    
     <!-- ディレクトリ名をプロパティ参照に変更 -->
     <!-- dependsで依存関係を追加 -->
     <target name="run" depends="jar">
         <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
     </target>
    
     <target name="clean-build" depends="clean,jar"/>
    
     <!-- antのデフォルトtarget -->
     <target name="main" depends="clean,run"/>
    

以上の修正を行うことで、ant を実行するだけでデフォルトのmainが実行され、compile jar runの順序でtargetが実行される

antコマンド

ant [options] [target [target2 [target3] ...]]
Options:
  -help, -h              print this message and exit
  -projecthelp, -p       print project help information and exit
  -version               print the version information and exit
  -diagnostics           print information that might be helpful to
                         diagnose or report problems and exit
  -quiet, -q             be extra quiet
  -silent, -S            print nothing but task outputs and build failures
  -verbose, -v           be extra verbose
  -debug, -d             print debugging information
  -emacs, -e             produce logging information without adornments
  -lib <path>            specifies a path to search for jars and classes
  -logfile <file>        use given file for log
    -l     <file>                ''
  -logger <classname>    the class which is to perform logging
  -listener <classname>  add an instance of class as a project listener
  -noinput               do not allow interactive input
  -buildfile <file>      use given buildfile
    -file    <file>              ''
    -f       <file>              ''
  -D<property>=<value>   use value for given property
  -keep-going, -k        execute all targets that do not depend
                         on failed target(s)
  -propertyfile <name>   load all properties from file with -D
                         properties taking precedence
  -inputhandler <class>  the class which will handle input requests
  -find <file>           (s)earch for buildfile towards the root of
    -s  <file>           the filesystem and use it
  -nice  number          A niceness value for the main thread:
                         1 (lowest) to 10 (highest); 5 is the default
  -nouserlib             Run ant without using the jar files from
                         ${user.home}/.ant/lib
  -noclasspath           Run ant without using CLASSPATH
  -autoproxy             Java1.5+: use the OS proxy settings
  -main <class>          override Ant's normal entry point