Using Ant with WebSphere Studio Application Developer -- Part 3 of 3

This article explains how you can create your own Java classes to add new Ant tasks to your custom build environment. In particular, it discusses how to add two Ant tasks to WebSphere Studio Application Developer -- ProjectBuild and GetJavacErrors.

Barry Searle (searle@ca.ibm.com), WebSphere Studio Application Developer Information Developer, IBM Toronto Lab

Photo: Barry SearleBarry Searle is the Migration Team Leader for WebSphere Studio Application Developer. He is a professional engineer who has worked at the IBM Toronto Lab for over ten years on various application development tools. Prior to that he had many years of industry experience developing command and control systems and leading complex communications development projects. You can reach Barry at searle@ca.ibm.com.



Ellen McKay (ecmckay@ca.ibm.com), WebSphere Studio Application Developer Information Developer, IBM Toronto Lab

Ellen McKay is an Information Developer for IBM Toronto Lab. She writes online help and publications for WebSphere Studio Application Developer. You can reach Ellen at ecmckay@ca.ibm.com.



21 March 2002

Introduction

Part 1 of this article explained how to run Ant both inside WebSphere® Studio Application Developer (hereafter called Application Developer) and outside it ("headless"). Part 2 explained how special Ant tasks are used for Application Developer production Ant builds of J2EE modules. This part will explain how to create you own AntTasks to provide specialized build functions.

With Part 1 and Part 2 of this article, you have a good set of production Ant build capabilities. However, there may be one or two special things that your build has to do. The "Why use Ant" section of Part 1 explained that one reason to use Ant is that it is written in JavaTM, and you can therefore write new Ant tasks in Java to extend the build capabilities; you just write a new Java program to perform the special operation that you need.

This article will show you how to add two Ant tasks to Application Developer -- ProjectBuild and GetJavacErrors.


Download files

The two download files are not part of Application Developer V4 or V5 Early Availability (EA) and are not officially supported. However, Application Developer V5 General Availability (GA) includes these features in the product and they are fully supported. If you have suggestions or encounter problems with the older V4 or V5 EA features, please e-mail Barry Searle . V5 GA is supported via the normal IBM support channel.

Download now .

com.ibm.ant.extras_V4.zip

The Version 4 download file ( com.ibm.ant.extras_V4.zip ) has been tested with Application Developer 4.0.2 and 4.0.3 and works as described, but will not work with future versions of the product (V5 or later). This code includes the setDebugInfo task from the article Non-Debug compilations in WebSphere Studio Application Developer .

com.ibm.ant.extras_V5EA.zip

The Version 5 Early Availability download file ( com.ibm.ant.extras_V5EA.zip ) has been tested with Application Developer 5.0 Early Availability and works as described. It will not work with future versions of the product (V5 GA or later) and it will not work with Version 4.x. The download code changes from Version 4 (described in this article) to Version 5 are:

  • HeadlessAntListener.java has been renamed to HeadlessAntLogger.java and now extends org.apache.tools.ant.DefaultLogger .
  • HeadlessAntRunner.java no longer creates a new HeadlessAntLogger( ) . Instead, it calls addBuildLogger("HeadlessAntLogger") .
  • HeadlessAntRunner.java no longer calls listener.setMessageOutputLevel(...) and super.processCommandLine(args) .

Important: If you download com.ibm.ant.extras_V5EA.zip , you must unzip it into your x:\Ws_installdir\ WSTOOLS\ECLIPSE \plugins directory, where it will create a com.ibm.ant.extras_5.0.0 subdirectory containing a runAnt.bat program and the various programs it requires. (The Version 4 code went into the x:\Ws_installdir\plugins directory.)

Application Developer V5 General Availability

Application Developer V5 GA contains the Headless Ant features and is fully supported. Do not use the V4 or V5 EA downloads. The RunAnt.bat file is now in the directory X:\WSAD5GA_INSTDIR\wstools\eclipse\plugins\com.ibm.etools.j2ee.ant_5.0.1 , and that directory also contains readme.htm . The Eclipse refresh task <eclipse.refreshLocal> has changed to <eclipse.refreshLocal resource="MyProject/MyFolder" depth="infinite"/> .


Overview of Part 1 of this article

Part 1 of this article explained:

  • What Ant is
  • Why it is often used
  • How to run Ant inside Application Developer
  • How to run Ant in a command-line window
  • How to start Application Developer "headless"
  • How to download and use the runAnt program, which contains the "headless" Ant support.

Overview of Part 2 of this article -- J2EE builds

Application Developer is an excellent tool for developing J2EE modules (EJB JARs, WARs, EARs, etc). During development, some of the deployment descriptor information is stored in ASCII XML files, but these files contain some information in a format convenient for interactive testing and debugging. As part of the Application Developer functions that create (export) stand-alone J2EE modules, this internally optimized deployment descriptor information is merged and changed into standard format. Part 2 includes a set of Application Developer Ant Tasks to perform these build and export functions using the "headless" operations described in Part 1.


Requirement for WorkspaceModifyOperation

If you browse the open source code for WebSphere Studio Workbench, you will discover that its project class ( IProject ) has a build(int type, IProgressMonitor monitor) method. Therefore you can create a new ProjectBuild Ant task for Application Developer in which you just specify the project name and build type (full, auto, or incremental). As we are interested in running production builds outside Application Developer, we cannot use the normal GUI ProgressMonitor and instead need to create a small AntConsoleProgressMonitor (described below) to log progress events. Then we can run our new ProjectBuild program, which works as expected except for one problem. Application Developer is an integrated development environment. Any operation that potentially changes the workspace, including any resource modification, needs to be put in a WorkspaceOperation wrapper. Then, events that typically occur as a result of workspace changes (firing resource deltas, autobuilds, etc.) are deferred until the outermost operation has successfully completed, instead of occurring haphazardly as intermediate changes occur.


ProjectBuildWorkspaceModifyOperation

The open source code for WebSphere Studio Workbench has a WorkspaceModifyOperation that is widely used to solve the preceding problem. It creates an IWorkspaceRunnable with a run method that calls your custom execute method to do the actual work. You can subclass this "cookie-cutter" framework code for your particular operation. We will create a ProjectBuildWorkspaceModifyOperation.java as shown below. The main areas of interest are the constructor and the project.build invocation inside the execute method.

package com.ibm.ant.extras;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.CoreException;

public class ProjectBuildWorkspaceModifyOperation extends
   WorkspaceModifyOperation
{
   private IProject project;
   private int buildTypeInt;
   public ProjectBuildWorkspaceModifyOperation(IProject proj,
      int buildType)
   {
      project = proj;
      buildTypeInt = buildType;
   }
   // Performs steps as a single logical workspace change.
   protected void execute(IProgressMonitor monitor) throws CoreException
   {
      String projectName = "unknown";
      try
      {  projectName = project.getDescription().getName();
         monitor.setTaskName("Building: " + projectName);
         project.build(buildTypeInt, monitor);
      } catch (CoreException e)
            {
            throw e;
      } finally
      {
            monitor.done();
      }
   }
}

Completed ProjectBuild

Having created the above ProjectBuildWorkspaceModifyOperation , most of the work involved in creating the ProjectBuild Ant task is just "boiler-plate" checking of parameters, etc. The actual work inside the try/catch statement below is four lines.

  • Construct a non-GUI AntConsoleProgressMonitor (discussed later)
  • Construct the worker ProjectBuildWorkspaceModifyOperation
  • Run that ProjectBuildWorkspaceModifyOperation
  • In case of errors, see whether or not to failonerror

A design decision that you need to make for every new Ant task is whether it needs to test and obey a failonerror parameter.

package com.ibm.ant.extras;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.IncrementalProjectBuilder;

public class ProjectBuild extends Task
{
   private IProject project = null;
   private String projectName;
   private String buildTypeString = "INCREMENTAL";
   private String failonerror = "true";
   private boolean failOnError = true;
   private int buildTypeInt =
      IncrementalProjectBuilder.INCREMENTAL_BUILD;

   public void execute() throws BuildException
   {  validateAttributes();
      project =  ResourcesPlugin.getWorkspace()
         .getRoot().getProject(projectName);
      if(project==null)
      {  displayError("ProjectBuild: " +projectName
             +" project==null");
         return;
      }
      if(!project.exists())
      {  displayError("ProjectBuild: " +projectName
            +" not found in Workspace.");
         return;
      }
      if(!project.isOpen())
      {  displayError("ProjectBuild: " +projectName
            +" is not open");
         return;
      }

   try
   { //non-GUI to display progress and handle cancel requests
      AntConsoleProgressMonitor monitor = new
                 AntConsoleProgressMonitor(this);
      monitor.setTaskName("Building: " + projectName);
      ProjectBuildWorkspaceModifyOperation op =
         new ProjectBuildWorkspaceModifyOperation(project, buildTypeInt);
      op.run(monitor);

   } catch (Exception e)
      {
      displayError("ProjectBuild: " +projectName
         +" Exception="+e.getMessage());
   }
}

protected void displayError(String msg) throws BuildException
{
   System.out.println(msg);
   if(failOnError)
      throw new BuildException(msg);
}
public void setProjectName(String name)
{  projectName = name;
}
public void setBuildType(String type)
{  buildTypeString = type;
}
public void setfailonerror(String str)
{  failonerror = str;
}
protected void validateAttributes() throws
BuildException
{  if (projectName == null)
   {  displayError("Must supply ProjectName");
      return;
   }
   if (buildTypeString.equalsIgnoreCase("INCREMENTAL")
      buildTypeInt =   IncrementalProjectBuilder.INCREMENTAL_BUILD;
   else if (buildTypeString.equalsIgnoreCase("FULL") )
      buildTypeInt = IncrementalProjectBuilder.FULL_BUILD;
   else if (buildTypeString.equalsIgnoreCase("AUTO"))
      buildTypeInt = IncrementalProjectBuilder.AUTO_BUILD;
   else
   {  displayError("Invalid BuildType="+buildTypeString
         +", must be INCREMENTAL or FULL or AUTO");
      return;
   }
   if(failonerror.equals("true"))
      failOnError = true;
   else if(failonerror.equals("false"))
      failOnError = false;
   else
      {  displayError("Invalid failonerror="+failonerror
              +", must be \"true\" or \"false\" ");
         return;
      }
   }
}

Specifying non-debug versus debug compilations

The ProjectBuild Ant task can have one other useful enhancement. The current Application Developer always generates debug javac compilations. Your build can specify DebugCompilation=false if you wish, and it will disable lineNumber , localVariable , and sourceFile debug code for the current Application Developer session (settings are not persistent to the next startup). Because this option is not available as part of the standard Application Developer, generating non-debug compilations is currently an unsupported feature (but one you may wish to try).

The following code is added into ProjectBuild as shipped in the download code.

private String debugcompilation = "";
private boolean debugCompilation;
public void setDebugCompilation(String str)
{  debugcompilation = str;
}
public void setDebugOptions(String str)
{
if(str==null || str.equals(""))
   return;

   Hashtable options = JavaCore.getJavaCore().getOptions();
   if(str.equalsIgnoreCase("true"))
   {  options.put("org.eclipse.jdt.core.compiler.debug.localVariable",
         "generate");
      options.put("org.eclipse.jdt.core.compiler.debug.lineNumber",
         "generate");
      options.put("org.eclipse.jdt.core.compiler.debug.sourceFile",
         "generate");
   }else if (str.equalsIgnoreCase("false"))
   {  options.put("org.eclipse.jdt.core.compiler.debug.localVariable",
         "do not generate");
      options.put("org.eclipse.jdt.core.compiler.debug.lineNumber",
         "do not generate");
      options.put("org.eclipse.jdt.core.compiler.debug.sourceFile",
         "do not generate");
   }
   JavaCore.setOptions(options);
}

AntConsoleProgressMonitor

The program above uses AntConsoleProgressMonitor , which is a simple non-GUI monitor that displays progress and handles cancellation requests. You can chose to display more or fewer messages to your console:

package com.ibm.ant.extras;
import org.apache.tools.ant.Task;
import org.eclipse.core.runtime.IProgressMonitor;
public class AntConsoleProgressMonitor implements IProgressMonitor
{
   private String taskInfo = "";
   private Task task;
   private String taskname = "unknown";

   public AntConsoleProgressMonitor(Task t)
   {  task = t;
      taskname = t.getTaskName();
   }
   public void beginTask(String name, int totalTime)
   {  taskInfo = "";
      taskname = name;
      task.log("beginTask "+name + " " + taskInfo + "...");
   }
   public void done()
   {  task.log("doneTask="+taskname);
      taskname = "unknown";
      }
      public void setTaskName(String name)
      {  taskname = name;
      }
      public void subTask(String arg0)
      {  task.log(taskname + " ... subtask: " + arg0    +"...");
      }
      public void displayMsg(String msg)
      {  task.log("Message="+msg);
      }
      public void setCurrentTaskInfo(String info)
      {  taskInfo = info;
         task.log("TaskInfo=" +info );
      }
      public void internalWorked(double arg0)
      {;}
       public void worked(int timework)
      {;}
      public boolean isCanceled()
      {  return false;
      }
      public void setCanceled(boolean arg0)
      {;}
   }

Summary of ProjectBuild Ant task

  1. Create a ProjectBuildWorkspaceModifyOperation class that invokes the build method on a project.
  2. Create a ProjectBuild class that invokes the ProjectBuildWorkspaceModifyOperation.
  3. Edit plugin.xml to add:
    <antTask name="projectBuild" class="com.ibm.ant.extras.ProjectBuild">
    </antTask>
  4. In your build.xml script, invoke your new projectBuild Ant task:
    <projectBuild ProjectName="myProject" DebugCompilation="false"
       BuildType="incremental" />

plugin.xml is in the downloadable code included with this example. build.xml is your Ant script.


GetJavacErrorCount

When a project is built in Application Developer, you may want to know if any javac errors occurred. Creating a new Ant task for this involves getting results from the Tasks view (which is where the error markers are listed), and returning data (the error count) back to the Ant task as a property value. Since this new Ant task does not modify any workspace resources, you do not need to put a wrapper around it, and the Ant task is relatively simple. The majority of the work is done in two lines inside the try section of the execute method. The key aspects are:

  • A getJavacErrorCount method that queries the Tasks View to count all javac error markers for a specified project.
  • An Ant task project property (default name= JavacErrorCount) that is set to return the error count back to the Ant build script.
package com.ibm.ant.extras;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaModelMarker;

public class GetJavacErrorCount extends Task
{
   private IProject project = null;
   private String projectName;
   private String propertyName = "JavacErrorCount";

   AntConsoleProgressMonitor monitor = null;
   int UNKNOWN_ERRORS = -1;

   public void execute() throws BuildException
   {
      int errorCount = -1;
      this.getProject().setUserProperty(
      propertyName, Integer.toString(errorCount));
      validateAttributes();
      project = ResourcesPlugin.getWorkspace()
         .getRoot().getProject(projectName);
         if(project==null) throw new BuildException("GetJavacErrorCount: "
            +projectName +" project==null");
         if(!project.exists()) throw new BuildException("GetJavacErrorCount: "
            +projectName + " not found in Workspace.");
         if(!project.isOpen()) throw new BuildException("GetJavacErrorCount: "
            +projectName +" is not open");
         try
         { //non-GUI to display progress and handle cancel requests
            AntConsoleProgressMonitor monitor =
               new AntConsoleProgressMonitor(this);
            monitor.setTaskName("GetJavacErrorCount: "
               +projectName);
            errorCount = getJavacErrorCount(project, monitor);

            monitor.displayMsg("Project " "+projectName+" "
               +propertyName+"="+errorCount );
            } catch (Exception e)
            {
               throw new BuildException(e);
            }
            this.getProject().setUserProperty(
               propertyName, Integer.toString(errorCount) );
         }

         // Returns number of reported errors,
         // -1 if errors can't be retrieved.
         private int getJavacErrorCount(IProject prj,
            AntConsoleProgressMonitor mntr)
         {
         project = ResourcesPlugin.getWorkspace().
            getRoot().getProject(projectName);
         if(project==null) throw new BuildException("ProjectBuild: "
            +projectName +" project==null");
         if(!project.exists()) throw new BuildException("ProjectBuild: "
            +projectName + " not found in Workspace.");
         if(!project.isOpen()) throw new BuildException("ProjectBuild:"
            +projectName +" is not open");
         monitor = mntr;
         try {
            IMarker[] markerList = prj.findMarkers(
            IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER,
               true, IResource.DEPTH_INFINITE);
            if (markerList == null || markerList.length == 0)
               return 0;

            IMarker marker = null;
            int numErrors = 0;
            for (int i = 0; i < markerList.length; i++)
            {
               marker = markerList[i];
               int severity = marker.getAttribute(IMarker.SEVERITY,
                  IMarker.SEVERITY_ERROR);
               // default severity = ERROR
               if (severity == IMarker.SEVERITY_ERROR)
               {
                  numErrors++;
               Integer lineNum = (Integer)
                  marker.getAttribute(IMarker.LINE_NUMBER);
               String resourceName = (String)
                  marker.getResource().getName();
               String message = (String)
                  marker.getAttribute(IMarker.MESSAGE);
               monitor.displayMsg(lineNum+" "
                  +resourceName+":"+message);
            }
         }
         return numErrors;
      } catch (CoreException e) {
      monitor.displayMsg("CoreException: " +
         e.getMessage());
      }
      return UNKNOWN_ERRORS;
   }

   public void setProjectName(String name)
   {  projectName = name;
   }
   public void setPropertyName(String name)
   {  propertyName = name;
   }

   protected void validateAttributes() throws BuildException
   {  if (projectName == null)
         throw new BuildException("Must supply ProjectName");
   }
}

As with the previous ProjectBuild example, to activate this new GetJavacErrorCount Ant task, you edit plugin.xml to add:

<anttask name="getJavacErrorCount"
   class="com.ibm.ant.extras.GetJavacErrorCount">
</anttask>

Also, in your build.xml script, invoke your new getJavacErrorCount Ant task:

<getJavacErrorCount ProjectName="MyProject"
   PropertyName="MyJavacErrorCount" />
<echo message="MyJavacErrorCount=${MyJavacErrorCount}" />

Summary

This article explained how you can create your own Java classes to add new Ant tasks to your custom build environment. In particular, it discussed how to wrapper tasks that modify workspace resources with a WorkspaceModifyOperation class and an AntConsoleProgressMonitor class, and then use those classes within your own task. It provided a ProjectBuild sample as well as a simple GetJavacErrorCount sample, which does not modify workspace resources and does not need to be put in a wrapper.


Downloads

DescriptionNameSize
Code samplecom.ibm.ant.extras_V4.zip  ( HTTP | FTP )0.1 MB
Code samplecom.ibm.ant.extras_V5EA.zip  ( HTTP | FTP )0.1 MB

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=14334
ArticleTitle=Using Ant with WebSphere Studio Application Developer -- Part 3 of 3
publish-date=03212002