We use Ivy for dependency management. In order to guarantee stability and traceability, we fix version numbers for all dependencies in our ivy files, plus we use transitive=false to avoid dependency trees to grow uncontrolled. The second has only the disadvantage that it may require a few tests to complete the ivy file.
Since we fix version numbers, we don't get updated about the existence of a later version of a package. What we don't want is to get the freshest version of a dependency at build time. What we want is to periodically check for available updates and later decide whether and which packages to update.
As an example, here are our Spring dependencies as of 01/14/2016
<dependency org="org.springframework" name="spring-core" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-aop" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-beans" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-context" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-context-support" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-expression" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-jdbc" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-orm" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-tx" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-web" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-webmvc" rev="4.2.4.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework" name="spring-test" rev="4.2.4.RELEASE" transitive="false" conf="test->*"/>
<dependency org="org.springframework.plugin" name="spring-plugin-core" rev="1.2.0.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework.plugin" name="spring-plugin-metadata" rev="1.2.0.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework.batch" name="spring-batch-core" rev="3.0.6.RELEASE" transitive="false" conf="runtime->*"/>
<dependency org="org.springframework.batch" name="spring-batch-infrastructure" rev="3.0.6.RELEASE" transitive="false" conf="runtime->*"/>
But we have a lot more. So I am asking if there is a smarter way to check for possible updates for all packages (we now have 101 packages).
Ant's ivy:report won't show the availability of a later version. Manually checking 101 packages on Maven is boring.
We also have a local Artifactory installation, I'm saying that if it could prove useful for purpose.
Any idea? What I would like to see is a report with current and latest version numbers of packages in an Ivy file
I just found an ivy task checkdepsupdate designed to solve your problem:
<target name="resolve" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:checkdepsupdate showTransitive="false" revisionToCheck="latest.release"/>
</target>
Taking the ivy file in my example below it prints the following report detailing, the latest releases for my 3rd party dependencies.
[ivy:checkdepsupdate] Dependencies updates available :
[ivy:checkdepsupdate] org.slf4j#slf4j-api 1.7.5 -> 1.7.13
[ivy:checkdepsupdate] org.slf4j#slf4j-log4j12 1.7.5 -> 1.7.13
[ivy:checkdepsupdate] junit#junit 4.11 -> 4.12
I think this might be what you are looking for.
Miscellaneous
At the risk of stating the obvious, by setting transitive=false you take upon yourself the job of managing the entire tree of dependencies. For simple projects that's fine but you're now discovering the downsides of this approach. Projects like Spring have deliberately split their deliverables into multiple jars to increase flexibility. It allows you to only download what you need and avoid the inclusion of one incredibly large monolithic spring jar.
I would recommend a couple of things to improve your ivy experience
Embrace ivy's management of transitive dependencies
Use dynamic revisions
Publish to a repository, in order to create a release record
Transitive dependencies and classpath management
In my ivy file I will generally only include the module that contains the class I'm using, letting ivy take care of the other dependencies. I also use ivy configurations to group dependencies by function. My end goal is to use configurations to populate a java classpath, so some of my dependencies are required at compile time, others at run-time, finally testing frequently requires jars that would never be shipped with the release.
Example ivy file:
<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.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>
The SLFJ project is an excellent example of how one would use a standard programming API, but at runtime decide on a particular implementation based on the jars included on the classpath. In the above example I tell my build to use the log4j implementation jar at runtime, which will in turn pull down a compatible version of log4j and everything it depends on.
Finally, note how each configuration extends the other? This means that the test configuration will include the jars in both the compile and runtime configurations. Exactly what I'd need when running a unit test using junit.
This is my standard resolve task in ANT:
<target name="resolve" depends="install-ivy" 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>
The compile and test classpaths are now auto-populated and ready for use as references:
<target name="compile" depends="resolve" 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="test" depends="compile" 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"/>
</classpath>
..
..
</junit>
</target>
And the resolve task has created a record of each classpath maintained by the build.
Dynamic revisions
When you publish to an ivy repository you can specify the release type. This allows ivy to automatically determine the latest published version of a particular release type. By default two types of release are supported:
integration
release
The former corresponds to the Maven concept of Snapshot releases. Built binaries under the control of another team within your organisation, but not ready for release yet. The latter is of course for binaries that are fully approved and released, ideal for 3rd party dependencies.
The following is an example showing the theorical use of the two dynamic revisions:
<dependencies>
<!-- compile dependencies -->
<dependency org="myorg" name="teamA" rev="latest.integration" conf="compile->default"/>
<dependency org="myorg" name="teamB" rev="latest.integration" conf="compile->default"/>
<dependency org="myorg" name="teamC" rev="latest.integration" conf="compile->default"/>
<dependency org="org.slf4j" name="slf4j-api" rev="latest.release" conf="compile->default"/>
<!-- runtime dependencies -->
<dependency org="org.slf4j" name="slf4j-log4j12" rev="latest.release" conf="runtime->default"/>
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="latest.release" conf="test->default"/>
</dependencies>
So this would achieve your desire. Your build would automatically register new dependencies from 3rd party projects.
Publish to a repository, in order to create a release record
Time does not stand still nor does a project's dependency tree. Due to the high number of direct dependencies a modern Java program may have it can become very confusing to resolve the dependencies.
But.... How does one reproduce an older build? We might tag our source code, but how does one keep track of the dependencies at that point in time.
I decide to publish each release into a Maven repository:
how to publish 3rdparty artifacts with ivy and nexus
Here's a snippet
<target name="prepare" description="Generate POM">
<!-- Optional: Intermediate file containing resolved version numbers -->
<ivy:deliver deliverpattern="${build.dir}/ivy.xml" pubrevision="${publish.revision}" status="release"/>
<!-- Generate the Maven POM -->
<ivy:makepom ivyfile="${build.dir}/ivy.xml" pomfile="${build.dir}/donaldduck.pom"/>
</target>
<target name="publish" depends="init,prepare" description="Upload to Nexus">
<ivy:publish resolver="nexus-deploy" pubrevision="${publish.revision}" overwrite="true" publishivy="false" >
<artifacts pattern="${build.dir}/[artifact](-[classifier]).[ext]"/>
</ivy:publish>
</target>
Since I'm using Nexus I need to generate a Maven POM file for my module. Notice the use of the tasks deliver and makepom? The first will create a temp ivy file containing the resolved version numbers of each of my dependencies. This means the resultant POM file in Maven contains the real versions I used to build my code.
You could expand upon this idea and additionally publish the following alongside your released binary:
Javadocs jar
Source code jar
Ivy reports jar
Junit reports jar
etc
In my opinion the release repository should be the unchanging record for your release and important compliment to the source code repository. Indeed in a large corporate organisation, this kind of file based release record could outlive your source code repository technology (Clearcase -> Subversion -> Git -> ??).
Related
I am dealing with a project using using ANT to build the source code into a EAR. The project over couple of years has grown to a mammoth size, more than fifty modules, and not surprisingly it takes 2 hours to build the source code.
The obvious decision that I made was to migrate this to a modular build using IVY for dependency management between sub-modules of the EAR, so I build only the modules that have changed and then package a new EAR every time. I am stuck trying to find out a good way of figuring out dependencies between these sub-modules. Note that 3rd party dependencies were easy to crack. I simply move all that is in the lib folder of these sub-modules to IVY. But, the former is where I am stuck.
This is what they do:
Copy source from all modules to a directory
Compile "everything" into a global.jar
Add this global.jar to the classpath
Build every individual module thereafter
Now how do I figure out that for example Module C depends on module A and B and so A and B should go as ivy dependency in the former? One way could be to add one module at a time in eclipse and try building, and then eleminate failures because of missing classes; but there has to be a better way than this, I cannot imagine spending the next one month of life figuring that out :)
First of all you need to create ivy repository. I don't know how to do it. Try to find it on ivy site. After that you need to create build.xml and ivy.xml file.
Example build.xml which builds some product which depends on module1
<project name="ivy.test" xmlns:ivy="antlib:org.apache.ivy.ant">
<target name="resolve">
<!-- this line tells ivy to use ${ivy.settings.file} where are ivy repositories; more info http://ant.apache.org/ivy/history/latest-milestone/settings.html -->
<ivy:configure file="${ivy.settings.file}"/>
<!-- resolve ivy dependencies from ivy.xml and put them in ivy cache dir -->
<ivy:resolve file="${build.dir}/ivy.xml" transitive="false" />
<!-- finally copy resolved dependencies to ${ivy.lib.dir} -->
<ivy:retrieve conf="default" pattern="${ivy.lib.dir}/[artifact].[ext]" />
</target>
</project>
Example ivy.xml used to resolve dependencies (will try to find module1 in one of ivy repository defined in ivysettings file)
<ivy-module version="2.0">
<info organisation="your.organisation" module="modulename">
<ivyauthor name="yourname" />
</info>
<configurations>
<conf name="default" description="default conf" />
</configurations>
<dependencies>
<dependency name="module1" org="your.organisation" rev="latest.release" conf="default->default" />
</dependencies>
</ivy-module>
Example ivy.xml used by module1 project (this project depends on module2 and module3; module1 is also published in repository)
<ivy-module version="2.0">
<info organisation="your.organisation" module="module1"> <!-- module name is used in dependency -->
<ivyauthor name="yourname"/>
</info>
<configurations>
<conf name="default" description="default configuration"/>
</configurations>
<publications>
<artifact name="module" ext="dll" type="dll" conf="default"/>
</publications>
<dependencies>
<dependency name="module2" org="your.organisation" rev="latest.release" conf="default->default" />
<dependency name="module3" org="your.organisation" rev="latest.release" conf="default->default" />
</dependencies>
</ivy-module>
I set up a project following the getting started guide. I have to use ivy. These are my dependencies:
<dependencies>
<dependency org="org.jboss.spec" name="jboss-javaee-all-6.0" rev="3.0.1.Final" conf="build->default" />
<dependency org="org.jboss.arquillian.junit" name="arquillian-junit-container" rev="1.0.3.Final" conf="test->default(*)" transitive="true"/>
<dependency org="org.jboss.arquillian.junit" name="arquillian-junit-core" rev="1.0.3.Final" conf="test->default(*)" transitive="true"/>
<dependency org="org.jboss.weld.arquillian.container" name="arquillian-weld-ee-embedded-1.1" rev="1.1.2.Final" conf="test->default(*)" />
<dependency org="org.jboss.weld" name="weld-core" rev="1.1.10.Final" conf="test->default(*)" />
<dependency org="org.slf4j" name="slf4j-log4j12" rev="1.6.4" conf="test->default(*)" />
</dependencies>
Problem: Although I map to "*", the deps are not resolved transitive. Do I have to add every single jar by hand, just because I am stuck with ivy? or am I missing something?
Clarification:
I use the mapping "myconf->default()" transitive="true".
I read this as follows: "take the default conf of the dependency and map it to "myconf". (): if the dependeny does not provide "default", use every conf it provides. and all this should be done transitive, meaning every sub-dependency will also be mapped.
But what I get is: just the jars specified, and a lot of CNFE when I run the test.
I read about arquillian-container poms that are referenced in maven projects and I am beginning to fear that there is no working "out of the box" dependency mapping mechanism for ivy and arquillian. I am happy Iif anyone can confirm this or provide a working (best: tested) dependency configuration that I can use. Thank you very much!
I'd recommend that your ivy file always declare a set of configurations. Configurations are the logical groupings of jars within your build.
The following example creates a configuration for the 3 classpaths used in a typical java build:
compile
runtime
test
(Note also the "extends" keyword)
<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.jboss.spec" name="jboss-javaee-all-6.0" rev="3.0.1.Final" conf="compile->default" />
<!-- test dependencies -->
<dependency org="org.jboss.arquillian.junit" name="arquillian-junit-container" rev="1.0.3.Final" conf="test->default"/>
<dependency org="org.jboss.arquillian.junit" name="arquillian-junit-core" rev="1.0.3.Final" conf="test->default"/>
<dependency org="org.jboss.weld.arquillian.container" name="arquillian-weld-ee-embedded-1.1" rev="1.1.2.Final" conf="test->default" />
<dependency org="org.jboss.weld" name="weld-core" rev="1.1.10.Final" conf="test->default" />
<dependency org="org.slf4j" name="slf4j-log4j12" rev="1.6.4" conf="test->default" />
</dependencies>
</ivy-module>
The configuration mappings will then map the local configuration to the remote one, as follows:
conf="compile->default"
The remote "default" configuration is normally all you need and will include the remote module's compilation dependencies. For a more detailed explanation of how Maven modules are translated I suggest reading the following answer:
How are maven scopes mapped to ivy configurations by ivy
Finally, your build file can use these configurations to create populated ANT classpaths:
<target name="init" description="Use ivy to resolve classpaths">
<ivy:resolve/>
<ivy:report todir='build/ivy' 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>
The "report" task is especially useful to document the versions of each jar on the classpath.
I have the following ivy file:
<configurations defaultconfmapping="buildtime">
<conf name="buildtime" visibility="private" description="Libraries needed only for compilation" />
<conf name="runtime" description="Libraries only needed at runtime" />
<conf name="test" description="Libraries only needed for testing" />
</configurations>
<dependencies>
<dependency org="net.java.dev" name="jvyaml" rev="0.2.1" conf="runtime" />
<dependency org="org.apache.solr" name="solr-core" rev="3.6.0" conf="runtime" />
</dependencies>
and I have an ant retrieve task that looks like this:
<target name="retrieve-all" depends="resolve">
<ivy:retrieve pattern="lib/[conf]/[artifact]-[revision].[ext]" conf="*" />
</target>
The weird thing is, that all the solr dependencies download into lib/runtime as I'd expect, but the jvyaml module does not! It 'resolves', but will not download into the lib/runtime directory unless I change the dependency declaration to:
<dependency org="net.java.dev" name="jvyaml" rev="0.2.1" conf="runtime->master" />
What is this master configuration and why is it needed to pull the jvyaml jar, but not solr?
Thanks
I would suggest restructuring your configurations as follows:
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo"/>
<configurations>
<conf name="compile" description="Libraries needed only for compilation" />
<conf name="runtime" description="Libraries only needed at runtime" extends="compile" />
<conf name="test" description="Libraries only needed for testing" extends="runtime" />
</configurations>
<dependencies>
<dependency org="net.java.dev" name="jvyaml" rev="0.2.1" conf="runtime->default" />
<dependency org="org.apache.solr" name="solr-core" rev="3.6.0" conf="runtime->default" />
</dependencies>
</ivy-module>
Important changes introduced:
Use the more standard "compile" configuration
Configuration inheritance using the "extends" attribute. Compile dependencies can then be automatically included in both the runtime and test configurations.
Use configuration mappings, for example: conf="runtime->default". This makes it obvious which local configuration is associated with which remote configuration.
Configuration mappings explained
Configurations are a powerful ivy feature. When ivy downloads Maven modules it performs an internal translation and assigns a standard set of configurations, listed in this answer:
How are maven scopes mapped to ivy configurations by ivy
When declaring a dependency it's a good idea to always make use of a configuration mapping, so that there is no doubt where the dependencies artifacts are assigned.
For example:
<dependency org="??" name="??" rev="??" conf="runtime->default" />
Here we're saying we want the remote module's default dependencies associated with our local runtime configuration.
In practice, there are only two remote configuration mappings you'll actually need:
default: The remote module's artifact and all it's runtime transitive dependencies
master: The remote module's artifact only (No transitive dependencies)
In conclusion, I think your problem was caused by the fact that the remote Maven module's "runtime" scope does not include Maven module's artifact, instead you were getting the non-existant transitive dependencies of the module jvyaml :-(
Some additional advice
I'd also suggest generating an ivy dependency management report, as follows:
<target name="init" description="Resolve dependencies and populate lib dir">
<ivy:resolve/>
<ivy:report todir="${build.dir}/ivy-report" graph="false"/>
<ivy:retrieve pattern="lib/[conf]/[artifact]-[revision].[ext]"/>
</target>
The report will help explain how each dependency ends up on different configurations. Also really useful for determining how transitive dependencies are being managed.
And finally, here's where the configuration inheritance pays off, creating ivy managed ANT classpaths:
<target name="init" description="Resolve dependencies and set classpaths">
<ivy:resolve/>
<ivy:report todir="${build.dir}/ivy-report" graph="false"/>
<ivy:cachepath pathid="compile.path" conf="compile"/>
<ivy:cachepath pathid="runtime.path" conf="runtime"/>
<ivy:cachepath pathid="test.path" conf="test"/>
</target>
Notice that the original solr-core is not retrieved either.
After your resolve, go the cache and check the ivy.xml files for both modules.
You will see that they publish their artifacts in conf=master only
<artifact name="jvyaml" type="jar" ext="jar" conf="master"/>
<artifact name="solr-core" type="jar" ext="jar" conf="master"/>
Which means, you have to do explicit configuration mapping to denote that your builtime configuration should evoke the 'master' configuration of your dependencies. (check configuration mapping).
HOWEVER, the dependencies of the solr-core, have the configuration mapping as you could see in the ivy.xml file:
<dependency org="org.apache.solr" name="solr-solrj" rev="3.6.0" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
I think it's the master(*) thingy.
What I usually do is in my own ivy.xml file when I declare dependencies I do the mapping:
<dependency org="net.java.dev" name="jvyaml" rev="0.2.1" conf="runtime->master" />
This one says the runtime be evoking the master configuration in the designated dependency.
You could do
conf="runtime,test->master"
as well
I'm writing a Java library for my project: I use Ivy for the dependencies management and the publishing of the JAR to my local repository.
When I update ivy.xml (i.e. to add a new external library), everything is OK: all artifacts are retrieved and used.
However, when I publish my library, the ivy-module xml generated by Ivy contains missing/wrong references (often to previous versions of the external libraries).
This is the target I have in my build.xml:
<target name="publish" depends="jar" description="Publish this project in the ivy repository">
<property name="revision" value="${version}" />
<ivy:publish artifactspattern="${jar.dir}/[artifact].[ext]" resolver="projects" pubrevision="${revision}" status="release" update="true" overwrite="true" />
<echo message="project ${ant.project.name} released with version ${revision}" />
</target>
And this is my ivy.xml:
<ivy-module version="2.0">
<info organisation="xyz" module="zyx"/>
<configurations defaultconfmapping="*->*,!javadoc,!sources" />
<dependencies>
<dependency org="ch.qos.logback" name="logback-classic" rev="0.9.28" />
<dependency org="commons-lang" name="commons-lang" rev="2.5"/>
<dependency org="commons-io" name="commons-io" rev="2.0"/>
<dependency org="org.simpleframework" name="simple-xml" rev="2.4.1">
<exclude module="stax"/>
<exclude module="stax-api"/>
</dependency>
<dependency name="AlmaUtils" rev="1.3.10"/>
<!-- Reflections -->
<dependency org="org.reflections" name="reflections" rev="0.9.5-RC2">
<exclude module="logback-classic"/>
</dependency>
<!-- Bouncycastle cryptography -->
<dependency org="org.bouncycastle" name="bcprov-ext-jdk16" rev="1.45"/>
<dependency org="jdom" name="jdom" rev="1.1">
<exclude module="xerces"/>
<exclude module="xalan"/>
</dependency>
<!-- Scripting -->
<dependency name="js-engine" rev="1.0"/>
<dependency org="rhino" name="js" rev="1.7R2"/>
<!-- JGA -->
<dependency name="jga" rev="0.8.1"/>
</dependencies>
Perhaps you should add the following target into your build.
<target name="clean-all" depends="clean" description="Purge ivy cache">
<ivy:cleancache/>
</target>
This will wipe the slate clean and ensure that your build is completely clean.
Ivy is basically an optimized downloader, however, sometimes it can make incorrect caching decisions when you upgrade the version of a complex tree of dependencies. Maven builds are also affected by this problem when the local repository is very large.
At our company, we use a base ant file that is included by everyone to do their builds. It contains the things we want to define globally and uniform, like build-test, test-coverage, build-release, publish on ivy, etc.
I would like to enforce that in the ivy resolve that is done for creating a release build, libraries that have test (integration) status are rejected. Basically, that for a release build, you can only use release-class libraries.
However, I cannot find a way to enforce this in the ivy resolve ant task (not in the ivy.xml file).
Does anybody have an idea on how to accomplish this?
Option 1
Strictly speaking you have two sets of resolved libraries, so this could be solved by having two ivy files. One listing dependencies on the latest integration revisions the other the latest release versions.
The build.xml file would then have two resolution targets, controlled by a release property
<target name="resolve-int" unless="release.build">
<ivy:resolve file="ivy-int.xml"/>
</target>
<target name="resolve-rel" if="release.build">
<ivy:resolve file="ivy-rel.xml"/>
</target>
<target name="resolve" depends="resolve-int,resolve-rel"/>
Option 2
Use a property to determine the desired dynamic revision:
ivy.xml
<ivy-module version="2.0">
<info organisation="com.myspotontheweb" module="demo"/>
<dependencies>
<dependency org="commons-lang" name="commons-lang" rev="${dynamic.revision}"/>
</dependencies>
</ivy-module>
build.xml
The property dynamic.revision has a default value of latest.integration
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="demo-ivy" default="resolve">
<property name="dynamic.revision" value="latest.integration"/>
<target name="resolve">
<ivy:resolve/>
</target>
..
</project>
A release build would then override this value, possibly from the command-line as follows:
ant -Ddynamic.revision=latest.release