- 浏览: 170299 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
wenjinglian:
...
Seam Maven integration -
wm920:
很好帖子···
jstl之forEach循环 -
steil:
[size=x-small][/size][color=re ...
jstl之forEach循环 -
alan3258:
深有同感,知音呀!我们这里做的也和你那里一样需求每时每刻都在变 ...
项目开发心得1 -
liyun_1981:
letitbe 写道shewaer 写道市场是主导,业务是根本 ...
项目开发心得1
New Ant 1.6 Features for Big Projects
by Stefan Bodewig
Learn new features of Ant 1.6 and see how they might impact the way you structure your build process.
Published July 2005
While the 1.5.x series of Ant releases brought a lot of improvements at the task level, it didn't change the way people used Ant. With Ant 1.6, things are a bit different. Several new features have been added to support big or just complex build scenarios. But to fully leverage their power, users may need to restructure their build process a little.
This article focuses on three of the new features — <macrodef>, <import>, <subant> tasks — to show you what you may gain by using them and how they may impact the way you'll be structuring your build setup.
Macros
Most build engineers sooner or later face a situation where they have to perform the same combination of tasks but with slightly different configurations in several places. A common example is creating a web application archive with different configurations for a development, a staging and a production system.
Let's say the web application has different web deployment descriptors depending on the target system and uses a different set of JSPs as well as a different set of libraries for the development environment. The configuration information would be placed in properties and the task to create the web archive would look similar to
<target name="war" depends="jar">
<war destfile="${war.name}"
webxml="${web.xml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
</war>
</target>
where support-libraries is a reference to a <fileset> defined elsewhere that points to a common set of additional libraries that is required by your application.
If you only want to create a single web archive at a time, you just need to set the properties correctly. You could load them from a properties file specific to your target for example.
Creating archives using Ant 1.5
Now, assume you wanted to create the archives for the staging and production systems at the same time to ensure that you are really packaging up the same application for both systems. With Ant 1.5 you'd probably use <antcall> to invoke the "war" target with different property settings, something like:
<target name="production-wars">
<antcall target="war">
<param name="war.name" value="${staging.war.name}"/>
<param name="web.xml" value="${staging.web.xml}"/>
</antcall>
<antcall target="war">
<param name="war.name" value="${production.war.name}"/>
<param name="web.xml" value="${production.web.xml}"/>
</antcall>
</target>
Of course, this assumes that both target systems will use the same jar and JSPs.
But this approach has a major drawback — it is slow. <antcall> re-parses your build file and re-runs all targets, the called target depends upon, for every invocation. In the example above the "jar" target would be run twice. Hopefully, it would do nothing on the second invocation since the "war" target depends on it.
Creating archives using Ant 1.6
With Ant 1.6 you can forget about using <antcall> for macros, instead you can create a new task by parameterizing existing tasks. The above example would thus change to:
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<sequential>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
</war>
</sequential>
</macrodef>
This defines a task named makewar that can be used like any other task. The task has two required attributes, webxml and destfile. To make an attribute optional, we'd have to provide a default value in the tasks definition. This example assumes that ${jar.name} and ${jsps} are constant during the build process and thus they still get specified as properties. Note that properties are expanded when the task is used, not where the macro is defined.
The attributes of the task get used almost exactly like properties, they get expanded via @{} instead of ${}. Unlike properties, they are mutable, i.e. their value can (and will) change with each invocation. They are also only available inside the block of your macro definition. This means that if your macro definition contains yet another macrodef'ed task, your inner macro will not see the attributes of the containing macro.
The new production-wars target would then look like:
<target name="production-wars">
<makewar destfile="${staging.war.name}"
webxml="${staging.web.xml}"/>
<makewar destfile="${production.war.name}"
webxml="${production.web.xml}"/>
</target>
This new code snippet not only performs a bit faster, but is also easier to read since the attribute names provide more information.
Macro tasks can also define nested elements. The nested <fileset> of the <war> task in <makewar> 's definition could be a candidate for this. Maybe the development target needs some additional files or wants to pick JSPs or resources from different places. The following adds an optional nested <morefiles> element to the <makewar> task
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<element name="morefiles" optional="true"/>
<sequential>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
<morefiles/>
</war>
</sequential>
</macrodef>
An invocation would look like:
<makewar destfile="${development.war.name}"
webxml="${development.web.xml}">
<morefiles>
<fileset dir="${development.resources}"/>
<lib refid="development-support-libraries"/>
</morefiles>
</makewar>
This has the same effect as if the nested elements of <morefiles> had been used inside the <war> task directly.
Even though the examples so far have only shown <macrodef> wrapping a single task, it is not limited to this.
The following macro will not only create the web archive but also ensure that the directory containing the final archive exists before it tries to write to it. In a real world build file you'd probably use a setup target to do this before you invoke the task.
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<element name="morefiles" optional="true"/>
<sequential>
<dirname property="@{destfile}.parent"
file="@{destfile}"/>
<mkdir dir="${@{destfile}.parent}"/>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
<morefiles/>
</war>
</sequential>
</macrodef>
Note two things here:
First, attributes are expanded before properties are expanded, so the construct ${@{destfile}.parent} will expand a property who's name consists of the value of the destfile attribute and a ".parent" postfix. This means you can "nest" attribute expansions into property expansions but not the other way around.
Second, this macro defines a property with a name based on an attribute's value since properties in Ant are global and immutable. A first attempt to use
<dirname property="parent"
file="@{destfile}"/>
instead would not lead to the desired result on the second <makewar> invocation in the "production-wars" target. The first invocation would define a new property named parent that points to the parent directory of ${staging.war.name}. The second invocation would see this property and not change its value.
It is expected that a future version of Ant will support some kind of scoped properties that are only defined during the execution of the macro. Until then using an attribute's name to construct property names is a work-around with the potential side effect of creating lots of properties.
Tip: If you look through your build files and find uses of <antcall> as a macro substitute, it is highly recommended that you evaluate converting this to real macros using macrodef. The performance impact may be significant and it may also lead to build files that are easier to read and maintain.
Import
There are several reasons to split a build file into multiple files.
1. The file may have become too large and needs to be split into separate sections to be easier to maintain
2. you have a certain set of functionality that is common to more than one build file and you want to share this.
Sharing common functionality/including files before Ant 1.6
Before Ant 1.6 your only option has been the XML way of entity includes, something like
<!DOCTYPE project [
<!ENTITY common SYSTEM "file:./common.xml">
]>
<project name="test" default="test" basedir=".">
<target name="setup">
...
</target>
&common;
...
</project>
taken from the Ant FAQ.
This approach has two major drawbacks. You can't use an Ant property to point to the file you want to include, so you are forced to hard-code locations into your build file. And the file you want to include is only a fragment of an XML file, it may not have a single root element and thus is more difficult to maintain with XML aware tools.
Sharing common functionality/including files with Ant 1.6
Ant 1.6 ships with a new task named import that you can use now. The example above would become
<project name="test" default="test" basedir=".">
<target name="setup">
...
</target>
<import file="common.xml"/>
...
</project>
Since it is a task, you can use all features of Ant to specify the file location. The major difference is that the imported file has to be a valid Ant build file itself and thus must have a root element named project. If you want to convert from entity includes to import, you must wrap <project> tags around the content of the imported file, Ant will then again strip them while it reads the file.
Note that the file name is resolved relative to the location of the build file by the Ant task, not the specified base directory. You won't notice any difference if you don't set project's basedir attribute or set it to ".". If you need to resolve a file against the base directory you can use a property as a work-around, something like
<property name="common.location" location="common.xml"/>
<import file="${common.location}"/>
The property common.location will contain the absolute path of the file common.xml and has been resolved relative to the base directory of the importing project.
With Ant 1.6 all tasks may be placed outside or inside of targets, with two exceptions. <import> must not be nested into a target and <antcall> must not be used outside of targets (since it would create an infinite loop otherwise).
But <import> can do more than just import another file.
First of all, it defines special properties named ant.file.NAME where NAME is replaced with the name attribute of the <project> tag of the file for each imported file. This property contains the absolute path of the imported file and can be used by the imported file to locate files and resources relative to its own position (as opposed to the base directory of the importing file).
This means that <project> 's name attribute has become more important in context of the <import> task. It is also used to provide alias names for the targets defined in the imported build file. If the following file is imported
<project name="share">
<target name="setup">
<mkdir dir="${dest}"/>
</target>
</project>
the importing build file can see the target either as "setup" or "share.setup". The later becomes important in context of target overrides.
Let's assume we have a build system consisting of multiple independent components each with its own build file. The build files are almost identical so we decide to move the common functionality into a shared and imported file. For simplicity let's only cover compilation of Java files and creating a JAR archive of the results. The shared file would look like
<project name="share">
<target name="setup" depends="set-properties">
<mkdir dir="${dest}/classes"/>
<mkdir dir="${dest}/lib"/>
</target>
<target name="compile" depends="setup">
<javac srcdir="${src}" destdir="${dest}/classes">
<classpath refid="compile-classpath"/>
</javac>
</target>
<target name="jar" depends="compile">
<jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"/>
</target>
</project>
This file wouldn't work as a stand-alone Ant build file since it doesn't define the "set-properties" target that "setup" depends on.
The build file for component A could then look like
<project name="A" default="jar">
<target name="set-properties">
<property name="dest" location="../dest/A"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-A.jar"/>
<path id="compile-classpath"/>
</target>
<import file="../share.xml"/>
</project>
It would just set up the proper environment and delegate the complete build logic to the imported file. Note that the build file creates an empty path as compilation CLASSPATH since it is self-contained. Module B depends on A, its build file would look like
<project name="B" default="jar">
<target name="set-properties">
<property name="dest" location="../dest/B"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-B.jar"/>
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
</target>
<import file="../share.xml"/>
</project>
You'll notice that the build file is almost identical to A's and so it seems as if it should be possible to push most of the set-properties target into shared.xml as well. In fact we can do so assuming we have a consistent naming convention for the dest and src targets.
<project name="share">
<target name="set-properties">
<property name="dest" location="../dest/${ant.project.name}"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-${ant.project.name}.jar"/>
</target>
... contents of first example above ...
</project>
ant.project.name is a built-in property that contains the value of the name attribute of the outer-most <project> tag. So if the build file for module A imports share.xml it will have the value A.
Note that all files are relative to the base directory of the importing build file, so the actual value of the src property depends on the importing file.
With this, A's build file would simply become
<project name="A" default="jar">
<path id="compile-classpath"/>
<import file="../share.xml"/>
</project>
And B's
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
</project>
Now assume that B adds some RMI interfaces and needs to run <rmic> after compiling the classes but before creating the jar. This is where target overrides come handy. If we define a target in the importing build file that has the same name as one in the imported build file, the importing one will be used. For example, B could use:
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
<target name="compile" depends="setup">
<javac srcdir="${src}" destdir="${dest}/classes">
<classpath refid="compile-classpath"/>
</javac>
<rmic base="${dest}/classes" includes="**/Remote*.class"/>
</target>
</project>
In the above example, the "compile" target would be used instead of the one in share.xml; however, this just duplicates the <javac> task from share, which is unfortunate. A better solution would be:
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
<target name="compile" depends="share.compile">
<rmic base="${dest}/classes" includes="**/Remote*.class"/>
</target>
</project>
which simply makes B's "compile" run <rmic> after the original "compile" target has been used.
If we wanted to generate some Java sources (via XDoclet for example) before compilation, we could use something like
<import file="../share.xml"/>
<target name="compile" depends="setup,xdoclet,share.compile"/>
<target name="xdoclet">
.. details of XDoclet invocation omitted ..
</target>
So you can completely override a target or enhance it by running tasks before or after the original target.
One danger must be noted here. The target override mechanism makes the importing build file depend on the name attribute used in the imported file. If anybody changes the name attribute of the imported file, the importing build file will break. The Ant development community is currently discussing a solution to this for a future version of Ant.
Tip: If you find very common structures in your build files, it may be worth to try and refactor the files into a (some) shared file(s) and use target overrides as necessary. This can make your build system more consistent and lets you reuse build logic.
Subant
In a sense, subant is two task in one since it knows two modes of operation.
If you use <subant>'s genericantfile attribute it kind of works like <antcall> invoking a target in the same build file that contains the task. Unlike <antcall>, <subant> takes a list or set of directories and will invoke the target once for each directory setting the project's base directory. This is useful if you want to perform the exact same operation in an arbitrary number of directories.
The second mode doesn't use the genericantfile attribute but takes a list or set of build files to iterate over, calling a target in each build file. This is kind of like using the <ant> task inside a loop.
The typical scenario for this second form is a build system of several modules that can be built independently but that wants a master build file to build all modules at once.
Building a master build file prior to Ant 1.6
Taking the example discussed in the import section, such a master build file would have used
<target name="build-all">
<ant dir="module-A" target="jar"/>
<ant dir="module-B" target="jar"/>
</target>
in Ant prior to Ant 1.6.
Building a master build file with Ant 1.6
With <subant> in Ant 1.6 this can be rewritten to
<target name="build-all">
<subant target="jar">
<filelist dir=".">
<file name="module-A/build.xml"/>
<file name="module-B/build.xml"/>
</filelist>
</subant>
</target>
which doesn't look like a big win since you still have to specify each sub build file individually. The situation changes if you switch to a <fileset> instead
<target name="build-all">
<subant target="jar">
<fileset dir="." includes="module-*/build.xml"/>
</subant>
</target>
which will automatically discover the build files for all modules. If you add a module C, the target inside the master build file doesn't need to change.
But be careful. Unlike <filelist>s or <path>s (which are also supported by <subant>) <fileset>s are not ordered. In our example module-B depended on module-A so we'd need to ensure that module-A gets built first and there is no way to do that using a <fileset>.
<fileset>s are still useful if the builds are not dependent on each other at all or they don't depend on each other for a given operation. A documentation target of module-B would probably not depend on module-A at all, neither would a target that updates your sources from your SCM system.
If you want to combine the auto-discovery of build files with ordering the builds according to their interdependencies, you'll have to write a custom Ant task. The basic idea is to write a task (let's call it <buildlist> for now) that uses a <fileset>, determines the dependencies and calculates the order <subant> has to use. It then creates a <path> that contains the build files in the correct order and places a reference to this path into the project. The invocation would look like
<target name="build-all">
<buildlist reference="my-build-path">
<fileset dir="." includes="module-*/build.xml"/>
</buildlist>
<subant target="jar">
<buildpath refid="my-build-path"/>
</subant>
</target>
The hypothetical buildlist task has already been discussed on the Ant user mailing-list and bug-tracking system. Chances are good that a future version of Ant will contain such a task.
A number of new features have been added to Ant 1.6. Many of these new capabilities make build templates easy to create, structure, and customize. In particular <import> and <target> overrides. The <import>, <macrodef>, and <subant> features stand a good chance of making Ant builds highly reusable. <scriptdef> (not discussed in this article) may be interesting for people who need some scripting but don't want to write a custom task in Java.
by Stefan Bodewig
Learn new features of Ant 1.6 and see how they might impact the way you structure your build process.
Published July 2005
While the 1.5.x series of Ant releases brought a lot of improvements at the task level, it didn't change the way people used Ant. With Ant 1.6, things are a bit different. Several new features have been added to support big or just complex build scenarios. But to fully leverage their power, users may need to restructure their build process a little.
This article focuses on three of the new features — <macrodef>, <import>, <subant> tasks — to show you what you may gain by using them and how they may impact the way you'll be structuring your build setup.
Macros
Most build engineers sooner or later face a situation where they have to perform the same combination of tasks but with slightly different configurations in several places. A common example is creating a web application archive with different configurations for a development, a staging and a production system.
Let's say the web application has different web deployment descriptors depending on the target system and uses a different set of JSPs as well as a different set of libraries for the development environment. The configuration information would be placed in properties and the task to create the web archive would look similar to
<target name="war" depends="jar">
<war destfile="${war.name}"
webxml="${web.xml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
</war>
</target>
where support-libraries is a reference to a <fileset> defined elsewhere that points to a common set of additional libraries that is required by your application.
If you only want to create a single web archive at a time, you just need to set the properties correctly. You could load them from a properties file specific to your target for example.
Creating archives using Ant 1.5
Now, assume you wanted to create the archives for the staging and production systems at the same time to ensure that you are really packaging up the same application for both systems. With Ant 1.5 you'd probably use <antcall> to invoke the "war" target with different property settings, something like:
<target name="production-wars">
<antcall target="war">
<param name="war.name" value="${staging.war.name}"/>
<param name="web.xml" value="${staging.web.xml}"/>
</antcall>
<antcall target="war">
<param name="war.name" value="${production.war.name}"/>
<param name="web.xml" value="${production.web.xml}"/>
</antcall>
</target>
Of course, this assumes that both target systems will use the same jar and JSPs.
But this approach has a major drawback — it is slow. <antcall> re-parses your build file and re-runs all targets, the called target depends upon, for every invocation. In the example above the "jar" target would be run twice. Hopefully, it would do nothing on the second invocation since the "war" target depends on it.
Creating archives using Ant 1.6
With Ant 1.6 you can forget about using <antcall> for macros, instead you can create a new task by parameterizing existing tasks. The above example would thus change to:
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<sequential>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
</war>
</sequential>
</macrodef>
This defines a task named makewar that can be used like any other task. The task has two required attributes, webxml and destfile. To make an attribute optional, we'd have to provide a default value in the tasks definition. This example assumes that ${jar.name} and ${jsps} are constant during the build process and thus they still get specified as properties. Note that properties are expanded when the task is used, not where the macro is defined.
The attributes of the task get used almost exactly like properties, they get expanded via @{} instead of ${}. Unlike properties, they are mutable, i.e. their value can (and will) change with each invocation. They are also only available inside the block of your macro definition. This means that if your macro definition contains yet another macrodef'ed task, your inner macro will not see the attributes of the containing macro.
The new production-wars target would then look like:
<target name="production-wars">
<makewar destfile="${staging.war.name}"
webxml="${staging.web.xml}"/>
<makewar destfile="${production.war.name}"
webxml="${production.web.xml}"/>
</target>
This new code snippet not only performs a bit faster, but is also easier to read since the attribute names provide more information.
Macro tasks can also define nested elements. The nested <fileset> of the <war> task in <makewar> 's definition could be a candidate for this. Maybe the development target needs some additional files or wants to pick JSPs or resources from different places. The following adds an optional nested <morefiles> element to the <makewar> task
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<element name="morefiles" optional="true"/>
<sequential>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
<morefiles/>
</war>
</sequential>
</macrodef>
An invocation would look like:
<makewar destfile="${development.war.name}"
webxml="${development.web.xml}">
<morefiles>
<fileset dir="${development.resources}"/>
<lib refid="development-support-libraries"/>
</morefiles>
</makewar>
This has the same effect as if the nested elements of <morefiles> had been used inside the <war> task directly.
Even though the examples so far have only shown <macrodef> wrapping a single task, it is not limited to this.
The following macro will not only create the web archive but also ensure that the directory containing the final archive exists before it tries to write to it. In a real world build file you'd probably use a setup target to do this before you invoke the task.
<macrodef name="makewar">
<attribute name="webxml"/>
<attribute name="destfile"/>
<element name="morefiles" optional="true"/>
<sequential>
<dirname property="@{destfile}.parent"
file="@{destfile}"/>
<mkdir dir="${@{destfile}.parent}"/>
<war destfile="@{destfile}"
webxml="@{webxml}">
<lib refid="support-libraries"/>
<lib file="${jar.name}"/>
<fileset dir="${jsps}"/>
<morefiles/>
</war>
</sequential>
</macrodef>
Note two things here:
First, attributes are expanded before properties are expanded, so the construct ${@{destfile}.parent} will expand a property who's name consists of the value of the destfile attribute and a ".parent" postfix. This means you can "nest" attribute expansions into property expansions but not the other way around.
Second, this macro defines a property with a name based on an attribute's value since properties in Ant are global and immutable. A first attempt to use
<dirname property="parent"
file="@{destfile}"/>
instead would not lead to the desired result on the second <makewar> invocation in the "production-wars" target. The first invocation would define a new property named parent that points to the parent directory of ${staging.war.name}. The second invocation would see this property and not change its value.
It is expected that a future version of Ant will support some kind of scoped properties that are only defined during the execution of the macro. Until then using an attribute's name to construct property names is a work-around with the potential side effect of creating lots of properties.
Tip: If you look through your build files and find uses of <antcall> as a macro substitute, it is highly recommended that you evaluate converting this to real macros using macrodef. The performance impact may be significant and it may also lead to build files that are easier to read and maintain.
Import
There are several reasons to split a build file into multiple files.
1. The file may have become too large and needs to be split into separate sections to be easier to maintain
2. you have a certain set of functionality that is common to more than one build file and you want to share this.
Sharing common functionality/including files before Ant 1.6
Before Ant 1.6 your only option has been the XML way of entity includes, something like
<!DOCTYPE project [
<!ENTITY common SYSTEM "file:./common.xml">
]>
<project name="test" default="test" basedir=".">
<target name="setup">
...
</target>
&common;
...
</project>
taken from the Ant FAQ.
This approach has two major drawbacks. You can't use an Ant property to point to the file you want to include, so you are forced to hard-code locations into your build file. And the file you want to include is only a fragment of an XML file, it may not have a single root element and thus is more difficult to maintain with XML aware tools.
Sharing common functionality/including files with Ant 1.6
Ant 1.6 ships with a new task named import that you can use now. The example above would become
<project name="test" default="test" basedir=".">
<target name="setup">
...
</target>
<import file="common.xml"/>
...
</project>
Since it is a task, you can use all features of Ant to specify the file location. The major difference is that the imported file has to be a valid Ant build file itself and thus must have a root element named project. If you want to convert from entity includes to import, you must wrap <project> tags around the content of the imported file, Ant will then again strip them while it reads the file.
Note that the file name is resolved relative to the location of the build file by the Ant task, not the specified base directory. You won't notice any difference if you don't set project's basedir attribute or set it to ".". If you need to resolve a file against the base directory you can use a property as a work-around, something like
<property name="common.location" location="common.xml"/>
<import file="${common.location}"/>
The property common.location will contain the absolute path of the file common.xml and has been resolved relative to the base directory of the importing project.
With Ant 1.6 all tasks may be placed outside or inside of targets, with two exceptions. <import> must not be nested into a target and <antcall> must not be used outside of targets (since it would create an infinite loop otherwise).
But <import> can do more than just import another file.
First of all, it defines special properties named ant.file.NAME where NAME is replaced with the name attribute of the <project> tag of the file for each imported file. This property contains the absolute path of the imported file and can be used by the imported file to locate files and resources relative to its own position (as opposed to the base directory of the importing file).
This means that <project> 's name attribute has become more important in context of the <import> task. It is also used to provide alias names for the targets defined in the imported build file. If the following file is imported
<project name="share">
<target name="setup">
<mkdir dir="${dest}"/>
</target>
</project>
the importing build file can see the target either as "setup" or "share.setup". The later becomes important in context of target overrides.
Let's assume we have a build system consisting of multiple independent components each with its own build file. The build files are almost identical so we decide to move the common functionality into a shared and imported file. For simplicity let's only cover compilation of Java files and creating a JAR archive of the results. The shared file would look like
<project name="share">
<target name="setup" depends="set-properties">
<mkdir dir="${dest}/classes"/>
<mkdir dir="${dest}/lib"/>
</target>
<target name="compile" depends="setup">
<javac srcdir="${src}" destdir="${dest}/classes">
<classpath refid="compile-classpath"/>
</javac>
</target>
<target name="jar" depends="compile">
<jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"/>
</target>
</project>
This file wouldn't work as a stand-alone Ant build file since it doesn't define the "set-properties" target that "setup" depends on.
The build file for component A could then look like
<project name="A" default="jar">
<target name="set-properties">
<property name="dest" location="../dest/A"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-A.jar"/>
<path id="compile-classpath"/>
</target>
<import file="../share.xml"/>
</project>
It would just set up the proper environment and delegate the complete build logic to the imported file. Note that the build file creates an empty path as compilation CLASSPATH since it is self-contained. Module B depends on A, its build file would look like
<project name="B" default="jar">
<target name="set-properties">
<property name="dest" location="../dest/B"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-B.jar"/>
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
</target>
<import file="../share.xml"/>
</project>
You'll notice that the build file is almost identical to A's and so it seems as if it should be possible to push most of the set-properties target into shared.xml as well. In fact we can do so assuming we have a consistent naming convention for the dest and src targets.
<project name="share">
<target name="set-properties">
<property name="dest" location="../dest/${ant.project.name}"/>
<property name="src" location="src"/>
<property name="jar.name" value="module-${ant.project.name}.jar"/>
</target>
... contents of first example above ...
</project>
ant.project.name is a built-in property that contains the value of the name attribute of the outer-most <project> tag. So if the build file for module A imports share.xml it will have the value A.
Note that all files are relative to the base directory of the importing build file, so the actual value of the src property depends on the importing file.
With this, A's build file would simply become
<project name="A" default="jar">
<path id="compile-classpath"/>
<import file="../share.xml"/>
</project>
And B's
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
</project>
Now assume that B adds some RMI interfaces and needs to run <rmic> after compiling the classes but before creating the jar. This is where target overrides come handy. If we define a target in the importing build file that has the same name as one in the imported build file, the importing one will be used. For example, B could use:
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
<target name="compile" depends="setup">
<javac srcdir="${src}" destdir="${dest}/classes">
<classpath refid="compile-classpath"/>
</javac>
<rmic base="${dest}/classes" includes="**/Remote*.class"/>
</target>
</project>
In the above example, the "compile" target would be used instead of the one in share.xml; however, this just duplicates the <javac> task from share, which is unfortunate. A better solution would be:
<project name="B" default="jar">
<path id="compile-classpath">
<pathelement location="../dest/A/module-A.jar"/>
</path>
<import file="../share.xml"/>
<target name="compile" depends="share.compile">
<rmic base="${dest}/classes" includes="**/Remote*.class"/>
</target>
</project>
which simply makes B's "compile" run <rmic> after the original "compile" target has been used.
If we wanted to generate some Java sources (via XDoclet for example) before compilation, we could use something like
<import file="../share.xml"/>
<target name="compile" depends="setup,xdoclet,share.compile"/>
<target name="xdoclet">
.. details of XDoclet invocation omitted ..
</target>
So you can completely override a target or enhance it by running tasks before or after the original target.
One danger must be noted here. The target override mechanism makes the importing build file depend on the name attribute used in the imported file. If anybody changes the name attribute of the imported file, the importing build file will break. The Ant development community is currently discussing a solution to this for a future version of Ant.
Tip: If you find very common structures in your build files, it may be worth to try and refactor the files into a (some) shared file(s) and use target overrides as necessary. This can make your build system more consistent and lets you reuse build logic.
Subant
In a sense, subant is two task in one since it knows two modes of operation.
If you use <subant>'s genericantfile attribute it kind of works like <antcall> invoking a target in the same build file that contains the task. Unlike <antcall>, <subant> takes a list or set of directories and will invoke the target once for each directory setting the project's base directory. This is useful if you want to perform the exact same operation in an arbitrary number of directories.
The second mode doesn't use the genericantfile attribute but takes a list or set of build files to iterate over, calling a target in each build file. This is kind of like using the <ant> task inside a loop.
The typical scenario for this second form is a build system of several modules that can be built independently but that wants a master build file to build all modules at once.
Building a master build file prior to Ant 1.6
Taking the example discussed in the import section, such a master build file would have used
<target name="build-all">
<ant dir="module-A" target="jar"/>
<ant dir="module-B" target="jar"/>
</target>
in Ant prior to Ant 1.6.
Building a master build file with Ant 1.6
With <subant> in Ant 1.6 this can be rewritten to
<target name="build-all">
<subant target="jar">
<filelist dir=".">
<file name="module-A/build.xml"/>
<file name="module-B/build.xml"/>
</filelist>
</subant>
</target>
which doesn't look like a big win since you still have to specify each sub build file individually. The situation changes if you switch to a <fileset> instead
<target name="build-all">
<subant target="jar">
<fileset dir="." includes="module-*/build.xml"/>
</subant>
</target>
which will automatically discover the build files for all modules. If you add a module C, the target inside the master build file doesn't need to change.
But be careful. Unlike <filelist>s or <path>s (which are also supported by <subant>) <fileset>s are not ordered. In our example module-B depended on module-A so we'd need to ensure that module-A gets built first and there is no way to do that using a <fileset>.
<fileset>s are still useful if the builds are not dependent on each other at all or they don't depend on each other for a given operation. A documentation target of module-B would probably not depend on module-A at all, neither would a target that updates your sources from your SCM system.
If you want to combine the auto-discovery of build files with ordering the builds according to their interdependencies, you'll have to write a custom Ant task. The basic idea is to write a task (let's call it <buildlist> for now) that uses a <fileset>, determines the dependencies and calculates the order <subant> has to use. It then creates a <path> that contains the build files in the correct order and places a reference to this path into the project. The invocation would look like
<target name="build-all">
<buildlist reference="my-build-path">
<fileset dir="." includes="module-*/build.xml"/>
</buildlist>
<subant target="jar">
<buildpath refid="my-build-path"/>
</subant>
</target>
The hypothetical buildlist task has already been discussed on the Ant user mailing-list and bug-tracking system. Chances are good that a future version of Ant will contain such a task.
A number of new features have been added to Ant 1.6. Many of these new capabilities make build templates easy to create, structure, and customize. In particular <import> and <target> overrides. The <import>, <macrodef>, and <subant> features stand a good chance of making Ant builds highly reusable. <scriptdef> (not discussed in this article) may be interesting for people who need some scripting but don't want to write a custom task in Java.
相关推荐
标题中的"ant 1.6工具下载"指的是获取Ant 1.6版本的软件。 Ant的核心概念是任务(Task),这些任务是可执行的操作单元,比如编译源代码、打包JAR文件、运行测试等。在Ant 1.6版本中,已经包含了丰富的内置任务,...
《Ant 1.6.jar.zip:构建自动化工具的深度解析》 在软件开发领域,构建自动化工具扮演着至关重要的角色,它们能够简化繁琐的手动编译和部署过程,提高开发效率。Ant,作为Apache Software Foundation的一员,是Java...
文件名列表中提到的"Ant1.6"可能是指一系列与Ant 1.6相关的文档,比如用户指南、API参考、示例脚本等。这些文档通常会包含以下内容: 1. **用户指南**:介绍如何设置和运行Ant,以及如何编写构建文件的基本结构和...
标签中的"ant_1", "ant_1.6", "ant_1.6.0", "ant-1.6.0"和"apache-ant-1.6"都指的是Apache Ant的不同版本,1.6.0是其中的一个稳定版本,发布于2003年,提供了许多改进和新特性,比如支持JUnit 3.8,对ivy依赖管理...
《Ant 1.6 源码解析与应用探索》 Apache Ant,简称为Ant,是Java语言开发的一个重要的构建工具,它以XML为基础,定义了项目构建的任务和依赖关系,使得开发者能够自动化地完成软件项目的编译、打包、测试等任务。在...
ant-1.6.jar
《Ant Launcher 1.6.jar:构建自动化利器详解》 在Java开发领域,Apache Ant是一个不可或缺的工具,它是一款基于Java的构建工具,用于管理软件项目的构建、部署和依赖关系。而`ant-launcher-1.6.jar`是Apache Ant...
在“ant-jakarta-log4j-1.6.jar.zip”中,包含的“ant-jakarta-log4j-1.6.jar”文件很可能是一个定制的Ant任务,用于集成Log4j到构建过程中,确保日志记录功能的正确配置和使用。 接下来,我们转向Log4j。Log4j是...
《Ant与ANTLR:深入解析ant-antlr-1.6.3.jar.zip》 在软件开发领域,构建工具和解析器生成器是不可或缺的部分。本文将深入探讨Ant与ANTLR这两个工具,以及它们在“ant-antlr-1.6.3.jar.zip”这个压缩包中的具体应用...
《Ant与XSLP在1.6版本中的应用与理解》 在Java开发领域,Ant和XSLP是两个非常重要的工具,它们在构建和处理XML文档时扮演着不可或缺的角色。"ant-xslp-1.6.jar.zip"这个文件正是这两个工具在1.6版本中的集成,为...
《Ant-Jsch-1.6.jar.zip:Ant与JSch的集成详解》 在Java开发领域,Ant和JSch是两个非常重要的工具。Ant是一种基于Java的构建工具,用于自动化软件项目的构建、编译、测试和部署过程。而JSch则是一个纯Java实现的SSH...
《Ant-Javamail-1.6.jar.zip:构建与邮件通信的利器》 在Java开发领域,Apache Ant和JavaMail是两个不可或缺的工具。Ant是Apache软件基金会的一个项目,它是一个基于Java的构建工具,类似于Unix的Make,但更符合...
《Ant与WebLogic集成:ant-weblogic-1.6.jar.zip深度解析》 在软件开发领域,Apache Ant和Oracle WebLogic Server是两个重要的组件。Ant是一个基于Java的构建工具,而WebLogic则是一款广泛使用的Java EE应用服务器...
在Ant-Vaj-1.6.jar.zip中,"ant-vaj-1.6.jar"是Ant的一个扩展,专门用于支持IBM Visual Age for Java(Vaj)的构建过程。 Vaj,全称IBM Visual Age for Java,是一款早期的Java集成开发环境(IDE),它提供了强大的...
标题“ant-swing-1.6.jar.zip”指的是一个压缩文件,其中包含了Ant Swing库的1.6版本。Ant Swing是Apache Ant项目的一个扩展,它允许Ant构建脚本与Java Swing组件进行交互,从而实现更复杂的GUI测试和操作。这个zip...
官方版本,亲测可用
在"ant-trax-1.6.jar.zip"这个压缩包中,包含了Ant的相关许可文件"ant.license.txt",这表明了Ant的使用遵循特定的开源协议,开发者在使用时需要遵守其中的规定。 接着,我们转向Trax,全称为Transformation API ...
Apache Ant是一个基于Java的构建工具,它在软件开发领域中扮演着重要的角色,尤其是在Java项目中。这个"apache-ant-1.6.0-bin.zip"文件包含了Ant 1.6.0版本的所有二进制文件,使得用户可以直接在本地环境中使用而...
"ant-jai-1.6.jar.zip"是一个包含特定IT组件的压缩文件,主要用于软件开发。这个文件的核心是"ant-jai-1.6.jar",这是一个Java Archive(JAR)文件,它集成了Ant(Apache Ant)和Java Advanced Imaging(JAI)库的...