Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linking Ant build files

Tags:

xml

ant

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.

like image 957
Graham Avatar asked Oct 09 '12 21:10

Graham


2 Answers

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 />.

like image 95
Patrice M. Avatar answered Sep 25 '22 02:09

Patrice M.


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>.

  • These calls won't interfere with ${basedir}.
  • These calls allow you to specify what properties and resources you want to include in these separate files. (Probable answer is none).
  • You don't have a problem with multiple targets sharing the same name. A compile target in your client build won't overlap the _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:

  • Create an Ivy project that includes the ivy.jar, and a XML file that sets everything up for you. I have one in Github you can look at. The XML file is called ivy.tasks.xml.
  • Then in your project, use svn:externals to import this project.
  • In your build.xml, you need to do two things:
    • Add the Ivy namespace in your <project> entity.
    • Use the <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.

like image 38
David W. Avatar answered Sep 23 '22 02:09

David W.