Defining a task -- or macro -- in ant-script - ant

We have a large build.xml file with some tasks repeated verbatim in multiple targets -- such as a long-winded <echo>, which updates the log-file(s) with contents of an object:
<echo file="foo.log" message="${run.name} completed with status ${run.status}. Errors: ${run.errors}, warnings: ${run.warnings}, processed ${run.processed}"/>
Can this be turned into something like <logrun file="file.log" run=${run}"/> -- in the XML? That is, without us writing Java-code to implement the new logrun-task?

The short answer is yes, using the Ant <macrodef> task.
Something like this:
<macrodef name="logrun">
<attribute name="file"/>
<attribute name="name"/>
<attribute name="status"/>
<attribute name="errors"/>
<attribute name="warnings"/>
<attribute name="processed"/>
<sequential>
<echo file="#{file}"
message="#{name} completed with status #{status}. Errors: #{errors}, warnings: #{warnings}, processed #{processed}${line.separator}"/>
</sequential>
</macrodef>
<logrun file="foo.log" name="foo.name" status="foo.status" errors="foo.errors" warnings="foo.warnings" processed="foo.processed" />
Notice the way named macro parameters are referenced in the "body" of the macro using #-prefix. I added a ${line.separator} at the end of the message parameter, so that the line written to the file is terminated. You might want to use append="true" in the echo task, in order that the file is not completely overwritten each time the macro is called.
A macro might cater for all your attribute edge cases, depends how complex they are.

Related

Optional attributes in a macro wrapping a Java Ant Task

Ant tasks implemented in Java, as opposed to XML Ant macros, have this peculiarity of offering a slightly different behavior for missing attributes.
In my case, I'm trying to wrap the <testng> Ant task, implemented in Java, with a macro. Specifically, I would like to expose most of the functionality offered by the TestNG ant task with some minor tweaks.
Among other similar attributes, timeOut seems a bit difficult to reproduce, since its omission behaves differently than specifying and empty string.
This is simplified version of my macro definition:
<macrodef name="my-wrapper">
<attribute name="timeOut" default=""/>
<element name="nested-elements" optional="true" implicit="true"/>
<sequential>
<testng timeOut="#{timeOut}">
<nested-elements/>
</testng>
</sequential>
</macrodef>
Which fails because Ant tries to convert the value to an integer:
Can't assign value '' to attribute timeout, reason: class java.lang.NumberFormatException with message 'For input string: ""'
I've been suggested to use <augment>, which seems to be the solution to this problem. However, I fail to understand how it should be used:
<macrodef name="my-wrapper">
<attribute name="timeOut" default=""/>
<element name="nested-elements" optional="true" implicit="true"/>
<sequential>
<augment unless:blank="timeOut" id="invocation" timeOut="#{timeOut}"/>
<testng id="invocation">
<nested-elements/>
</testng>
</sequential>
</macrodef>
The above fails because of a forward reference:
java.lang.IllegalStateException: Unknown reference "invocation"
Invering the order of <testng> and <augment> doesn't really work because the <testng> task starts executing before being augmented.
What I would need is a way to conditionally add an XML attribute to a task call. Is this possible only using Ant XML syntax?
In this situation, the simplest solution would just be to set the default for timeOut to a valid value. It expects a string that can be resolved as an integer, so try using -1 if you don't want there to be a max timeout.
<macrodef name="my-wrapper">
<attribute name="timeOut" default="-1"/>
<element name="nested-elements" optional="true" implicit="true"/>
<sequential>
<testng timeOut="#{timeOut}">
<nested-elements/>
</testng>
</sequential>
</macrodef>

Any way to check if an optional element in macrodef is provided?

is there any way to check if a given element X is passed to the macrodef. I have a case to decide if the element X should be required or optional. To achieve this I made the element optional for all the cases,
but I want to make validation in case the element is missing, if it’s allowed to be missing :-).
The macro is looking like this:
<macrodef name="test">
<attribute name="attribute1"/>
......
<attribute name="attributeN/>
<element name="X" optional="true/>
<element name="Y" optional="true/>
<sequential>
<local>
<!--here check if the element <X/> is passed -->
</local>
</sequential>
</macrodef>
<test attribute1="1", attributeN="N">
<!--Here do not provide element X. Only provide Y-->
<Y>
<nestedY1>Some text1</nestedY1>
<nestedY2>Some text2</nestedY2>
</Y>
</test>
The element X is looking just like element Y. I mean, in case it is present, it will contain another nested elements.
Maybe I am wrong in the way I understand this concept. I will try to give another example.
Currently the element X is mandatory and my task is to make it optional in some cases but mandatory in another cases. I want to be able to use the macro both ways, but I don’t know how to implement this task:
<macrodef name="test">
<attribute name="attribute1"/>
<element name="X"/>
<element name="MandatoryX" optional="true/>
<sequential>
<local>
<!--here check if the element <MandatoryX/> is passed and if Yes than make sure that element X is passed too-->
</local>
</sequential>
</macrodef>
<test attribute1="1">
<!--Here MandatoryX is missing and X can be missing too-->
</test>
or
<test attribute1="1">
<MandatoryX>In case MandatoryX is present, than element X must be present too</MandatoryX>
<X>Here X is mandatory</X>
</test>
I figured out a way of doing this. A bit kludgey, but works for me. The key is to use the <echoxml> task to write the macro element to a file, then read the file and look for some pattern in it. I write <stuff> and </stuff> around the macro element. When the macro element is not provided, the stuff element gets written out as simply <stuff />, and this can be searched for.
Note, I am also using antcontrib, hence the <if> block.
<macrodef name="processFiles">
<attribute name="workDir"/>
<attribute name="tempDir"/>
<element name="extra-deletes" optional="true"/>
<sequential>
<echoxml file="#{tempDir}/extra-deletes.xml"><stuff><extra-deletes/></stuff></echoxml>
<local name="extra-deletes-prop"/>
<loadfile property="extra-deletes-prop" srcfile="#{tempDir}/extra-deletes.xml"/>
<if>
<not><contains string="${extra-deletes-prop}" substring="<stuff />"/></not>
<then>
<delete>
<fileset dir="#{workDir}">
<extra-deletes/>
</fileset>
</delete>
</then>
</if>
</sequential>
</macrodef>
This macro would get called with some expression involving <filename .../> to identify which files to delete. (This is derived from a more complicated script, but basically there is some pattern of files to delete for every project and other projects have extra, project-specific delete patterns.)
So it would be called like this:
<processFiles workDir="..." tempDir="...">
<extra-deletes>
<or>
<filename name="..."/>
<filename regex="..."/>
</or>
</extra-deletes>
</processFiles>
... or in the case with no 'extra-deletes' to perform,
<processFiles workDir="..." tempDir="..."/>

ANT Build: Can the token itself be parsed from other values from within the property file?

Can the token itself be parsed from other values from within the property file?
Is it possible to evaluate the token key, without hardcoding the token? Can the token itself be parsed from other values from within the property file?
For example, if the properties file has the following tokens (test.properties):
module_no = 01
module_code = bb
title_01_aa = ABC
title_02_aa = DEF
title_03_aa = GHI
title_01_bb = JKL
title_02_bb = MNO
title_03_bb = PQR
Contents of build.xml
<?xml version="1.0" encoding="utf-8"?>
<project default="repl">
<property file="test.properties" />
<target name="repl">
<replace file="test.txt" token="module_title" value="title_${module_no}_${module_code}" />
</target>
</project>
Sample content with text:
Welcome to module_title.
The replace task will result in:
Welcome to title_01_bb.
How to achieve this instead?
Welcome to JKL.
This might be very basic, but please do guide me in the right direction. Thank you.
Nested property expansion does not work by default in Ant as described in the documentation:
Nesting of Braces
In its default configuration Ant will not try to balance braces in property expansions, it will only consume the text up to the first closing brace when creating a property name. I.e. when expanding something like ${a${b}} it will be translated into two parts:
the expansion of property a${b - likely nothing useful.
the literal text } resulting from the second closing brace
This means you can't use easily expand properties whose names are given by properties, but there are some workarounds for older versions of Ant. With Ant 1.8.0 and the the props Antlib you can configure Ant to use the NestedPropertyExpander defined there if you need such a feature.
If you check the workarounds link, one solution is to use a macrodef to copy the property:
<property file="test.properties" />
<target name="repl">
<gettitleprop name="titleprop" moduleno="${module_no}" modulecode="${module_code}" />
<replace file="test.txt" token="module_title" value="${titleprop}" />
</target>
<macrodef name="gettitleprop">
<attribute name="name"/>
<attribute name="moduleno"/>
<attribute name="modulecode"/>
<sequential>
<property name="#{name}" value="${title_#{moduleno}_#{modulecode}}"/>
</sequential>
</macrodef>

Referencing global Ant property after <local> is used

I can create a local property in Ant within a "block" scope.
But how can I now reference the global property within the same block?
<property name="myprop" value="global"/>
...
<sequential>
<local name="myprop"/>
<property name="myprop" value="local"/>
<echo message="my local prop is ${myprop}"/> //<-- this works fine
<echo message="my global prop is ????"/> //<-- HOW?
</sequential/>
No ant-contrib please.
I'd also like to keep this in Ant, not resorting to JS.
The only thing I could think of was "copying" the global property under a different name to be used in this block
<local name="myglobprop"/>
<property name"myglobprop" value="${myprop}"/>
<local name="myprop"/>
<property name="myprop" value="local"/>
<echo message="my global prop is ${myglobprop}"/>
But that is rather unsightly and seems redundant. I am really just looking for an out-of-scope property reference method.
Edit - use case
My attempt to coerce Ant to do delayed expansion:
Let's say I have a property whose value is a combination of several other properties.
<property name="mycmdline" value="${cmd}=${type}"/>
If ${cmd} and ${type} are known before the above property, all is great. However in my case, these values are not defined (no property set). These values only become known at later stage inside a build macrodef.
I have another simple macrodef that will perform delayed expansion:
<property name="mycmdline" value="${cmd}=${type}"/>
...
<macrodef name="property-expand">
<attribute name="name"/>
<attribute name="value"/>
<sequential>
<fail if="#{name}" message="Property #{name} is already set"/>
<property name="#{name}" value="#{value}"/>
</sequential>
</macrodef>
Finally my build macrodef would contain the following snippet:
<local name="cmd"/>
<local name="type"/>
<local name="mycmdline"/>
<property name="cmd" value="#{cmd}"/>
<property name="type" value="#{type}"/>
<property-expand name="mycmdline" value="${mycmdline}"/>
The last line is obviously wrong. I am creating a (now local) property called mycmdline with expanded value of global (now out of scope) ${mycmdline}. The macrodef for delayed expansion works great on it's own, but the problem is that the global ${mycmdline} is out of scope and cannot be referenced.
What I really wanted is:
<property name="mycmdline" value="${cmd}=${type}"/>
...
<!-- In macrodef now -->
<local name="mycmdline"/>
<property-expand name="mycmdline" value="GLOBAL:${mycmdline}"/>
Instead, what I have to do is:
<property name="unexpanded_mycmdline" value="${cmd}=${type}"/>
...
<!-- In macrodef now -->
<local name="mycmdline"/>
<property-expand name="mycmdline" value="${unexpanded_mycmdline}"/>
Why?
It may not look like a lot of difference, but it's about readability. unexpanded_mycmdline and mycmdline are now two different names, when trying to follow the way the value of property gets used through a script, it now makes a disconnected jump from one property name to another (not matter how similar the names may look). The whole unexpanded_ prefix looks out of place and doesn't fit with the rest of the naming conventions, unless I name all my global variables with some prefix, which doesn't make sense either.

Default element for Ant's macrodef?

I would like to create a macro as such:
<macrodef name="testing">
<element name="test" implicit="yes"/>
<sequential>
<test/>
</sequential>
</macrodef>
And then use it:
<testing>
<echo message="hello world"/>
</testing>
However, I would like to specify a default for the implicit element... something like:
<macrodef name="testing">
<element name="test" implicit="yes">
<echo message="hello world"/>
</element>
<sequential>
<test/>
</sequential>
</macrodef>
So I can then use it as such:
<testing/>
Except where I want to change the default element.
Is this possible without defining a task via a Java class? So far, I don't see any documentation that indicates how to do it, if so.
Update
I ended up resolving my particular issue by using refid for filesets (which is what I actually was trying to pull into an element). Using the refid, it was simple to just use a macrodef attribute, which CAN have a default value.
Another alternative would be to create a new base macro which uses the element, and then I could have kept my existing macro as using that one... but still, there is no real default mechanism for an element (which would be nice).
So, Simon gets the answer since he's correct! Thanks!
This is not possible based on the nested element element documentation for the macrodef task.
There is a Bugzilla issue open for exactly the functionality you describe, unfortunately it has been open since 2004.
if you define your macrodef as:
<macrodef name="testing">
<element name="additional" optional="true"/>
<sequential>
<echo message="hello"/>
<additional/>
</sequential>
</macrodef>
the following invocation:
<target name="testing-call">
<mylib:testing/>
<mylib:testing>
<additional>
<echo message="world!"/>
</additional>
</mylib:testing>
</target>
will output:
[echo] hello
[echo] hello
[echo] world!

Resources