How to include ant-contrib.jar dynamically in Ant - ant

I'm looking for a way to include a .jar from within an Ant file so I can use it straight away and call its methods in my targets.
In my case it's ant-contrib-1.0b3.jar.

The best way is to put the Ant-Contrib jarfile inside you project. For example, let's say the build.xml is in the root of your project. Create a directory called ant.lib\ant-contrib inside your project, then put the ant-contrib*.jar in this folder. You can use this method for other optional Ant tasks that you might need (for example, Ivy, Findbugs, Cobrrtura, etc).
Then, in your build.xml file, you can do this:
<taskdef resource="net/sf/antcontrib/antlib.xml">
<classpath>
<fileset dir="${basedir}/ant.lib/ant-contrib"/>
</classpath>
</taskdef>
I like doing it this way because the optional jars with the tasks are included with the project. If you check everything into your version control system, someone can checkout your code, and do the build without downloading Ant-Contrib and installing it themselves.
You can define an XML namespaces. This gives your Ant-Contrib tasks a prefix in order to avoid task name collisions in case you use other optional ant tasks that have the same task name. Plus, it alerts users that this is not a standard Ant task.
If you use an XML namespace, you need to put a XMLNS declaration in your <project> heading. This will contain a URI that will connect your Ant Contrib tasks to your XML namespace. For example, the ac: namespace is for all Ant Contrib tasks:
<project name="my.project" default="package" basedir="."
xmlns:ac="http://ant-contrib.sourceforge.net">
<taskdef resource="net/sf/antcontrib/antlib.xml"
uri="http://ant-contrib.sourceforge.net">
<classpath>
<fileset dir="${basedir}/ant.lib/ant-contrib"/>
</classpath>
</taskdef>
What this does is match the XML namespace (xmlns) of ac with the URI http://ant-contrib.sourceforge.net. The URI could be anything. For example:
<project name="my.project" default="package" basedir="."
xmlns:ac="hamburger:with-fries">
<taskdef resource="net/sf/antcontrib/antlib.xml"
uri="hamburger:with-fries">
<classpath>
<fileset dir="${basedir}/ant.lib/ant-contrib"/>
</classpath>
</taskdef>
The standard is to use something like antlib:net.sf.antcontrib:
<project name="my.project" default="package" basedir="."
xmlns:ac="antlib:net.sf.antcontrib">
<taskdef resource="net/sf/antcontrib/antlib.xml"
uri="antlib:net.sf.antcontrib">
<classpath>
<fileset dir="${basedir}/ant.lib/ant-contrib"/>
</classpath>
</taskdef>
However, I like using the URL of the project. That way, if someone wants documentation on Ant-Contrib tasks, they know the URL where the Ant-Contrib project lives.
In all three cases above, I've defined the XML namespace with ac. Thus, you have to prefix all Ant-Contrib task names with ac:. You could use antcontrib or whatever you like. With the ac: namespace, your Ant-contrib tasks will look like this:
<ac:if>
<istrue value="${include.debug.code}"/>
<ac:then>
[...]
</ac:then>
<ac:else>
[...]
</ac:else>
<ac:if>
If you skip the whole namespace thing, you can simply use the Ant-Contrib tasks as documented:
<if>
<istrue value="${include.debug.code}"/>
<then>
[...]
</then>
<else>
[...]
</else>

The best solution is to integrate the apache ivy dependency manager. Ivy can be used to manage all your build classpaths Maven-style!
Example
ivy.xml
This file describes your project's 3rd party dependencies. Ivy uses configurations to logically group files together. In your case note the special "build" configuration uses to configure ANT tasks needed by the build:
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo"/>
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="test" description="Required for test only" extends="runtime"/>
<conf name="build" description="3rd party ANT tasks"/>
</configurations>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.slf4j" name="slf4j-api" rev="1.6.4" conf="compile->default"/>
<!-- runtime dependencies -->
<dependency org="org.slf4j" name="slf4j-simple" rev="1.6.4" conf="runtime->default"/>
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="4.10" conf="test->default"/>
<!-- Build dependencies -->
<dependency org="ant-contrib" name="ant-contrib" rev="1.0b3" conf="build->default"/>
</dependencies>
</ivy-module>
Note:
Dependencies are retrieved by default from Maven Central that now hosts approx 90% of open source jars
build.xml
<project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant" xmlns:antcontrib="antlib:net.sf.antcontrib">
<target name="bootstrap" description="Install ivy">
<mkdir dir="${user.home}/.ant/lib"/>
<get src="https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.apache.ivy&a=ivy&v=LATEST&e=jar"
dest="${user.home}/.ant/lib/ivy.jar"/>
</target>
<target name="init" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:report todir='build/ivy-reports' graph='false' xml='false'/>
<ivy:cachepath pathid="compile.path" conf="compile"/>
<ivy:cachepath pathid="runtime.path" conf="runtime"/>
<ivy:cachepath pathid="test.path" conf="test"/>
<ivy:cachepath pathid="build.path" conf="build"/>
</target>
<target name="taskdefs" depends="init" description="Declare 3rd party ANT tasks">
<taskdef uri="antlib:net.sf.antcontrib" classpathref="build.path"/>
</target>
<target name="build" depends="taskdefs" description="Build logic using ant-contrib tasks">
<echo message="The first five letters of the alphabet are:"/>
<antcontrib:for list="a,b,c,d,e" param="letter">
<sequential>
<echo>Letter #{letter}</echo>
</sequential>
</antcontrib:for>
</target>
<target name="clean" description="Cleanup build files">
<delete dir="build"/>
</target>
<target name="clean-all" depends="clean" description="Additionally purge ivy cache">
<ivy:cleancache/>
</target>
</project>
Notes:
There is a special "bootstrap" target used to kick-start a new installation. Unfortunately ivy is not distributed with core ANT
The cachepath ivy task is used to create ANT paths containing the jars downloaded and cached by ivy.
The ivy report task is very useful to understand the transitive dependencies of your 3rd party libraries.
antcontrib is now distributed as an ANT lib explaining the weird namespacing stuff.

Related

Minimum config to use Ivy to set a path in an Ant build?

I want to use an Ant task that is defined in an artifact. The artifact exists in the main Maven repositories and has some dependencies.
I want to use Ivy and Ant to:
Declare the dependency on that artifact and its transitive dependencies, so that they are resolved when the script is run.
Retrieve all the jar files as an Ant path, that I can feed into Ant's taskdef.
Refer to that set of resolved jar files in some other part of the build script.
So far, the documentation I have found does not optimize for this use case. Instead, it suggests to write the files ivy.xml, ivysettings.xml; I don't like that, the dependencies are small enough that I would like to fit everything in a single build script.
Any ideas?
The ivy cachepath task is a resolve task that can be used to create an ANT path. What is perhaps not well known is that this resolving task can also be used inline, in other words, you can specify the dependencies directly without an ivy file.
<ivy:cachepath pathid="tasks.path">
<dependency org="org.codehaus.groovy" name="groovy-all" rev="2.4.7" conf="default"/>
</ivy:cachepath>
For a related answer that utilizes an ivy file to manage multiple classpaths:
ivy:retrieve select which ivy.xml file to use
Example
The following example is a little contrived by demonstrates ivy downloading the jar associated with the groovy task. I have also included a utility target that I use to install the ivy jar as well.
build.xml
<project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<available classname="org.apache.ivy.Main" property="ivy.installed"/>
<!--
============
Main targets
============
-->
<target name="resolve" depends="install-ivy">
<ivy:cachepath pathid="tasks.path">
<dependency org="org.codehaus.groovy" name="groovy-all" rev="2.4.7" conf="default"/>
</ivy:cachepath>
</target>
<target name="build" depends="resolve">
<taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy" classpathref="tasks.path"/>
<groovy>
ant.echo "Hello world"
</groovy>
</target>
<!--
==================
Supporting targets
==================
-->
<target name="install-ivy" description="Install ivy" unless="ivy.installed">
<mkdir dir="${user.home}/.ant/lib"/>
<get dest="${user.home}/.ant/lib/ivy.jar" src="http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.4.0/ivy-2.4.0.jar"/>
<fail message="Ivy has been installed. Run the build again"/>
</target>
<target name="clean" description="Cleanup build files">
<delete dir="${build.dir}"/>
</target>
<target name="clean-all" depends="clean" description="Additionally purge ivy cache">
<ivy:cleancache/>
</target>
</project>

How to import properties and targets from ant build file properly?

I want to make project with two modules. App and server. Server depends on app. When I compile server I want to include class files from app into the build. But it resolves classpath relatively to server and not to app because of import issues. How to make ant resolve app locations relatively to app and server locations relatively to server. I din't get how it's done in ant docs. Could you please explain in a more simple way? Code snippet to clarify question little bit.
App build.xml:
<project name="app">
<property name="app.build.dir" location="build"/>
<target name="compile">
<echo message="Compiling app to ${app.build.dir}"/>
</target>
</project>
Server build.xml:
<project name="server">
<property name="server.build.dir" location="build"/>
<include file="../app/build.xml"/>
<target name="compile" depends="app.compile">
<echo message="Compiling server to ${server.build.dir} using classpath: ${app.build.dir}"/>
</target>
</project>
Output:
Buildfile: D:\work\test\ant-test2\server\build.xml
app.compile:
[echo] Compiling to D:\work\test\ant-test2\server\build
compile:
[echo] Compiling server to D:\work\test\ant-test2\server\build using classpath: D:\work\test\ant-test2\server\build
BUILD SUCCESSFUL
Total time: 0 seconds
Desired output:
Buildfile: D:\work\test\ant-test2\server\build.xml
app.compile:
[echo] Compiling to D:\work\test\ant-test2\app\build
compile:
[echo] Compiling server to D:\work\test\ant-test2\server\build using classpath: D:\work\test\ant-test2\app\build
BUILD SUCCESSFUL
Total time: 0 seconds
Multi module builds are difficult because there are no standards, each build author have his own approach to solving this problem.
My personal preference is to emulate how Maven does it. Each module creates and publishes a jar file to the "local" repository. This jar file is then a dependency of the other modules that consume its classes. This approach creates clean separation between modules and means you don't need to build the entire project when working on one sub-module.
So how is this done using ANT? Well you'll need to embrace another Maven concept, dependency management. The ivy plugin provides this feature to ANT.
Example
My dummy project. A single module called "app" which is dependency of the "server" module
├── build.xml <-- Builds all modules in correct order
├── app
│   ├── build.xml
│   ├── ivy.xml <-- Describes module dependencies
│   └── src
| ..
└── server
├── build.xml
├── ivy.xml <-- Dependency on the "app" module
└── src
..
Unless you customize locations, ivy uses the following directories to store files:
~/.ivy2/cache <-- Downloaded 3rd party dependencies go here
~/.ivy2/local <-- Repository which is private to the user.
Creating alternative storage locations and leveraging Maven repository managers is beyond the scope of this question.
After running this example by build produces the following explicitly versioned files:
~/.ivy2/local/com.myspotontheweb/demo-app/1.0.0/jars/demo-app.jar
~/.ivy2/local/com.myspotontheweb/demo-server/1.0.0/wars/demo-server.war
build.xml
Builds all modules in the correct order. This is determined by the module dependencies documented in each module's ivy.xml file (See ivy buildlist task). This is a very useful feature when you have a large number of interdependent modules.
<project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<available classname="org.apache.ivy.Main" property="ivy.installed"/>
<target name="install-ivy" unless="ivy.installed">
<mkdir dir="${user.home}/.ant/lib"/>
<get dest="${user.home}/.ant/lib/ivy.jar" src="http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.3.0/ivy-2.3.0.jar"/>
<fail message="Ivy has been installed. Run the build again"/>
</target>
<target name="build-list" depends="install-ivy">
<ivy:buildlist reference="build-path">
<fileset dir="." includes="**/build.xml" excludes="build.xml"/>
</ivy:buildlist>
</target>
<target name="build" depends="build-list">
<subant buildpathref="build-path">
<target name="clean"/>
<target name="publish"/>
</subant>
</target>
<target name="clean" depends="build-list">
<subant buildpathref="build-path">
<target name="clean"/>
</subant>
</target>
<target name="clean-all" depends="clean">
<ivy:cleancache/>
</target>
</project>
Notes:
Contains logic to ensure the ivy jar dependency is installed if missing
Ivy will cache downloaded 3rd party dependencies. The "clean-all" task is useful for ensuring the build is sweaky clean :-)
app/ivy.xml
Lists the 3rd party dependencies that the module has. This is a very useful Maven feature. Dependencies get downloaded automatically from Maven Central. No need to commit them into your source code repository.
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo-app"/>
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="test" description="Required for test only" extends="runtime"/>
</configurations>
<publications>
<artifact name="demo-app"/>
</publications>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.slf4j" name="slf4j-api" rev="1.7.5" conf="compile->default"/>
<!-- runtime dependencies -->
<dependency org="org.slf4j" name="slf4j-log4j12" rev="1.7.5" conf="runtime->default"/>
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="4.11" conf="test->default"/>
</dependencies>
</ivy-module>
Note:
Ivy configurations are used to classify and group dependencies. Used later to populate classpaths
app/build.xml
Pretty standard build process. Code is compiled tested and packaged. Note how ivy configurations are used to control the classpaths.
The "publish" target is worthy of special note it pushes the built jar into a local location where it can be picked up by other module builds.
<project name="demo-app" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<!--
================
Build properties
================
-->
<property name="src.dir" location="src/main/java"/>
<property name="resources.dir" location="src/main/resources"/>
<property name="test.src.dir" location="src/test/java"/>
<property name="build.dir" location="target"/>
<property name="dist.dir" location="${build.dir}/dist"/>
<property name="jar.main.class" value="org.demo.App"/>
<property name="jar.file" value="${dist.dir}/${ant.project.name}.jar"/>
<property name="pub.revision" value="1.0"/>
<property name="pub.resolver" value="local"/>
<!--
===========
Build setup
===========
-->
<target name="resolve" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:report todir='${build.dir}/ivy-reports' graph='false' xml='false'/>
<ivy:cachepath pathid="compile.path" conf="compile"/>
<ivy:cachepath pathid="test.path" conf="test"/>
</target>
<!--
===============
Compile targets
===============
-->
<target name="resources" description="Copy resources into classpath">
<copy todir="${build.dir}/classes">
<fileset dir="${resources.dir}"/>
</copy>
</target>
<target name="compile" depends="resolve,resources" description="Compile code">
<mkdir dir="${build.dir}/classes"/>
<javac srcdir="${src.dir}" destdir="${build.dir}/classes" includeantruntime="false" debug="true" classpathref="compile.path"/>
</target>
<target name="compile-tests" depends="compile" description="Compile tests">
<mkdir dir="${build.dir}/test-classes"/>
<javac srcdir="${test.src.dir}" destdir="${build.dir}/test-classes" includeantruntime="false" debug="true">
<classpath>
<path refid="test.path"/>
<pathelement path="${build.dir}/classes"/>
</classpath>
</javac>
</target>
<!--
============
Test targets
============
-->
<target name="test" depends="compile-tests" description="Run unit tests">
<mkdir dir="${build.dir}/test-reports"/>
<junit printsummary="yes" haltonfailure="yes">
<classpath>
<path refid="test.path"/>
<pathelement path="${build.dir}/classes"/>
<pathelement path="${build.dir}/test-classes"/>
</classpath>
<formatter type="xml"/>
<batchtest fork="yes" todir="${build.dir}/test-reports">
<fileset dir="${test.src.dir}">
<include name="**/*Test*.java"/>
<exclude name="**/AllTests.java"/>
</fileset>
</batchtest>
</junit>
</target>
<!--
=====================
Build project
=====================
-->
<target name="build" depends="test" description="Create executable jar archive">
<ivy:retrieve pattern="${dist.dir}/lib/[artifact]-[revision](-[classifier]).[ext]" conf="runtime"/>
<manifestclasspath property="jar.classpath" jarfile="${jar.file}">
<classpath>
<fileset dir="${dist.dir}/lib" includes="*.jar"/>
</classpath>
</manifestclasspath>
<jar destfile="${jar.file}" basedir="${build.dir}/classes">
<manifest>
<attribute name="Main-Class" value="${jar.main.class}" />
<attribute name="Class-Path" value="${jar.classpath}" />
</manifest>
</jar>
</target>
<!--
=====================
Publish project
=====================
-->
<target name="publish" depends="build" description="Publish artifacts to shared repo">
<ivy:buildnumber organisation="${ivy.organisation}" module="${ivy.module}" revision="${pub.revision}"/>
<ivy:publish resolver="${pub.resolver}" pubrevision="${ivy.new.revision}">
<artifacts pattern="${build.dir}/dist/[artifact].[ext]"/>
</ivy:publish>
</target>
<!--
=============
Clean project
=============
-->
<target name="clean" description="Cleanup build files">
<delete dir="${build.dir}"/>
</target>
</project>
Notes:
The ivy buildnumber task is really useful for ensuring your build number is properly incremented each time you run a build. It looks at the files previously published.
server/ivy.xml
This module has a single dependency on the latest version of the "app" module. The actual revision number is determined at build time based on the files present in the local repository.
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo-server"/>
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="test" description="Required for test only" extends="runtime"/>
</configurations>
<publications>
<artifact name="demo-server" type="war"/>
</publications>
<dependencies>
<!-- runtime dependencies -->
<dependency org="com.myspotontheweb" name="demo-app" rev="latest.integration" conf="runtime"/>
</dependencies>
</ivy-module>
server/build.xml
This build just packages up the libraries into a WAR file. What makes it noteworthy is it's use of the ivy retrieve task. It will pull the "app" module dependency and all its transitive dependencies. It can be difficult to keep track of these manually.
<project name="demo-server" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<!--
================
Build properties
================
-->
<property name="build.dir" location="target"/>
<property name="dist.dir" location="${build.dir}/dist"/>
<property name="war.file" value="${dist.dir}/${ant.project.name}.war"/>
<property name="pub.revision" value="1.0"/>
<property name="pub.resolver" value="local"/>
<!--
===========
Build setup
===========
-->
<target name="resolve" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:report todir='${build.dir}/ivy-reports' graph='false' xml='false'/>
</target>
<!--
=====================
Build project
=====================
-->
<target name="build" depends="resolve" description="Create executable jar archive">
<ivy:retrieve pattern="${build.dir}/lib/[artifact]-[revision](-[classifier]).[ext]" conf="runtime"/>
<war destfile="${war.file}" webxml="src/resources/web.xml">
<lib dir="${build.dir}/lib"/>
</war>
</target>
<!--
=====================
Publish project
=====================
-->
<target name="publish" depends="build" description="Publish artifacts to shared repo">
<ivy:buildnumber organisation="${ivy.organisation}" module="${ivy.module}" revision="${pub.revision}"/>
<ivy:publish resolver="${pub.resolver}" pubrevision="${ivy.new.revision}">
<artifacts pattern="${build.dir}/dist/[artifact].[ext]"/>
</ivy:publish>
</target>
<!--
=============
Clean project
=============
-->
<target name="clean" description="Cleanup build files">
<delete dir="${build.dir}"/>
</target>
</project>
A simple approach would be the following: In the build.xml for app instead of
<property name="app.build.dir" location="build"/>
use
<property name="app.build.dir" location="../app/build"/>
If you specify a property by location (and with a relative path), ant resolves the path relative to your current project. With this notation, ant first goes up a directory level and then down to the app dir, which is right from both of your projects.
A better approach would be to put the settings used by both build scripts in a separate property file and include this file from both builds.

Is there any Ant feature, that copies classpath dependencies to WEB-INF/lib?

I can't affect the procedure of copying classpath dependencies to WEB-INF/lib category: there is no special ANT task that copies those jars (at least, i cant find any 'copy' task with related "WEB-INF/lib" string as PATH argument), but they appeared after project building. How to affect this procedure? Basically, i need to exclude JAXB jars to avoid dependency conflict. At the same time i need this jars at compile-time, so i can't remove them. Maybe, it is easier to erase those jars manually, using 'delete' task?
What your struggling with is multiple classpath management. In a typical build there are at least 4 types of classpath:
compile: Classes that your code directly invokes
runtime: Classes that your code indirectly invokes via other classes
provided: Classes that you need to compile against, but whose implementation will be provided by the target platform
test: Additional classes (like junit) that are needed when you're testing code but which are not shipped with your final application
It was the Maven build tool which formally identified these common classpaths and provided a dependency management system for resolving and populating classpaths during the build process.
The bad news is that ANT pre-dates Maven and therefore leaves classpath management completely up to the programmer.... Typically this is done by putting jars into different directories or using complicated filesets within your build logic.
The good news is that there is an ANT plugin called ivy which performs Maven-like dependency management. It's worth learning, especially, if you program a lot with open source libraries (which increasingly use Maven now).
Example (without ivy)
The files which make up the individual classpaths must be managed at the top of the build. Obviously the files must be separately downloaded into the "lib" directory. As the number of files increases this approach becomes unwieldy.
<project name="demo" default="build">
<!--
================
File collections
================
-->
<fileset dir="lib" id="compile.files">
<include name="*.jar"/>
<exclude name="slf4j-log4j12.jar"/>
<exclude name="log4j.jar"/>
<exclude name="junit.jar"/>
<exclude name="hamcrest-core.jar"/>
</fileset>
<fileset dir="lib" id="runtime.files">
<include name="*.jar"/>
<exclude name="junit.jar"/>
<exclude name="hamcrest-core.jar"/>
</fileset>
<fileset dir="lib" id="test.files">
<include name="*.jar"/>
</fileset>
<!--
===============
Compile targets
===============
-->
..
..
<target name="compile" depends="init,resolve, resources" description="Compile code">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false" debug="true">
<classpath>
<fileset refid="compile.files"/>
</classpath>
</javac>
</target>
<!--
===============
Distribution targets
===============
-->
..
..
<target name="package" depends="test" description="Create the WAR file">
<copy todir="build/lib">
<fileset refid="runtime.files"/>
</copy>
<war destfile="${war.file}" webxml="${resources.dir}/web.xml">
<fileset dir="${resources.dir}" excludes="web.xml"/>
<lib dir="${build.dir}/lib"/>
</war>
</target>
Example (Using ivy)
Very high level introduction to ivy and it's tasks. See the "retrieve" ivy task below which delivers the functionality you're looking for.
build.xml
<project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<!--
===========
Build setup
===========
-->
<target name="bootstrap" description="Install ivy">
<mkdir dir="${user.home}/.ant/lib"/>
<get src="http://search.maven.org/remotecontent?filepath=org/apache/ivy/ivy/2.2.0/ivy-2.2.0.jar"
dest="${user.home}/.ant/lib/ivy.jar"/>
</target>
<!--
============================
Resolve project dependencies
============================
-->
<target name="resolve" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:report todir='${ivy.reports.dir}' graph='false' xml='false'/>
<ivy:cachepath pathid="compile.path" conf="compile"/>
<ivy:cachepath pathid="runtime.path" conf="runtime"/>
<ivy:cachepath pathid="test.path" conf="test"/>
</target>
<!--
===============
Compile targets
===============
-->
..
..
<target name="compile" depends="init,resolve, resources" description="Compile code">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false" debug="true" classpathref="compile.path"/>
</target>
<!--
===============
Distribution targets
===============
-->
..
..
<target name="package" depends="test" description="Create the WAR file">
<ivy:retrieve pattern="${build.dir}/lib/[artifact].[ext]" conf="runtime"/>
<war destfile="${war.file}" webxml="${resources.dir}/web.xml">
<fileset dir="${resources.dir}" excludes="web.xml"/>
<lib dir="${build.dir}/lib"/>
</war>
</target>
Notes
The "bootstrap" target is designed to install ivy (It's not packaged with ANT core)
The "cachepath" task is used to create custom ANT paths
The "retrieve" task populates the WAR file's WEB-INF/lib directory with the jars needed at runtime (managed by ivy configuration)
ivy.xml
This file lists your project's dependencies. It uses configurations to logically group jars together and enables the ivy "cachpath" task to create matching classpaths within your build. Finally the 3rd party jars are downloaded and cached during the build process. This is very convenient and it means you can reduce the size of your project.
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo"/>
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="test" description="Required for test only" extends="runtime"/>
</configurations>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.slf4j" name="slf4j-api" rev="1.7.2" conf="compile->default"/>
<!-- runtime dependencies -->
<dependency org="org.slf4j" name="slf4j-log4j12" rev="1.7.2" conf="runtime->default"/>
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="4.10" conf="test->default"/>
</dependencies>
</ivy-module>

ant script cant support nested If statement?

Hi i am trying to build an ant script that copies a certain lib file based on a if condition. however it doesnt seem to work as i get this error:
build.xml:20: fileset doesn't support the nested "if" element.
this is the part where it fails:
<target name="resolve">
<delete dir="${lib.dir}">
<include name="*" />
</delete>
<copy todir="${lib.dir}">
<fileset dir="ext-libs" >
<if name="${release}" value="true">
<include name="hello-client-[^DEBUG]*.jar" />
</if>
<else>
<include name="hello-client-*DEBUG.*.jar" />
</else>
</fileset>
</copy>
</target>
#JoseK is right. ANT filesets do not support nested "if" statements. In fact the "if" statement is not part of core ANT the recommended approach is to use conditional targets (See example)
#slipset is on the right track. Ivy configurations can be used to selectively choose your dependencies.
Example
This example is designed to be invoked in one of two ways
$ ant clean build
$ tree
.
|-- build.xml
|-- ivy.xml
`-- lib
|-- slf4j-api-1.6.4.jar
`-- slf4j-simple-1.6.4.jar
Or
$ ant -Drelease=1 clean build
$ tree
.
|-- build.xml
|-- ivy.xml
`-- lib
|-- logback-classic-1.0.3.jar
|-- logback-core-1.0.3.jar
`-- slf4j-api-1.6.4.jar
build.xml
<project name="demo" default="build" xmlns:ivy="antlib:org.apache.ivy.ant">
<target name="resolve">
<ivy:resolve/>
</target>
<target name="retrieve-alt" depends="resolve" unless="release">
<ivy:retrieve pattern="lib/[artifact]-[revision](-[classifier]).[ext]" conf="altruntime"/>
</target>
<target name="retrieve-release" depends="resolve" if="release">
<ivy:retrieve pattern="lib/[artifact]-[revision](-[classifier]).[ext]" conf="runtime"/>
</target>
<target name="build" depends="retrieve-alt,retrieve-release"/>
<target name="clean">
<delete dir="lib"/>
</target>
</project>
Notes:
The if and unless clauses on the targets perform a conditional test on the existence of the "release" property.
The ivy retrieve task uses a configuration to decide which jars should be used to populate the "lib" directory.
The retrieve pattern includes a "classifier" pattern, just in case you ivy mapping pulls down additional Maven artifacts like the source or javadoc jars.
ivy.xml
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo"/>
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="altruntime" description="Alternative 'runtime' configuration" extends="compile"/>
<conf name="test" description="Required for test only" extends="altruntime"/>
</configurations>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.slf4j" name="slf4j-api" rev="1.6.4" conf="compile->default"/>
<!-- runtime dependencies -->
<dependency org="ch.qos.logback" name="logback-classic" rev="1.0.3" conf="runtime->default"/>
<!-- altruntime dependencies -->
<dependency org="org.slf4j" name="slf4j-simple" rev="1.6.4" conf="altruntime->default"/>
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="4.10" conf="test->default"/>
</dependencies>
</ivy-module>
Note:
I would highly recommend always specifying a configuration mapping for each dependency. This will then map directly to how you intend to use the jars, for example populate a classpath.
Appendix
How to use ivy configurations
Ivy configurations can be used to emulate Maven scopes, but in fact an ivy configuration can represent any logical grouping of dependencies.
Here are the 3 standard classpaths required in any Java build:
<configurations>
<conf name="compile" description="Required to compile application"/>
<conf name="runtime" description="Additional run-time dependencies" extends="compile"/>
<conf name="test" description="Required for test only" extends="runtime"/>
</configurations>
Note the "extends" syntax that enables you to create larger sets. For example, the runtime set of jars also includes anything needed to compile the code your code.
Ivy configurations are difficult to understand until you realise that they can be used to selectively populate an ANT path:
<ivy:cachepath pathid="compile.path" conf="compile"/>
<javac ..... classpathref="compile.path"/>
Or used to selectively populate a directory
<ivy:retrieve pattern="build/WEB-INF/lib/[artifact].[ext]" conf="runtime"/>
Configuration mappings
Mappings are used to decide how groups of jars in your project relate to groups of jars in other projects.
This normally happens as follows:
<dependency org="org.slf4j" name="slf4j-api" rev="1.6.4" conf="compile->default"/>
Here our compile configuration is populate by the remote default configuration (normally the other modules compile dependencies)
Well, the apache docs are clear that you cannot nest an <if> within a <fileset>
You might have seen an example of the ant-contrib fileset task which does have the if as a conditional argument
You can download and use that instead.
I've done this a bit differently myself.
I've got
<target name="internal-resolve" description="retrieve dependencies with ivy">
<ivy:retrieve pattern = "${basedir}/lib/[conf]/[artifact].[ext]" conf = "${configuration}" />
</target>
Which puts the jars in a directory called lib/${configuration} where configuration is passed as a parameter in
<target name = "resolve-compile">
<antcall target = "internal-resolve">
<param name = "configuration" value = "compile"/>
</antcall>
</target>
So whenever resolve-compile is called, the jars are put in lib/compile.
This gives me:
<path id = "compile.class.path">
<fileset dir="${basedir}/lib/compile" includes="*.jar"/>
</path>
and finally
<target name="compile"
depends="init, resolve-compile"
description="Compile all Java-classes">
<javac deprecation="false"
encoding="utf8"
debug="true"
srcdir="${java.src.dir}"
destdir="${build.classes.dir}"
classpathref="compile.class.path"/>
</target>
Given this, you could easily created your own resolve-build which would drop your jars in lib/build
Granted, you'd then need to add a build configuration to your ivy.xml as
<configurations>
<conf name="compile" visibility="public" description="Whatever"/>
<conf name="build" visibility="public" description="Whatever"/>
</configuration>

Using Ivy to download/install ant-contrib, bsf, beanshell, commons-logging

I am building a project using Ant and Ivy. The build.xml file depends on ant-contrib, bean scripting framework, beanshell, and commons-logging.
Ant searches for libraries in several places, including ${user.home}/.ant/lib.
Is there any way in the build.xml file to have these libraries automatically download and install in the ${user.home}/.ant/lib directory if they are not already present, perhaps using Ivy itself?
Thanks, Ralph
The only jar you need in your ant lib is ivy :-)
Declare your dependencies as normal within your ivy.xml file. Make use of a configuration to collectively group the jars associated with ANT tasks:
<configurations>
<conf name="tasks" description="Ant tasks"/>
</configurations>
<dependencies>
<dependency org="ant-contrib" name="cpptasks" rev="1.0b5" conf="tasks->default"/>
<dependency org="junit" name="junit" rev="3.8" conf="tasks->default"/>
..
In your build.xml file you can create a path from this configuration
<ivy:resolve/>
<ivy:cachepath pathid="tasks.path" conf="tasks"/>
<taskdef name="task1" classname="??" classpathref="tasks.path"/>
<taskdef name="task2" classname="??" classpathref="tasks.path"/>
I happened on this question as I was reading the Ivy cachefileset documentation, which states:
Please prefer the use of retrieve +
standard ant path creation, which make
your build more independent from ivy
(once artifacts are properly
retrieved, ivy is not required any
more).
The Ivy cachepath documentation similarly states:
If you want to make your build more
independent from Ivy, you could
consider using the retrieve task. Once
the artifacts are properly retrieved,
you can use standard Ant path creation
which makes Ivy not necessary any
more.
So, it would seem a better answer would be to modify Mark's response to something that uses retrieve in conjunction with ant paths. Something along the lines of the following:
Mark's Response (modified)
<configurations>
<conf name="tasks" description="Ant tasks"/>
</configurations>
<dependencies>
<dependency org="ant-contrib" name="cpptasks" rev="1.0b5"
conf="tasks->default"/>
<dependency org="junit" name="junit" rev="3.8" conf="tasks->default"/>
..
In your build.xml file you can create a path from this config
<ivy:retrieve conf="tasks"
pattern="${dir.where.you.want.taskdef.jars}/[artifact]-[revision].[ext] />
<path id="tasks.path">
<fileset dir="${dir.where.you.want.taskdef.jars}">
<include name="**/*.jar"/>
</fileset>
</path>
<taskdef name="task1" classname="??" classpathref="tasks.path"/>
<taskdef name="task2" classname="??" classpathref="tasks.path"/>
This would even allow you to move the retrieve task into a separate ant file that deals with dependencies. Thereby, you don't have to rely on ivy after your dependencies are retrieved into their directories.
The intention of ivy is that you use it to pull down your jars (resolve & retrieve). Once you have them in place, you can switch back to using standard Ant.
Note: I would just pull these dependencies into the lib directory. That would simplify the retrieve task:
<ivy:retrieve conf="tasks" />
Also Note: visit the 'Path-like Structures' section of this page for more on "standard ant path creation"
I'd use ant to install everything INTO ant =D
Just use depends="init-ant-contrib, init-ivy"
<!-- ANT-CONTRIB Auto Installer -->
<available property="ant-contrib-exists"
file="${ant.library.dir}/ant-contrib-1.0b3.jar" />
<target name="download-ant-contrib" unless="ant-contrib-exists">
<mkdir dir="${ant.library.dir}" />
<get src="http://downloads.sourceforge.net/project/ant-contrib/ant-contrib/1.0b3/ant-contrib-1.0b3-bin.zip?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fant-contrib%2Ffiles%2Fant-contrib%2F1.0b3%2F&use_mirror=cdnetworks-us-1"
dest="${ant.library.dir}/ant-contrib-1.0b3-bin.zip"
username="true" />
<unzip src="${ant.library.dir}/ant-contrib-1.0b3-bin.zip"
dest="${ant.library.dir}"
overwrite="no" />
<move todir="${ant.library.dir}">
<fileset file="${ant.library.dir}/ant-contrib/*.jar" />
<fileset file="${ant.library.dir}/ant-contrib/lib/*.jar" />
</move>
<delete file="${ant.library.dir}/ant-contrib-1.0b3-bin.zip" />
<delete dir="${ant.library.dir}/ant-contrib" />
</target>
<target name="init-ant-contrib" depends="download-ant-contrib">
<taskdef resource="net/sf/antcontrib/antcontrib.properties">
<classpath>
<pathelement location="${ant.library.dir}/ant-contrib-1.0b3.jar" />
</classpath>
</taskdef>
</target>
<!-- IVY Auto Installer -->
<property name="ivy.install.version" value="2.1.0-rc2" />
<condition property="ivy.home" value="${env.IVY_HOME}">
<isset property="env.IVY_HOME" />
</condition>
<property name="ivy.home" value="${user.home}/.ant" />
<property name="ivy.jar.dir" value="${ivy.home}/lib" />
<property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
<available file="${ivy.jar.file}" property="ivy-exists" />
<target name="download-ivy" unless="ivy-exists">
<mkdir dir="${ivy.jar.dir}" />
<!-- download Ivy from web site so that it can be used even without any special installation -->
<get src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
dest="${ivy.jar.file}"
usetimestamp="true" />
</target>
<target name="init-ivy" depends="download-ivy">
<!-- try to load ivy here from ivy home, in case the user has not already dropped
it into ant's lib dir (note that the latter copy will always take precedence).
We will not fail as long as local lib dir exists (it may be empty) and
ivy is in at least one of ant's lib dir or the local lib dir. -->
<path id="ivy.lib.path">
<fileset dir="${ivy.jar.dir}" includes="*.jar" />
</path>
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant"
classpathref="ivy.lib.path" />
</target>
Now that you have ant-contrib & ivy, everything else should be a simple ivy.xml & ivy-resolve away:
<target name="resolve" depends="init-ivy">
<ivy:retrieve />
</target>
I'm sure you can find similar methods of installing whatever ant task you might need.

Resources