buildframework/helium/tools/common/common.antlib.xml
author Bob Rosenberg <bob.rosenberg@nokia.com>
Thu, 16 Sep 2010 17:45:27 +0100
changeset 657 5720fe8b820c
parent 628 7c4a911dc066
child 645 b8d81fa19e7d
permissions -rw-r--r--
joinsysdef wasn't properly translating the namespace prefix for "replace".

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
============================================================================ 
Name        : common.antlib.xml 
Part of     : Helium 

Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
All rights reserved.
This component and the accompanying materials are made available
under the terms of the License "Eclipse Public License v1.0"
which accompanies this distribution, and is available
at the URL "http://www.eclipse.org/legal/epl-v10.html".

Initial Contributors:
Nokia Corporation - initial contribution.

Contributors:

Description:

============================================================================
-->
<!--* @package framework -->
<antlib xmlns:au="org.apache.ant.antunit" xmlns:hlm="http://www.nokia.com/helium">
    
    
    <!-- Macro to execute bldmake command. To be removed if not used. -->
    <macrodef name="bldmakeBldfilesMacro" uri="http://www.nokia.com/helium">
        <attribute name="dir"/>
        <sequential>
            <exec executable="${build.drive}/epoc32/tools/bldmake.bat" dir="@{dir}" failonerror="${failonerror}">
                <arg value="bldfiles"/>
                <arg value="-k"/>
            </exec>
        </sequential>
    </macrodef>


    <!-- Macro to execute abld command. Once used in rombuild.ant.xml. -->
    <macrodef name="abldMacro" uri="http://www.nokia.com/helium">
        <attribute name="dir"/>
        <attribute name="command"/>
        <attribute name="platform"/>
        <sequential>
            <exec executable="@{dir}/abld.bat" dir="@{dir}" failonerror="${failonerror}">
                <arg value="@{command}"/>
                <arg value="@{platform}"/>
                <arg value="-k"/>
            </exec>
        </sequential>
    </macrodef>

    <!-- This macro generate a file that contains a list of path from a path structure.

        <hlm:pathToFileListMacro file="output.lst">
            <path>
                <pathelement path="${helium.dir}"/>
            </path>
        </hlm:pathToFileListMacro> 
    -->
    <scriptdef name="pathToFileListMacro" language="beanshell" uri="http://www.nokia.com/helium">
        <attribute name="file"/>
        <element name="path" type="path"/>
        <![CDATA[
import java.io.FileWriter;
FileWriter out = new FileWriter(attributes.get("file"));

paths = elements.get("path");
for (int i = 0 ; i < paths.size() ; i++) {
    String[] files = paths.get(i).list();
    for (int j = 0; j < files.length ; j++) {
        out.write(files[j] + "\n");
    }
}
out.close();
    ]]>   
    </scriptdef>

    
   
    <!-- This Macro is a wrapper to command line tool.
    
    Currently supported command line tools are the configuration tool and sbs tool.
    Usage: 
    * name - name of the tool
    * toolvarset - reference id for variables to be passed to the tool.
        
        <hlm:toolMacro name="configuration">
            <hlm:toolvarset refid="cnftool.conf.50"/>
        </hlm:toolMacro>
    -->
    <scriptdef name="toolMacro" language="beanshell" uri="http://www.nokia.com/helium">
        <element name="toolvarset" classname="com.nokia.helium.core.ant.types.VariableSet"/>
        <attribute name="name"/>
        <![CDATA[
import com.nokia.ant.util.ToolsProcess;
import com.nokia.tools.*;
import com.nokia.helium.core.ant.types.VariableSet;
import org.apache.tools.ant.types.Reference;

Reference ref;
java.lang.String toolName = attributes.get("name");
confTool =  ToolsProcess.getTool(toolName);
varSets = elements.get("toolvarset");
for (i = 0; i < varSets.size(); ++i) {
    try {
        varSet = (VariableSet)varSets.get(i);
        if (varSet.isReference()) {
            varSet = varSet.getRefid().getReferencedObject(project);
        }
        confTool.execute(varSet,project);
    }
    catch (BuildException e) {
        self.log("Error: " + e);
        //self.log("Tool Argument Validation failure");
        throw e;
    }
}
    ]]>
    </scriptdef>
    
    <!-- ConE tool macro to run the command for each argument -->
    <scriptdef name="conEToolMacro" language="beanshell" uri="http://www.nokia.com/helium">
        <element name="arg" classname="com.nokia.helium.core.ant.types.VariableImpl"/>
        <attribute name="name"/>
        <![CDATA[
import com.nokia.ant.util.ToolsProcess;
import com.nokia.tools.*;
import com.nokia.helium.core.ant.types.VariableImpl;

conETool =  ToolsProcess.getTool("ConE");
vars = elements.get("arg");
for (i = 0; i < vars.size(); ++i) {
    var = (VariableImpl)vars.get(i);
    conETool.storeVariables(var.getName(), var.getValue());
}
conETool.execute(project);
    ]]>
    </scriptdef>
    
    
    <!-- This macro will read the line from file which matches the regexp -->
    <scriptdef name="grepMacro" language="beanshell" uri="http://www.nokia.com/helium">
        <attribute name="filename"/>
        <attribute name="output"/>
        <attribute name="regexp"/>
        <![CDATA[
import java.util.*;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

if (attributes.get("filename") == null ||  attributes.get("output") == null || attributes.get("regexp") == null) {
    throw new BuildException("Filename/output/regexp attribute is not set for readLineMacro");
}
    
String search = attributes.get("regexp");
String output = "";
pattern = Pattern.compile(search);

BufferedReader input =  new BufferedReader(new FileReader(attributes.get("filename")));
try {
    String line = null;
    Matcher match = null;
    while (( line = input.readLine()) != null) {
        match = pattern.matcher(line);
        if (match.find()) {
            //self.log("Line containg " + search + " is = " + line + " returned vaue = " + match.group(1));
            int checkIndex = output.indexOf(match.group(1), 0);
            if (checkIndex == -1 ) {
                output = output + match.group(1) + ",";
            }
        }
    }
}
catch (IOException ex) {
    ex.printStackTrace();
}
project.setNewProperty(attributes.get("output") , output);

        ]]>
    </scriptdef>

    
    <!-- This task allow to dump the content of a text file to the shell. -->
    <scriptdef name="echoFileMacro" language="beanshell" uri="http://www.nokia.com/helium">
        <attribute name="file"/>
        <![CDATA[        
//Open the file for reading
try {
    java.io.BufferedReader in = new java.io.BufferedReader(new java.io.FileReader(attributes.get("file")));
    while ((thisLine = in.readLine()) != null) {
        self.log(thisLine);
    }
}
catch (java.io.IOException e) {
    self.log("Error: " + e);
    throw e;
}        
        ]]>
    </scriptdef>
    
    
    <!-- This task create the herder information in the symbian log file -->
    <scriptdef name="symbianLogHeaderMacro" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="config"/>
        <attribute name="command"/>
        <attribute name="dir"/>
import log2xml
log2xml.symbian_log_header(self, attributes.get('config'), attributes.get('command'), attributes.get('dir'))
    </scriptdef>

    
    <!-- This task create the footer information in the symbian log file -->
    <scriptdef name="symbianLogFooterMacro" language="jython" uri="http://www.nokia.com/helium">
import log2xml
log2xml.symbian_log_footer(self)
    </scriptdef>

    
    <!-- A generic assert macro similar to AntUnit "assertTrue". -->
    <macrodef name="assert" uri="http://www.nokia.com/helium">
        <attribute name="message" default="Value is not true."/>
        <element name="condition" implicit="yes"/>
        <sequential>
            <if>
                <isset property="hlm.enable.asserts"/>
                <then>
                    <au:assertTrue message="@{message}">
                        <condition/>
                    </au:assertTrue>
                </then>
                <else>
                    <trycatch property="assert.try">
                        <try>
                            <au:assertTrue message="@{message}">
                                <condition/>
                            </au:assertTrue>
                        </try>
                        <catch>
                            <echo message="Warning: @{message}"/>
                            <hlm:hlmassertmessage assertName="hlm:assert" message="Warning: @{message}"/>
                        </catch>
                    </trycatch>
                </else>
            </if>
        </sequential>
    </macrodef>
    
    
    <!-- A generic assert macro similar to AntUnit "assertFileExists". -->
    <macrodef name="assertFileExists" uri="http://www.nokia.com/helium">
        <attribute name="file"/>
        <attribute name="message" default="@{file} does not exists."/>
        <sequential>
            <if>
                <isset property="hlm.enable.asserts"/>
                <then>
                    <au:assertFileExists file="@{file}" message="Warning: @{message}"/>
                </then>
                <else>
                    <trycatch property="assert.try">
                        <try>
                            <au:assertFileExists file="@{file}" message="Warning: @{message}"/>                                
                        </try>
                        <catch>
                            <echo message="Warning: @{message}"/>
                            <hlm:hlmassertmessage assertName="hlm:assertFileExists" message="Warning: @{message}"/>
                        </catch>
                    </trycatch>
                </else>
            </if>
        </sequential>
    </macrodef>

    
    <!-- A generic assert macro similar to AntUnit "assertPropertySet". -->
    <macrodef name="assertPropertySet" uri="http://www.nokia.com/helium">
        <attribute name="property"/>
        <attribute name="message" default="@{property} is not set."/>
        <sequential>
            <if>
                <isset property="hlm.enable.asserts"/>
                <then>
                    <au:assertPropertySet name="@{property}" message="@{message}"/>
                </then>
                <else>
                    <trycatch property="assert.try">
                        <try>
                            <au:assertPropertySet name="@{property}" message="@{message}"/>
                        </try>
                        <catch>
                            <echo message="Warning: @{message}"/>
                            <hlm:hlmassertmessage assertName="hlm:assertPropertySet" message="Warning: @{message}"/>
                        </catch>
                    </trycatch>
                </else>
            </if>
        </sequential>
    </macrodef>

    
    <!-- Script definition to collect target dependencies -->
    <scriptdef name="dependencies" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="target"/>
        <attribute name="format"/>
parsedTargets = []
targetList = []

def collectTargetDependencies(targetName, indent):    
    targetObject = project.getTargets().get(targetName)
    if targetObject is None :
        print  "Target '" + targetName + "' not found."   
    else :
        dependenciesEnum = targetObject.getDependencies()
        while dependenciesEnum.hasMoreElements():
            dependency = dependenciesEnum.nextElement()
            if dependency not in parsedTargets:
                collectTargetDependencies(dependency, indent + 1)
                parsedTargets.append(dependency)
                targetList.append((dependency, indent))

target = str(attributes.get('target'))

collectTargetDependencies(target, 1)
targetList.append((target, 0))
    
format = str(attributes.get('format'))

if format == 'nested':    
    for target, indent in targetList:        
        indentString = ''.join(['   ' for x in range(indent)])
        print indentString + str(target)
elif format == 'executable':
    print "Top level targets:\n"
    print ''.join([str(target) + ' ' for target, indent in targetList[:-1] if indent == 1])
    print "\n\nAll targets in sequence:\n"
    print ''.join([str(target) + ' ' for target, indent in targetList[:-1]])
    </scriptdef>
    
    
    <!-- This new task allows to save a reference to a file. -->
    <scriptdef name="referenceToFileMacro" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="refid"/>
        <attribute name="output"/>
    <![CDATA[
from java.io import FileWriter

refid = str(attributes.get("refid"))
output = str(attributes.get("output"))

if refid == None:
    raise Exception("'refid' attribute must be defined!")    
if output == None:
    raise Exception("'output' attribute must be defined!")
    
self.log("Creating %s using %s" % (output, refid))

reference = project.getReference(refid)
if reference == None:
    raise Exception("Unknown reference '%s'" % refid)
output = FileWriter(output)
i = reference.iterator()
while i.hasNext():
    path = i.next().toString()
    self.log(path)
    output.write(path + "\n")
output.close()
    ]]></scriptdef>
    
    
    <!-- Reads password from .netrc file for a specific type of service. -->
    <scriptdef name="netrcPasswordMacro" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="output-prop"/>
        <attribute name="result-prop"/>
        <attribute name="type"/>
        <![CDATA[
import netrc
import os
result = "0"
try:
    netrc_file = netrc.netrc()
    self.log("Type: %s" % str(attributes.get("type")))
    netrc_info = netrc_file.authenticators(str(attributes.get("type")))
    if netrc_info == None:
        raise Exception("No entry found for Type: %s" % str(attributes.get("type")))
    (n_username, n_account, n_password) = netrc_info
    if attributes.get('output-prop') != None:
        project.setProperty(str(attributes.get('output-prop')), str(n_password))
except Exception, e:
    result = "-1"
    print "Warning: %s" % e
if attributes.get('result-prop') != None:
    project.setProperty(str(attributes.get('result-prop')), str(result))
        ]]>
    </scriptdef>

    
    <!-- Reads user name from .netrc file for a specific type of service. -->
    <scriptdef name="netrcUsernameMacro" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="output-prop"/>
        <attribute name="result-prop"/>
        <attribute name="type"/>
        <![CDATA[
import netrc
import os
result = "0"
try:
    netrc_file = netrc.netrc()
    self.log("Type: %s" % str(attributes.get("type")))
    netrc_info = netrc_file.authenticators(str(attributes.get("type")))
    if netrc_info == None:
        raise Exception("No entry found for Type: %s" % str(attributes.get("type")))
    (n_username, n_account, n_password) = netrc_info
    if attributes.get('output-prop') != None:
        project.setProperty(str(attributes.get('output-prop')), str(n_username))
except Exception, e:
    result = "-1"
    print "Warning: %s" % e
if attributes.get('result-prop') != None:
    project.setProperty(str(attributes.get('result-prop')), str(result))
        ]]>
    </scriptdef>
     
    
    <!-- Check availability of synergy. -->   
    <scriptdef  name="ccmAvailableMacro" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="resultproperty"/>
        <![CDATA[
import nokia.nokiaccm
import logging
import ccm.extra

logging.basicConfig(level=logging.INFO)

session = None
result = "0"
cache = None
if project.getProperty("ccm.cache.xml")is not None:
    cache = str(project.getProperty("ccm.cache.xml"))
try:
    database = project.getProperty('ccm.database')
    
    if project.getProperty('ccm.user.login') == None :
        raise Exception("'ccm.user.login' property is not defined")
        
    username = project.getProperty('ccm.user.login')
    
    if project.getProperty('ccm.user.password') == None :
        raise Exception("'ccm.user.password' property is not defined")
        
    password = project.getProperty('ccm.user.password')
    
    
    engine = project.getProperty('ccm.engine.host')
    dbpath = project.getProperty('ccm.database.path')
    provider = ccm.extra.CachedSessionProvider(opener=nokia.nokiaccm.open_session, cache=cache)
    if database != None:
        session = provider.get(username, password, database=database, reuse=False)
    else:
        session = provider.get(username, password, engine, dbpath, reuse=False)
    session.close()
    provider.close()
except Exception, e:
    print "ERROR: %s" % e
    if str(e).find("access denied") != -1:
        result = "-2"
    else:
        result = "-1"
self.log("Result: %s" % attributes.get('resultproperty'))
project.setProperty(str(attributes.get('resultproperty')), str(result))
        ]]>
    </scriptdef>

    
    <macrodef name="signalMacro" uri="http://www.nokia.com/helium">
        <attribute name="logfile"/>
        <attribute name="phase" default=""/>
        <attribute name="signal.input" />
        <attribute name="skip.count" default="false" />
        <attribute name="result" default="not-set"/>
        <sequential>
            <var name="signal.errors.total" value="" unset="true"/>
            <if>
                <isfalse value="@{skip.count}" />
                <then>
                    <hlm:metadataCountSeverity severity="error" 
                        log="@{logfile}"
                        database="${metadata.dbfile}" property="signal.errors.total"/>
                </then>
                <else>
                    <if>
                        <equals arg1="@{result}" arg2="not-set"/>
                        <then>
                            <fail message="The result attribute is missing for signalMacro." />
                        </then>
                    </if>
                    <var name="signal.errors.total" value="@{result}" />
                </else>
            </if>
            <hlm:generateBuildStatus output-dir="${build.signal.status.dir}/" 
                file="@{logfile}" />

            <!-- signal for errors -->
            <var name="internal.signal.macro.log.basename" value="" unset="true"/> 
            <basename property="internal.signal.macro.log.basename" file="@{logfile}" />
            <hlm:signal name="@{signal.input}" result="${signal.errors.total}"
                message="Found ${signal.errors.total} errors under @{logfile}.">
                <signalNotifierInput>
                    <signalInput refid="@{signal.input}" />
                    <notifierInput>
                        <fileset casesensitive="false" dir="${build.signal.status.dir}"
                            includes="${internal.signal.macro.log.basename}.status.html" />
                    </notifierInput>
                </signalNotifierInput>
            </hlm:signal>
        </sequential>
    </macrodef>

    
    <!-- A simple test macro -->
    <macrodef name="fooMacro" uri="http://www.nokia.com/helium">
        <sequential>
            <echo>foo</echo>
            <runtarget target="hello"/>
        </sequential>
    </macrodef>
    
    
    <!-- Asserts that two XML resources are logically equal.
    
    This looks at the XML content rather than just diffing the strings. -->
    <scriptdef name="assertXmlEqual" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="control"/>
        <attribute name="test"/>
        <attribute name="failonerror"/>
        <![CDATA[
import logging

import java.io
import org.custommonkey.xmlunit

control_id = str(attributes.get('control'))
test_id = str(attributes.get('test'))

control_resource = project.getReference(control_id)
test_resource = project.getReference(test_id)
control_reader = java.io.InputStreamReader(control_resource.getInputStream())
test_reader = java.io.InputStreamReader(test_resource.getInputStream())

org.custommonkey.xmlunit.XMLUnit.setIgnoreWhitespace(True)
org.custommonkey.xmlunit.XMLUnit.setIgnoreComments(True)
diff = org.custommonkey.xmlunit.Diff(control_reader, test_reader)
detailed_diff = org.custommonkey.xmlunit.DetailedDiff(diff)
differences = detailed_diff.getAllDifferences()
print 'Differences total = ' + str(differences.size())
for diff in differences.toArray():
    print logging.warning(diff.toString())

try:
    control_reader = java.io.InputStreamReader(control_resource.getInputStream())
    test_reader = java.io.InputStreamReader(test_resource.getInputStream())
    org.custommonkey.xmlunit.XMLAssert.assertXMLEqual(control_reader, test_reader)
    print 'XML is similar'
except Exception, e:
    print 'XML is NOT similar!'
    failonerror = str(attributes.get('failonerror'))
    if failonerror == 'true' or failonerror == 'None':
        raise e
        ]]>
    </scriptdef>
    
    
    <!-- unsubst task, will unsubst a given drive. -->
    <scriptdef name="unsubst" language="jython" uri="http://www.nokia.com/helium">
        <attribute name="drive"/>
        <attribute name="failonerror"/>
""" internal.codescanner.drive """
import os
if os.sep == '\\':
    import fileutils
    self.setTaskName('unsubst')
    drive = attributes.get('drive')
    failonerror = attributes.get('failonerror')
    if (failonerror == None or str(failonerror) == "true"):
        failonerror = True
    else:
        failonerror = False
    if drive == None or len(str(drive)) != 2:
        if failonerror:
            raise Exception("'drive' attribute is missing or invalid: " + str(drive))
        else:
            self.log("Drive doesn't need unsubsting: " + str(drive))
    else:
        drive = str(drive)
        try:
            self.log(str("Unsubsting %s..." % drive))
            fileutils.unsubst(drive)
        except Exception, e:
            if failonerror:
                raise e
            else:
                self.log(str("Error: %s" % e))
    </scriptdef>
    
    
    <!-- Touches the files in the paths given. -->
    <scriptdef name="touch" language="jython" uri="http://www.nokia.com/helium">
        <element name="path" type="path"/>
        <![CDATA[
import fileutils
for id in range(elements.get("path").size()):
    iterator = elements.get("path").get(int(id)).iterator()
    while iterator.hasNext():
        path = str(iterator.next())
        fileutils.touch(path)
        ]]>
    </scriptdef>
    
    <!--
        The fileBackupMacro enables the user to take the backup of any file(s).
        By default it will back up log files from srcdir to destdir.
        e.g
        <pre>
            <hlm:fileBackupMacro srcDir="${compile.log.dir}" destDir="${compile.log.dir}/backuplogs" regExp="(.*?_compile.log)$"/>
            <hlm:fileBackupMacro file="${compile.log.dir}/${build.id}.clean.cmaker.log"/>
        </pre>
    -->
    <macrodef name="fileBackupMacro" uri="http://www.nokia.com/helium">
        <attribute name="regExp" default=""/>
        <attribute name="file" default=""/>
        <attribute name="srcDir" default=""/>
        <attribute name="destDir" default=""/>
        <sequential>
            <!--Check is it required to do failback up or need to take backup log files
                from specified source dir to destination dir -->
            <if>
                <and>
                    <not>
                        <length string="@{file}" trim="true" length="0" when="greater"/>
                    </not>
                    <not>
                        <length string="@{srcDir}" trim="true" length="0" when="greater"/>
                    </not>
                </and>
                <then>
                    <fail message="'fileBackupMacro' requires 'file' or 'srcdir' attributes set"/>
                </then>
            </if>
            
            <!-- Set the backup format for taking back up -->
            <var name="files.backup.format" unset="true"/>
            <tstamp>
                <format property="files.backup.format" pattern="ddMMyyyyHHmmssSS" unit="millisecond"/>
            </tstamp>
            
            <!-- Set dest dir if not set -->
            <var name="files.dest.dir" unset="true"/>
            <if>
                <length string="@{destDir}" trim="true" length="0" when="greater"/>
                <then>
                    <property name="files.dest.dir" value="@{destDir}"/>
                </then>
                <else>
                    <property name="files.dest.dir" value="@{srcDir}"/>
                </else>
            </if>
            
            <!-- Set dest dir if not set -->
            <var name="files.regular.exp" unset="true"/>
            <if>
                <length string="@{regExp}" trim="true" length="0" when="greater"/>
                <then>
                    <property name="files.regular.exp" value="@{regExp}"/>
                </then>
                <else>
                    <property name="files.regular.exp" value="^(.*?\.log)$"/>
                </else>
            </if>
            
            <!-- To validate the unittest cases.-->
            <script language="beanshell"> <![CDATA[
                backupFormat = project.getProperty("files.backup.format"); 
                project.setProperty("file.backup.format",backupFormat);
                ]]>
            </script>
                 
            <!-- Check need to do file back up -->
            <if>
                <and>
                    <length string="@{file}" trim="true" length="0" when="greater"/>
                    <available file="@{file}"/>
                </and>
                <then>
                    <basename property="file.name" file="@{file}"/>
                    <dirname property="file.dir.path" file="@{file}"/>
                    <copy file="@{file}" tofile="${file.dir.path}/${file.name}.${files.backup.format}.bak" overwrite="true" failonerror="${failonerror}"/>
                </then>
            </if>
            
            <!-- Check need to do mutliple files back up -->
            <if>
                <and>
                    <length string="@{srcDir}" trim="true" length="0" when="greater"/>
                    <available file="@{srcDir}" type="dir"/>
                </and>
                <then>
                    <mkdir dir="${files.dest.dir}"/>
                    <copy todir="${files.dest.dir}" overwrite="true" failonerror="${failonerror}">
                        <regexpmapper from="${files.regular.exp}" to="\1.${files.backup.format}.bak" casesensitive="false"/>
                        <fileset dir="@{srcDir}"/>
                    </copy>
                </then>
            </if>
        </sequential>
    </macrodef>
    
    <!-- Macro to generate an Ant database file for Helium. 
        
    @scope private
    -->
    <macrodef name="databaseMacro" uri="http://www.nokia.com/helium">
        <attribute name="file"/>
        <attribute name="scope"/>
        <sequential>
            <mkdir dir="${basedir}/build"/>
            <hlm:database output="@{file}" scope="@{scope}">
                <fileset dir="${helium.dir}">
                    <include name="config/signaling_config_default.ant.xml"/>
                    <include name="config/stages_config_default.ant.xml"/>
                    <include name="config/metadata_filter_config_default.ant.xml"/>
                    <include name="tools/**/*.antlib.xml"/>
                </fileset>
            </hlm:database>
        </sequential>
    </macrodef>
    
</antlib>