本文是实战每晚构建系列的第二篇,主要叙述在设计构建平台时要考虑的一些开源或第三方技术,其中既有有类似于"Hello world"的入门介绍,也有精髓内容解析,还有注意点提醒。
1、相关开源或第三方技术
在进行设计之前,我们有必要了解一些开源或第三方在项目构建方面的技术。学习这些技术的最好方式是弄到一份,仔细阅读文档,实践一些小的例子,在工作当中使用之。
1.1 ant 项目构建工具
为了让大家更好地了解后面的设计,本节出了介绍基本知识外,还介绍了这个工具的主要特点中的三点:多个文件组成配置文件,目标依赖性,扩展,另外讲述了ant配置脚本的面向对象特性。
简述
Ant是Apache开源运动中的一份子,它是和大家所熟悉的Make系统一样的基于Java的构建工具。他克服了Make的os依赖性,同样也可以调用os特有的指令。不像Make使用操作系统的脚本命令,ant使用java 类来扩展自身。Ant的配置文件是流行的xml格式。
下面是一个最简单的build.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="projectTemplate" default="init" basedir=".">
<target name="init" >
<property name="lib.dir" value="lib"/>
<echo message="Hello ${user.name}! lib.dir is set to ${lib.dir}" >
</echo>
</target>
</project>
运行ant命令将产生下面的结果:
gongys$ ant
gongys$ Hello gongys! lib.dir is set to lib
在这个简单的build.xml显示了ant配置文件定义目标(target),定义属性(property),访问属性的方法,其中${user.name}是个系统属性。
多个xml文件定义ant配置文件
下面我们给出一个相对复杂的build.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE project [
<!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">
]>
<project name="projectTemplate" default="init" basedir=".">
<target name="init" depends="init.variables">
<property name="lib.dir" value="lib"/>
<echo message="Hello ${user.name}! lib.dir is set to ${lib.dir}" />
<echo message="build.dir is set to ${build.dir} in build-abstract.xml " >
</echo>
</target>
<target name=" clean" depends="init" >
<del dir="${build.dir}"/>
</target>
&build-abstract;
</project>
其中<!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">定义了一个名为build-abstract的实体,其内容为当前目录下的build-abstract.xml文件。&build-abstract;引用了这个实体,这样在build.xml文件就可以用build-abstract.xml定义的目标啦。
下面是build-abstract.xml的内容:
<target name="init.variables">
<property name="build.dir" value="tempbuild"/>
</target>
开发和定义自己的task
ant是一个可以扩充的构建工具,开发者可以开发自己的java类来扩充ant。下面是一个简单的扩充类:
package com.mydomain;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class MyVeryOwnTask extends Task {
private String msg;
// The method executing the task
public void execute() throws BuildException {
System.out.println(msg);
}
// The setter for the "message" attribute
public void setMessage(String msg) {
this.msg = msg;
}
}
这个扩展任务将有一个属性message,ant在执行这个任务时会调用execute方法。下面是在build.xml配置文件中使用这个扩展的示例:
<?xml version="1.0"?>
<project name="OwnTaskExample" default="main" basedir=".">
<taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask">
<classpath>
<pathelement location="where/u/put/the/class/"/>
</classpath>
<target name="main">
<mytask message="Hello World! MyVeryOwnTask works!"/>
</target>
</project>
目标依赖性
了解ant的另外一点是target的依赖性,上面这个比较复杂一点的build.xml的依赖性如下图所示:

这样的依赖图使得执行命令ant init 时先执行init.variable目标中的指令,执行clean目标时先执行依次执行init.variables和init目标。
到目前为止,还没有哪一个集成工具开发出自动分析ant配置文件依赖性图的插件,但是命令行下已经有了。
这个工具名叫vizant,也就是一个实现了扩展ant任务的jar文件,还包含了一些文档和例子,下面是我产生上面的目标依赖图的build.xml
<?xml version="1.0"?>
<!-- $Id: build.xml,v 1.1 2003/04/29 10:25:12 gongys Exp $ -->
<project name="Vizant" basedir="." default="dot">
<property name="build" location="output"/>
<property name="vizant.antfile" value="${buildfile}"/>
<property name="dot.format" value="png"/>
<target name="init">
<echo message="${vizant.antfile}" />
<tstamp/>
<mkdir dir="${build}"/>
</target>
<target name="defvizant">
<taskdef name="vizant" classname="net.sourceforge.vizant.Vizant" classpath="vizant.jar"/>
</target>
<target name="vizant" depends="defvizant,init">
<vizant antfile="${vizant.antfile}" outfile="${build}/build.dot" uniqueref="true"/>
</target>
<target name="dot" depends="vizant">
<exec executable="${basedir}/dot.exe" v
<arg line="-T${dot.format} ${build}/build.dot -o ${build}/out.${dot.format}"/>
</exec>
</target>
</project>
你在要分析的项目目录下执行如下命令便可在output/out.png的依赖图形文件。
gongys$ ant -f vizant/build.xml -Dbuildfile=build.xml
-f vizant/build.xml定义了ant配置文件,-Dbuildfile=build.xml定义了要分析的ant配置文件。
Ant配置脚本的面向对象性
从上面可以知道一个ant的配置脚本可以由多个配置文件组成,一个配置文件由目标和属性定义语句组成。我们可以把属性看成是面向对象中的成员变量,目标看成是方法,这样一个配置文件就定义了一个"类",而且它的成员都是静态的,就是说不需要生成"对象"。一个类是可以运行的如果它的配置文件的顶级元素是<project>,这就好像我们的java类实现了public static void main(String[] args)方法一样。可以用xml中的定义和引用实体的方式来申明一个"类"继承了另一个"类",这样我们可以实现面向对象当中的"类继承层次图";我们可以用<ant>任务来实现跨对象之间的调用(要求这些对象的类是可以运行的),这样就形成了"对象协作图";我们可以用<antcall>和目标的depends属性来实现对象内部的"方法调用"。
注意Ant配置脚本的面向对象模型没办法实现方法重载或覆盖。