views:

372

answers:

1

I'm going to post a question that I've seen variants of elsewhere, but where I've not quite seen the answer I came up with. I'll subsequently post my answer.

In order to modularize my build script using macros, I would like to put both the updatetask and the task I want to execute if it is not up to date, in the same macro.
How do I do this - utilizing the script tag is ok - if the only unique attribute being passed in is a path containing backslashes, and I need to be able pass different values to the script tag on different invocations. I need to avoid any issues that might happen with string literals when a backslash is involved. Using the unique attribute I need to work around the ant immutable property behavior, and work around the ant pattern that typically uses 2 targets to handle uptodate processing decisions and need to work around the javascript handling of backslashes in string literals.

+1  A: 

If you find this tip useful, and use it, please give credit in your code to Canova, email [email protected] Also, please note I'm working with ant 1.7. With local scoping in Ant 1.8 there are some additional options so that immutability is not such a big challenge, but some of these other tips will still be helpful.

First, regarding the issue of optionally executing a task based on the results of uptodate within a macro - which means you don't need 2 targets. To do this, use the condition tag. The <or> tag will cause it to only execute the second condition if the first condition fails. The <scriptcondition> tag allows to use javascript to execute other ant tasks. Here's an example (@ tags indicate macrodef attributes):

<condition property="whatever" value="false">
  <or>
    <uptodate>
      <srcfiles dir="@{srcdir}" includes="@{srcincludes}" excludes="@{srcexcludes}"/>
      <mapper><chainedmapper>
        <flattenmapper/><!-- use any mappers you need to match source to target files-->
        <globmapper from="*.jxw" to="@{targetdir}\*W.java"/>
      </chainedmapper></mapper>
    </uptodate>
    <!-- w/ java 1.6 or later, you get a rhino javascript interpreter included w/ java-->
    <scriptcondition language="javascript" value="true">
      self.setValue(true);
      echo = project.createTask("echo");
      myArg1="@{myArg1}";
      myArg2="@{myArg2}";
      // need to create a reference from a classpath refid
      myReference = new org.apache.tools.ant.types.Reference(project,"@{my.classpath.id.string}");
      // get a handle to the ant java task, which we will use to execute a java program
      javaTask = project.createTask("java");
      javaTask.setFork(true);
      javaTask.setFailonerror(true);
      javaTask.setClassname("com.mycompany.mypackage.MySpecialClass");
      javaTask.setClasspathRef(myReference);
      javaTask.createArg().setValue(myArg1);
      javaTask.createArg().setValue(myArg2);

      //output the command line to standard out, for reference
      echo.setMessage(javaTask.getCommandLine());
      echo.perform();
      javaTask.perform();
    </scriptcondition>
  </or>
</condition>

Now, if you are like me, you may want to do some processing with the attributes that are your macrodef inputs and produce some derived values which can be referenced in your macrodef script. If you are processing simply involves concatentation of attributes and strings, you can do that in the block were you specify your attributes be specifying a second set of attributes with a default setting that contains the concatentation steps. However, if you need to do something that you can't plug into an attribute default, you will need to put it in a property. Because properties are immutable, you need to take some extra steps to give your property unique names. The tstamp comes in handy to help with this. Typically some combination of the parameters passed to your macro will be unique, but if this unique combination icludes backslashes, you'll want to derive a secondary unique identifier using the tstamp tag so that you don't run into backslash issues in your javascript when you want to use these derived properties. Here's how to create your unique properties which you can easily reference in your script.

<macrodef name="public.macro.example">
  <attribute name="srcpath"/>
    <sequential>
      <tstamp prefix="@{srcpath}"><format pattern="ddhhmmssSSS" property="time"/></tstamp>
      <private.macro.example srcpath="@{srcpath}" propertyPrefix="prop${@{srcpath}.time}"/>
    </sequential>
</macrodef>

<macrodef name="private.macro.example">
  <attribute name="srcpath"/>
  <attribute name="prefix"/>
  <sequential>
    <pathconvert property="@{prefix}.src"/>
    <!-- now you can do special things with ${@{prefix}.src}, even in javascript -->
    <script language="javascript">
      self.setValue(true);
      echo = project.createTask("echo");
      myPrefix="@{prefix}";
      mySpecialPropertyKey=myPrefix+".src";
      //if your special property contains backslashes or other special js characters
      // you need to use project.getProperty instead of a string literal to get the value
      mySpecialPropertyVal=project.getProperty(mySpecialPropertyKey);
      // do something with this derived value in javascript
      echo.setMessage("my special property = "+mySpecialPropertyVal);
      echo.perform();
    </script>
  </sequential>
</macrodef>

Before I came up with the above solution, I came up with a hack style of solution for overriding an ant property with a new value. While you may find this useful to override a property, because I'm making a direct call to an ant class, there is a risk that this might not work the same in future versions of ant. Because this seems like more of a hack, my intent is to use the 2 macrodef approach listed above rather than this approach when possible. Note this particular variation doesn't support backslash characters because the attributes are directly referenced in a javascript string literal. I used this simple variation initially to create my unique prefix, which prevented the need to a 2 macrodef approach to working around property immutability. You could however adapt this macrodef to use a 2nd macrodef and unique prefix in order to get the "@{value}" into the javascript using a project.getProperty command.

<macrodef name="public.canova.setproperty">
  <attribute name="name"/>
  <attribute name="value"/>
  <sequential>
    <script language="javascript">
      project.setUserProperty("@{name}","@{value}");
    </script>
  <sequential>
</macrodef>

At first blush, some of this may seem a bit complex, but if you do your macrodef work right and build component style macros (i.e. don't put spaghetti code in your macro), your ant scripts should actually become shorter, easier to understand and easier to maintain and the logs will be easier to follow. Tip - only use javascript when necessary and when you use it, it is preferrable to use it within a macro so that it is encapsulated and hidden away from the main 'logic' of your ant script, thereby aiding in the self documenting and readable nature of your main 'logic'. Use comments when things are not obvious.

Canova
One problem with this technique. @{value} is inlined as a string literal in the script. So if value contains a backslash or a double-quote, the result is not as expected.
gawi