Top 15 Ant Best Practices

文章讲的是如何编写好的ANT,这是英文的,现在虽然没有有,不过个个感觉比觉好,先放在这里,以后也许有需要。下面还有一篇iBATIS翻译作者刘涛的读书读记。

英文:http://www.onjava.com/lpt/a/4456

Before Ant, building and deploying Java applications required a hodgepodge of platform-specific scripts, makefiles, proprietary IDEs, or manual processes. Now, nearly every open source Java project uses Ant. A great number of companies use Ant for internal projects as well. The widespread use of Ant in these projects has naturally led to an increased need for a set of well-established best practices.

This article summarizes several of my favorite Ant tips or best practices. Many were inspired by mistakes made on previous projects, or from horror stories relayed to me from other developers. One person told me of a project where XDoclet-generated code was placed into a version- control tool that locks files. When a developer changes a source file, they must remember to manually check out and lock all of the files that will be regenerated. They must then manually run the code generator, and only then can they tell Ant to compile the code. Here are some problems with this approach:

  • Generated code should not be stored in version control.
  • Ant (or XDoclet, in this case) should automatically determine which files will be affected by the next build. Programmers should not have to figure this out manually.
  • The Ant buildfile should define correct target dependencies so that programmers do not have to invoke targets in a particular order in order to get a good build.

When I start any new project, I begin by creating the Ant buildfile. Ant defines the build process and is used by every programmer on the team throughout the day. All of the tips in this article assume the Ant buildfile is an important artifact that must be written with care, maintained in version control, and refactored periodically. So here now are my top 15 Ant best practices.

1. Adopt Consistent Style Conventions

Ant users either love the XML buildfile syntax or hate it. Rather than jump into the middle of this fascinating debate, let's look at a few simple ways to keep the XML buildfile clean.

First and foremost, spend time formatting your XML so it looks visually appealing. Ant works with ugly or pretty XML, but ugly XML is hard to read. Provided you leave a blank line between targets, indent consistently, and avoid exceeding 90 or so columns of text, XML is surprisingly readable. Throw in a good editor or IDE that syntax highlights the XML, and you should not have any trouble getting by.

Also pick meaningful, human-readable names for targets and properties. For example, dir.reports is a better name than rpts. The specific naming convention is not important -- just come up with something and stick to it.

2. Put build.xml in the Project Root Directory

The Ant buildfile can reside anywhere, but putting build.xml in the top-level project directory keeps things simple and clean. This is the most common convention, and programmers expect to find build.xml in this location. Having the buildfile at the top directory also makes it conceptually easy to see how relative paths point to different directories in your project tree. Here is a typical project layout:

[root dir]
  | build.xml
  +--src
  +--lib (contains 3rd party JARs)
  +--build (generated by the build)
  +--dist (generated by the build)

When build.xml is in the top directory, you can compile code from the command line without changing your working directory, provided you are somewhere within the project directory tree. Just type this: ant -find compile. The -find argument tells Ant to search ancestor directories until it locates the buildfile.

3. Prefer a Single Buildfile

Some people prefer to break up large projects into several small buildfiles, each of which is responsible for a small portion of the overall build. This is strictly a matter of opinion, but beware that breaking up the build often makes it harder to wrap your head around the overall process. Be careful not to over-engineer a clever hierarchy of buildfiles when a single file can usually do the job.

Even if your project is divided into many different buildfiles, programmers expect to find a master build.xml in the project root directory. Make sure this buildfile is available, even if it only delegates actual work to subordinate builds.

4. Provide Good Help

Strive to make the buildfile self-documenting. Adding target descriptions is the easiest way to accomplish this. When you type ant -projecthelp, you see a listing of each target containing a description. For example, you might define a target like this:

<target name="compile" 
  description="Compiles code, output goes to the build dir.">

The simple rule is to include descriptions for all of the targets you wish programmers to invoke from the command line. Internal targets should not include description attributes. Internal targets may include targets that perform intermediate processing, such as generating code or creating output directories.

Another way to provide help is to include XML comments in the buildfile. Or, define a target named help that prints detailed usage information when programmers type ant help.

<target name="help" 
        description="Display detailed usage information">
  <echo>Detailed help...</echo>
</target>

5. Provide a Clean Target

Every buildfile should include a target that removes all generated files and directories, bringing everything back to its original, pristine state. All files remaining after a clean should be those found in version control. For example:

<target name="clean" 
    description="Destroys all generated files and dirs.">
  <delete dir="${dir.build}"/>
  <delete dir="${dir.dist}"/>
</target>

Do not automatically invoke clean, unless perhaps you have some special target for generating a full release. When programmers are merely compiling or performing other tasks, they do not want the buildfile to perform a full cleanup before proceeding. This is both annoying and counterproductive. Trust programmers to decide when they are ready to clean all files.

6. Manage Dependencies Using Ant

Suppose your application consists of a Swing GUI, a web interface, an EJB tier, and shared utility code. In large systems, you need to clearly define which Java packages belong to which layer of the system. Otherwise, you end up being forced to compile hundreds or thousands of files each time you change something. Poor dependency management leads to overly complex, brittle systems. Changing the layout of a GUI panel should not cause you to recompile your servlets and EJBs.

As systems get bigger, it is easy to inadvertently introduce server-side code that depends on client-side code, or vice versa. This is because the typical IDE project compiles everything using a monolithic classpath. Ant lets you control the build more effectively.

Design your Ant buildfile to compile large projects in stages. First, compile shared utility code. Place the results into a JAR file. Then, compile a higher level portion of the project. When you compile higher-level code, compile against the JAR file(s) created in the first step. Repeat this process until you reach the highest level of the system.

Building in stages enforces dependency management. If you are working on a low-level framework Java class and accidentally refer to a higher-level GUI panel, the code will not compile. This is due to the fact that when the buildfile compiles the low-level framework, it does not include the high-level GUI panel code in the source path.

7. Define and Reuse Paths

A buildfile is often easier to understand if paths are defined once in a central location, and then reused throughout the buildfile. Here is an example that shows this in action.

<project name="sample" default="compile" basedir=".">
  <path id="classpath.common">
    <pathelement location="${jdom.jar.withpath}"/>
    ...etc
  </path>
  <path id="classpath.client">
    <pathelement location="${guistuff.jar.withpath}"/>
    <pathelement location="${another.jar.withpath}"/>
    <!-- reuse the common classpath -->
    <path refid="classpath.common"/>
  </path>
  <target name="compile.common" depends="prepare">
    <javac destdir="${dir.build}" srcdir="${dir.src}">
          <classpath refid="classpath.common"/>
          <include name="com/oreilly/common/**"/>
    </javac>
  </target>
</project>

Techniques like this always gain more value as the project grows and the build gets progressively more complex. You will probably have to define different paths to compile each tier of the application, as well as paths to run unit tests, run the application, run XDoclet, generate JavaDocs, etc. This modular path approach is preferable to a gigantic path for everything. Failure to modularize makes it easy to lose track of dependencies.

8. Define Proper Target Dependencies

Suppose the dist target depends on the jar target, which depends on compile, which depends on prepare. Ant buildfiles ultimately define a dependency graph, which must be carefully defined and maintained.

Periodically review the dependencies to ensure that your builds do the right amount of work. Large buildfiles tend to degrade over time as more targets are added, so you end up with unnecessary dependencies that cause your builds to work too hard. For example, you might find yourself regenerating the EJB code when the programmer actually only wanted to compile some GUI code that does not use EJB at all.

Omitting dependencies in an effort to "optimize" the build is another common mistake. This is a mistake because it forces programmers to remember to invoke a series of targets in a particular order in order to get a decent build. A better solution exists: provide public targets (those with descriptions) that contain correct dependencies, and another set of "expert" targets that let you manually execute individual build steps. These steps do not guarantee a complete build, but let expert users bypass steps during quick and dirty coding sessions.

9. Use Properties for Configurability

Any piece of information that needs to be configured, or that might change, should be defined as an Ant property. The same is true for values that are used in more than one place in the buildfile. Properties should be defined either at the top of a buildfile or in a standalone properties file for maximum flexibility. Here is how properties look when defined in the buildfile:

<project name="sample" default="compile" basedir=".">
  <property name="dir.build" value="build"/>
  <property name="dir.src" value="src"/>
  <property name="jdom.home" value="../java-tools/jdom-b8"/>
  <property name="jdom.jar" value="jdom.jar"/>
  <property name="jdom.jar.withpath" 
                    value="${jdom.home}/build/${jdom.jar}"/>
    etc...
</project>

Or, you can use a properties file:

<project name="sample" default="compile" basedir=".">
  <property file="sample.properties"/>
   etc...
</project>

And in sample.properties:

dir.build=build
dir.src=src
jdom.home=../java-tools/jdom-b8
jdom.jar=jdom.jar
jdom.jar.withpath=${jdom.home}/build/${jdom.jar}

Having a separate file for properties is beneficial because it explicitly defines the configurable portion of the build. You can provide a different version of this properties file for different platforms, or for developers working on different operating systems.

10. Keep the Build Process Self-Contained

To the greatest extent possible, do not refer to external paths and libraries. Above all, do not rely on the programmer's CLASSPATH setting. Instead, use relative paths throughout your buildfile and define your own paths. If you refer to an explicit path such as C:\java\tools, other developers will not be able to use your buildfile because they are highly unlikely to use the same directory structure.

If you are deploying an open source project, provide a distribution that includes all JAR files necessary to compile your code, subject to licensing restrictions, of course. For internal projects, dependent JAR files should be managed under version control and checked out to a well-known location.

When you do have to refer to external paths, define the paths as properties. This lets programmers override those settings to conform to their own machines. You can also refer to environment variables using this syntax:

<property environment="env"/>
<property name="dir.jboss" value="${env.JBOSS_HOME}"/>

11. Use Version Control

The buildfile is an important artifact that should be versioned, just like source code. When you tag or label your code, apply the same tag or label to your buildfile. This lets you go back to a previous release and build the software using the buildfile as it was back then.

In addition to the buildfile, you should maintain third-party JAR files in version control. Again, this makes it possible to recreate previous releases of your software. This also makes it easier to ensure that all developers have the same JAR files, because they can check them out of version control to a path relative to the buildfile.

Generally, avoid storing build output in version control. Provided that your source code is versioned properly, you should be able to recreate any previous release through the build process.

12. Use Ant as the Least Common Denominator

Suppose your team uses an IDE. Why bother with Ant when programmers can click the lightning bolt icon to rebuild the whole application?

The problem with IDEs is one of consistency and reproducibility across a team of members. IDEs are almost always designed for individual programmer productivity, not for consistent builds across a team of developers. Typical IDEs require each programmer to define his or her own project file. Programmers may have different directory structures, may use different versions of various libraries, or may be working on different platforms. This leads to situations where code that compiles fine for Bob may not build properly for Sally.

Regardless of what IDE your team uses, set up an Ant buildfile that all programmers use. Make a rule that programmers perform an Ant build before checking new code into version control. This ensures that code is always built from the same Ant buildfile. When problems arise, perform a clean build using the project-standard Ant buildfile, not someone's particular IDE.

Programmers should be free to use whatever IDE or editor they are comfortable with. Use Ant as a common baseline to ensure the code is always buildable.

13. Use zipfileset

People often use Ant to create WAR, JAR, ZIP, and EAR files. These files generally require a particular internal directory structure, one that usually does not match the directory structure of your source code and build environment.

An extremely common practice is to write an Ant target that copies a bunch of files to a temporary holding area using the desired directory structure, and then create the archive from there. This is not the most efficient approach. Using a zipfileset is a better solution. This lets you select files from any location and place them in the archive file using a different directory structure. Here is a small example:

<ear earfile="${dir.dist.server}/payroll.ear"
    appxml="${dir.resources}/application.xml">
  <fileset dir="${dir.build}" includes="commonServer.jar"/>
  <fileset dir="${dir.build}">
    <include name="payroll-ejb.jar"/>
  </fileset>
  <zipfileset dir="${dir.build}" prefix="lib">
    <include name="hr.jar"/>
    <include name="billing.jar"/>
  </zipfileset>
  <fileset dir=".">
    <include name="lib/jdom.jar"/>
    <include name="lib/log4j.jar"/>
    <include name="lib/ojdbc14.jar"/>
  </fileset>
  <zipfileset dir="${dir.generated.src}" prefix="META-INF">
    <include name="jboss-app.xml"/>
  </zipfileset>
</ear>

In this example, all JAR files are placed in the lib directory of the EAR. The hr.jar and billing.jar files are copied from our build directory, therefore we use zipfileset to move them to the lib directory inside of the EAR. The prefix attribute specifies the destination directory in the EAR.

14. Perform the Clean Build Test

Assuming your buildfile has clean and compile targets, perform the following test. First, type ant clean. Second, type ant compile. Third, type ant compile again. The third step should do absolutely nothing. If files compile a second time, something is wrong with your buildfile.

A buildfile should perform work only when input files change with respect to corresponding output files. A build process that compiles, copies, or performs some other work when it is not necessary to perform the work is inefficient. Even small inefficiencies become big problems when a project grows in size.

15. Avoid Platform-Specific Ant Wrappers

For whatever reason, some people like to ship their products with simple batch files or scripts called something like compile. Look inside of the script and you will find the following:

ant compile

Developers are familiar with Ant and are perfectly capable of typing ant compile. Do not include a platform-specific script that does nothing but invoke Ant. Your script becomes one more little annoyance for people to study and understand when they are looking at your tool for the first time. It is also likely that you will fail to provide scripts for every operating system out there, which will really annoy some users.

Summary

Too many companies rely on manual processes and ad hoc procedures for compiling code and creating software distributions. Teams that do not use a defined build process with Ant or similar tools spend an amazing amount of time tracking down problems when code compiles for some developers but fails for others.

Creating and maintaining build scripts is not glamorous work, but is necessary. A well-crafted Ant buildfile lets you focus on what you enjoy most -- writing code!

References

Eric M. Burke is an O'Reilly author and a principal software engineer with Object Computing, Inc. in St. Louis, MO.

刘涛的读书笔记:http://starrynight.blogdriver.com/starrynight/634792.html

本文选自ONJava的“Top 15 Ant Best Practices”,作为备忘的笔记。

1、使用良好的规范编写Ant脚本
使用易于阅读的格式编写Build脚本-在每个Target之间插入空行;使用美观易于阅读的缩进;没行不要超过90个字符;为每个Target和属性挑选有意思的,易于理解的名字等等。

2、把build.xml放在项目的根目录下
把build.xml放在项目根目录下是个好习惯,这样做的好处是,你可以项目工作目录的任何子目录下编译代码,只需使用命令:
ant -find compile。选项-find使Ant在当前目录的所有上级目录中查找Build脚本。

3、尽量只使用一个的Build File

4、提供友好的Build脚本使用帮助
为每个Target加入description属性,这样,可以使用命令:
ant -projecthelp 列出所有Target的描述信息。
或者,也可以为Build脚本编写一个help target,如:
<target name="help"
description="Display detailed usage information">
<echo>Detailed help...</echo>
</target>

5、提供一个Clean Target
每个Build脚本都应该包括一个Target,用来清除所有的生成文件和目录,运行Clean Target以后保留下来的目录应该都是在CVS上能找到的。如:
<target name="clean"
description="Destroys all generated files and dirs.">
<delete dir="${dir.build}"/>
<delete dir="${dir.dist}"/>
</target>
不要自动执行clean,除非你有一个创建所有东西的Target。

6、使用Ant管理依赖
小心设计你的Build脚本,使一个大项目的构建可以按步骤进行。首先,应该编译代码的公共部分,把它们打包成jar文件。然后,编译项目更高层次的其他代码。
这样,如果只需要低层次的公共代码,你不必花时间编译其他高层次的代码。

7、定义和重用文件路径
把路径定义集中在同一个地方能使Build脚本更易于管理和理解。

8、正确定义Target的相互依赖关系
经常检查并重构你的Build脚本,清除Target之间不不必要的依赖关系。

9、使用属性来配置Ant
任何可能需要修改的属性,或是不只使用一次的属性都要在Build脚本开始的地方定义,或在一个单独的Properties文件定义。

10、保持Build过程的独立性
为了获得最大程度的独立性,不要在Build脚本中包含任何位于Build目录下的目录或文件;不要依赖于开发人员的CLASSPATH环境变量。相反,要在Build脚本中使用相对路径。
如果你需要使用使用外部的目录(如tomcat的安装目录等),可以把它定义成一个属性变量。

11、使用版本控制管理
Build脚本是非常重要的配置管理项,应该和代码一样,放在版本控制工具里管理。当你为代码贴标签和版本时,也应该为Build脚本贴标签和版本。
通常情况下,不要把构建的生成物放入版本控制工具。

12、使用Ant作为构建任务的最小公约数
团队内部的开发人员可以自由选择开发工具。因此使用Ant作为构建任务的基线,使代码可以随时构建。

13、使用zipfileset
Ant脚本通常用于创建WAR,JAR,ZIP和EAR文件。这些文件通常需要一个特定的内部文件结构。
一般的办法是把所需的目录和文件复制到一个临时目录,创建出所需的文件结构,然后在临时目录下创建,这是一个笨办法。
更有效的办法是使用zipfileset,它让你可以从任何位置选择文件,按所需的目录结构放入archived文件中。下面是一个例子:
<ear earfile="${dir.dist.server}/payroll.ear"
appxml="${dir.resources}/application.xml">
<fileset dir="${dir.build}" includes="commonServer.jar"/>
<fileset dir="${dir.build}">
<include name="payroll-ejb.jar"/>
</fileset>
<zipfileset dir="${dir.build}" prefix="lib">
<include name="hr.jar"/>
<include name="billing.jar"/>
</zipfileset>
<fileset dir=".">
<include name="lib/jdom.jar"/>
<include name="lib/log4j.jar"/>
<include name="lib/ojdbc14.jar"/>
</fileset>
<zipfileset dir="${dir.generated.src}" prefix="META-INF">
<include name="jboss-app.xml"/>
</zipfileset>
</ear>
在上面的例子中,所有的jar文件都放入EAR文件的lib目录下。先前创建的hr.jar和billing.jar从${dir.build}目录中复制到EAR文件的lib目录下。

14、执行Build脚本的Clean测试
假设Build脚本有clean和compile两个Target,执行以下的测试:
第一步,运行ant clean
第二步,运行ant compile
第三步,再运行ant compile
第三步应该什么都不做,如果代码被重新编译,说明Build脚本有些问题。只有当输入文件发生变化时,Build脚本的Target才应该工作。

15、避免平台相关的Ant包装脚本
有些人喜欢使用批处理命令包装Ant脚本,如编写一个build.bat文件,其中包含命令:ant compile。其实这样做并没有必要。开发人员更喜欢直接使用ant compile,因为它可以在不同的平台下工作。

<!-- sidebar begins -->

你可能感兴趣的:(xml,jboss,ant,脚本,读书)