Create a Union and Macrodef-style element with dynamic content at runtime in Ant

856 Views Asked by At

I've a build script built in Ant which has a macrodef that takes a few default parameters, target, root and the like, and then an optional two, extrasrc-f and extrasrc-c. After they've come in, I like to do a uptodate check on all relevant resources, then only do a build if the target is out of date.

What I have at the moment,

<?xml version="1.0" encoding="UTF-8"?>
<project name="Custom build" default="default">

    <taskdef resource="net/sf/antcontrib/antlib.xml"
        classpath="C:/dev/ant/ant-contrib/ant-contrib-1.0b3.jar"/>

    <macrodef name="checkuptodate">
        <attribute name="target" />
        <element name="resource" />
        <sequential>
            <condition property="needbuild">
                <and>
                    <resourcecount when="greater" count="0"> <resource /> </resourcecount>
                    <not>
                        <uptodate targetfile="@{target}">
                            <srcresources> <resource /> </srcresources>
                        </uptodate>
                    </not>
                </and>
            </condition>
        </sequential>
    </macrodef>

    <macrodef name="projbuild">
        <attribute name="root" />
        <attribute name="target" />

        <element name="extrasrc-f" optional="true" />
        <element name="extrasrc-c" optional="true" />
        <sequential>
            <local name="needbuild" />
            <checkuptodate target="@{root}/bin/@{target}">
                <resource>
                    <union>
                        <extrasrc-f />
                        <fileset dir="@{root}/src" includes="**/*.java" />
                    </union>
                </resource>
            </checkuptodate>

            <if>
                <istrue value="${needbuild}" />
                <then>
                    <javac
                        srcdir="@{root}/src"
                        destdir="@{root}/bin"
                        includeantruntime="false"
                    >
                        <extrasrc-c />
                    </javac>
                </then>
            </if>

        </sequential>
    </macrodef>

    <target name="default">

        <projbuild root="." target="EntryPoint.class">
            <extrasrc-f>
                <fileset dir="Proj2/src" includes="**/*.java" />
                <fileset dir="Proj3/src" includes="**/*.java" />
            </extrasrc-f>
            <extrasrc-c>
                <classpath location="Proj2/src" />
                <classpath location="Proj3/src" />
            </extrasrc-c>
        </projbuild>

    </target>

</project>

But as you can see, at this point in time, for me it's inefficient, to do what I want, I've to create and pass in at least one fileset, and multiple classpaths. What I'd really like to do is just pass in a list of directories, then create the extrasrc-f and extrasrc-c elements on the fly from that information, but for the life of me, I've no idea how I'm able to do that.

I've read up plenty about many of Ant and Ant-Contrib funky classes, but I haven't read anything that would allow me to do something like this, which I do find odd, because to me it looks an obvious situation.

Am I approaching this in a very wrong way, or is there something I'm missing? If I'm really misusing Ant, I'd love pointers in the right direction about how to do this properly, create a catchall, template build in a macrodef (or target, if that's the only way to do it) which tests multiple source files against one file that gets built, while also passing in extra class or library paths too, preferably in one single list.

1

There are 1 best solutions below

2
On

Perhaps you can use a couple of <scriptdef> tasks to help break up those macros.

First, one that takes a comma-separated list of directories and generates the <union> from them. You supply the refid you want to use to refer to the union as the id attribute. There are optional includes and excludes.

<scriptdef name="dirs2union" language="javascript">
    <attribute name="dirs" />
    <attribute name="id" />
    <attribute name="includes" />
    <attribute name="excludes" />
    <![CDATA[
      var dirs = attributes.get( "dirs" ).split( "," );
      var includes = attributes.get( "includes" );
      var excludes = attributes.get( "excludes" );

      var union = project.createDataType( "union" );
      project.addReference( attributes.get( "id" ), union );

      for ( var i = 0; i < dirs.length; i++ ) {
          var fs = project.createDataType( "fileset" );
          fs.setDir( new java.io.File( dirs[i] ) );
          if ( includes )
              fs.setIncludes( includes );
          if ( excludes )
              fs.setExcludes( excludes );

          union.add( fs );
      }
    ]]>
</scriptdef>

The second - very similar - script does the equivalent for path generation:

<scriptdef name="dirs2path" language="javascript">
    <attribute name="dirs" />
    <attribute name="id" />
    <![CDATA[
      var dirs = attributes.get( "dirs" ).split( "," );

      var path = project.createDataType( "path" );
      project.addReference( attributes.get( "id" ), path );

      for ( var i = 0; i < dirs.length; i++ ) {
          var pe = project.createDataType( "path" );
          pe.setLocation( new java.io.File( dirs[i] ) );
          path.add( pe );
      }
    ]]>
</scriptdef>

An example use might then be something like:

<property name="dirs" value="Proj2/src,Proj3/src" />

<dirs2union dirs="${dirs}" id="my.union" includes="**/*.java" />
<dirs2path  dirs="${dirs}" id="my.path" />

... later (e.g.) ...

<union refid="my.union" />
<classpath refid="my.path" />

You could then modify your macros to either take the dirs attribute and generate the union and classpath internally, or perhaps generate these once elsewhere and just pass in the references.

I've not attempted to include the @{root} directories in this illustration, but it should be possible to adapt the above for that.