环境:Ubuntu 12.04, java 1.7.0, ant 1.8.2。html
Apache Ant 是一个软件自动化构建工具,构建过程包括编译、测试和部署等。它和 Make 工具类似,但由 Java 实现,因此要求 Java 运行环境,很是适合构建 Java 程序。java
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 语句中 > 字符的两边不要有空格。
咱们如今来考虑一下咱们的构建过程。
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
老是有人会告诉你不要使用 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。
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
为方便阅读测试结果,咱们能够生成一个测试报告。首先,让 <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 入门教程