Ant: The Definitive Guide, 2nd Edition
|
12.2. Extending the Task Class
Usually, you extend an Ant task class like org.apache.tools.ant.Task when you write custom tasks. Ant comes with a selection of task classes meant to be extended:
The Task class (i.e., org.apache.tools.ant.Task) is used for most of this chapter, though some samples will use MatchingTask. The methods of the Task class appear in Table 12-2.
12.2.1. The Task Life Cycle
Tasks go through a well-defined life cycle, and here are the specific stages:
12.2.2. Accessing the Project and Properties in Code
When you extend the Task class, you have access to a great deal of data about the project. The Task class inherits the getProject( ) method, which returns a Project object that holds such items as the project's name and properties. You can see selected methods of the Project class in Table 12-3. You can do nearly anything with these methods, from setting a project's default target and logging text to reading property values and setting property values. That's a typical way for custom tasks to perform their work: reading property values with the Project object's getProperty( ) method and setting property values with setProperty( ). After a property has been set, it can be accessed in the build file, letting the custom task communicate with the rest of the build file.
Letting a custom task interact with the rest of the build through the use of properties is an important part of creating custom tasks. Take a look at Example 12-3, which is the code for an Ant task that reports the name of the project using the ant.project.name property, and the current location in the build file with the getLocation( ) method. Example 12-3. Accessing projects and properties (ch12/projecttask/src/Project.java)
import org.apache.tools.ant.Task; public class Project extends Task { public void execute( ) { String name = getProject( ).getProperty("ant.project.name"); System.out.println("Welcome to project " + name + " at " + getLocation( )); } }
Example 12-4 shows the build file for this custom task. Example 12-4. Build file for accessing properties (ch12/projecttask/build.xml)
<?xml version="1.0"?> <project name="TheTask" basedir="." default="main"> <property name="src" location="src"/> <property name="output" location="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="project.jar"/> <project/> </target> <target name="jar" depends="compile"> <jar destfile="project.jar" basedir="${output}"/> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}"/> </target> </project> The results show that the build file reports the name of the project as set by the project element's name attribute, and the line location in the build file: %ant Buildfile: build.xml compile: [mkdir] Created dir: /home/steven/ant/ch12/projecttask/output [javac] Compiling 1 source file to /home/steven/ant/ch12/projecttask/output jar: [jar] Building jar: /home/steven/ant/ch12/projecttask/project.jar main: [project] Welcome to project TheTask at /home/steven/ant/ch12/projecttask/build.xml:9: BUILD SUCCESSFUL Total time: 3 seconds 12.2.3. Handling Attributes in Custom Tasks
If your custom task supports attributes, Ant will pass the value of the attribute to your code if you have a setter method, much as you'd use in a JavaBean. For example, if you have an attribute named language, define a method, e.g., public void setLanguage(String language). Ant will pass this method the string value (after performing any needed property expansion) of the language attribute. Strings are the most common attribute values, but you can ask Ant to perform conversions of attribute values to other data types based on the type of the argument in your setter method. Here are the possible data types and how they're handled:
Example 12-5 shows the code to handle a String attribute named language and displays the value assigned to this attribute in the build file. The setLanguage( ) method will be passed the attribute's value. Example 12-5. Accessing attributes (ch12/attributetask/src/Project.java)
import org.apache.tools.ant.Task; import org.apache.tools.ant.BuildException; public class Project extends Task { private String language; public void execute( ) throws BuildException { System.out.println("The language is " + language); } public void setLanguage(String language) { this.language = language; } }
The build file that builds the custom task in Example 12-5 and then uses it appears in Example 12-6. In this example, Ant builds the code for the new task, project, and uses that task, setting the language attribute to "English". The code for this task reads the value of the language attribute and displays it during the build. Example 12-6. Build file for accessing attributes (ch12/attributetask/build.xml)
<?xml version="1.0"?> <project basedir="." default="main"> <property name="src" value="src"/> <property name="output" value="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="Project.jar"/> <project language="English"/> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}"/> </target> <target name="jar" depends="compile"> <jar destfile="Project.jar" basedir="${output}"/> </target> </project>
Here's what the build output looks like: %ant Buildfile: build.xml compile: [javac] Compiling 1 source file to /home/steven/ant/ch12/attributetask/output jar: [jar] Building jar: /home/steven/ant/ch12/attributetask/Project.jar main: [project] The language is English BUILD SUCCESSFUL Total time: 4 seconds
12.2.4. Making Builds Fail
Want to make a build fail? Make your task code throw an org.apache.tools.ant.BuildException. For example, if your custom task supports a failonerror attribute, you might use code something like this: public void setFailonerror(boolean failOnError) { this.fail = failOnError; } public void execute( ) throws BuildException { if (fail) { if error... throw new BuildException("Attribute language is required"); } else { .... } }
Ant will display the text you pass to the BuildException constructor in the fail message. 12.2.5. Handling Nested Text
Ant tasks can support nested text, and custom tasks can support such text as well. Take a look at Example 12-7, which includes a project task that contains the nested text "No worries.". Example 12-7. Build file for accessing nested text (ch12/nestedtext/build.xml)
<?xml version="1.0"?> <project basedir="." default="main"> <property name="src" value="src"/> <property name="output" value="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="Project.jar"/> <project>No worries.</project> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}"/> </target> <target name="jar" depends="compile"> <jar destfile="Project.jar" basedir="${output}"/> </target> </project>
In your task's code, you can receive access to an element's nested text with the addText() method. The text will be passed to this method, and Example 12-8 shows how to retrieve that text and display it. Example 12-8. Accessing nested text (ch12/nestedtext/src/Project.java)
import org.apache.tools.ant.Task; public class Project extends Task { String text; public void addText(String text) { this.text = text; } public void execute( ) { System.out.println(text); } } Here's what you get when you run this build file and the custom task with the nested text "No worries." in Example 12-7: %ant Buildfile: build.xml compile: [mkdir] Created dir: /home/steven/ant/ch12/nestedtext/output [javac] Compiling 1 source file to /home/steven/ant/ch12/nestedtext/output jar: [jar] Building jar: /home/steven/ant/ch12/nestedtext/Project.jar main: [project] No worries. BUILD SUCCESSFUL Total time: 7 seconds The supporting code for the custom task recovered the nested text and, in this case, displayed it during the build.
12.2.6. Handling Nested Elements
Nested text is one thing, but what if you have nested elements in a custom task? For instance, assume that your custom task has nested elements named nested, as in Example 12-9, and suppose that these elements have an attribute named language. How can you recover the values of the language attributes? Example 12-9. Nested elements in a custom task (ch12/nestedelement/build.xml)
<?xml version="1.0"?> <project basedir="." default="main"> <property name="src" value="src"/> <property name="output" value="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="Project.jar"/> <project> <nested language="English"/> <nested language="German"/> </project> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}"/> </target> <target name="jar" depends="compile"> <jar destfile="Project.jar" basedir="${output}"/> </target> </project> In the code supporting this custom task, shown in Example 12-10, you need a class, Nested, representing the nested element, and you can use a method named createNested( ) to handle calls from Ant for each nested element. Each time createNested( ) is called, the code adds the new nested element to a Vector named nesteds. The language attribute of each nested element is passed to the setLanguage( ) method and can be recovered with the getLanguage( ) method. After the Vector is filled, the execute() method is called and the code iterates over the Vector, displaying the language attribute value for each nested element. Example 12-10. Handling nested elements (ch12/nestedelement/src/Project.java)
import java.util.Vector; import java.util.Iterator; import org.apache.tools.ant.Task; import org.apache.tools.ant.BuildException; public class Project extends Task { public void execute( ) { for (Iterator iterator = nesteds.iterator( ); iterator.hasNext( );){ Nested element = (Nested)iterator.next( ); System.out.println("The language is " + element.getLanguage( )); } } Vector nesteds = new Vector( ); public Nested createNested( ) { Nested nested = new Nested( ); nesteds.add(nested); return nested; } public class Nested { public Nested( ) {} String language; public void setLanguage(String language) { this.language= language; } public String getLanguage( ) { return language; } } }
Here's what the build file looks like when running. The support code handled the nested elements and recovered the value of the language attributes: %ant Buildfile: build.xml compile: [javac] Compiling 1 source file to /home/steven/ant/ch12/nestedelement/output jar: [jar] Building jar: /home/steven/ant/ch12/nestedelement/Project.jar main: [project] The language is English [project] The language is German BUILD SUCCESSFUL Total time: 3 seconds
12.2.7. Using Filesets
You can make your custom tasks support filesets with the right code. Example 12-11 shows a custom task, project, acting as a fileset with an include nested element. In this case, the custom project element will display all the .java files in and below the base directory. Example 12-11. Supporting filesets in a build file (ch12/fileset/src/Project.java)
<?xml version="1.0"?> <project basedir="." default="main"> <property name="src" value="src"/> <property name="output" value="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="Project.jar"/> <project dir="${basedir}"> <include name="**/*.java"/> </project> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}"/> </target> <target name="jar" depends="compile"> <jar destfile="Project.jar" basedir="${output}"/> </target> </project>
To handle filesets, you extend the MatchingTask class. In this example, the code that supports the custom task reads the value assigned to the dir attribute and uses the org.apache.tools.ant.DirectoryScanner class's getIncludedFiles( ) method to scan that directory. This method returns an array of filenames, which the code displays. All the support code appears in Example 12-12. Example 12-12. Supporting filesets (ch12/fileset/src/Project.java)
import java.io.File; import org.apache.tools.ant.Task; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.taskdefs.MatchingTask; public class Project extends MatchingTask { private File directory; public void setDir (File directory) { this.directory = directory; } public void execute( ) throws BuildException { DirectoryScanner directoryscanner = getDirectoryScanner(directory); String[] files = directoryscanner.getIncludedFiles( ); for (int loopIndex = 0; loopIndex < files.length; loopIndex++) { System.out.println(files[loopIndex]); } } }
Project.java is the only .java file in the project, and that's the file the custom project task picks up: C:\ant\ch12\fileset>ant Buildfile: build.xml compile: [javac] Compiling 1 source file to /home/steven/ant/ch12/fileset/output jar: [jar] Building jar: /home/steven/ant/ch12/fileset/Project.jar main: [project] src/Project.java BUILD SUCCESSFUL Total time: 4 seconds
Extending MatchingTask to support includes and excludes nested elements, you can make your task support filesets. 12.2.8. Running External Programs
Custom Ant tasks are often wrappers for existing programs. You can launch an external program from the support code for a custom task if you use the org.apache.tools.ant.taskdefs.Execute class. Example 12-13 shows how this works and launches Windows WordPad and opens the project's build file in it. Example 12-13. Executing external programs (ch12/executetask/src/Project.java)
import java.io.IOException; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.types.Commandline; public class Project extends Task { public void execute( ) { Commandline commandline = new Commandline( ); commandline.setExecutable("C:\\Program Files\\Windows NT\\Accessories\\wordpad. exe"); commandline.createArgument( ).setValue("C:\\ant\\ch12\\executetask\\build.xml"); Execute runner = new Execute( ); runner.setCommandline(commandline.getCommandline( )); try { runner.execute( ); } catch (IOException e) { System.out.println(e.getMessage( )); } } } In this case, the code creates an org.apache.tools.ant.types.Commandline object holding the path and name of the executable to launch, uses the Commandline object's createArgument( ).setValue method to specify the file to open, and uses the execute( ) method of the org.apache.tools.ant.taskdefs.Execute class to open WordPad. The build file for this custom task appears in Example 12-14. Example 12-14. Build file for executing external programs (ch12/executetask/build.xml)
<?xml version="1.0"?> <project basedir="." default="main"> <property name="src" value="src"/> <property name="output" value="output"/> <target name="main" depends="jar"> <taskdef name="project" classname="Project" classpath="Project.jar"/> <project/> </target> <target name="compile"> <mkdir dir="${output}"/> <javac srcdir="${src}" destdir="${output}" /> </target> <target name="jar" depends="compile"> <jar destfile="Project.jar" basedir="${output}"/> </target> </project> If you run this build file in Windows (after updating the hardcoded paths in the Java code as needed), it'll launch WordPad, opening build.xml. 12.2.9. Running Scripts
While discussing how to execute external programs, Ant includes an optional task named script that can run scripts such as those written in JavaScript. You need bsf.jar, from http://jakarta.apache.org/bsf/ (not the IBM version), in the Ant lib directory to run this task. You'll need one or more of these .jar files, depending on the scripting language you want to use:
The attributes for the script task appear in Table 12-4.
In script, you can access Ant tasks with the Name.createTask method, where Name is the project's name. For instance, Example 12-15 shows how to use the echo task from JavaScript to display numbers using a loop. Ant properties are available to your script's code, as in this case, where the message property's value is displayed.
Example 12-15. Build file for executing JavaScript (ch12/script/build.xml)
<project name="js" default="main" basedir="."> <property name="message" value="No worries."/> <target name="main"> <script language="javascript"> <![CDATA[ echo = js.createTask("echo"); main.addTask(echo); for (loopIndex = 1; loopIndex <= 10; loopIndex++) { echo.setMessage(loopIndex); echo.execute( ); } echo.setMessage(message); ]]> </script> </target> </project> Here's what this build file looks like at work, where JavaScript is executing the Ant echo task. Cool. %ant Buildfile: build.xml main: [echo] 1 [echo] 2 [echo] 3 [echo] 4 [echo] 5 [echo] 6 [echo] 7 [echo] 8 [echo] 9 [echo] 10 [echo] No worries. BUILD SUCCESSFUL Total time: 1 second Want to work with Ant types like filesets in script? Use the project object's createDataType( ) method. Here's an example that creates Java File objects from a fileset, all in JavaScript: importClass(java.io.File); fileset = project.createDataType("fileset"); fileset.setDir(new File(dir)); fileset.setIncludes(includes); directoryscanner = fileset.getDirectoryScanner(project); files = directoryscanner.getIncludedFiles( ); for (loopIndex=0; loopIndex < files.length; loopIndex++) { var filename = files[loopIndex]; var file = new File(fileset.getDir(project), filename); } |
|