Ant releases have become more frequent than my blog posts by now, oh my. I won't promise anything.
While most of the world was on vacation Antoine (again) put together a new relase of Apache Ant and announced 1.8.2 late December. As usual a full list of fixed issues can be found in Bugzilla. This time it not only contains bug fixes (quite a few of them, though) but also some new features like improved support for Apache Harmony, gcj and OpenJDK7 (where javac likes to break command line backwards compatibility again).
The issues that came up most frequently and have been fixed are "xslt no longer uses the specified classpath" and "propertyfile with prefix doesn't work as expected" IIRC.
We've added a new collection so that the order of files inside <copy> has become predictable. Detecting Ant 1.8.2 means either
<antversion property="Ant-1.8.2-or-later" atleast="1.8.2"/>
or
<available property="Ant-1.8.2-or-later" classname="org.apache.tools.ant.util.LinkedHashtable"/>
path: /en/Apache/Ant | #
Last weekend Antoine announced Ant 1.8.1. This is more or less a
pure bugfix release which addresses a few important issues. The fix
with the biggest impact likely is that
<extension-point>
and <import>
finally work together as intended - and even documented.
The full list of issues raised against Ant that have been fixed with this release can be found in Bugzilla. We've adressed almost all issues raised against Ant 1.8.0 so far.
Thanks to the new <augment>
task, detecting Ant
1.8.1 means either
<antversion property="Ant-1.8.1-or-later" atleast="1.8.1"/>
or
<available property="Ant-1.8.1-or-later" classname="org.apache.tools.ant.taskdefs.AugmentReference"/>
path: /en/Apache/Ant | #
So Apache Ant 1.8.0 is out and I'm supposed to follow a tradition I started with Ant 1.6.2. If you can be sure that you are at least using Ant 1.7.0 then the built-in <antversion> task/condition will do:
<antversion property="Ant-1.8.0-or-later" atleast="1.8.0"/>
otherwise the trick of checking for a class that has been introduced with Ant 1.8.0 can always be used:
<available property="Ant-1.8.0-or-later" classname="org.apache.tools.ant.types.resources.MappedResource"/>
path: /en/Apache/Ant | #
Antoine Levy-Lambert has recently built the first release candidate for Ant 1.8.0 and called for a vote, so we should be close to the first Ant release since eighteen months. This release mostly brings enhancements and bug fixes to many tasks and types (this is the real strength of Ant IMHO) but there also are a few core changes, the full list is here.
My personal top five changes (I know there are six items, but the first one doesn't count ;-):
- More than 275 fixed Bugzilla issues.
- Lexically scoped local properties, i.e. properties that are only
defined inside a target, sequential block or similar environment.
This is very useful inside of
<macrodef>
s where a macro can now define a temporary property that will disappear once the task has finished. <import>
can now import from any file- or URL-providing resource - this includes<javaresource>
. This means<import>
can read build file snippets from JARs or fixed server URLs. There are several other improvements in the area of import.- Various improvements to the directory scanning code that help with symbolic link cycles (as can be found on MacOS X Java installations for example) and improve scanning performance. For big directory trees the improvement is dramatic.
- The way developers can extend Ant's property expansion algorithm
has been rewritten (breaking the older API) to be easier to use a
be more powerful. The whole local properties mechanism is
implemented using that API and could be implemented in a separate
library without changes in Ant's core. Things like the
yet-to-be-released props
Antlib can now provide often required "scripty" fuctions
without touching Ant itself.
At the same time the if and unless attributes have been rewritten to do the expected thing if applied to a property expansion (i.e.if="${foo}"
will mean "yes, do it" if${foo}
expands to true, in Ant 1.7.1 it would mean "no" unless a property named "true" existed). This adds "testing conditions" as a new use-case to property expansion. - A new top-level element
<extension-point>
assists in writing re-usable build files that are meant to be imported.<extension-point>
has a name and a dependency-list like<target>
and can be used like a<target>
from the command line or a dependy-list but the importing build file can add targets to the<extension-point>
's depends list.
In Ant 1.7.1 one would use something likeimported.xml: <project name="imported"...> ... <target name="setup"> ... </target> <target name="compile" depends="setup"> ... </target> </project> importing.xml <project ...> ... <import file="imported.xml"> <target name="setup" depends="imported.setup"> ... stuff that should happen before compile ... </target> </project>
to define a pre-compilation stage by target overriding. With some planning it can be improved toimported.xml: <project name="imported"...> ... <target name="setup"> ... </target> <target name="ready-to-compile" depends="setup"/> <target name="compile" depends="ready-to-compile"> ... </target> </project> importing.xml <project ...> ... <import file="imported.xml"> <target name="ready-to-compile" depends="imported.ready-to-compile"> ... stuff that should happen before compile ... </target> </project>
In Ant 1.8.0 one would write this asimported.xml: <project name="imported"...> ... <target name="setup"> ... </target> <extension-point name="ready-to-compile" depends="setup"/> <target name="compile" depends="ready-to-compile"> ... </target> </project> importing.xml <project ...> ... <import file="imported.xml"> <target name="pre-compile" extensionOf="ready-to-compile"> ... stuff that should happen before compile ... </target> </project>
and thepre-compile
target was added toready-to-compile
dependeny-list.
extension-point
and some changes
in import
and its new cousin include
have
been inspired by Easyant
which can now use an un-patched version of Ant together with a
custom ProjectHelper
to create a build system quite
different from Ant's original ideas. ProjectHelper
is the mechanism that allowed
me
to sketch
JavaFront
or Nicolas Lalevée to
write GroovyFront
which lets you write build files in Groovy.
path: /en/Apache/Ant | #
I guess I've heard "Ant is an XML programming language" and "Ant would be fine wasn't it for all the XML" once too often. Last week I managed to free up a few hours to build a (probably completely useless) prototype to prove it doesn't have to be that way:
$ cat src/etc/examples/Simple2.java package org.example; import org.apache.ant.javafront.annotations.AntProject; import org.apache.ant.javafront.annotations.AntTarget; import org.apache.tools.ant.Project; @AntProject() public class Simple2 { private final Project p; public Simple2(Project p) { this.p = p; } @AntTarget(Name="hello", Description="says hello", Depends="nonsense") public void foo() { p.log("Hello, world!"); p.log("This is " + p.getProperty("ant.version") + " calling."); } @AntTarget public void nonsense() {} } $ ant -lib build/lib/ant-javafront-0.1.jar -f src/etc/examples/Simple2.java -projecthelp Buildfile: src/etc/examples/Simple2.java [buildfile] Compiling 1 source file to /tmp/javafront Main targets: hello says hello $ ant -lib build/lib/ant-javafront-0.1.jar -f src/etc/examples/Simple2.java hello Buildfile: src/etc/examples/Simple2.java nonsense: hello: Hello, world! This is Apache Ant version 1.7.1 compiled on June 27 2008 calling. BUILD SUCCESSFUL Total time: 0 seconds
This is stock Ant 1.7.1 (should work with Ant 1.7.0 and probably 1.6.5 as well) and Java 5 together with a custom ProjectHelper (more on this later).
The build file is a Java source file that gets compiled on demand.
Each public no-arg method that has the AntTarget
annotation becomes a target where the method's name forms the
default for the target name.
Ant provides an extension point, the ProjectHelper
class which is responsible for configuring an
Ant Project
instance from a source (usually
a File
).
My prototype
extends Ant's built in ProjectHelper
class and
overrides a single method. The majority of that class is concerned
with compiling the "build file" and performing some reflection.
Apart from that, two
trivial annotations
and
a very
simple subclass of Ant's Target
are all that it
takes to replace XML with Java source code. Replacing XML with any
custom format you can think up would be as straight forward, it's
just that nobody seems to know you can plug in your own parser -
maybe it's time for a "little known Ant extension points"
article.
My experiment doesn't do all that its XML cousin can do, in
particular it doesn't support <import>
, but that
can be faked to a certain extent by making the "build file" inherit
from a base class. The other thing is automatic handling of IDs,
but that is something you'd need when using Ant tasks and should
probably go into the builder (see below). It does
support <ant>
and friends as well as namespaces,
as demonstrated by an
example
of
running AntUnit
on the build file itself.
Inside your "target" you may perform any Java code as you see fit. Of course this means you'll only use the target execution engine of Ant. Reusing Ant tasks from Java might be pretty daunting, so I threw in a little Ant tag builder library based on the builder pattern and a fluent interface, not very complete and really just a teaser. For example:
$ cat src/etc/examples/Simple3.java package org.example; import org.apache.ant.javafront.annotations.AntProject; import org.apache.ant.javafront.annotations.AntTarget; import org.apache.tools.ant.Project; import org.apache.ant.javafront.builder.TagBuilder; @AntProject(Name="simple3", DefaultTarget="hello") public class Simple3 { private Project p; public void setProject(Project p) { this.p = p; } @AntTarget(Name="-setup") public void setup() { TagBuilder.forProject(p) .property().withName("world").andValue("world").execute(); } @AntTarget(Depends="-setup") public void hello() { TagBuilder.forProject(p).echo().message("Hello, ${world}!").execute(); } } $ ant -lib build/lib/ant-javafront-0.1.jar -f src/etc/examples/Simple3.java Buildfile: src/etc/examples/Simple3.java [buildfile] Compiling 1 source file to /tmp/javafront -setup: hello: [echo] Hello, world! BUILD SUCCESSFUL Total time: 0 seconds $ ant -lib build/lib/ant-javafront-0.1.jar -f src/etc/examples/Simple3.java -Dworld=reader Buildfile: src/etc/examples/Simple3.java -setup: hello: [echo] Hello, reader! BUILD SUCCESSFUL Total time: 0 seconds
or
$ cat src/etc/examples/AntUnitTest.java package org.apache.ant.javafront.example; import org.apache.ant.javafront.BuildFileBase; import org.apache.ant.javafront.annotations.AntProject; import org.apache.ant.javafront.annotations.AntTarget; import org.apache.tools.ant.Project; import static org.apache.ant.javafront.builder.EchoBuilder.echoMessage; import static org.apache.ant.javafront.builder.DeleteBuilder.deleteDir; import static org.apache.ant.javafront.builder.MkdirBuilder.mkdir; @AntProject(Name="example using AntUnit", BaseDir="../../..", DefaultTarget="antunit") public class AntUnitTest extends BuildFileBase { private static final String FILE = "build.xml"; private static final String OUTPUT = "${output}"; public AntUnitTest(Project p) { super(p); build().property().withName("output").andLocation("build/testoutput") .execute(); } @AntTarget(Description="describes this build file") public void describe() { echoMessage(getProject(), "${ant.version}"); echoMessage(getProject(), "Demonstrates running an AntUnit test."); } @AntTarget public void setUp() { mkdir(getProject(), OUTPUT); } @AntTarget public void tearDown() { deleteDir(getProject(), OUTPUT); } @AntTarget public void testCopy() { build().copy().withAttribute("verbose", "true") .file(FILE).toDir(OUTPUT) .execute(); build().tagWithNs("assertFileExists", "antlib:org.apache.ant.antunit") .withAttribute("file", OUTPUT + "/" + FILE) .execute(); } @AntTarget(Description="runs the test") public void antunit() { build().tagWithNs("antunit", "antlib:org.apache.ant.antunit") .withChild(build().tagWithNs("plainlistener", "antlib:org.apache.ant.antunit")) .withChild(build().tag("file").withAttribute("file", "${ant.file}")) .execute(); } } $ ant -lib build/lib/ant-javafront-0.1.jar -lib ../ant-core/trunk/lib/optional/ant-antunit-1.1.jar -f src/etc/examples/AntUnitTest.java Buildfile: src\etc\examples\AntUnitTest.java antunit: [antunit] Build File: /home/stefan/dev/ASF/ant-javafront/src/etc/examples/AntUnitTest.java [antunit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0,062 sec [antunit] Target: testCopy took 0,031 sec BUILD SUCCESSFUL Total time: 0 seconds $ ant -lib build/lib/ant-javafront-0.1.jar -lib ../ant/lib/optional/ant-antunit-1.1.jar\ -f src/etc/examples/AntUnitTest.java setUp testCopy tearDown Buildfile: src/etc/examples/AntUnitTest.java setUp: [mkdir] Created dir: /home/stefan/dev/ASF/ant-javafront/build/testoutput testCopy: [copy] Copying 1 file to /home/stefan/dev/ASF/ant-javafront/build/testoutput [copy] Copying /home/stefan/dev/ASF/ant-javafront/build.xml to /home/stefan/dev/ASF/ant-javafront/build/testoutput/build.xml tearDown: [delete] Deleting directory /home/stefan/dev/ASF/ant-javafront/build/testoutput BUILD SUCCESSFUL Total time: 0 seconds
I must admit that I don't really see that much of a point in the experiment itself, other than proving that it can be done. The files I've presented so far are longer and more verbose than their XML equivalents would have been. There may be cases where you realy want some non-declarative stuff like performing a loop, here using Java may help, something like.
$ cat src/etc/examples/EchoExamples.java package org.example; import org.apache.ant.javafront.BuildFileBase; import org.apache.ant.javafront.annotations.AntProject; import org.apache.ant.javafront.annotations.AntTarget; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import static org.apache.ant.javafront.builder.EchoBuilder.echoMessage; @AntProject(Description="lists all build files in the current directory", DefaultTarget="list") public class EchoExamples extends BuildFileBase { public EchoExamples(Project p) { super(p); } @AntTarget(Description="list the files in the example dir") public void list() { FileSet fs = (FileSet) build().tag("fileset") .withAttribute("dir", ".") .withAttribute("includes", "*.java") .build(); DirectoryScanner ds = fs.getDirectoryScanner(); for (String file : ds.getIncludedFiles()) { echoMessage(getProject(), "found " + file); } } } $ ant -lib build/lib/ant-javafront-0.1.jar -f src/etc/examples/EchoExamples.java Buildfile: src/etc/examples/EchoExamples.java [buildfile] Compiling 1 source file to /tmp/javafront list: [echo] found AntUnitTest.java [echo] found EchoExamples.java [echo] found Simple.java [echo] found Simple2.java [echo] found Simple3.java BUILD SUCCESSFUL Total time: 0 seconds
Right now I don't expect to put more work into the experiment. The tag builder library may be useful on its own, but what I've seen so far hasn't convinced my that Java would be a better language to describe your builds. What I have shown is that Ant doesn't need XML at all and now I feel better 8-).
path: /en/Apache/Ant | #