Table of Contents
List of Tables
List of Examples
Table of Contents
Schmant tasks are the building blocks of build scripts. Since build scripts are programs, it's entirely possible to program all build code in them. However, it can sometimes be worth while to implement commonly used build logic in tasks. Some advantages of tasks compared with having complex code in build scripts are:
The disadvantage is that tasks require a greater effort to implement.
Collections of tasks are packaged in task packages, which is the standard way of distributing Schmant tasks. A task package is a Zip file containing zero or more task implementations, other classes and the task reference documentation sources.
This guide explains how tasks are implemented and how they can be grouped together in task packages. It assumes that the reader is familiar with how Schmant build scripts work and the roles and life cycles of tasks and task factories. See the User's Guide for documentation on how to use Schmant.
Table of Contents
A build script uses TaskFactory:s to configure and create
Task:s. Behind the scenes, a task factory uses
a TaskSpecification object for storing task configuration in.
When the build script asks the task factory to create a
Task, the task factory calls the task specification's
createTask
method. If the build script continues
to configure the task factory after it has created the task, the factory makes a copy of
the task specification using the specification's copyProperties
method and proceeds configuring the copy. By doing so, it makes sure that the
created task can read its configuration from the specification without the risk
of anyone modifying it.
Writing a Schmant task involves implementing a Task, a TaskSpecification and a TaskFactory class. There are a number of abstract stub implementations that a Task implementation may inherit. It is highly recommended, but not required, that it inherits AbstractTask or any of its subclasses. AbstractTask implements both Task and TaskSpecification. Task factory classes may inherit AbstractTaskFactory or any of its subclasses.
See the API documentation for details on the abstract Task and TaskFactory implementations.
The fastest way of implementing a new task is probably to get, um, inspiration from an existing task. Schmant's source distribution contains the source code for all of Schmant's own tasks.
Tasks can be developed in any Java IDE. The target Java version should be 6.0.
When a task is run from Schmant, all classes in the Jar files in the
Schmant distribution's lib
directory is
available to it. This includes the Schmant classes and the classes in the
EntityFS core, util, Jar and Zip Jars. Additional Jar files can be packaged in the
task's task package. See the section called “Task package file layout”.
The tools/create_task_package_eclipse_workspace.js
script in the Schmant distribution can be used to create an empty Eclipse
workspace for developing a task and a task package in.
Below is a commented version of the task and task factory implementation for a fictive task that translate the contents of a text file to the Robber's language.
Example 2.1. Implementation of RobbersLanguageTranslatorTask
// package se.rovarspraket; // The translator task is a process task. It takes the file from its source // property, translates it, and puts the result in the location referenced by // the target property. // // The task produces one object (the translated file), which makes it a // Producer. // // Note: This task would have been easier to implement if it had inherited the // AbstractTextInsertionTask. For pedagogic reasons, it does not. public final class RobbersLanguageTranslatorTask extends AbstractProcessTask< RobbersLanguageTranslatorTask> implements Producer<WritableFile> { // This ObjectTransformer is used to cast each argument given to the // addTranslatedCharacters method into a Character object when flattening the // arguments. // // Inherit from CastingTransformer. private static final class CastingToCharacterTransformer extends CastingTransformer<Character> { // Define a singleton instance. private static final CastingToCharacterTransformer INSTANCE = new CastingToCharacterTransformer(); } // The characters that are translated by default private static final Set<Character> DEFAULT_TRANSLATED_CHARACTERS = new HashSet<Character>( Arrays.asList( new Character[] { 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z' })); // The vowel that should be inserted. Default is "o". private char m_vowel = 'o'; // A set of characters to translate private final Set<Character> m_translatedCharacters = new HashSet<Character>(DEFAULT_TRANSLATED_CHARACTERS); // The translated file is assigned to this property when the task is run. private final AtomicReference<WritableFile> m_produced = new AtomicReference<WritableFile>(); // Make the constructor package private. We want that only the task factory // should be able to instantiate this class. RobbersLanguageTranslatorTask() { // Nothing } // This is required by the Producer interface. @Override public WritableFile get() { return m_produced.get(); } // Setters called by the task factory void setVowel(char c) { m_vowel = c; } void addTranslatedCharacter(char c) { m_translatedCharacters.add(c); } // Add one or several translated characters. This method will flatten the // argument into a list. void addTranslatedCharacters(Object o) { // This method is inherited from the AbstractArgumentChecker class. It // checks that none of the supplied objects are null check(o); // Flatten the argument by adding it to a FlatteningList. FlatteningList<Character> l = new FlatteningList<Character>(); // Add all arguments. use a custom ObjectTransformer that casts each // argument into a Character. l.add(o, CastingToCharacterTransformer.INSTANCE); // Add the flattened argument to the set. m_translatedCharacters.addAll(l); } void clearTranslatedCharacters() { m_translatedCharacters.clear(); } // Replace the default, not very good, log header. @Override protected String getDefaultLogHeader() { return "Totroranonsoslolatotinongog " + getSource(); } // Create the regular expression pattern that is used to replace the text private Pattern createPattern() { StringBuilder sb = new StringBuilder("["); for (Character c : m_translatedCharacters) { sb.append(c); } // Make the pattern case-insensitive return Pattern.compile( sb.append("]").toString(), Pattern.CASE_INSENSITIVE); } // This method is called from AbstractTask when the task is run. @Override protected void runInternal(Report r) { // Interpret the source and target properties. // Since the strategy will only return one object, we can call get() to get // it. ReadableFile source = ArgumentInterpreter.getInstance().interpret( getSource(), InterpretAsReadableFileStrategy.AS_SINGLE).get(); // The target is a new writable file. If there already is a file at the // target location, the overwrite strategy decides what to do with it. WritableFile target = ArgumentInterpreter.getInstance(). interpret( getTarget(), new InterpretAsNewWritableFileStrategy( getOverwriteStrategy(), ArgumentInterpretationStrategy.ALLOW_ONE_AND_ONLY_ONE_RESULT_OBJECT)). get(); Pattern pat = createPattern(); // Read the source file to a String and create a Matcher on the string String inText = Files.readTextFile(source); Matcher mat = pat.matcher(inText); // The text replacement loop StringBuilder res = new StringBuilder(); int lastMatch = 0; while(mat.find()) { // Copy all characters between this match and the previous match res.append(inText.substring(lastMatch, mat.start())); // The secret substitution... res.append(mat.group()); res.append(m_vowel); res.append(mat.group()); lastMatch = mat.end(); } // Copy all trailing characters res.append(inText.substring(lastMatch, inText.length())); // Write the result to the target file Files.writeText(target, res.toString()); // The target is our produced object m_produced.set(target); } // This method is called when the task (specification) is copied by the task // factory. It copies all properties to the new task. @Override public void copyProperties(RobbersLanguageTranslatorTask spec) { // We must call this super.copyProperties(spec); // The character is immutable, so it can safely be copied to the spec spec.m_vowel = m_vowel; // The list is mutable, so we only copy the list contents, not the list // itself. spec.m_translatedCharacters.addAll(m_translatedCharacters); } } //
The task factory:
Example 2.2. Implementation of RobbersLanguageTranslatorTF
// package se.rovarspraket; public final class RobbersLanguageTranslatorTF extends AbstractProcessTaskFactory< RobbersLanguageTranslatorTF, RobbersLanguageTranslatorTask> { // Setters. All values are set on the task specification, which happens to be // the task object itself. public RobbersLanguageTranslatorTF setVowel(char c) { getSpecification().setVowel(c); return this; } public RobbersLanguageTranslatorTF addTranslatedCharacter(char c) { getSpecification().addTranslatedCharacter(c); return this; } // Add a single translated character or an array or a collection of translated // characters. The argument will be flattened in // RobbersLanguageTranslatorTask. public RobbersLanguageTranslatorTF addTranslatedCharacters(Object o) { getSpecification().addTranslatedCharacters(o); return this; } public RobbersLanguageTranslatorTF clearTranslatedCharacters() { getSpecification().clearTranslatedCharacters(); return this; } // AbstractTaskFactory wants us to implement this. It // is called every time that the task factory needs to create a new // TaskSpecification object (which happens to be the Task itself). @Override protected RobbersLanguageTranslatorTask createSpecification() { return new RobbersLanguageTranslatorTask(); } } //
And this is how the task can be used from a build script:
Example 2.3. Using the Robber's language translator task
enableTaskPackage("se.rovarspraket"); // The source file var source = new CharSequenceReadableFile("Rhododendron"); // Create the target file in a RAM directory var target = new FutureFile( SchmantFileSystems.createRamFileSystem(), "translated.txt"); new dr.RobbersLanguageTranslatorTF(). setSource(source). setTarget(target). run(); // Exercise: What does this print? info(Files.readTextFile(target.getFile()));
Table of Contents
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 type | Description |
---|---|
org.schmant.project.eclipse.EclipseProjectCreatorPlugin | Try 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.IntelliJModuleCreatorPlugin | Try 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.
A task can use this directory to store library files that it wants to use but does not want to have on the script's classpath. This can for instance be runtime dependencies for external programs.
All Jar files in this directory are appended to the classpath of the running script when the task package is enabled. This directory contains the task and task factory implementations as well as their runtime dependencies, except for the dependencies which are available by default through Schmant (see the section called “Development environment”).
Task reference documentation files. Described in Chapter 4, Writing task reference documentation.
Overview documentation for the task package. Described in Chapter 4, Writing task reference documentation.
The task package manifest file. It contains meta information about the task package. The task package dtd file, taskpackage-1.1.dtd, has the full documentation of the manifest file format. See below for an example.
This is the net.findbugs
task package's
taskpackage.xml
file:
Example 3.1. Findbugs task package manifest
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE taskpackage SYSTEM "http://www.schmant.org/dtd/taskpackage-1.1.dtd"> <!-- The name of the task package. This should be the same as the task package directory name. --> <taskpackage name="net.findbugs"> <!-- Repeat for each Java package that should be imported automatically in the script. Classes in imported Java packages do not have to be used by their fully qualified names, just like how the import statement in Java files work. --> <javapackage>org.schmant.task.findbugs</javapackage> </taskpackage>
This is the taskpackage.xml
file for
the com.tarsec.javadoc.pdfdoclet
extension
task package:
Example 3.2. PDFDoclet task package manifest
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE taskpackage SYSTEM "http://www.schmant.org/dtd/taskpackage-1.1.dtd"> <!-- The classloader attribute tells the task package manager that this task package should be loaded in the same class loader as the org.schmant.task.base task package, making this an extension task package. --> <taskpackage name="com.tarsec.javadoc.pdfdoclet" classloader="org.schmant.task.base"> <javapackage>org.schmant.task.jdk.javadoc.pdfdoclet.ext</javapackage> </taskpackage>
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...
Table of Contents
Reference documentation for tasks and their task factories is built from the documentation sources in the task package.
The reference documentation for a task is written in an XML file formatted according to the tfdoc-1.2 DTD.
Text that is used in several task documentation pages can be put in separate
files and included using XML entity references. The system id for an external
file is generated from its location in the task package. For instance, if the
task package org.my.task.package
keeps some
frequently occurring text in the
org.my.task.package/src/doc/taskref/CommonText.xmlf
file, it can be referenced using the system id
http://www.schmant.org/org.my.task.package/src/doc/taskref/CommonText.xmlf
.
There are some common external entities that can be used from all task reference documentation files to include common text:
This document is generated automatically when the task reference is built and it contains the <category> tag with contents. It must be included by task reference files.
Documentation of the classpathEntries
and classpathDecorators
properties for tasks
that are ClasspathConfigurable.
Documentation of the overwriteStrategy
property for generator tasks.
Documentation of the logHeader
,
logFooter
,
traceLogging
and
reportLevel
properties for all
Task:s.
A note about TaskDependency consequences when using a task that can schedule subtasks in a TaskExecutor. The note should be included in the description for the task executor property.
Each property gets an anchor (<a>
tag) with the name
[task_factory_name]_[property_name]
that can be used to link to the property. For instance, the UntarTF task
factory's fileNameEncodingCharset
property's
anchor's name is UntarTF_fileNameEncodingCharset
.
Below is the documentation file for a fictional process task that translates text file contents to the Robber Language:
Example 4.1. Task reference documentation
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE taskdoc SYSTEM "http://www.schmant.org/dtd/tfdoc-1.2.dtd" [ <!ENTITY taskCategories SYSTEM "http://www.schmant.org/taskref/taskcategories.xmlf"> <!ENTITY taskFactory SYSTEM "http://www.schmant.org/taskref/TaskFactory.xmlf"> <!ENTITY generatorTaskFactory SYSTEM "http://www.schmant.org/taskref/GeneratorTaskFactory.xmlf"> ]> <taskdoc> <!-- Use an external entity reference to a file containing task category information. The included file is generated when the task documentation is built. --> &taskCategories; <!-- Place this task in the text tasks category. It would have been nice to be able to call the task package "se.rövarspråket" instead, but Java's XML parser could not locate files under the se.rövarspråket directory. In the future, when we're all zipping around in our flying cars, the whole world will use UTF-8. Sigh. (Robbers Language is rövarspråket in Swedish.) --> <taskfactory name="RobberLanguageTF" package="se.rovarspraket" java-package="se.rovarspraket" category="catText" entityfs-aware="yes"> <!-- The master detective! --> <author>Bill Bergson (Kalle Blomkvist)</author> <since>1.0</since> <!-- Document this task's restriction. The restriction element is optional. --> <restriction>This task only works with text files.</restriction> <!-- Since the task is a process task, it is also an action and a generator task. --> <implements interface="ActionTaskFactory"/> <implements interface="GeneratorTaskFactory"/> <implements interface="ProcessTaskFactory"/> <!-- Use a CDATA block to be able to insert our own HTML formatting. Note the link to the target property. If you want to link to a class, use an explicit class link like |api:class:(class name)?linkClass=|. See ApiLinksTF documentation. --> <produces><![CDATA[The interpreted value of the <a href="#RobbersLanguageTF_target">target</a> property.]]></produces> <short-description>Translate text files to the Robber Language.</short-description> <!-- Note the link to the example. --> <description><![CDATA[This task translates text files to <a href="http://en.wikipedia.org/wiki/Rövarspråket">the Robber Language</a>.</p> <p>The vowel to use when translating can be set in the <a href="#RobberLanguageTF_vowel">vowel</a> property.</p> <p>See <a href="#robberLanguageTF_ex_1">this example</a>.]]></description> <!-- Insert common properties --> &taskFactory; &generatorTaskFactory; <!-- The propertyset element is a container for properties. --> <propertyset> <property name="source" required="yes"> <description>The text file to translate.</description> <!-- Document the property's setter method. If the property has several setter methods, they can be listed after each other. --> <setter-method name="setSource"> <!-- The setter method description may be omitted if it is obvious what the method does. --> <description>Set one text file.</description> <!-- List all setter method parameters. Note how the interpretation is documented. See the documentation for the ArgumentInterpreterLinksTF for information on the format of the argument interpreter reference. --> <parameter name="o" type="Object" interpreter="|ai:ai_readable_file;InterpretAsReadableFileStrategy|">A text file.</parameter> </setter-method> </property> <property name="target" required="yes"> <description>The target location where the translated file will be put.</description> <setter-method name="setTarget"> <parameter name="o" type="Object" interpreter= "|ai:ai_new_writable_file;InterpretAsNewWritableFileStrategy|">The target file location.</parameter> </setter-method> </property> <property name="vowel"> <description>The vowel to use when translating.</description> <setter-method name="setVowel"> <parameter name="c" type="char">The vowel</parameter> </setter-method> <!-- This property has a default value. --> <default-value><![CDATA[<code>o</code>]]></default-value> </property> <property name="translatedCharacters"> <description>The collection of characters that are translated.</description> <setter-method name="addTranslatedCharacter"> <description>Add one translated character.</description> <parameter name="c" type="char">The character to translate.</parameter> </setter-method> <!-- This setter method flattens the argument list. --> <setter-method name="addTranslatedCharacters"> <description>Add one or several translated characters.</description> <parameter name="o" type="Object">One character or an array or collection of characters.</parameter> </setter-method> <setter-method name="clearTranslatedCharacters"> <description>Clear the list of translated characters.</description> </setter-method> </property> </propertyset> <examplesintro>For more examples, see somewhere else.</examplesintro> <example id="robberLanguageTF_ex_1"> <!-- This description will be used in the examples reference. --> <short-description>Translate all files in a directory hierarchy to the Robber Language.</short-description> <!-- This description is displayed above the example. --> <description><![CDATA[Translate all files in the directory hierarchy under <code>src</code> to the Robber Language, putting the translated files in a directory hierarchy under <code>target</code>.]]></description> <!-- Use an include tag to include the example code. The example text will be inserted by the IncludeFilesTask when building the documentation. By keeping examples in separate files, they are made easier to test. --> <code>|include:se.rövarspråket/src/doc/taskref/robberLanguageTF_ex1.js|</code> </example> </taskfactory> </taskdoc>
Documentation for the entire task package is put in the
task package name/src/doc/taskref/overview.xml
file. It is formatted according to the
tpdoc-1.0 DTD.
Below is an example of an overview.xml
file.
Example 4.2. Task package documentation
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE taskpackagedoc SYSTEM "http://www.schmant.org/dtd/tpdoc-1.0.dtd"> <!-- In the best of worlds, it would have been possible to call "rovarspraket" "rövarspråket" instead. Java's XML parser did not agree at all with that. --> <taskpackagedoc name="se.rovarspraket"> <short-description>Contains tasks for working with the Robber Language.</short-description> <long-description>This text contains the task package documentation.</long-description> </taskpackagedoc>
How to build the task reference documentation is documented in the Task Package index page.
Table of Contents
This chapter describes how the Emma task package is built and packaged from the source files in its development environment. This can serve as a blueprint for other task packages.
This is the layout of the Emma task package's Eclipse workspace:
![]() |
Java project containing the task and task factory implementations.
The task package root directory.
Task reference documentation sources.
Task package documentation.
Documentation files that are copied to the task package.
Jar dependencies, test data and build scripts.
Unit test classes.
The task package is built using the Schmant script
build_from_eclipse.js
(included in the
Schmant distribution's samples). It does the following:
Compiles the projects in the workspace.
Builds a Jar file containing the classes from the Schmant_task_emma project.
Builds another Jar file containing the classes from the Schmant_task_emma_test project.
Builds and postprocesses the task reference documentation.
Builds and postprocesses the Javadocs.
Adds version information to other documentation files.
Builds a binary distribution task package Zip file.
Builds a source distribution task package Zip file.
[User's Guide] Schmant User's Guide.