Chapter 3. Creating a task package

Table of Contents

Extension task packages
Task package file layout
Accessing task package resources from tasks

Custom tasks and task factories can be used by just including their classes in the class path and importing the task (Java) packages in the script. However, if tasks are to be distributed, they should be packaged in a task package.

A task package has a name that should be globally unique. There are no enforced naming rules, but a good practice is to name a task package similar to Java package, for instance net.findbugs for the Findbugs task package or org.junit.junit4 for the JUnit 4 task package.

To use a task package, a build script must enable it before it can use its task. When a task package is enabled, its libraries are added to the script's classpath. (Or added to the classpath of a class loader unique to the task package if the script is run with isolated class loaders.) Enabling a task package also imports a number of Java packages so that class names in those packages don't have to be fully qualified when used by the build script.

In addition to tasks, a task package may also contain Plugin:s. Plugins adds additional functionality to tasks in other task packages or to classes in Schmant itself. Every plugin extension point is identified by a globally unique name. The name should be the fully qualified name of the plugin implementation required.

Plugins are registered in the org.schmant,plugin.PluginRegistry. A task that has a plugin extension point can use it to get all available implementations.

Schmant itself has the following plugin extension points:

Table 3.1. Plugin extension points

Name / required typeDescription
org.schmant.project.eclipse.EclipseProjectCreatorPluginTry to create an Eclipse Project from the contents of a directory. The first registered plugin that is able to create a Project object from the contents is used.
org.schmant.project.intellij.IntelliJModuleCreatorPluginTry to create an IntelliJ IDEA Project (module) from the contents of a directory. The first registered plugin that is able to create a Project object from the contents is used.


Several plugins may be registered for each extension point. The first registered plugin has the highest precedence.

Task packages are distributed in Zip files. Just like a Java Jar file, the task package file can be used as-is or unpacked. When running a build script, Schmant uses the task package path to find task packages. It works just like Java's classpath.

A standard task package is a standalone module containing tasks, requiring nothing from other task packages. An extension task package is another kind of task package that extends the functionality of another task package. An example from the Schmant distribution is the com.tarsec.javadoc.pdfdoclet task package that adds an ExtJavadocTaskDecorator for creating PDF Javadocs to the Javadoc task.

An extension task package is implemented just like a standard task package, except that it has the classes of the task package to extend available to it. If a build script using an extension task package is run with isolated class loaders (a separate class loader for each task package), the extension task package uses the same class loader as the task package that it extends.

Information on which task package that an extension task package extends is put in the task package manifest file taskpackage.xml. See Example 3.2, “PDFDoclet task package manifest” for an example.

This is a schematic overview over the contents of a task package file:

/root directory       -- The task package root directory. This directory
|                        should have the same name as the task package itself,
|                        for instance net.findbugs.
+ extlib              -- (Optional) Libraries used when running external
| |                      programs.
| + jar files
+ lib                 -- Libraries that are included in the script's classpath
| |                      when the task package is enabled.
| + jar files
+ src                 -- Source files.
| + doc               -- Documentation sources.
|   + taskref         -- Task reference documentation sources.
|     + taskref files -- Task reference documentation files.
|     + overview.xml  -- Task package overview documentation.
+ taskpackage.xml     -- Task package manifest file.

This is the net.findbugs task package's taskpackage.xml file:


This is the taskpackage.xml file for the com.tarsec.javadoc.pdfdoclet extension task package:


Below is an image showing the layout of the org.at4j task package file:

This image shows the layout of the org.junit.junit4 task package file:

The JUnit4TF task runs JUnit in a separate Java process. The extlib catalog of the task package contains Jar files that are necessary for running JUnit4. The task runs a class in the launcher Jar that sets up the environment before running the unit tests.

Task package management in Schmant is performed by the TaskPackageManager class. It contains the getTaskPackage method that a task can use to access its TaskPackage.

The example below shows how a task launches a separate Java process with a classpath consisting of the Jar files in its extlib directory.

Example 3.3. Launching a process using extlib files

// First get the java command to use
// This method invocation gets a "java" or "java.exe" command from:
// a) The Java installation referenced by the JAVA_HOME environment variable, if
//    that is set.
// b) The directories of the PATH environment variable, if that is set.
File javaCmd = JdkUtil.getJdkExecutable("java", "", "exe");

// Create a classpath string from all Jar files found in the extlib directory.

// Our task package.
// The TaskPackageManager class has a static InheritableThreadLocal variable
// containing the task package manager.
TaskPackage tp = TaskPackageManager.
  get().
  getTaskPackage("org.my.task.package");

// Our task package's root directory
Directory tpRoot = tp.getRootDirectory();

Collection<EFile> jarEFiles = Directories.getAllFilesMatching(
  Directories.getDirectory(tpRoot, "extlib"),
  new Glob("*.jar"));

// Make all Jar files File-backed. This is necessary since they are to be used
// by another process.
// The makeFileBacked method copies the file to a temporary directory if it is
// not already File backed. The copy is deleted when the build script
// terminates.
StringBuilder classpath = new StringBuilder();
for (EFile jarEFile : jarEFiles)
{
  classpath.append(
    ECFileResolvableUtil.getFileObject(
      tp.makeFileBacked(jarEFile)).getAbsolutePath());
  classpath.append(File.pathSeparatorChar);
}

// Create an argument list for the process. "true" means that added strings
// containing spaces will be quoted.
ArgumentList al = new ArgumentList(true);

// The program to run
al.add(javaCmd.getAbsolutePath());

// Add the classpath
al.add("-cp").add(classpath.toString());

// Add the class to run
al.add("org.mytask.MyTaskLauncher");

// Save the output from the process to a string
SaveToStringProcessOutputStrategy sos = new SaveToStringProcessOutputStrategy();

// Create a configuration object for the process that we will launch.
ProcessSettings settings = new ProcessSettings().
  setStdoutStrategy(sos).
  setStderrStrategy(sos).
  setArgumentList(al);

// Run the program and wait until it terminates.
ProcessResult result = ProcessSupport.execAndWait(settings);

// Deal with the result...