tags:

views:

174

answers:

3

My question is similar to avoiding-re-building-prerequisites-in-ant, except that my need doesn't involve created objects, but processes invoked, so the solutions discussed there won't work for me. At least I think so - but I'm new to ant.

My situation is that I'm writing a set of ant targets, and I need the lets-call-it setup target to be executed once and only once, no matter which target is invoked. Here's a greatly simplified example:


    <?xml version="1.0"?>
    <project name="Ant_Test" basedir=".">
      <target name="setup">
        <echo message="In setup" />
      </target>
      <target name="do.several" depends="setup">
        <echo message="In do.several, calling do.first" />
        <antcall target="do.first" />
        <echo message="In do.several, calling do.second" />
        <antcall target="do.second" />
      </target>
      <target name="do.first" depends="setup">
        <echo message="In do.first" />
      </target>
      <target name="do.second" depends="setup">
        <echo message="In do.second" />
      </target>
    </project>

I need setup to be invoked exactly once, regardless of whether do.several, do.first, or do.second are invoked. With my naive attempt above, invoking do.several results in three calls to setup.

I've thought of setting a property (let's call it setup.has.been.invoked), and using that to conditionally invoke setup from within each target, but it appears that property setting is limited to the scope it's done in, so if in setup, I set setup.has.been.invoked to true, that value only exists within setup.

What am I missing? Is there a section of the tutorials or online documentation I've skipped? Any pointers or hints?

+2  A: 

You should remove the antcalls and add do.first and do.second as dependencies of do.several:

<target name="do.several" depends="setup, do.first, do.second">
</target>

This will make sure, that setup is only called once:

setup:
     [echo] In setup

do.first:
     [echo] In do.first

do.second:
     [echo] In do.second

do.several:

BUILD SUCCESSFUL
Total time: 0 seconds

Documentation says why a property set in setup does not work with antcall:

The called target(s) are run in a new project; be aware that this means properties, references, etc. set by called targets will not persist back to the calling project.

Peter Lang
Fantastic, thanks!
CPerkins
+1  A: 

See the difference, between include and import in the Ant manual. Also use macrodefs.

I've slightly adapted your example, you'll need several files: build.xml, common.xml and macrodef_project_setup.xml

build.xml

<?xml version="1.0"?>
<project name="Ant_Test" basedir="." default="init">

<import file="common.xml"/>

<!-- This target overridden by the one in common.xml -->
<target name="common.init"/>

<target name="setup" depends="init"/>

<target name="do.several" depends="common.setup">
    <echo message="In do.several, calling do.first" />
    <antcall target="do.first" />
    <echo message="In do.several, calling do.second" />
    <antcall target="do.second" />
</target>

<target name="do.first" depends="common.setup">
    <echo message="In do.first" />
</target>
<target name="do.second" depends="common.setup">
    <echo message="In do.second" />
</target>

</project>

common.xml

<?xml version="1.0"?>
<project name="common">

<target name="init">
    <project_setup option="Stack.Over.Flow"/>
</target>

<target name="setup">
</target>

<import file="macrodef_project_setup.xml"/>

</project>

macrodef

<?xml version="1.0"?>
<project name="project_setup" basedir=".">

<macrodef name="project_setup">
        <attribute name="option" default=""/>
        <sequential>

            <!-- some process -->
            <echo>THIS IS MY SETUP OPTION: @{option}</echo>

        </sequential>
</macrodef>

</project>

Output:

ant -p
Buildfile: build.xml
Main targets:

Other targets:

 common.setup
 do.first
 do.second
 do.several
 init
 setup
Default target: init

Default target is now init.

ant
Buildfile: build.xml

Ant_Test.init:
     [echo] In setup initialization
     [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow

But you could still use ant setup.

ant setup
Buildfile: build.xml

Ant_Test.init:
     [echo] In setup initialization 
     [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow

Run it with do.several.

ant do.several
Buildfile: build.xml

Ant_Test.init:
     [echo] In setup initialization 
     [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow

Ant_Test.do.several:
     [echo] In do.several, calling do.first

Ant_Test.do.first:
     [echo] In do.first

     [echo] In do.several, calling do.second

Ant_Test.do.second:
     [echo] In do.second
Thanks, Dean. That's very impressive. I will indeed look at include, import, and macrodef in more detail. Thanks.
CPerkins
+1  A: 

An alternative to the answers you've already received is to create a custom task container that makes sure it does not repeat an action. I have such a custom task in my personal Antlib, but there's a whole load of other junk in there that you probably don't want, so maybe you could just copy the source and add it to your own project. It looks something like this:

import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
import org.apache.tools.ant.BuildException;
import java.util.List;
import java.util.LinkedList;

/**
 * Task container to ensure that some set of actions is only performed
 * once per build.  On completion of the actions a property is set that
 * prevents subsequent executions.
 */
public class Once extends Task implements TaskContainer
{
    private final List<Task> tasks = new LinkedList<Task>();

    private String property;

    /**
     * The name of the property to consult in order to determine whether
     * the nested tasks should be performed.  This is a required attribute.
     */
    public void setProperty(String property)
    {
        this.property = property;
    }

    public void addTask(Task task)
    {
        tasks.add(task);
    }     

    @Override
    public void execute() throws BuildException
    {
        if (property == null || property.length() == 0)
        {
            throw new BuildException("Property name must be specified.");
        }
        // If no value is specified, the tasks are performed if the property
        // is set to any value.  If a value is specified, the tasks are only
        // performed if the property matches that value.
        if (getProject().getProperty(property) == null)
        {
            for (Task task : tasks)
            {
                task.perform();
            }
            getProject().setProperty(property, "done");
        }
    }
}
Dan Dyer
Thanks, Dan. I wasn't aware that a custom TaskContainer was a possibility. It's clear that I have more reading to do.
CPerkins
Great work Dan! Cant wait to try out uncommons with Ivy, Hudson and especially using the maven-ant tasks - makes build, deploy, test much easier. Thanks.