I have a client and a server that I'm building with Ant. The client relies on the server being built first before it can build. I have all of the required libraries except the server ear file bundled in the client.
I created 3 ant files build.xml
, build-client.xml
, and build-server.xml
. (Click on any of the file names to see them) The files build-client.xml
and build-server.xml
are included in build.xml
so that it can run targets from them.
The problem I'm having is that the basedir
variable changes from build file to build file. So if I run targets in build-client.xml
from build.xml
the basedir variable is relative to build.xml
.
How do I change the basedir variable multiple times within an Ant script?
Also, looking at these build files, do you see a better way of doing what I'm trying to do? Right now I'm thinking that I have to have both the client war and server ear in the same location before I can make the final bundled ear. My idea about this may be flawed though because these scripts seem unnecessarily complex.
The ant documentation for <import>
task gives you information about how to accomplish this.
Resolving files against the imported file
Suppose your main build file called importing.xml imports a build file imported.xml, located anywhere on the file system, and imported.xml reads a set of properties from imported.properties:
<!-- importing.xml -->
<project name="importing" basedir="." default="...">
<import file="${path_to_imported}/imported.xml"/>
</project>
<!-- imported.xml -->
<project name="imported" basedir="." default="...">
<property file="imported.properties"/>
</project>
This snippet however will resolve imported.properties against the basedir
of importing.xml, because the basedir
of imported.xml is ignored by Ant. The right way to use imported.properties is:
<!-- imported.xml -->
<project name="imported" basedir="." default="...">
<dirname property="imported.basedir" file="${ant.file.imported}"/>
<property file="${imported.basedir}/imported.properties"/>
</project>
As explained above ${ant.file.imported}
stores the path of the build script, that defines the project called imported, (in short it stores the path to imported.xml) and <dirname>
takes its directory. This technique also allows imported.xml to be used as a standalone file (without being imported in other project).
Basically, you can't really use the ${basedir}
variable, nor the basedir="./../GrahamsProjClient"
attribute in your project tag, but instead you can construct it:
<!-- build-client.xml -->
<project name="GPClient" default="dist" >
<dirname property="client.root.dir" file="${ant.file.GPClient}"/>
<property name="real.basedir" value="${client.root.dir}/../GrahamsProjClient"/>
<!-- Then from then on, replace ${basedir} with ${real.basedir} -->
...
</project>
You can do the same for the build-server.xml, the only thing to watch for is that the project name is featured in the ${ant.file.[project name]}
file attribute for <dirname />
.
My normal rule is not to use <ant>
or <subant>
in a normal build process because it breaks dependency checking. We had a developer break up a build.xml into seven separate build files, and due to the constant calling of <ant>
tasks to do stuff in other build files, he was executing the same target up to 14 times. And, then he wondered why his build was taking so long. Stitching the seven build files back together into a single build.xml
and using the depends
parameter of <target>
shortened the build to less than two minutes.
However, what you have in this case are really two separate projects and one build.xml
that you're using to call those two separate projects. In this case, you are better off using <ant>
and <subant>
calls than <import>
.
${basedir}
._compile_
target in your server build.Subant is more powerful, but trickier to implement. With Subant, you can have it search for the build.xml files. Most of the time using <ant>
is just easier and does what you want.
What I'd really recommend is using Ivy to handle the dependency issues. Not only can Ivy handle the server dependency in your client, but it can also handle all third party jar dependencies. No more storing jarfiles in your projects. When you store jar files in a project, you lose information about their actual version and their history. You see a commons-io.jar
in your project, and you have no idea what version it was or even if it's the official `commons-io.jar, or one of your developers munged it as some point.
The problem is that Ivy takes a bit of work to implement. You need to use an Ivy repository manager like Nexus, Artifactory, or Archiva. (Actually, these are Maven repository managers, but Ivy works quite nicely with them.)
Then, you need to import the ivy.jar
into your project and get the ivysettings.xml
file to point to your Maven Ivy repository server.
If you use Subversion as your version control system, you can do the following:
ivy.tasks.xml
.svn:externals
to import this project. build.xml
, you need to do two things:
<project>
entity.<import>
task to import the Ivy XML file that has everything setup.The advantage is that changing your Ivy project will automatically change all projects that interact with Ivy. For example, if you change the URL of the Ivy server, or you need to redefine the Ivy cache directories.
One you do that, you simple create an ivy.xml
file that defines your dependencies, and use <ivy:cachepath>
and <ivy:retrieve>
to retrieve the third party jars you need. This would include that server jar your client needs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With