[Java] Apache Ant 構建基礎教程

環境:Ubuntu 12.04, java 1.7.0, ant 1.8.2。html

 

前言

    Apache Ant 是一個軟件自動化構建工具,構建過程包括編譯、測試和部署等。它和 Make 工具類似,但由 Java 實現,因此要求 Java 運行環境,很是適合構建 Java 程序。java

 
    Ant 和 Make 明顯不一樣之處在於 Ant 使用 XML 來表述構建過程與依賴關係,而 Make 使用 Makefile 格式文件。Ant 默認的構建文件名爲 build.xml。每個 build.xml 文件包含一個 <project> 和至少一個默認的 <target>,<target> 中包含許多 task elements。每一個 task element 有一個做爲引用的惟一 ID。
 
    Ant 是 Apache 開源項目,以 Apache License 發行。
 

歷史

    Ant(意爲 Another Neat Tool,另外一個好用的工具。)最初的構想來自於 James Duncan Davidson,當時他正在開發 Sun 公司的 JSP/Servlet 引擎,也就是後來的 Apache Tomcat。當時 Solaris 平臺有一個專屬版本的 make 能夠用來構建 JSP/Servlet,可是在開源世界不能對使用那個平臺來構建 Tomcat 作出控制,所以 Ant 被開發出來,做爲一個與平臺無關的工具來構建 Tomcat,並使用 XML 文件做爲其構建文件。Ant 1.1 在 2000 年 7 月 19 日做爲獨立產品公開發行。
 
    有一些提案被用於 Ant 2,例如 James Duncan Davidson 的 AntEater, Peter Donald 的 Myrmidon,Conor MacNeill 的 Mutant。但它們都沒有被開發者社區普遍接受。
 
    在 2002 年的時候,Ant 是大部分 Java 項目的構建工具。大多數開源項目開發者都將 build.xml 文件包含在他們的發行版本中。
 
    由於 Ant 能夠輕鬆將 JUnit 集成到構建過程,這給測試驅動開發甚者極限編程的開發者帶來極大意願來使用它。
 

搭建環境

  1. 安裝 JDK。web

  咱們將安裝 Oracle JDK 而不是 Open JDK(也能夠用安裝 Open JDK),所以首先添加第三方庫:apache

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update

  

  而後安裝 oracle-java7-set-default:編程

$ sudo apt-get install oracle-java7-set-default

 

  2. 安裝 ant(Another Neat Tool):oracle

$ sudo apt-get install ant-optional

 

準備項目

  咱們將源代碼文件和生成的文件分開保管,咱們將源代碼文件保存在 src 目錄,全部生成的文件保存在 build 目錄,其子目錄 classes 用以保存編譯後的 Java 類文件,子目錄 jar 用以保存 JAR 文件。app

 

  首先建立 src 目錄:框架

$ mkdir src

 

  接下來讓咱們建立一個 Java 類,該類會使用標準輸出打印一句話 Hello World。讓咱們建立保存該類的源代碼文件 src/oata/HelloWorld.java:maven

package oata;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

 

  編譯 HelloWorld.java 源代碼並運行:ide

$ mkdir build/classes
$ javac -sourcepath src -d build/classes src/oata/HelloWorld.java
$ java -cp build/classes oata.HelloWorld
Hello World

 

  接着咱們建立一個 jar 文件,要建立一個 jar 文件並不難。但要建立一個可啓動的 jar 文件則須要有如下幾步:建立一個 manifest 文件,其中包括啓動類,建立目標目錄並將文件歸檔。

$ echo Main-Class: oata.HelloWorld>myManifest
$ mkdir build/jar
$ jar cfm build/jar/HelloWorld.jar myManifest -C build/classes .
$ java -jar build/jar/HelloWorld.jar
Hello World

 

  注意:在 echo Main-Class 語句中 > 字符的兩邊不要有空格。

 

運行 Java 程序的四個步驟

  咱們如今來考慮一下咱們的構建過程。

    1. 編譯 - 編譯源代碼,不然沒法啓動程序。

    2. 執行 - 運行程序或者編譯命令,下文 build.xml 中每一個 target 對應着一個執行。

    3. 打包 - 雖然目前咱們的程序只有一個類,但若是咱們要對外發布咱們的程序,沒人願意下載幾百個文件。所以咱們要建立 jar 文件,最好是可執行 jar 文件。

    4. 清理 - 清理自動生成的東西。事實證實許多錯誤都是沒有作好清理工做致使。

   

  Ant 默認的構建文件是 build.xml,構建過程當中每個步驟就是一個 target。如今爲咱們的項目新建一個構建文件 ./build.xml:

<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>

 

  如今可使用 Ant 進行編譯、打包和運行程序:

# 編譯
$ ant compile

# 打包
$ ant jar

# 運行
$ ant run

 

  也能夠簡化爲:

$ ant compile jar run

 

  對比一下使用 JDK 自身工具和使用 Ant 的構建過程:

java-only Ant
$ mkdir build/classes
$ javac
    -sourcepath src
    -d build/classes
    src/oata/HelloWorld.java
$ echo Main-Class: oata.HelloWorld>myManifest
$ mkdir build/jar
$ jar cfm
    build/jar/HelloWorld.jar
    mf
    -C build/classes
    .



$ java -jar build/jar/HelloWorld.jar
  
<mkdir dir="build/classes"/>
<javac
    srcdir="src"
    destdir="build/classes"/>
<!-- automatically detected -->
<!-- obsolete; done via manifest tag -->
<mkdir dir="build/jar"/>
<jar
    destfile="build/jar/HelloWorld.jar"

    basedir="build/classes">
    <manifest>
        <attribute name="Main-Class" value="oata.HelloWorld"/>
    </manifest>
</jar>
<java jar="build/jar/HelloWorld.jar" fork="true"/>
  

 

加強版構建文件

  許多時候,咱們在構建過程當中會反覆引用相同的目錄,main-class 和 jar 文件,這些目前都是硬編碼在構建文件中,另外咱們還得記住構建步驟不能搞錯。

 

  爲解決反覆引用相同的東西和避免硬編碼,咱們可使用 properties,而主類咱們可使用 <project> 標籤的屬性來指定,另外使用依賴包來保持構建過程穩步有序。讓咱們從新編輯咱們的 build.xml:

<project name="HelloWorld" basedir="." default="main">

    <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"/>

    <property name="main-class"  value="oata.HelloWorld"/>



    <target name="clean">
        <delete dir="${build.dir}"/>
    </target>

    <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"/>
    </target>

    <target name="jar" depends="compile">
        <mkdir dir="${jar.dir}"/>
        <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
            <manifest>
                <attribute name="Main-Class" value="${main-class}"/>
            </manifest>
        </jar>
    </target>

    <target name="run" depends="jar">
        <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/>
    </target>

    <target name="clean-build" depends="clean,jar"/>

    <target name="main" depends="clean,run"/>

</project>

 

   如今只須要執行 ant:

$ ant
Buildfile: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml

clean:
   [delete] Deleting directory /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build

compile:
    [mkdir] Created dir: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
    [javac] /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:19: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 1 source file to /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes

jar:
    [mkdir] Created dir: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar
      [jar] Building jar: /home/xavier/Exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar

run:
     [java] Hello World

main:

BUILD SUCCESSFUL
Total time: 5 seconds
View Code

 

使用第三方庫

  老是有人會告訴你不要使用 syso-statements,即不要使用 System.out.println() 來記錄日誌,而應該使用日誌 API。下面咱們就在咱們的項目引入一個第三方庫記錄日誌 Log4J。

 

  咱們將第三方庫文件放在 lib 目錄下。你能夠點擊這裏下載 Log4J 庫。建立 lib 目錄,並將 log4j-1.2.13.jar 放到 lib 目錄下。

$ wget https://archive.apache.org/dist/logging/log4j/1.2.13/logging-log4j-1.2.13.tar.gz

$ cd lib
$ tar zvxf logging-log4j-1.2.13.tar.gz

$ mv logging-log4j-1.2.13/dist/lib/log4j-1.2.13.jar .

 

  接下來要修改咱們的源代碼和構建文件,使得在編譯和運行咱們的程序時能夠訪問到這個第三方庫。

  $ vi src/oata/HelloWorld.java

package oata;

import org.apache.log4j.Logger; import org.apache.log4j.BasicConfigurator; public class HelloWorld {
    static Logger logger = Logger.getLogger(HelloWorld.class); public static void main(String[] args) {
 BasicConfigurator.configure(); logger.info("Hello World"); // the old SysO-statement
    }
}

 

  如今還不能執行 ant,由於 Log4J 還不在咱們的類搜索路徑中。咱們要作的不是修改 CLASSPATH 環境變量,由於這樣作可能會影響到其餘項目,咱們只是在這個項目中引入 Log4J,咱們要告訴 ant 全部第三方庫(jar 文件)都放在 ./lib 目錄下:

  $ vi build.xml

<project name="HelloWorld" basedir="." default="main">

    <property name="src.dir" value="src"></property>
    <property name="build.dir" value="build"></property>
    <property name="classes.dir" value="${build.dir}/classes"></property>
    <property name="jar.dir" value="${build.dir}/jar"></property>
    <property name="main-class" value="oata.HelloWorld"></property>

  <!-- 新增 --> <property name="lib.dir" value="lib"></property> <path id="classpath"> <fileset dir="${lib.dir}" includes="**/*.jar"></fileset> </path> <target name="clean"> <delete dir="${build.dir}"></delete> </target>
  <!-- 修改後 --> <target name="compile"> <mkdir dir="${classes.dir}"></mkdir> <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"></javac> </target> <target name="jar" depends="compile"> <mkdir dir="${jar.dir}"></mkdir> <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}"> <manifest> <attribute name="Main-Class" value="${main-class}"></attribute> </manifest> </jar> </target>

  <!-- 修改後 --> <target name="run" depends="jar"> <java fork="true" classname="${main-class}"> <classpath> <path refid="classpath"></path> <path location="${jar.dir}/${ant.project.name}.jar"></path> </classpath> </java> </target> <target name="clean-build" depends="clean,jar"></target> <target name="main" depends="clean,run"></target> </project>

 

  運行 ant:

$ ant run

...

run:
     [java] 1 [main] INFO oata.HelloWorld  - Hello World

 

  以上內容表示名爲 run 的任務日誌爲:[java] 1 [main] INFO oata.HelloWorld  - Hello World

  1. [java]:表示 ant 任務正在運行 java 命令。

  2. 1:Log4J 庫定義的字段,詳情請查閱 Apache Log4J

  3. [main]:表示當前線程爲主線程。

  4. INFO:表示日誌級別。

  5. oata.HelloWorld:日誌消息來自的類名。

  6. -:分隔符。

  7. Hello World:日誌消息。

 

配置文件

  雖然咱們使用了 Log4J,但目前爲止咱們仍然是硬編碼,由於咱們只是簡單調用了 BasicConfigurator.configure(),若是咱們想輸出不一樣格式的日誌消息,那咱們就應該使用一個 property 文件。

 

  咱們在源代碼刪除 BasicConfigurator.configure() 所在行,以及相關的 import 語句。此時運行 ant 會提示:

...
     [java] log4j:WARN No appenders could be found for logger (oata.HelloWorld).
     [java] log4j:WARN Please initialize the log4j system properly.

 

  如今讓咱們爲 Log4J 建立一個配置文件 src/log4j.properties,這是 Log4J 默認的配置文件名,它會自動搜索該配置文件。

log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%m%n

 

  以上配置文件表示建立一個輸出通道(Appender)到控制檯的標準輸出 stdout,標準輸出流將打印出日誌消息(%m),消息末尾添加一個換行符(%n)。這個和以前的 System.out.println() 效果相同。

  建立了配置文件,咱們還要激活該配置文件,編輯構建文件 build.xml,修改 <target name="compile">:

    <target name="compile">
        <mkdir dir="${classes.dir}"></mkdir>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"></javac>
 <copy todir="${classes.dir}"> <fileset dir="${src.dir}" excludes="**/*.java"></fileset> </copy>
    </target>

 

  <copy> 節點表示複製全部的非 .java 後綴的資源文件到 build 目錄,這樣咱們就能夠啓動 build 目錄下的程序而且將這些資源文件包含到 jar 文件中。運行 ant:

$ ant 
Buildfile: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml

clean:
   [delete] Deleting directory /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build

compile:
    [mkdir] Created dir: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
    [javac] /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:20: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 1 source file to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes
     [copy] Copying 2 files to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes

jar:
    [mkdir] Created dir: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar
      [jar] Building jar: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar

run:
     [java] Hello World

 

  log4j.properties 被複制到了 build/classes 目錄,run 的輸出日誌爲 [java] Hello World。

 

測試 Java 類

  Ant 內置了 JUnit,你能夠直接使用 JUnit 測試框架來測試你的代碼。新建一個測試類 src/HelloWorldTest.java:

public class HelloWorldTest extends junit.framework.TestCase {

    public void testNothing() {
    }
    
    public void testWillAlwaysFail() {
        fail("An error message");
    }
    
}

 

  注:本文直接使用 ant 內置 JUnit 致使了 package junit.framework does not exist 的報錯。嘗試將 /usr/share/java/ant-junit4-1.8.2.jar 複製到 ./lib 目錄依然沒法解決報錯。最後只能下載一個 junit.jar 到 ./lib 目錄中才解決該問題。

$ wget http://search.maven.org/remotecontent?filepath=junit/junit/4.11/junit-4.11.jar -O ./lib

 

  由於咱們的項目尚未真正的業務邏輯,因此這個測試類很是簡單,只是展現如何使用它而已。要了解更多關於 JUnit 測試框架,請查閱 junit 手冊。

  讓咱們把 juni 指令添加到咱們的構建文件 build.xml 中:

    ...

    <path id="application" location="${jar.dir}/${ant.project.name}.jar"/>

    <target name="run" depends="jar">
        <java fork="true" classname="${main-class}">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>
        </java>
    </target>
    
    <target name="junit" depends="jar">
        <junit printsummary="yes">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>
            
            <batchtest fork="yes">
                <fileset dir="${src.dir}" includes="*Test.java"/>
            </batchtest>
        </junit>
    </target>

    ...

 

  咱們給咱們這項目生成的 jar 文件路徑一個 ID,並讓它成爲一個全局變量,這樣咱們在 target run 中就可使用它的 ID 來引用它。printsummary=yes 能夠輸出更多的信息給咱們,而不是簡單的 FAILED 或 PASSED,例如失敗了多少項,什麼失敗了,printsummary 均可以提供。classpath 是用來尋找咱們的類。batchtest 是爲了更方便測試在未來你添加了新的測試用例,約定俗成的測試類命名爲 *Test.java。

$ ant junit
Buildfile: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml

compile:
    [javac] /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build.xml:20: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
     [copy] Copying 1 file to /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/classes

jar:
      [jar] Building jar: /home/xavier/exploration/000_build_java_application_with_ant/AntHelloWorld2/build/jar/HelloWorld.jar

junit:
    [junit] Running HelloWorldTest
    [junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0.02 sec
    [junit] Test HelloWorldTest FAILED

BUILD SUCCESSFUL
Total time: 4 seconds
View Code

 

  爲方便閱讀測試結果,咱們能夠生成一個測試報告。首先,讓 <junit> 負責記錄測試數據;其次,將數據轉換爲可閱讀文本。編輯構建文件 build.xml:

    ...
    <property name="report.dir" value="${build.dir}/junitreport"/>
    ...
    <target name="junit" depends="jar">
        <mkdir dir="${report.dir}"/>
        <junit printsummary="yes">
            <classpath>
                <path refid="classpath"/>
                <path refid="application"/>
            </classpath>
            
            <formatter type="xml"/>
            
            <batchtest fork="yes" todir="${report.dir}">
                <fileset dir="${src.dir}" includes="*Test.java"/>
            </batchtest>
        </junit>
    </target>
    
 <target name="junitreport"> <junitreport todir="${report.dir}"> <fileset dir="${report.dir}" includes="TEST-*.xml"/> <report todir="${report.dir}"/> </junitreport> </target>

 

  由於咱們可能會產生大量的文件,而這些文件默認都保存在當前目錄下,所以咱們定義了一個 report 目錄,ant 會在運行 junit 以前建立該目錄並將日誌輸出到該目錄。由於日誌格式爲 XML,這可讓 junitreport 對其進行解析。另一個 target junitreport 會將 report 目錄下全部 XML 文件建立相應的可閱讀的 HTML 格式文檔。你能夠打開 ${report.dir}/index.html 文件查看全部測試結果(看起來很像 JavaDoc)。

 

  你能夠將測試與製做測試報告分爲兩個任務,由於生成 HTML 報告須要必定的時間,而你徹底沒有必要在測試的時候停下來等待報告生成。

  

 

附: 

1. Tutorial Hello World with Ant

2. Ant 入門教程

相關文章
相關標籤/搜索