Schmant is a build tool for building and packaging software applications and
libraries. It provides an environment for running build scripts and tasks and tools that
the scripts may use. Build scripts may be written in JavaScript, Groovy, JRuby
or Jython. See the scripting language guides for
details.
A Schmant build script has access to all API:s of the Java platform,
any number of user-supplied classes, as well as the features of the
script language used. It has also access to the file system APIs of
EntityFS. This makes it possible to write very powerful and
customizable build scripts.
This manual, the User's Guide, is written for users that want to
use Schmant for their build scripts. Much of the documentation in this manual
is accompanied by concrete, working example scripts written in the different
supported script languages.
This manual should be used together with the
Task factory index, the
API documentation and the
EntityFS documentation.
The Task Author's Guide explains how tasks and
task factories are implemented.
If you have questions about Schmant that is not answered by the
documentation, feel free to ask questions on the user's mailing list.
For programmers that are new to Schmant, the amount of new concepts can
sometimes feel daunting. In order to make the most of Schmant, it is important
that you feel comfortable with:
As always, when writing scripts, first make sure that your script works before you
try to make it work faster or be more generic.
Feedback and contributions from users is essential for making Schmant a
better application. Please share your thoughts and opinions on Schmant with
the rest of the community through the mailing lists.
Chapter 4. Build scripts and tasks
The build script is a script that binds together a sequence of
tasks. The script can be written in any supported script language. See
the scripting language guides.
The mandatory Hello World script looks like this when written in JavaScript:
Example 4.1. A Hello World script
The script in the example above uses the info
method to log output to its current Report. Report objects
are used for handling output from a build script and have logging methods much like
any logging API. Read more on reports in the section called “Reports and logging”.
The next example shows a script that compiles the Java source files found in the
directory src
and its subdirectories, puts the compiled classes in a temporary directory and then
bundles them in a Jar file.
Example 4.2. Compile Java files and build a Jar file
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import org.entityfs.util.Directories
import org.schmant.support.io.TempFileUtil
import org.schmant.support.entityfs.SchmantFileSystems
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// Compile files from the directory /home/me/myproject/src and put the resulting
// class files in a temporary directory. The source files have compilation
// dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
// /home/me/myproject/optlib/opt.jar
// A temporary java.io.File directory for the compiled classes. This, along with
// all of its contents, will be automatically deleted when Schmant exits (unless
// the -k flag was used when Schmant was launched).
def ctarget =
TempFileUtil.createTempDir()
// This is the lib directory expressed as a (read only) EntityFS
Directory.
// By using a
Directory, the script can use the utility methods of
//
Directories to extract the files it wants. See below.
def libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true)
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
def depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"))
// Compile the Java source files.
new
Jdk6JavacTF().
addSource(new
File("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run()
// A timestamp for the built archive
def timestamp = new
SimpleDateFormat("yyyyMMddHHmm").
format(new
Date())
// Build the Jar file. Put it in /home/me/myproject
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")).
run()
// Compile files from the directory /home/me/myproject/src and put the resulting
// class files in a temporary directory. The source files have compilation
// dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
// /home/me/myproject/optlib/opt.jar
// A temporary java.io.File directory for the compiled classes. This, along with
// all of its contents, will be automatically deleted when Schmant exits (unless
// the -k flag was used when Schmant was launched).
ctarget =
TempFileUtil.createTempDir();
// This is the lib directory expressed as a (read only) EntityFS
Directory.
// By using a
Directory, the script can use the utility methods of
//
Directories to extract the files it wants. See below.
libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true);
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar");
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"));
// Compile the Java source files.
new
Jdk6JavacTF().
addSource(new
File("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run();
// A timestamp for the built archive
// java.text.SimpleDateFormat must be fully qualified since the java.text
// package classes are not automatically imported by Schmant. The "Packages"
// prefix is for telling JavaScript that it is a Java package. It is required
// sometimes. See the chapter on script language support for more information.
timestamp = new Packages.java.text.SimpleDateFormat("yyyyMMddHHmm").
format(new
Date());
// Build the Jar file. Put it in /home/me/myproject
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")).
run();
# Compile files from the directory /home/me/myproject/src and put the resulting
# class files in a temporary directory. The source files have compilation
# dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
# /home/me/myproject/optlib/opt.jar
# A temporary java.io.File directory for the compiled classes. This, along with
# all of its contents, will be automatically deleted when Schmant exits (unless
# the -k flag was used when Schmant was launched).
ctarget = Schmant::
TempFileUtil.createTempDir
# This is the lib directory expressed as a (read only) EntityFS
Directory.
# By using a
Directory, the script can use the utility methods of
#
Directories to extract the files it wants. See below.
#
#
File is not included in the Java module, so we have to access it through the
# Java module instead.
libDir = Schmant::
SchmantFileSystems.getEntityForDirectory(
Java::JavaIo::
File.new("/home/me/myproject/lib"), true)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars = Schmant::
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add(Java::JavaIo::
File.new("/home/me/myproject/optlib/opt.jar"))
# Compile the Java source files.
Schmant::Jdk6JavacTF.new.
addSource(Java::JavaIo::
File.new("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run
# A timestamp for the built archive
timestamp = Java::JavaText::SimpleDateFormat.new("yyyyMMddHHmm").
format(Java::JavaUtil::
Date.new)
# Build the Jar file. Put it in /home/me/myproject
Schmant::JarTF.new.
addSource(ctarget).
setTarget(
Java::JavaIo::
File.new("/home/me/myproject/myproject" + timestamp + ".jar")).
run
# Compile files from the directory /home/me/myproject/src and put the resulting
# class files in a temporary directory. The source files have compilation
# dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
# /home/me/myproject/optlib/opt.jar
# A temporary java.io.File directory for the compiled classes. This, along with
# all of its contents, will be automatically deleted when Schmant exits (unless
# the -k flag was used when Schmant was launched).
ctarget =
TempFileUtil.createTempDir()
# This is the lib directory expressed as a (read only) EntityFS
Directory.
# By using a
Directory, the script can use the utility methods of
#
Directories to extract the files it wants. See below.
libDir =
SchmantFileSystems.getEntityForDirectory(
File("/home/me/myproject/lib"), True)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add(
File("/home/me/myproject/optlib/opt.jar"))
# Compile the Java source files.
Jdk6JavacTF(). \
addSource(
File("/home/me/myproject/src")). \
addClasspathEntries(depjars). \
setTarget(ctarget).run()
# A timestamp for the built archive
#
#
SimpleDateFormat is not automatically imported by the preparation
# script.
from java.text import SimpleDateFormat
timestamp =
SimpleDateFormat("yyyyMMddHHmm"). \
format(
Date())
# Build the Jar file. Put it in /home/me/myproject
JarTF(). \
addSource(ctarget). \
setTarget(
File("/home/me/myproject/myproject" + timestamp + ".jar")). \
run()
There are two important things to point out in the example above:
- "TF" in, for instance, Jdk6JavacTF
stands for Task Factory. Task objects are never created
directly, only via their task factories. Task factories may be reused for creating
several task objects.
- Both Java's File and EntityFS file and directory objects are used by
the script. Tasks can handle both of them because they use
interpreted arguments for some of their methods.
The following sections will explain each of the bullet points in more detail.
Task:s and TaskFactory:s are
closely related.
A task object is used just once for running a task and then it is discarded.
A task factory is a factory object used for creating one or more task objects.
It can be reused to create several tasks with similar
configurations, for instance when processing files recursively in a directory
hierarchy (see example Example 4.7, “Set version information in all XML files in a directory hierarchy, overwriting
the original files, using a RecursiveActionTF”).
Build scripts always use task factories to create tasks; tasks objects are never
created directly.
To create a task, a build script creates a task factory and sets all of the
task's configuration on the factory using its setter methods.
Then the script creates one or several tasks from the factory and runs them.
Behind the scenes, the task factory configures a TaskSpecification
object, which it hands over to the Task object that it creates.
After creating the task, if the script continues to configure the same task
factory, the task factory makes a copy of the task specification object so that the configuration
of the already created task does not change. After creating the next task, the
task specification is copied again, and so on. This ensures that the configuration
of a running task never is modified.
There are four different categories of tasks:
- Action tasks
An action task factory has a configurable source
property. It creates action tasks that read the data in the
source
property and
perform some action with it. Examples: ChmodTF and DomParseXmlTF.
- Generator tasks
A generator task factory has a configurable target
property. It creates generator tasks that generates data that they write to the
target
. Example: SvnExportTF.
- Process tasks
A process task factory has both a source
and a target
property. It creates process tasks that
read data from the source
, transforms it (compiles it, for instance), and write
the result to the target
. A process task is both an action task and a
generator task. Examples: Jdk6JavacTF and TextReplaceTF.
- Plain tasks
A plain task factory does not have
source
or target
properties. Examples: JavaTF and CompoundTF.
The source
and
target
properties are interpreted
(see below), so the exact types of the properties varies from task to task.
source
is often a file or a directory and
target
is often a directory or a
FutureFile (see below).
Both Task and TaskFactory
implement java.lang.Runnable
. Calling run()
on a
TaskFactory is a shortcut for calling create().run()
on it.
The reason for separating the task and task factory functionality into two
different classes is illustrated by
Example 4.7, “Set version information in all XML files in a directory hierarchy, overwriting
the original files, using a RecursiveActionTF” below. It shows how an
action task is created and run for each XML file in a directory
hierarchy. The task factory is a template for creating tasks that only differ
in their source
properties.
The Task factory index lists all available task factories.
Some task factory property setter methods take Object
arguments. These arguments are interpreted, which means that
it is up to the task to interpret them into something useful.
This gives a great deal of flexibility in the different kinds of variables that a Schmant
script may use. For instance, a Schmant script may use
java.io.File
objects as simple pointers to
the local file system and EntityFS objects when more advanced file system
operations are needed, as shown in Example 4.2, “Compile Java files and build a Jar file”.
The example below shows how different types can be used as the
source property for a CopyTF.
Example 4.3. Using different source types with CopyTF
import java.io.File
import org.entityfs.util.ManualNamedReadableFile
import org.schmant.task.io.CopyTF
import org.schmant.support.entityfs.SchmantFileSystems
// d is the target directory. It can, for instance, be a
File or a
Directory.
// Copy a java.io.File:
new
CopyTF().
setSource(new
File(props.getStringValue("java.io.tmpdir"), "f1")).
setTarget(d).run()
// Copy a (read only) EntityFS file entity (
EFile):
new
CopyTF().
setSource(
// Get a read only
EFile entity for the file.
SchmantFileSystems.getEntityForFile(
new
File(props.getStringValue("java.io.tmpdir"), "f2"), true)).
setTarget(d).run()
// Copy an inline file
new
CopyTF().
setSource(
new
ManualNamedReadableFile(
"f3",
"Contents of f3")).
setTarget(d).run()
// d is the target directory. It can, for instance, be a
File or a
Directory.
// Copy a java.io.File:
new
CopyTF().
setSource(new
File(props.getStringValue("java.io.tmpdir"), "f1")).
setTarget(d).run();
// Copy a (read only) EntityFS file entity (
EFile):
new
CopyTF().
setSource(
// Get a read only
EFile entity for the file.
SchmantFileSystems.getEntityForFile(
new
File(props.getStringValue("java.io.tmpdir"), "f2"), true)).
setTarget(d).run();
// Copy an inline file
new
CopyTF().
setSource(
new
ManualNamedReadableFile(
"f3",
"Contents of f3")).
setTarget(d).run();
# d is the target directory. It can, for instance, be a
File or a
Directory.
# Copy a java.io.File:
Schmant::CopyTF.new.
setSource(
Java::JavaIo::
File.new($props.getStringValue("java.io.tmpdir"), "f1")).
setTarget($d).run
# Copy a (read only) EntityFS file entity (
EFile):
Schmant::CopyTF.new.
setSource(
# Get a read only
EFile entity for the file.
Schmant::
SchmantFileSystems.getEntityForFile(
Java::JavaIo::
File.new($props.getStringValue("java.io.tmpdir"), "f2"),
true)).
setTarget($d).run
# Copy an inline file
Schmant::CopyTF.new.
setSource(
Schmant::
ManualNamedReadableFile.new(
"f3",
"Contents of f3")).
setTarget($d).run
# d is the target directory. It can, for instance, be a
File or a
Directory.
# Copy a java.io.File:
CopyTF(). \
setSource(
File(props.getStringValue("java.io.tmpdir"), "f1")). \
setTarget(d).run()
# Copy a (read only) EntityFS file entity (
EFile):
#
# Get a read only
EFile entity for the file.
CopyTF(). \
setSource( \
SchmantFileSystems.getEntityForFile( \
File(props.getStringValue("java.io.tmpdir"), "f2"), True)). \
setTarget(d).run();
# Copy an inline file
CopyTF(). \
setSource( \
ManualNamedReadableFile( \
"f3", \
"Contents of f3")). \
setTarget(d).run()
The reference documentation for each task describes
how it interprets each of its interpreted properties. Most
tasks use the methods of ArgumentInterpreter,
but some tasks have custom interpretation methods of their own.
ArgumentInterpreter is described in more detail,
along with descriptions of possible implementations of different interpreted
types, in Appendix A, ArgumentInterpreter.
If a task factory property can take a collection of objects, for instance
several source files for copying, it flattens the arguments
given to the property's setter method. This means that the build script can give
one or several arguments to the method. Multiple arguments can be stored in
arrays or Collection:s. The arrays or collections may even contain
other arrays or collections. All arguments are flattened to a single list using
a FlatteningList.
Example 4.4. Flattening an argument list
import
CopyTF
// Use a two-dimensional Groovy array as the source to a copy task.
// Copy the files f1, f2, f3 and f4 to the directory d.
new
CopyTF().
addSources([[f1, f2], [f3], f4]).
setTarget(d).run()
// Use a two-dimensional JavaScript array as the source to a copy task.
// Copy the files f1, f2, f3 and f4 to the directory d.
new
CopyTF().
addSources([[f1, f2], [f3], f4]).
setTarget(d).run();
# Use a two-dimensional Ruby list as the source to a copy task.
# Copy the files f1, f2, f3 and f4 to the directory d.
Schmant::CopyTF.new.
addSources([[$f1, $f2], [$f3], $f4]).
setTarget($d).run
# Use a two-dimensional Python list as the source to a copy task.
# Copy the files f1, f2, f3 and f4 to the directory d.
CopyTF(). \
addSources([[f1, f2], [f3], f4]). \
setTarget(d).run()
For an interpreted task factory property, instead of setting the property
value directly, a build script may instead set a Producer object
that will yield the property value when the task is run. This is useful for
setting properties that may not yet be created when the task is configured,
and may be a little more compact than using a closure.
All generator tasks and some non-generator tasks are
Producer:s. There are also a few other Producer
implementations, such as the FutureProperty that produces
the property value of another object.
The following example shows how an object produced by one task is fed to another
task.
Example 4.5. Using a produced file as the source in another task.
import java.text.SimpleDateFormat
import java.util.Date
import org.entityfs.util.CharSequenceReadableFile
import org.schmant.support.FutureFile
import org.schmant.support.entityfs.SchmantFileSystems
import org.schmant.task.io.CopyTF
import org.schmant.task.io.gzip.GZipTF
// Create the file timestamp.gz in the directory dir. dir may be a
File
// directory or an EntityFS
Directory
// An in-memory temporary directory that will hold the intermediate file.
def tmpDir =
SchmantFileSystems.createRamFileSystem()
new
GZipTF().
// Set the task producing the timestamp file as the source.
setSource(
// The object produced by this task is the target file.
new
CopyTF().
setSource(
new
CharSequenceReadableFile(
"timestamp: " +
new
SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new
Date()))).
setTarget(
new
FutureFile(tmpDir, "timestamp")).
// Running the task returns the
Task object, which in this case is
// also a
Producer.
run()).
// The target for the gzip task.
setTarget(
new
FutureFile(targetDir, "timestamp.gz")).run()
// Create the file timestamp.gz in the directory dir. dir may be a
File
// directory or an EntityFS
Directory
// An in-memory temporary directory that will hold the intermediate file.
tmpDir =
SchmantFileSystems.createRamFileSystem();
new
GZipTF().
// Set the task producing the timestamp file as the source.
setSource(
// The object produced by this task is the target file.
new
CopyTF().
setSource(
new
CharSequenceReadableFile(
"timestamp: " +
new
SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new
Date()))).
setTarget(
new
FutureFile(tmpDir, "timestamp")).
// Running the task returns the
Task object, which in this case is
// also a
Producer.
run()).
// The target for the gzip task.
setTarget(
new
FutureFile(targetDir, "timestamp.gz")).run();
# Create the file timestamp.gz in the directory dir. dir may be a
File
# directory or an EntityFS
Directory
# An in-memory temporary directory that will hold the intermediate file.
tmpDir = Schmant::
SchmantFileSystems.createRamFileSystem
Schmant::GZipTF.new.
# Set the task producing the timestamp file as the source.
setSource(
# The object produced by this task is the target file.
Schmant::CopyTF.new.
setSource(
Schmant::
CharSequenceReadableFile.new(
"timestamp: " +
Java::JavaText::
SimpleDateFormat.new("yyyyMMdd HH:mm:ss").
format(Java::JavaUtil::
Date.new))).
setTarget(
Schmant::
FutureFile.new(tmpDir, "timestamp")).
# Running the task returns the
Task object, which in this case is
# also a
Producer.
run).
# The target for the gzip task.
setTarget(
Schmant::
FutureFile.new($targetDir, "timestamp.gz")).run
# Create the file timestamp.gz in the directory dir. dir may be a
File
# directory or an EntityFS
Directory
# An in-memory temporary directory that will hold the intermediate file.
tmpDir =
SchmantFileSystems.createRamFileSystem()
#
SimpleDateFormat is not automatically imported by the preparation
# script.
from java.text import SimpleDateFormat
# Running the CopyTF task returns the
Task object, which in this case is also
# a
Producer. This is then used as the source of the gzip task.
GZipTF(). \
setSource( \
CopyTF(). \
setSource( \
CharSequenceReadableFile( \
"timestamp: " + \
SimpleDateFormat("yyyyMMdd HH:mm:ss"). \
format(
Date()))). \
setTarget( \
FutureFile(tmpDir, "timestamp")). \
run()). \
setTarget( \
FutureFile(targetDir, "timestamp.gz")).run()
A build script may need to run a task for each file in a directory
hierarchy, for instance when processing text files like in the examples below.
This can be accomplished in a couple of ways. The most straightforward is perhaps
to use an EntityFS FilteringIterator together with a
DepthFirstIterator like in the example below.
Example 4.6. Set version information in all XML files in a directory hierarchy, overwriting
the original files, using a recursive iterator.
import org.entityfs.util.*
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.entityfs.util.itr.FilteringIterator
import org.schmant.support.FutureFile
import org.schmant.task.text.TextReplaceTF
// Process all XML files from the directory hierarchy under the EntityFS
//
Directory src. Change all occurrences of the text !!!VERSION!!!
// for the contents of the variable version.
// NOTE: This is not the easiest way to accomplish this. See the next example!
// Create a recursive, filtered iterator that returns all XML files in the
// directory hierarchy
def itr = new
FilteringIterator(
// It does not matter if a depth first or breadth first iterator is used here.
Directories.getDepthFirstIterator(src),
// The EFileNameExtensionFilter does only return EntityFS
EFile:s.
new
EFileNameExtensionFilter("xml"))
// Iterate over all files returned by the iterator
while(itr.hasNext())
{
def f = itr.next()
def fname =
Entities.getName(f)
// The temporary file to put the process result in.
def tmpTarget = new
FutureFile(
// Get f's parent directory.
Entities.getParent(f),
fname + ".tmp")
// The text processing task
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version).
setSource(f).
setTarget(tmpTarget).run()
// Replace the source file with the processed file.
//
// Instead of replacing the source file manually, the TextReplaceTF could have
// been wrapped in a ReplaceSourceFileTF. See the next example.
Entities.delete(f)
Entities.rename(tmpTarget.getEntity(), fname)
}
// Process all XML files from the directory hierarchy under the EntityFS
//
Directory src. Change all occurrences of the text !!!VERSION!!!
// for the contents of the variable version.
// NOTE: This is not the easiest way to accomplish this. See the next example!
// Create a recursive, filtered iterator that returns all XML files in the
// directory hierarchy
itr = new
FilteringIterator(
// It does not matter if a depth first or breadth first iterator is used here.
Directories.getDepthFirstIterator(src),
// The EFileNameExtensionFilter does only return EntityFS
EFile:s.
new
EFileNameExtensionFilter("xml"));
// Iterate over all files returned by the iterator
while(itr.hasNext()) {
f = itr.next();
fname =
Entities.getName(f);
// The temporary file to put the process result in.
tmpTarget = new
FutureFile(
// Get f's parent directory.
Entities.getParent(f),
fname + ".tmp");
// The text processing task
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version).
setSource(f).
setTarget(tmpTarget).run();
// Replace the source file with the processed file.
// We have to use Entities.deleteEntity here instead of Entities.delete since
// "delete" is a reserved word in JavaScript.
//
// Instead of replacing the source file manually, the TextReplaceTF could have
// been wrapped in a ReplaceSourceFileTF. See the next example.
Entities.deleteEntity(f);
Entities.rename(tmpTarget.getEntity(), fname);
}
# Process all XML files from the directory hierarchy under the EntityFS
#
Directory src. Change all occurrences of the text !!!VERSION!!!
# for the contents of the variable version.
# NOTE: This is not the easiest way to accomplish this. See the next example!
# Create a recursive, filtered iterator that returns all XML files in the
# directory hierarchy
itr = Schmant::
FilteringIterator.new(
# It does not matter if a depth first or breadth first iterator is used here.
Schmant::
Directories.getDepthFirstIterator($src),
# The EFileNameExtensionFilter does only return EntityFS
EFile:s.
Schmant::
EFileNameExtensionFilter.new("xml"))
# Iterate over all files returned by the iterator
while itr.hasNext()
f = itr.next()
fname = Schmant::
Entities.getName(f)
# The temporary file to put the process result in.
tmpTarget = Schmant::
FutureFile.new(
# Get f's parent directory.
Schmant::
Entities.getParent(f),
fname + ".tmp")
# The text processing task
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", $version).
setSource(f).
setTarget(tmpTarget).run()
# Replace the source file with the processed file.
#
# Instead of replacing the source file manually, the TextReplaceTF could have
# been wrapped in a ReplaceSourceFileTF. See the next example.
Schmant::
Entities.delete(f)
Schmant::
Entities.rename(tmpTarget.getEntity(), fname)
end
# Process all XML files from the directory hierarchy under the EntityFS
#
Directory src. Change all occurrences of the text !!!VERSION!!!
# for the contents of the variable version.
# NOTE: This is not the easiest way to accomplish this. See the next example!
# Create a recursive, filtered iterator that returns all XML files in the
# directory hierarchy
#
# It does not matter if a depth first or breadth first iterator is used here.
#
# The EFileNameExtensionFilter does only return EntityFS
EFile:s.
itr =
FilteringIterator( \
Directories.getDepthFirstIterator(src), \
EFileNameExtensionFilter("xml"))
# Iterate over all files returned by the iterator
while itr.hasNext():
f = itr.next()
fname =
Entities.getName(f)
# The temporary file to put the process result in.
tmpTarget =
FutureFile( \
Entities.getParent(f), \
fname + ".tmp")
# The text processing task
TextReplaceTF(). \
addReplace("!!!VERSION!!!", version). \
setSource(f). \
setTarget(tmpTarget).run()
# Replace the source file with the processed file.
#
# Instead of replacing the source file manually, the TextReplaceTF could have
# been wrapped in a ReplaceSourceFileTF. See the next example.
Entities.delete(f)
Entities.rename(tmpTarget.getEntity(), fname)
A simpler and more compact solution, however, is to use a
RecursiveActionTF or a RecursiveProcessTF.
Example 4.7. Set version information in all XML files in a directory hierarchy, overwriting
the original files, using a RecursiveActionTF
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.schmant.arg.DirectoryAndFilter
import org.schmant.task.meta.RecursiveActionTF
import org.schmant.task.proxy.ReplaceSourceFileTF
import org.schmant.task.text.TextReplaceTF
// Process all XML files from the directory hierarchy under the directory src.
// Change all occurrences of the text !!!VERSION!!! for the contents of the
// variable version.
//
// Use a RecursiveActionTF since we use a ReplaceSourceFileTF to replace the
// source file. ReplaceSourceFileTF is an action task since it does not have a
// separate target property.
new
RecursiveActionTF().
// src may for instance be a java.io.File directory or an EntityFS
//
Directory
setSource(new
DirectoryAndFilter(src, new
EFileNameExtensionFilter("xml"))).
setTaskFactory(
// The ReplaceSourceFileTF sets the target property of its nested task to be
// a temporary file. When the nested task has run, it overwrites the source
// file with the contents of the temporary file.
new
ReplaceSourceFileTF().
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version))).run()
// Process all XML files from the directory hierarchy under the directory src.
// Change all occurrences of the text !!!VERSION!!! for the contents of the
// variable version.
//
// Use a RecursiveActionTF since we use a ReplaceSourceFileTF to replace the
// source file. ReplaceSourceFileTF is an action task since it does not have a
// separate target property.
new
RecursiveActionTF().
// src may for instance be a java.io.File directory or an EntityFS
//
Directory
setSource(new
DirectoryAndFilter(src, new
EFileNameExtensionFilter("xml"))).
setTaskFactory(
// The ReplaceSourceFileTF sets the target property of its nested task to be
// a temporary file. When the nested task has run, it overwrites the source
// file with the contents of the temporary file.
new
ReplaceSourceFileTF().
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version))).run();
# Process all XML files from the directory hierarchy under the directory src.
# Change all occurrences of the text !!!VERSION!!! for the contents of the
# variable version.
#
# Use a RecursiveActionTF since we use a ReplaceSourceFileTF to replace the
# source file. ReplaceSourceFileTF is an action task since it does not have a
# separate target property.
Schmant::RecursiveActionTF.new.
# src may for instance be a java.io.File directory or an EntityFS
#
Directory
setSource(Schmant::
DirectoryAndFilter.new(
$src,
Schmant::
EFileNameExtensionFilter.new("xml"))).
setTaskFactory(
# The ReplaceSourceFileTF sets the target property of its nested task to be
# a temporary file. When the nested task has run, it overwrites the source
# file with the contents of the temporary file.
Schmant::ReplaceSourceFileTF.new.
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", $version))).run
# Process all XML files from the directory hierarchy under the directory src.
# Change all occurrences of the text !!!VERSION!!! for the contents of the
# variable version.
#
# Use a RecursiveActionTF since we use a ReplaceSourceFileTF to replace the
# source file. ReplaceSourceFileTF is an action task since it does not have a
# separate target property.
#
# src may for instance be a java.io.File directory or an EntityFS
Directory
#
# The ReplaceSourceFileTF sets the target property of its nested task to be a
# temporary file. When the nested task has run, it overwrites the source file
# the contents of the temporary file.
RecursiveActionTF(). \
setSource(
DirectoryAndFilter(src,
EFileNameExtensionFilter("xml"))). \
setTaskFactory( \
ReplaceSourceFileTF(). \
setTaskFactory( \
TextReplaceTF(). \
addReplace("!!!VERSION!!!", version))).run()
The two examples above does exactly the same thing. RecursiveActionTF
used together with ReplaceSourceFileTF solves the problem of keeping the
results from the text processing task in a temporary file before the original file
is replaced, so that that does not have to be implemented in the build script.
If the build script does not want to overwrite the original files, but
instead wants to put the results from the text processing in a new location, it
can use the RecursiveProcessTF. It creates
the target directory hierarchy and puts the created files where
they should be.
Example 4.8. Set version information in all XML files in a directory hierarchy, putting
the target files in a new directory hierarchy, using a recursive iterator.
import org.entityfs.util.*
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.entityfs.util.itr.FilteringIterator
import org.schmant.support.FutureFile
import org.schmant.task.text.TextReplaceTF
// Copy all XML files from the directory hierarchy under the directory src to
// a directory hierarchy under the directory target. Change all occurrences of
// the text !!!VERSION!!! for the contents of the variable version.
//
// Create a recursive, filtered iterator that returns all XML files in the
// directory hierarchy
def itr = new
FilteringIterator(
// It does not matter if a depth first or depth last iterator is used here
Directories.getDepthFirstIterator(src),
// The EFileNameExtensionFilter does only return _files_.
new
EFileNameExtensionFilter("xml"))
// Iterate over all files returned by the iterator
while(itr.hasNext())
{
def f = itr.next()
// f's location relative to src
def floc =
Entities.getRelativeLocation(f, src)
// The location of the target file
def tloc = new
FutureFile(target, floc)
// The text processing task
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version).
setSource(f).
setTarget(tloc).run()
}
// Copy all XML files from the directory hierarchy under the directory src to
// a directory hierarchy under the directory target. Change all occurrences of
// the text !!!VERSION!!! for the contents of the variable version.
//
// Create a recursive, filtered iterator that returns all XML files in the
// directory hierarchy
itr = new
FilteringIterator(
// It does not matter if a depth first or depth last iterator is used here
Directories.getDepthFirstIterator(src),
// The EFileNameExtensionFilter does only return _files_.
new
EFileNameExtensionFilter("xml"));
// Iterate over all files returned by the iterator
while(itr.hasNext()) {
f = itr.next();
// f's location relative to src
floc =
Entities.getRelativeLocation(f, src);
// The location of the target file
tloc = new
FutureFile(target, floc);
// The text processing task
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version).
setSource(f).
setTarget(tloc).run();
}
# Copy all XML files from the directory hierarchy under the directory src to
# a directory hierarchy under the directory target. Change all occurrences of
# the text !!!VERSION!!! for the contents of the variable version.
#
# Create a recursive, filtered iterator that returns all XML files in the
# directory hierarchy
itr = Schmant::
FilteringIterator.new(
# It does not matter if a depth first or depth last iterator is used here
Schmant::
Directories.getDepthFirstIterator($src),
# The EFileNameExtensionFilter does only return _files_.
Schmant::
EFileNameExtensionFilter.new("xml"))
# Iterate over all files returned by the iterator
while itr.hasNext
f = itr.next
# f's location relative to src
floc = Schmant::
Entities.getRelativeLocation(f, $src)
# The location of the target file
tloc = Schmant::
FutureFile.new($target, floc)
# The text processing task
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", $version).
setSource(f).
setTarget(tloc).run
end
# Copy all XML files from the directory hierarchy under the directory src to
# a directory hierarchy under the directory target. Change all occurrences of
# the text !!!VERSION!!! for the contents of the variable version.
#
# Create a recursive, filtered iterator that returns all XML files in the
# directory hierarchy
#
# It does not matter if a depth first or depth last iterator is used here
#
# The EFileNameExtensionFilter does only return _files_.
itr =
FilteringIterator( \
Directories.getDepthFirstIterator(src), \
EFileNameExtensionFilter("xml"))
# Iterate over all files returned by the iterator
while itr.hasNext():
f = itr.next()
# f's location relative to src
floc =
Entities.getRelativeLocation(f, src)
# The location of the target file
tloc =
FutureFile(target, floc)
# The text processing task
TextReplaceTF(). \
addReplace("!!!VERSION!!!", version). \
setSource(f). \
setTarget(tloc).run()
Example 4.9. Set version information in all XML files in a directory hierarchy, putting
the target files in a new directory hierarchy.
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.schmant.arg.DirectoryAndFilter
import org.schmant.task.meta.RecursiveProcessTF
import org.schmant.task.text.TextReplaceTF
// Copy all XML files from the directory hierarchy under the directory src to
// a directory hierarchy under the directory target. Change all occurrences of
// the text !!!VERSION!!! for the contents of the variable version.
//
// Use RecursiveProcessTF since the nested task factory needs both a source and
// a target property.
new
RecursiveProcessTF().
// src may for instance be a java.io.File directory or an EntityFS
//
Directory
setSource(new
DirectoryAndFilter(src,
new
EFileNameExtensionFilter("xml"))).
setTarget(target).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version)).run()
// Copy all XML files from the directory hierarchy under the directory src to
// a directory hierarchy under the directory target. Change all occurrences of
// the text !!!VERSION!!! for the contents of the variable version.
//
// Use RecursiveProcessTF since the nested task factory needs both a source and
// a target property.
new
RecursiveProcessTF().
// src may for instance be a java.io.File directory or an EntityFS
//
Directory
setSource(new
DirectoryAndFilter(src,
new
EFileNameExtensionFilter("xml"))).
setTarget(target).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version)).run();
# Copy all XML files from the directory hierarchy under the directory src to a
# directory hierarchy under the directory target. Change all occurrences of the
# text !!!VERSION!!! for the contents of the variable version.
#
# Use RecursiveProcessTF since the nested task factory needs both a source and a
# target property.
Schmant::RecursiveProcessTF.new.
# src may for instance be a java.io.File directory or an EntityFS
Directory
setSource(Schmant::
DirectoryAndFilter.new($src,
Schmant::
EFileNameExtensionFilter.new("xml"))).
setTarget($target).
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", $version)).run
# Copy all XML files from the directory hierarchy under the directory src to a
# directory hierarchy under the directory target. Change all occurrences of the
# text !!!VERSION!!! for the contents of the variable version.
#
# Use RecursiveProcessTF since the nested task factory needs both a source and a
# target property.
#
# src may for instance be a java.io.File directory or an EntityFS
#
Directory
RecursiveProcessTF(). \
setSource(
DirectoryAndFilter( \
src, \
EFileNameExtensionFilter("xml"))). \
setTarget(target). \
setTaskFactory( \
TextReplaceTF(). \
addReplace("!!!VERSION!!!", version)).run()
To determine where to put the target entities, the RecursiveProcessTF
uses can use either a TargetStrategy or a closure object.
In the examples above, the default target strategy
DefaultTargetStrategy is used. It puts the created
entities in the same location relative to the target
directory as the source entities have relative to the source
directory.
The table below lists the different available TargetStrategy:s.
Table 4.1. Target strategies
If a closure is used, it should take three parameters – an
EntityView (the processed entity), a
DirectoryView (the target base directory) and a
RelativeLocation (the source entity's location relative to the
source base directory). When executed, the closure should return something that
can be interpreted by the future entity interpreter (a
FutureEntity, for instance).
The following example shows how a closure can be used to
put big XML files in one directory hierarchy and small XML files in another.
Example 4.10. Set version information in all XML files in a directory hierarchy, putting
the target files in a two new directory hierarchies.
import org.entityfs.el.RelativeLocation
import org.entityfs.util.Files
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.schmant.arg.DirectoryAndFilter
import org.schmant.support.FutureFile
import org.schmant.task.meta.RecursiveProcessTF
import org.schmant.task.text.TextReplaceTF
// src is an EntityFS
DirectoryView.
new
RecursiveProcessTF().
setSource(new
DirectoryAndFilter(src, new
EFileNameExtensionFilter("xml"))).
setTarget(target).
setTargetStrategy({
// A closure
source, targetDir, relLoc ->
// Get the file size. The filter ensures that this closure will only be
// called with file sources.
def size =
Files.getSize(source)
def base
if (size < 2048)
{
// Put it in the "small" hierarchy
base = "small/"
}
else
{
// Put it in the "big" hierarchy
base = "big/"
}
// The relLoc argument is the source file's location relative to the source
// base directory (src)
def loc = new
RelativeLocation(base + relLoc.location)
return new
FutureFile(targetDir, loc)
}).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version)).run()
// src is an EntityFS
DirectoryView.
new
RecursiveProcessTF().
setSource(new
DirectoryAndFilter(src, new
EFileNameExtensionFilter("xml"))).
setTarget(target).
setTargetStrategy(
// A closure
function(source, targetDir, relLoc) {
// Get the file size. The filter ensures that this closure will only be
// called with file sources.
var size =
Files.getSize(source);
if (size < 2048) {
// Put it in the "small" hierarchy
var base = "small/";
} else {
// Put it in the "big" hierarchy
var base = "big/";
}
// The relLoc argument is the source file's location relative to the
// source base directory (src)
var loc = new
RelativeLocation(base + relLoc.getLocation());
return new
FutureFile(targetDir, loc);
}).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", version)).run();
# src is an EntityFS
DirectoryView.
#
# Use Proc.new to create a new function object
Schmant::RecursiveProcessTF.new.
setSource(
Schmant::
DirectoryAndFilter.new(
$src,
Schmant::
EFileNameExtensionFilter.new("xml"))).
setTarget($target).
setTargetStrategy(
Proc.new { |source, targetDir, relLoc|
# Get the size of the source file. The filter ensures that this closure
# will only be called with file arguments.
size = Schmant::
Files.getSize(source)
if size < 2048 then
# Put the file in the "small" hierarchy
base = "small/"
else
# Put the file in the "big" hierarchy
base = "big/"
end
# The "relLoc" variable is set to the source entity's location relative to
# the source base directory (src).
loc = Schmant::
RelativeLocation.new(base + relLoc.location)
# This object is returned from the script.
Schmant::
FutureFile.new(targetDir, loc)
}).
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", $version)).run
# src is an EntityFS
DirectoryView.
#
# Jython lacks closure support. We can get pretty close by using lambda
# expression or function variables.
# Define the function that will be used to calculate the target location. Since
# this function spans several lines, we cannot use an inline lambda expression
def generateTargetLocation(source, targetDir, relLoc):
# Get the file size. The filter ensures that this closure will only be called
# with file sources.
size =
Files.getSize(source)
if size < 2048:
# Put it in the "small" hierarchy
base = "small/"
else:
# Put it in the "big" hierarchy
base = "big/"
# The relLoc argument is the source file's location relative to the source
# base directory (src)
loc =
RelativeLocation(base + relLoc.getLocation())
return
FutureFile(targetDir, loc)
RecursiveProcessTF(). \
setSource(
DirectoryAndFilter(src,
EFileNameExtensionFilter("xml"))). \
setTarget(target). \
setTargetStrategy(generateTargetLocation). \
setTaskFactory(
TextReplaceTF().
addReplace("!!!VERSION!!!", version)).run()
Chapter 5. Environment and resources
In addition to all tasks, Schmant script programmers have access to an
environment that contains support and utility classes as well as some built-in script functions. The
built-in functions are script language-dependent and are discussed in the
scripting language guides.
Information and error output from scripts and tasks are written to a
Report instance. Every execution thread has its own
instance which can be retrieved using the ReportManager
method getReport()
. For some script languages,
the environment preparation script defines convenience functions for logging
from a script, see the scripting language guides. Scripts
written in other languages may use the Report methods directly, like
ReportManager.getReport().info("Info, info")
.
Just like any other logging API, output to a Report is
categorized using a log level. Report uses five different levels.
Table 5.1. Report logging levels
Level | Corresponding Java log Level | Description | Flags |
---|
error | SEVERE | Errors that are fatal enough to abort the script execution. | -q -q |
warning | WARNING | Warning messages; messages that the script operator should see, but that
are not fatal enough to stop the script execution. | -q |
info | INFO | Information messages, such as information about executing tasks. | |
debug | FINE | Debug messages that give more details about what tasks are doing. | -v |
trace | FINEST | Detailed messages that give a lot of information about what tasks are
doing. | -v -v |
The "Flags" column in the table above contains the flags that is used for
starting Schmant with the different log levels.
By default, the lowest level logged is info
,
which means that debug
and
trace
messages are discarded.
The log level can be changed in several ways:
By using the -v
or -q
flags one or several times when launching the build script. This sets the default log level
for all Report:s.
By calling ReportManager.getReport().setLevel(…)
directly from the script. The log level is set for the current thread's
Report and for all Report:s for threads that will subsequently be created
by the current thread.
By calling a task or task factory's setReportLevel
method. This changes the report level temporarily when running that task.
When a new execution thread is created, for instance by a
TaskExecutor, the created thread inherits the log level from
its parent thread.
There are different Report implementations. By default
StdoutReport is used. It logs output to standard out and
standard err. Use the -r
argument when launching
Schmant to use another ReportFactory for creating other types
of Report:s.
The RedirectReportTF task can be used to redirect the output from a
nested task to a file.
Schmant makes heavy use of EntityFS and
build scripts do also have access to it. EntityFilter:s, EntityFS'
utility classes and iterators are expressive tools for working with files and
directories. See below for an example.
Example 5.1. Iterating over files and directories
import java.util.concurrent.TimeUnit
import org.entityfs.util.*
import org.entityfs.util.filter.entity.*
import org.entityfs.util.filter.regexp.EntityNameGlobFilter
import org.entityfs.util.itr.FilteringIterator
// Write, to stdout, the contents of all XML files that have not been modified
// in the last six hours and that have a directory with a name that starts with
// "here" somewhere in their search paths. Iterate in the directory hierarchy
// under the
Directory d.
//
// Filters can be combined using Groovy operators (& is and)
itr = new
FilteringIterator(
Directories.getDepthLastIterator(d),
new
EFileNameExtensionFilter("xml") &
new
EntityRecentModificationFilter(6,
TimeUnit.HOURS) &
new
SuperParentOrFilter(
new
EntityNameGlobFilter("here*")))
while(itr.hasNext())
{
java.lang.System.out.println(
Files.readTextFile(itr.next()))
}
A common trick when using files checked out from Subversion is to hide
all .svn
directories using an
EntityFilter.
Example 5.2. Hiding .svn
directories when creating
an EclipseWorkspace
import org.entityfs.util.filter.entity.EntityNameFilter
import org.schmant.project.eclipse.EclipseWorkspace
// Create an
EclipseWorkspace from the contents of directory wos
// Hide all .svn directories.
//
// ~ is used to negate the filter
def wWos = new
EclipseWorkspace(
wos.newView(~(new
EntityNameFilter(".svn"))))
// Now the .svn directories in the workspace will be invisible.
# Create an
EclipseWorkspace from the contents of directory wos
# Hide all .svn directories.
wWos = Schmant::
EclipseWorkspace.new(
$wos.newView(Schmant::
EntityNameFilter.new(".svn").not))
# Now the .svn directories in the workspace will be invisible.
Build scripts are in no way required to use EntityFS objects. One reason
for not doing that is that Java's File:s are easier to use for someone
that is not familiar with EntityFS. A script may also mix and match EntityFS
entities and Java File:s. The next example shows how to move between the
two worlds.
Example 5.3. From Java files to EntityFS and back again
import java.io.File
import org.entityfs.util.cap.entity.ECFileResolvableUtil
import org.entityfs.util.cap.fs.FSCFileResolvableUtil
import org.schmant.support.entityfs.SchmantFileSystems
// Create a new
File object for the directory /home/me/workspace:
def d = new
File("/home/me/workspace")
// Create a new read/write and locking EntityFS
FileSystem with the root
// directory in d. The method returns the file system's root directory.
def rootDir =
SchmantFileSystems.getEntityForDirectory(d, false)
// Get the
File object representing the root directory.
def rootDirFile =
ECFileResolvableUtil.getFileObject(rootDir)
// Now rootDirFile.getCanonicalPath().equals(d.getCanonicalPath())
// (the getCanonicalPath() will resolve all symbolic links in the paths)
// Get the entity in the file system fs that corresponds to the
File rootDirFile
def rd =
FSCFileResolvableUtil.getEntityForFile(
rootDir.getFileSystem(),
rootDirFile)
// Now rd == rootDir
// Create a new
File object for the directory /home/me/workspace:
d = new
File("/home/me/workspace");
// Create a new read/write and locking EntityFS
FileSystem with the root
// directory in d. The method returns the file system's root directory.
rootDir =
SchmantFileSystems.getEntityForDirectory(d, false);
// Get the
File object representing the root directory.
rootDirFile =
ECFileResolvableUtil.getFileObject(rootDir);
// Now rootDirFile.getCanonicalPath().equals(d.getCanonicalPath())
// (the getCanonicalPath() will resolve all symbolic links in the paths)
// Get the entity in the file system fs that corresponds to the
File rootDirFile
rd =
FSCFileResolvableUtil.getEntityForFile(
rootDir.getFileSystem(),
rootDirFile);
// Now rd == rootDir
# Create a new
File object for the directory /home/me/workspace:
d = Java::JavaIo::
File.new("/home/me/workspace")
# Create a new read/write and locking EntityFS
FileSystem with the root
# directory in d. The method returns the file system's root directory.
rootDir = Schmant::
SchmantFileSystems.getEntityForDirectory(d, false)
# Get the
File object representing the root directory.
rootDirFile = Schmant::
ECFileResolvableUtil.getFileObject(rootDir)
# Now rootDirFile.getCanonicalPath().equals(d.getCanonicalPath())
# (the getCanonicalPath() will resolve all symbolic links in the paths)
# Get the entity in the file system fs that corresponds to the
File rootDirFile
# The org.entityfs.util.cap.fs package is not included in the Schmant module.
rd = Java::OrgEntityfsUtilCapFs::
FSCFileResolvableUtil.getEntityForFile(
rootDir.getFileSystem(),
rootDirFile)
# Now rd == rootDir
# Create a new
File object for the directory /home/me/workspace:
d =
File("/home/me/workspace")
# Create a new read/write and locking EntityFS
FileSystem with the root
# directory in d. The method returns the file system's root directory.
rootDir =
SchmantFileSystems.getEntityForDirectory(d, False)
# Get the
File object representing the root directory.
rootDirFile =
ECFileResolvableUtil.getFileObject(rootDir)
# Now rootDirFile.getCanonicalPath().equals(d.getCanonicalPath())
# (the getCanonicalPath() will resolve all symbolic links in the paths)
#
FSCFileResolvableUtil is not imported by Schmant
from org.entityfs.util.cap.fs import FSCFileResolvableUtil
# Get the entity in the file system fs that corresponds to the
File rootDirFile
rd =
FSCFileResolvableUtil.getEntityForFile( \
rootDir.getFileSystem(), \
rootDirFile)
# Now rd == rootDir
All tasks are not EntityFS-aware, though. Tasks that use external libraries
that don't use EntityFS or that launch external processes do not support EntityFS
entities. For those tasks, EntityFS entities are translated to Java File:s
in the argument interpretation process. Tasks that don't support EntityFS entities
cannot use nice EntityFS features such as directory views or in-memory file systems.
The task reference documentation for each Schmant task has information
on whether it supports EntityFS entities or not.
EntityFS-aware tasks support locking file systems that prevent that parallel
build threads accidentally modify the same files or directories. The file systems
returned by SchmantFileSystems' methods are all
locking, if that is not explicitly disabled.
For more information on using EntityFS with Schmant, see
Appendix B, EntityFS cookbook.
The following variables are available to all running scripts:
props
– PropertiesJava system properties and properties set using the
-p
argument when launching Schmant.
args
– List<String>Script arguments given after the script file on the command
line.
org_schmant_scriptFile
– EFileThe currently running script file (read only).
Environment variables are accessed through the
java.lang.System.getenv
method.
Schmant scripts can use EntityFS Properties
like in the example below.
Example 5.4. Using EntityFS property methods
import org.entityfs.util.Directories
import org.entityfs.util.properties.PropertiesUtil
// Load the build.properties file from the directory d
def buildProperties =
PropertiesUtil.loadFromFile(
Directories.getFile(d, "build.properties"))
// Set a couple of variables
def version = buildProperties.getStringValue("version")
def buildNo = buildProperties.getIntValue("buildNo", 17)
// Load the build.properties file from the directory d
buildProperties =
PropertiesUtil.loadFromFile(
Directories.getFile(d, "build.properties"));
// Set a couple of variables
version = buildProperties.getStringValue("version");
buildNo = buildProperties.getIntValue("buildNo", 17);
# Load the build.properties file from the directory d
buildProperties = Schmant::
PropertiesUtil.loadFromFile(
Schmant::
Directories.getFile($d, "build.properties"))
# Set a couple of variables
version = buildProperties.getStringValue("version")
buildNo = buildProperties.getIntValue("buildNo", 17)
# Load the build.properties file from the directory d
buildProperties =
PropertiesUtil.loadFromFile( \
Directories.getFile(d, "build.properties"))
# Set a couple of variables
version = buildProperties.getStringValue("version")
buildNo = buildProperties.getIntValue("buildNo", 17)
Schmant has several utility classes with static methods which can
be used by build scripts.
In addition to Schmant's own utility classes, many of EntityFS' utility
classes may be useful. See Appendix B, EntityFS cookbook.
Temporary files and directories
Build scripts often have to use temporary files for storing data that is
being processed, or sometimes even entire temporary directory hierarchies for,
for example, compiled classes before they are added to a Jar file.
The TempFileUtil class has a set of static methods
for creating temporary files and directories. Entities created using
TempFileUtil are automatically deleted when Schmant
exits (unless it was started with the -k
flag).
As long as the tasks that are used are EntityFS-aware, temporary
directories may well be in a RAM file system. The
SchmantFileSystems.createRamFileSystem() method
can be used to create a RAM file system configured for using with a build
script. See the section called “In-memory file systems”.
By default, (process) tasks that use temporary directories use the default
temporary directory of their target's file system.
TemporaryDirectoryConfigurable task factories can be
configured with a custom temporary files directory – a RAM file system
directory, for instance.
Including other script files
Just like for any other program, it sometimes makes sense to split a
build script into several different files. This is especially true for larger
and more complex build scripts. The mechanisms for including script files into
other script files are script language-dependent and are discussed in the
scripting language guides.
Several related tasks are bundled into task packages.
Task packages are used for distributing logical groupings of tasks and also
for isolating tasks with conflicting library dependencies. A task package is a
Zip file that contains task implementations and task reference documentation.
Schmant comes with a basic set of task packages. More task packages can be
downloaded from the Schmant site or from elsewhere on the Internet (well,
at least in the future, hopefully).
Extra task packages that scripts want to use are put in the Schmant
installation's task
directory or
listed on the Schmant command line with the
-t
option. See Chapter 3, Running Schmant. The task package Zip file can be used directly,
or it can be unpacked to a directory.
The org.schmant.task.base
task package is enabled by default. Other
task packages must be enabled by a build script before it can use it. Enabling a task
package adds a collection of Jar files to the running script's classpath and
imports a set of Java packages so that fully qualified class names don't have
to be used to reference task factories and other classes in the task package.
For most script languages, a task package is enabled by calling the
enableTaskPackage
method. See the
scripting language guides.
In the example below, the classes compiled in Example 4.2, “Compile Java files and build a Jar file”
are analyzed with Findbugs. The ExtFindbugsTF task factory is in the
net.findbugs
task package that is distributed
with Schmant.
Example 6.1. Enabling the Findbugs task package and running Findbugs.
// enableTaskPackage net.findbugs
// The net.findbugs task package is enabled in the script manifest above. The
// task package has to be enabled before the script is loaded so that the
// classes in the task package are available to be imported below.
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import org.entityfs.util.Directories
import org.schmant.support.io.TempFileUtil
import org.schmant.support.entityfs.SchmantFileSystems
import org.schmant.task.findbugs.*
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// Compile files from the directory /home/me/myproject/src and put the resulting
// class files in a temporary directory. The source files have compilation
// dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
// /home/me/myproject/optlib/opt.jar
// A temporary java.io.File directory for the compiled classes. This, along with
// all of its contents, will be automatically deleted when Schmant exits (unless
// the -k flag is used).
def ctarget =
TempFileUtil.createTempDir()
// This is the lib directory expressed as a (read only) EntityFS
Directory.
// By using a
Directory, the script can use the utility methods of
//
Directories to extract the files it wants. See below.
def libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true)
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
def depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"))
// Compile the Java source files.
new
Jdk6JavacTF().
addSource(new
File("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run()
// A timestamp for the built archive
def timestamp = new
SimpleDateFormat("yyyyMMddHHmm").
format(new
Date())
// Build the Jar file. Put it in /home/me/myproject
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")).
run()
// Run Findbugs.
new
ExtFindbugsTF().
// If the findbugsExecutable property is not set, the task looks for the
// findbugs executable by checking if the FINDBUGS_HOME environment variable
// is set. If not, it searches for findbugs in the directories referenced by
// the PATH environment variable.
// setFindbugsExecutable("c:\\Java\\findbugs\\bin\\findbugs.bat").
//
// Add classpath dependencies that should not be analyzed
addAuxClasspathEntries(depjars).
// Add all source files
addSourceCodeContainer(new
File("/home/me/myproject/src")).
// The compiled classes are located under the directory ctarget (and
// subdirectories)
addSource(ctarget).
setFindbugsReportFormat(
FindbugsReportFormat.HTML).
setTarget(
new
File("/home/me/myproject/myproject" + timestamp + ".html")).run()
// Compile files from the directory /home/me/myproject/src and put the resulting
// class files in a temporary directory. The source files have compilation
// dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
// /home/me/myproject/optlib/opt.jar
// A temporary java.io.File directory for the compiled classes. This, along with
// all of its contents, will be automatically deleted when Schmant exits (unless
// the -k flag was used when Schmant was launched).
ctarget =
TempFileUtil.createTempDir();
// This is the lib directory expressed as a (read only) EntityFS
Directory.
// By using a
Directory, the script can use the utility methods of
//
Directories to extract the files it wants. See below.
libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true);
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar");
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"));
// Compile the Java source files.
new
Jdk6JavacTF().
addSource(new
File("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run();
// A timestamp for the built archive
// java.text.SimpleDateFormat must be fully qualified since the java.text
// package classes are not automatically imported by Schmant. The "Packages"
// prefix is for telling JavaScript that it is a Java package. It is required
// sometimes. See the chapter on script language support for more information.
timestamp = new Packages.java.text.SimpleDateFormat("yyyyMMddHHmm").
format(new
Date());
// Build the Jar file. Put it in /home/me/myproject
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")).
run();
enableTaskPackage("net.findbugs");
// Run Findbugs.
new
ExtFindbugsTF().
// If the findbugsExecutable property is not set, the task looks for the
// findbugs executable by checking if the FINDBUGS_HOME environment variable
// is set. If not, it searches for findbugs in the directories referenced by
// the PATH environment variable.
// setFindbugsExecutable("c:\\Java\\findbugs\\bin\\findbugs.bat").
//
// Add classpath dependencies that should not be analyzed
addAuxClasspathEntries(depjars).
// Add all source files
addSourceCodeContainer(new
File("/home/me/myproject/src")).
// The compiled classes are located under the directory ctarget (and
// subdirectories)
addSource(ctarget).
setFindbugsReportFormat(
FindbugsReportFormat.HTML).
setTarget(
new
File("/home/me/myproject/myproject" + timestamp + ".html")).run();
# Compile files from the directory /home/me/myproject/src and put the resulting
# class files in a temporary directory. The source files have compilation
# dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
# /home/me/myproject/optlib/opt.jar
# A temporary java.io.File directory for the compiled classes. This, along with
# all of its contents, will be automatically deleted when Schmant exits (unless
# the -k flag was used when Schmant was launched).
ctarget = Schmant::
TempFileUtil.createTempDir
# This is the lib directory expressed as a (read only) EntityFS
Directory.
# By using a
Directory, the script can use the utility methods of
#
Directories to extract the files it wants. See below.
#
#
File is not included in the Java module, so we have to access it through the
# Java module instead.
libDir = Schmant::
SchmantFileSystems.getEntityForDirectory(
Java::JavaIo::
File.new("/home/me/myproject/lib"), true)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars = Schmant::
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add(Java::JavaIo::
File.new("/home/me/myproject/optlib/opt.jar"))
# Compile the Java source files.
Schmant::Jdk6JavacTF.new.
addSource(Java::JavaIo::
File.new("/home/me/myproject/src")).
addClasspathEntries(depjars).
setTarget(ctarget).run
# A timestamp for the built archive
timestamp = Java::JavaText::SimpleDateFormat.new("yyyyMMddHHmm").
format(Java::JavaUtil::
Date.new)
# Build the Jar file. Put it in /home/me/myproject
Schmant::JarTF.new.
addSource(ctarget).
setTarget(
Java::JavaIo::
File.new("/home/me/myproject/myproject" + timestamp + ".jar")).
run
enableTaskPackage "net.findbugs"
# Run Findbugs.
Schmant::ExtFindbugsTF.new.
# If the findbugsExecutable property is not set, the task looks for the
# findbugs executable by checking if the FINDBUGS_HOME environment variable
# is set. If not, it searches for findbugs in the directories referenced by
# the PATH environment variable.
# setFindbugsExecutable("c:\\Java\\findbugs\\bin\\findbugs.bat").
#
# Add classpath dependencies that should not be analyzed
addAuxClasspathEntries(depjars).
# Add all source files
addSourceCodeContainer(Java::JavaIo::
File.new("/home/me/myproject/src")).
# The compiled classes are located under the directory ctarget (and
# subdirectories)
addSource(ctarget).
setFindbugsReportFormat(Schmant::
FindbugsReportFormat::HTML).
setTarget(
Java::JavaIo::
File.new("/home/me/myproject/myproject" + timestamp + ".html")).
run
# Compile files from the directory /home/me/myproject/src and put the resulting
# class files in a temporary directory. The source files have compilation
# dependencies to all Jar files in /home/me/myproject/lib and to the Jar file
# /home/me/myproject/optlib/opt.jar
# A temporary java.io.File directory for the compiled classes. This, along with
# all of its contents, will be automatically deleted when Schmant exits (unless
# the -k flag was used when Schmant was launched).
ctarget =
TempFileUtil.createTempDir()
# This is the lib directory expressed as a (read only) EntityFS
Directory.
# By using a
Directory, the script can use the utility methods of
#
Directories to extract the files it wants. See below.
libDir =
SchmantFileSystems.getEntityForDirectory(
File("/home/me/myproject/lib"), True)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add(
File("/home/me/myproject/optlib/opt.jar"))
# Compile the Java source files.
Jdk6JavacTF(). \
addSource(
File("/home/me/myproject/src")). \
addClasspathEntries(depjars). \
setTarget(ctarget).run()
# A timestamp for the built archive
#
#
SimpleDateFormat is not automatically imported by the preparation
# script.
from java.text import SimpleDateFormat
timestamp =
SimpleDateFormat("yyyyMMddHHmm"). \
format(
Date())
# Build the Jar file. Put it in /home/me/myproject
JarTF(). \
addSource(ctarget). \
setTarget(
File("/home/me/myproject/myproject" + timestamp + ".jar")). \
run()
enableTaskPackage("net.findbugs")
# Run Findbugs.
#
# If the findbugsExecutable property is not set, the task looks for the
# findbugs executable by checking if the FINDBUGS_HOME environment variable is
# set. If not, it searches for findbugs in the directories referenced by the
# PATH environment variable.
#
# The compiled classes are located under the directory ctarget (and
# subdirectories)
ExtFindbugsTF(). \
addAuxClasspathEntries(depjars). \
addSourceCodeContainer(
File("/home/me/myproject/src")). \
addSource(ctarget). \
setFindbugsReportFormat(
FindbugsReportFormat.HTML). \
setTarget( \
File("/home/me/myproject/myproject" + timestamp + ".html")).run()
If a script uses task packages containing tasks with conflicting library
dependencies, Schmant can be run with the classloader argument
-c isolated
, giving all task packages their own
java.lang.ClassLoader
:s. See
Chapter 9, Advanced topics.
The task packages that are included with Schmant, are listed in the
Task package index. The
Task Author's Guide contains information on how to create
new task packages.
Chapter 7. Task executors
Modern computers with multiple processor cores, hyperthreading and whatnot are
designed for running several threads in parallel. It would be a shame to let
all that processing power be wasted by running just single threaded builds, wouldn't it?
Using several build threads may even give a speed
boost on a uniprocessor system since different tasks have different performance
profiles – some tasks are CPU bound, others are I/O bound.
Schmant comes with the TaskExecutor for distributing task
execution over a collection of threads. Tasks are added to the task executor
and it executes them in parallel using a fixed number of threads from a thread
pool.
This performance boost does come with added complexity, though. When parallelizing
tasks, dependencies between different tasks have to be dealt with; Task B may need the
results from Task A to run. In Schmant, that task B is
dependent on task A is
expressed by adding an object implementing the TaskDependency
interface together with B to the task executor.
Task, but not TaskFactory, does that, so
A can be used to express the dependency when adding
B.
A TaskExecutor is used by
Creating it.
Adding tasks to it.
Calling waitFor()
on it to wait
for all tasks to be run.
Calling shutdown()
to release
all of its resources.
A script can also call waitFor(TaskDependency)
to wait for a specific dependency to be met.
Example 7.1. Compile Java files and build a Jar file, using a task executor
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import org.entityfs.util.*
import org.entityfs.util.filter.entity.*
import org.schmant.run.TaskExecutor
import org.schmant.support.entityfs.*
import org.schmant.support.io.*
import org.schmant.task.io.TreeCopyTF
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// A temporary directory for the compiled classes. This, along with all of its
// contents, will be automatically deleted when Schmant exits (unless the -k
// flag is used).
def ctarget =
TempFileUtil.createTempDir()
// The lib directory that contains Jar files that the code to compile uses.
def libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true)
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
def depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"))
// Create a read only, locking
Directory object for the source directory.
// A locking file system eliminates the risk of accidental concurrent
// modification of files by parallel build threads.
def src =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/src"),
true)
// Create a task executor that, by default, will use two build threads, unless
// the noOfBuildThreads property is set to another value.
def te = new
TaskExecutor().
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)).
start()
try {
// A task for compiling all source files
//
TaskExecutor.add returns a
TaskDependency object for the added task.
def javacDep = te.add(
new
Jdk6JavacTF().
addSource(src).
addClasspathEntries(depjars).
setTarget(ctarget))
// A task for copying all non-Java files from the source hierarchy to the
// target hierarchy.
def copyDep = te.add(
new
TreeCopyTF().
// Create a view of the source hierarchy that hides all .java files.
setSource(
src.newView(
// Groovy lets us use the ~ operator to negate the filter.
~ new
EFileNameExtensionFilter("java"))).
setTarget(ctarget))
// A timestamp for the built archive
def timestamp = new
SimpleDateFormat("yyyyMMddHHmm").
format(new
Date())
// Build the Jar file. Put it in /home/me/myproject
// The Jar build task depends on both the compiling and the copying tasks.
te.add(
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")),
// Use an array for the two dependencies.
[javacDep, copyDep])
// Wait for all tasks to complete
te.waitFor()
} finally {
te.shutdown()
}
// A temporary directory for the compiled classes. This, along with all of its
// contents, will be automatically deleted when Schmant exits (unless the -k
// flag is used).
ctarget =
TempFileUtil.createTempDir();
// The lib directory that contains Jar files that the code to compile uses.
libDir =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/lib"), true);
// Get all jar files from the lib directory. The jar files are returned as a
//
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar");
// Add a dependency from the optlib directory. This is a Java
File
depjars.add(new
File("/home/me/myproject/optlib/opt.jar"));
// Create a read only, locking
Directory object for the source directory.
// A locking file system eliminates the risk of accidental concurrent
// modification of files by parallel build threads.
src =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/src"),
true);
// Create a task executor that, by default, will use two build threads, unless
// the noOfBuildThreads property is set to another value.
te = new
TaskExecutor().
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)).
start();
try {
// A task for compiling all source files
//
TaskExecutor.add returns a
TaskDependency object for the added task.
javacDep = te.add(
new
Jdk6JavacTF().
addSource(src).
addClasspathEntries(depjars).
setTarget(ctarget));
// A task for copying all non-Java files from the source hierarchy to the
// target hierarchy.
copyDep = te.add(
new
TreeCopyTF().
// Create a view of the source hierarchy that hides all .java files.
setSource(
src.newView(
new
EFileNameExtensionFilter("java").not())).
setTarget(ctarget));
// A timestamp for the built archive
timestamp = new Packages.java.text.SimpleDateFormat("yyyyMMddHHmm").
format(new
Date());
// Build the Jar file. Put it in /home/me/myproject
// The Jar build task depends on both the compiling and the copying tasks.
te.add(
new
JarTF().
addSource(ctarget).
setTarget(new
File("/home/me/myproject/myproject" + timestamp + ".jar")),
// Use an array for the two dependencies
[javacDep, copyDep]);
// Wait for all tasks to complete
te.waitFor();
} finally {
te.shutdown();
}
# A temporary directory for the compiled classes. This, along with all of its
# contents, will be automatically deleted when Schmant exits (unless the -k
# flag is used).
ctarget = Schmant::
TempFileUtil.createTempDir
# The lib directory that contains Jar files that the code to compile uses.
libDir = Schmant::
SchmantFileSystems.getEntityForDirectory(
Java::JavaIo::
File.new("/home/me/myproject/lib"), true)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars = Schmant::
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add Java::JavaIo::
File.new("/home/me/myproject/optlib/opt.jar")
# Create a read only, locking
Directory object for the source directory.
# A locking file system eliminates the risk of accidental concurrent
# modification of files by parallel build threads.
src = Schmant::SchmantFileSystems.getEntityForDirectory(
Java::JavaIo::
File.new("/home/me/myproject/src"),
true)
# Create a task executor that, by default, will use two build threads, unless
# the noOfBuildThreads property is set to another value.
te = Schmant::
TaskExecutor.new.
setNumberOfThreads($props.getIntValue("noOfBuildThreads", 2)).
start
begin
# A task for compiling all source files
#
TaskExecutor.add returns a
TaskDependency object for the added task.
javacDep = te.add(
Schmant::Jdk6JavacTF.new.
addSource(src).
addClasspathEntries(depjars).
setTarget(ctarget))
# A task for copying all non-Java files from the source hierarchy to the
# target hierarchy.
copyDep = te.add(
Schmant::TreeCopyTF.new.
# Create a view of the source hierarchy that hides all .java files.
setSource(
src.newView(
Schmant::
EFileNameExtensionFilter.new("java").not)).
setTarget(ctarget))
# A timestamp for the built archive
# SimpleDateFormat and Date are not included in the Schmant module, so we have
# to access them through the Java module instead.
ts = Java::JavaText::SimpleDateFormat.new("yyyyMMddHHmm").
format(Java::JavaUtil::
Date.new)
# Build the Jar file. Put it in /home/me/myproject
# The Jar build task depends on both the compiling and the copying tasks.
te.add(
Schmant::JarTF.new.
addSource(ctarget).
setTarget(
Java::JavaIo::
File.new("/home/me/myproject/myproject" + ts + ".jar")),
# Use an array for the two dependencies
[javacDep, copyDep])
# Wait for all tasks to complete
te.waitFor
ensure
te.shutdown
end
# A temporary directory for the compiled classes. This, along with all of its
# contents, will be automatically deleted when Schmant exits (unless the -k
# flag is used).
ctarget =
TempFileUtil.createTempDir()
# The lib directory that contains Jar files that the code to compile uses.
libDir =
SchmantFileSystems.getEntityForDirectory(
File("/home/me/myproject/lib"), True)
# Get all jar files from the lib directory. The jar files are returned as a
#
Set of EntityFS
EFile:s
depjars =
Directories.getAllFilesMatching(libDir, "*.jar")
# Add a dependency from the optlib directory. This is a Java
File
depjars.add(
File("/home/me/myproject/optlib/opt.jar"))
# Create a read only, locking
Directory object for the source directory.
# A locking file system eliminates the risk of accidental concurrent
# modification of files by parallel build threads.
src =
SchmantFileSystems.getEntityForDirectory(
File("/home/me/myproject/src"), \
True)
# Create a task executor that, by default, will use two build threads, unless
# the noOfBuildThreads property is set to another value.
te =
TaskExecutor(). \
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)). \
start()
try:
# A task for compiling all source files
#
TaskExecutor.add returns a
TaskDependency object for the added task.
javacDep = te.add(
Jdk6JavacTF(). \
addSource(src). \
addClasspathEntries(depjars). \
setTarget(ctarget))
# A task for copying all non-Java files from the source hierarchy to the
# target hierarchy.
#
# Create a view of the source hierarchy that hides all .java files.
copyDep = te.add(
TreeCopyTF(). \
setSource(
src.newView(
EFileNameExtensionFilter("java").not())). \
setTarget(ctarget))
# A timestamp for the built archive
#
#
SimpleDateFormat is not automatically imported by the preparation
# script.
from java.text import SimpleDateFormat
timestamp =
SimpleDateFormat("yyyyMMddHHmm"). \
format(
Date())
# Build the Jar file. Put it in /home/me/myproject
# The Jar build task depends on both the compiling and the copying
# tasks.
#
# Use a list for the two dependencies
te.add(
JarTF(). \
addSource(ctarget). \
setTarget(
File("/home/me/myproject/myproject" + timestamp + ".jar")), \
[javacDep, copyDep])
# Wait for all tasks to complete
te.waitFor()
finally:
te.shutdown()
When scheduling a task that depends on the results from several other
tasks, its dependencies can be given in any of the following ways:
By listing all dependencies on the add
method's argument list. Since scripts cannot use variable length argument
lists[], all dependencies have to be listed in an array. In JavaScript,
this is expressed as te.add(task
,
[dep1
, dep2
,
…, depN
])
.
By passing in all dependencies in a Collection, such
as an ArrayList.
By using an CompoundTaskDependency.
The task executor will behave in the same way regardless of the way that the dependencies
are given. Just use the way that is most convenient for the task being scheduled.
Task factories may be reused to create several tasks:
Example 7.2. Reusing a task factory
import org.schmant.run.TaskExecutor
import org.schmant.task.jdk.java.JavaTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// Create a task executor with two parallel build threads
def te = new
TaskExecutor().
setNumberOfThreads(2).
start()
try {
// Compile the source files in the src directory (hierarchy) and put the
// classes in bin
javacTask = new
Jdk6JavacTF().
setSource(src).
setTarget(bin).create()
te.add(javacTask)
// Create a task factory for running a Java program
tf = new
JavaTF().
setClassToRun("Test1").
// This is not configurable. Use the global variable bin.
addClasspathEntry(bin)
// Configure the task factory for the first program run, create a task and add
// it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=first"),
javacTask)
// Configure the task factory for the second program run, create a task and
// add it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=second"),
javacTask)
// Wait for all tasks to finish
te.waitFor()
} finally {
te.shutdown()
}
// Create a task executor with two parallel build threads
te = new
TaskExecutor().
setNumberOfThreads(2).
start();
try {
// Compile the source files in the src directory (hierarchy) and put the
// classes in bin
javacTask = new
Jdk6JavacTF().
setSource(src).
setTarget(bin).create();
te.add(javacTask);
// Create a task factory for running a Java program
tf = new
JavaTF().
setClassToRun("Test1").
// This is not configurable. Use the global variable bin.
addClasspathEntry(bin);
// Configure the task factory for the first program run, create a task and add
// it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=first"),
javacTask);
// Configure the task factory for the second program run, create a task and
// add it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=second"),
javacTask);
// Wait for all tasks to finish
te.waitFor();
} finally {
te.shutdown();
}
# Create a task executor with two parallel build threads
te = Schmant::
TaskExecutor.new.
setNumberOfThreads(2).
start
begin
# Compile the source files in the src directory (hierarchy) and put the
# classes in bin
javacTask = Schmant::Jdk6JavacTF.new.
setSource($src).
setTarget($bin).create
te.add(javacTask)
# Create a task factory for running a Java program
tf = Schmant::JavaTF.new.
setClassToRun("Test1").
# This is not configurable. Use the global variable bin.
addClasspathEntry($bin)
# Configure the task factory for the first program run, create a task and add
# it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=first"),
javacTask)
# Configure the task factory for the second program run, create a task and
# add it to the task executor
te.add(
tf.addJvmOption("-Dexample.config=second"),
javacTask)
# Wait for all tasks to finish
te.waitFor
ensure
te.shutdown
end
# Create a task executor with two parallel build threads
te =
TaskExecutor(). \
setNumberOfThreads(2). \
start()
try:
# Compile the source files in the src directory (hierarchy) and put the
# classes in bin
javacTask = Jdk6JavacTF(). \
setSource(src). \
setTarget(bin).create()
te.add(javacTask)
# Create a task factory for running a Java program
#
# Use the classpath entries from the global variable "bin"
tf = JavaTF(). \
setClassToRun("Test1"). \
addClasspathEntry(bin)
# Configure the task factory for the first program run, create a task and add
# it to the task executor
te.add( \
tf.addJvmOption("-Dexample.config=first"), \
javacTask)
# Configure the task factory for the second program run, create a task and
# add it to the task executor
te.add( \
tf.addJvmOption("-Dexample.config=second"), \
javacTask)
# Wait for all tasks to finish
te.waitFor()
finally:
te.shutdown()
Instead of registering a task with a TaskExecutor, a script
may register a closure instead. A closure is a block of code that has access to
the variables of the surrounding scope, but that may be executed at a later time.
See Wikipedia article on closure.
Example 7.3. Running a closure with a task executor
// enableTaskPackage org.at4j
import org.entityfs.util.*
import org.schmant.run.TaskExecutor
import org.schmant.support.FutureFile
import org.schmant.task.at4j.tar.TarTF
// After tar:ing the directory d to a file in the directory targetD, create an
// info file containing the size of the tar file.
def te = new
TaskExecutor().
start()
try
{
def targetFile = new
FutureFile(targetD, "d.tar")
def tarTask = new
TarTF().
setSource(d).
setTarget(targetFile).create()
te.add(tarTask)
// Add a closure for creating the information file. This closure does not have
// any arguments, but it has access to the variables in the scope where it is
// defined.
// Note the curly braces.
te.add(
{
def sz =
Files.getSize(targetFile.file)
def infoFile =
Directories.newFile(targetD, "d.txt")
Files.writeText(infoFile, "" + sz)
},
// The tar task must be run before the closure.
tarTask)
te.waitFor()
}
finally
{
te.shutdown()
}
// After tar:ing the directory d to a file in the directory targetD, create an
// info file containing the size of the tar file.
enableTaskPackage("org.at4j");
te = new
TaskExecutor().
start();
try {
targetFile = new
FutureFile(targetD, "d.tar");
tarTask = new
TarTF().
setSource(d).
setTarget(targetFile).create();
te.add(tarTask);
// Add a closure for creating the information file. This closure does not have
// any arguments, but it has access to the variables in the scope where it is
// defined.
te.add(function() {
var sz =
Files.getSize(targetFile.getFile());
var infoFile =
Directories.newFile(targetD, "d.txt");
Files.writeText(infoFile, "" + sz);
},
// The tar task must be run before the closure.
tarTask);
te.waitFor();
} finally {
te.shutdown();
}
# After tar:ing the directory d to a file in the directory targetD, create an
# info file containing the size of the tar file.
enableTaskPackage "org.at4j"
te = Schmant::
TaskExecutor.new.
start();
begin
targetFile = Schmant::
FutureFile.new($targetD, "d.tar")
tarTask = Schmant::TarTF.new.
setSource($d).
setTarget(targetFile).create
te.add tarTask
# Bind the global variable to a variable defined in this scope. The closure
# does not have access to the global variable.
targetDir = $targetD
# Add a closure for creating the information file. This closure does not have
# any arguments, but it has access to the variables from the scope where it is
# defined.
te.add(
Proc.new {
sz = Schmant::
Files.getSize targetFile.file
infoFile = Schmant::
Directories.newFile(targetDir, "d.txt")
Schmant::
Files.writeText(infoFile, sz.to_s)
},
# The tar task must be run before the closure.
tarTask)
te.waitFor
ensure
te.shutdown
end
# After tar:ing the directory d to a file in the directory targetD, create an
# info file containing the size of the tar file.
enableTaskPackage("org.at4j")
te =
TaskExecutor().start()
try:
targetFile =
FutureFile(targetD, "d.tar")
tarTask = TarTF(). \
setSource(d). \
setTarget(targetFile).create()
te.add(tarTask)
# Jython does not support closures. Function variables and lambda functions
# may be used instead
#
# The function that will create the info file. It uses global variables and
# the targetFile variable that is defined in this scope.
def createInfoFile():
sz =
Files.getSize(targetFile.getFile())
infoFile =
Directories.newFile(targetD, "d.txt")
Files.writeText(infoFile, str(sz))
# Add the function that creates the info file to the task executor. It must be
# run after the tar task.
#
# The function cannot take any arguments.
te.add(createInfoFile, tarTask)
te.waitFor()
finally:
te.shutdown()
Closures may be used in several places in Schmant, for instance:
The next example shows how a closure may be used instead of a task factory
when running a RecursiveActionTF.
Example 7.4. Using a closure instead of a task factory
// enableTaskPackage org.at4j
import org.at4j.comp.CompressionLevel
import org.entityfs.util.*
import org.entityfs.util.filter.entity.EFileNameExtensionFilter
import org.schmant.arg.DirectoryAndFilter
import org.schmant.run.TaskExecutor
import org.schmant.support.FutureFile
import org.schmant.task.at4j.bzip2.BZip2TF
import org.schmant.task.meta.RecursiveActionTF
// For each text file in the directory hierarchy under the directory d, bzip2 it
// with medium compression if it is less than 4096 bytes, and with high
// compression if it is larger
// Use this task executor to run the tasks.
def te = new
TaskExecutor().
setNumberOfThreads(2).
start()
try
{
// This constant will be used in the closure, which demonstrates that the
// closure has access to the surrounding scope
def MIN_SIZE_FOR_MAX_COMPRESSION = 4096 // bytes
new
RecursiveActionTF().
// Use a filter that will only return text files
setSource(new
DirectoryAndFilter(
d,
new
EFileNameExtensionFilter(".txt"))).
// Instead of running the entire RecursiveActionTF in the task executor, use
// it to run each task that the RecursiveActionTF creates instead.
setTaskExecutor(te).
// Use a closure instead of a task. Since this is run in a
// RecursiveActionTF, the closure will be called with a
ClosureParameters
// object containing a source entity. If the task would have been run in a
// RecursiveProcessTF instead, the
ClosureParameters object would
// have contained both a source and a target entity.
//
// Note the curly braces
setTaskFactory{
cParams ->
def textFile = cParams.source
def targetName =
Entities.getName(textFile) + ".bz2"
def targetDir =
Entities.getParent(textFile)
// Medium or high compression?
def compressionLevel
if (
Files.getSize(textFile) < MIN_SIZE_FOR_MAX_COMPRESSION)
{
compressionLevel =
CompressionLevel.DEFAULT
}
else
{
compressionLevel =
CompressionLevel.BEST
}
new
BZip2TF().
setSource(textFile).
setTarget(new
FutureFile(targetDir, targetName)).
setCompressionLevel(compressionLevel).run()
}.run()
te.waitFor()
}
finally
{
te.shutdown()
}
// For each text file in the directory hierarchy under the directory d, bzip2 it
// with medium compression if it is less than 4096 bytes, and with high
// compression if it is larger
enableTaskPackage("org.at4j");
// Use this task executor to run the tasks.
te = new
TaskExecutor().
setNumberOfThreads(2).
start();
try {
// This constant will be used in the closure, which demonstrates that the
// closure has access to the surrounding scope
var MIN_SIZE_FOR_MAX_COMPRESSION = 4096; // bytes
new
RecursiveActionTF().
// Use a filter that will only return text files
setSource(new
DirectoryAndFilter(
d,
new
EFileNameExtensionFilter(".txt"))).
// Instead of running the entire RecursiveActionTF in the task executor, use
// it to run each task that the RecursiveActionTF creates instead.
setTaskExecutor(te).
// Use a closure instead of a task. Since this is run in a
// RecursiveActionTF, the closure will be called with a
ClosureParameters
// object containing a source entity. If the task would have been run in a
// RecursiveProcessTF instead, the
ClosureParameters object would
// have contained both a source and a target entity.
setTaskFactory(function(cParams) {
var textFile = cParams.source;
var targetName =
Entities.getName(textFile) + ".bz2";
var targetDir =
Entities.getParent(textFile);
// Medium or high compression?
if (
Files.getSize(textFile) < MIN_SIZE_FOR_MAX_COMPRESSION) {
var compressionLevel =
CompressionLevel.DEFAULT;
} else {
var compressionLevel =
CompressionLevel.BEST;
}
new
BZip2TF().
setSource(textFile).
setTarget(new
FutureFile(targetDir, targetName)).
setCompressionLevel(compressionLevel).run();
}).run();
te.waitFor();
} finally {
te.shutdown();
}
# For each text file in the directory hierarchy under the directory d, bzip2 it
# with medium compression if it is less than 4096 bytes, and with high
# compression if it is larger
enableTaskPackage "org.at4j"
# Use this task executor to run the tasks.
te = Schmant::
TaskExecutor.new.
setNumberOfThreads(2).
start
begin
# This constant will be used in the closure, which demonstrates that the
# closure has access to the surrounding scope
MIN_SIZE_FOR_MAX_COMPRESSION = 4096; # bytes
Schmant::RecursiveActionTF.new.
# Use a filter that will only return text files
setSource(Schmant::
DirectoryAndFilter.new(
$d,
Schmant::
EFileNameExtensionFilter.new(".txt"))).
# Instead of running the entire RecursiveActionTF in the task executor, use
# it to run each task that the RecursiveActionTF creates instead.
setTaskExecutor(te).
# Use a closure instead of a task. Since this is run in a
# RecursiveActionTF, the closure will be called with a
ClosureParameters
# object containing a source entity. If the task would have been run in a
# RecursiveProcessTF instead, the
ClosureParameters object would
# have contained both a source and a target entity.
setTaskFactory(
Proc.new { |cParams|
textFile = cParams.source
targetName = Schmant::
Entities.getName(textFile) + ".bz2"
targetDir = Schmant::
Entities.getParent(textFile)
# Medium or high compression?
if Schmant::
Files.getSize(textFile) < MIN_SIZE_FOR_MAX_COMPRESSION
compressionLevel = Schmant::
CompressionLevel::DEFAULT
else
compressionLevel = Schmant::
CompressionLevel::BEST
end
Schmant::BZip2TF.new.
setSource(textFile).
setTarget(Schmant::
FutureFile.new(targetDir, targetName)).
setCompressionLevel(compressionLevel).run
}).run
te.waitFor
ensure
te.shutdown
end
# For each text file in the directory hierarchy under the directory d, bzip2 it
# with medium compression if it is less than 4096 bytes, and with high
# compression if it is larger
# Jython does not support closures. The closest that we can get is using lambda
# expressions or function variables
#
# Define the function that will be called by the task
def compressFile(cParams):
textFile = cParams.getSource()
targetName =
Entities.getName(textFile) + ".bz2"
targetDir =
Entities.getParent(textFile)
# Medium or high compression?
if
Files.getSize(textFile) < MIN_SIZE_FOR_MAX_COMPRESSION:
compressionLevel =
CompressionLevel.DEFAULT
else:
compressionLevel =
CompressionLevel.BEST
BZip2TF(). \
setSource(textFile). \
setTarget(
FutureFile(targetDir, targetName)). \
setCompressionLevel(compressionLevel).run()
enableTaskPackage("org.at4j")
# Use this task executor to run the tasks.
te =
TaskExecutor(). \
setNumberOfThreads(2). \
start()
try:
# This constant will be used in the closure, which demonstrates that the
# closure has access to the surrounding scope
MIN_SIZE_FOR_MAX_COMPRESSION = 4096; # bytes
# Create the recursive action task
# * Use a filter that will only return text files
# * Instead of running the entire RecursiveActionTF in the task executor, use
# it to run each task that the RecursiveActionTF creates instead.
# * Use a function variable instead of a task. Since this is run in a
# RecursiveActionTF, the function will be called with a
ClosureParameters
# object containing a source entity. If the task would have been run in a
# RecursiveProcessTF instead, the
ClosureParameters object would
# have contained both a source and a target entity.
RecursiveActionTF(). \
setSource(
DirectoryAndFilter( \
d, \
EFileNameExtensionFilter(".txt"))). \
setTaskExecutor(te). \
setTaskFactory(compressFile).run()
te.waitFor()
finally:
te.shutdown()
Dependencies for tasks with subtasks
Some tasks such as the RecursiveActionTF or the
JavaWorkspaceBuilderTF create and schedule their own tasks when they are run. When
using such a task as a task dependency, the subtasks are not included in that
dependency, and that is probably not what the programmer intended. Use
the dependency from the task's
getDependencyForTasksScheduledByThisTask
method instead. That dependency will not be satisfied until all scheduled tasks
have been run.
Example 7.5. Dependencies for task with subtasks
// enableTaskPackage org.at4j
import org.entityfs.util.Entities
import org.schmant.run.TaskExecutor
import org.schmant.support.FutureFile
import org.schmant.task.at4j.tar.TarTF
import org.schmant.task.io.gzip.GZipTF
import org.schmant.task.meta.RecursiveActionTF
// Gzip all files in a directory hierarchy under d, and then build a tar archive
// containing all files. The tar archive is put in the directory targetD.
// A task executor
def te = new
TaskExecutor().start()
try {
gzipTask = new
RecursiveActionTF().
setSource(d).
// Let the recursive action task schedule all tasks that it creates instead
// of running them right away.
setTaskExecutor(te).
// Use a closure that compresses a file and then deletes the source file.
// Note the curly braces.
setTaskFactory{
cParams ->
def targetName =
Entities.getName(cParams.source) + ".gz"
def targetDir =
Entities.getParent(cParams.source)
new
GZipTF().
setSource(cParams.source).
setTarget(new
FutureFile(targetDir, targetName)).
run()
// Delete the original file.
Entities.delete(cParams.source)
// Run the recursive action task now. This will make it schedule all of its
// tasks in the task executor.
}.run()
tarTask = new
TarTF().
setSource(d).
setTarget(new
FutureFile(targetD, "d.tar")).
create()
// The tar task depends on all tasks scheduled by the recursive action task
// above
te.add(tarTask, gzipTask.dependencyForTasksScheduledByThisTask)
te.waitFor()
}
finally
{
te.shutdown()
}
// Gzip all files in a directory hierarchy under d, and then build a tar archive
// containing all files. The tar archive is put in the directory targetD.
enableTaskPackage("org.at4j");
// A task executor
te = new
TaskExecutor().start()
try {
gzipTask = new
RecursiveActionTF().
setSource(d).
// Let the recursive action task schedule all tasks that it creates instead
// of running them right away.
setTaskExecutor(te).
// Use a closure that compresses a file and then deletes the source file.
setTaskFactory(
function(cParams) {
var targetName =
Entities.getName(cParams.source) + ".gz";
var targetDir =
Entities.getParent(cParams.source);
new
GZipTF().
setSource(cParams.source).
setTarget(new
FutureFile(targetDir, targetName)).
run();
// Delete the original file.
// "delete" is a reserved word in JavaScript. Use the deleteEntity
// method instead.
Entities.deleteEntity(cParams.source);
}
// Run the recursive action task now. This will make it schedule all of its
// tasks in the task executor.
).run();
tarTask = new
TarTF().
setSource(d).
setTarget(new
FutureFile(targetD, "d.tar")).
create();
// The tar task depends on all tasks scheduled by the recursive action task
// above
te.add(tarTask, gzipTask.getDependencyForTasksScheduledByThisTask());
te.waitFor();
} finally {
te.shutdown();
}
# Gzip all files in a directory hierarchy under d, and then build a tar archive
# containing all files. The tar archive is put in the directory targetD.
enableTaskPackage "org.at4j"
# A task executor
te = Schmant::
TaskExecutor.new.start
begin
gzipTask = Schmant::RecursiveActionTF.new.
setSource($d).
# Let the recursive action task schedule all tasks that it creates instead
# of running them right away.
setTaskExecutor(te).
# Use a closure that compresses a file and then deletes the source file.
setTaskFactory(
Proc.new { |cParams|
targetName = Schmant::
Entities.getName(cParams.source) + ".gz"
targetDir = Schmant::
Entities.getParent(cParams.source)
Schmant::GZipTF.new.
setSource(cParams.source).
setTarget(Schmant::
FutureFile.new(targetDir, targetName)).
run
# Delete the original file.
Schmant::
Entities.delete cParams.source
}
# Run the recursive action task now. This will make it schedule all of its
# tasks in the task executor.
).run
tarTask = Schmant::TarTF.new.
setSource($d).
setTarget(Schmant::
FutureFile.new($targetD, "d.tar")).
create
# The tar task depends on all tasks scheduled by the recursive action task
# above
te.add(tarTask, gzipTask.dependencyForTasksScheduledByThisTask)
te.waitFor
ensure
te.shutdown
end
# Gzip all files in a directory hierarchy under d, and then build a tar archive
# containing all files. The tar archive is put in the directory targetD.
enableTaskPackage("org.at4j")
# A task executor
te =
TaskExecutor().start()
try:
# The function that will be used for compressing the file
def gzipFile(cParams):
targetName =
Entities.getName(cParams.source) + ".gz"
targetDir =
Entities.getParent(cParams.source)
GZipTF(). \
setSource(cParams.source). \
setTarget(
FutureFile(targetDir, targetName)). \
run()
# Delete the original file.
Entities.delete(cParams.source)
# Create the recursive gzip task
# * Let the recursive action task schedule all tasks that it creates instead
# of running them right away.
# * Use a closure that compresses a file and then deletes the source file.
# * Run the recursive action task now. This will make it schedule all of its
# tasks in the task executor.
gzipTask = RecursiveActionTF(). \
setSource(d). \
setTaskExecutor(te). \
setTaskFactory(gzipFile).run()
tarTask = TarTF(). \
setSource(d). \
setTarget(
FutureFile(targetD, "d.tar")). \
create()
# The tar task depends on all tasks scheduled by the recursive action task
# above
te.add(tarTask, gzipTask.getDependencyForTasksScheduledByThisTask())
te.waitFor()
finally:
te.shutdown()
Task executors and future entities
A FutureEntity such as a FutureFile
is used for representing an entity that does not yet exist when scheduling a
task. See below for a simple example that uses a FutureFile
to represent a file created by one task and then used by another.
Example 7.6. Using future files to represent files that do not yet exist
import org.entityfs.util.Directories
import org.schmant.support.FutureFile
import org.schmant.task.io.gzip.GZipTF
import org.schmant.task.text.TextReplaceTF
// te is a task executor.
// d is a
Directory where the file f.txt to be processed is.
// Note: in this example, we could have fed the result from TextReplaceTF right
// into GZipTF's source property instead since TextReplaceTF is a
Producer.
// A
FutureFile representing the result of the text processing task
// created below.
def fp = new
FutureFile(d, "fp.txt")
// Create a task for processing the template file f.txt
def pt = new
TextReplaceTF().
setSource(
Directories.getFile(d, "f.txt")).
setTarget(fp).
addReplace("!!!VERSION!!!", "1.0").create()
te.add(pt)
// Create a task for GZip'ping the processed file
te.add(
new
GZipTF().
// Reuse the future file, this time as the source parameter. Since this task
// depends on the task creating the file, the future file will exist when
// this task is run.
setSource(fp).
setTarget(new
FutureFile(d, "fp.txt.gz")),
// This task must depend on the processing task
pt)
// te is a task executor.
// d is a
Directory where the file f.txt to be processed is.
// Note: in this example, we could have fed the result from TextReplaceTF right
// into GZipTF's source property instead since TextReplaceTF is a
Producer.
// A
FutureFile representing the result of the text processing task
// created below.
fp = new
FutureFile(d, "fp.txt");
// Create a task for processing the template file f.txt
pt = new
TextReplaceTF().
setSource(
Directories.getFile(d, "f.txt")).
setTarget(fp).
addReplace("!!!VERSION!!!", "1.0").create();
te.add(pt);
// Create a task for GZip'ping the processed file
te.add(
new
GZipTF().
// Reuse the future file, this time as the source parameter. Since this task
// depends on the task creating the file, the file will exist when this task
// is run.
setSource(fp).
setTarget(new
FutureFile(d, "fp.txt.gz")),
// This task must depend on the processing task
pt);
# te is a task executor.
# d is a
Directory where the file f.txt to be processed is.
# Note: in this example, we could have fed the result from TextReplaceTF right
# into GZipTF's source property instead since TextReplaceTF is a
Producer.
# A
FutureFile representing the result of the text processing task
# created below.
fp =
FutureFile(d, "fp.txt")
# Create a task for processing the template file f.txt
pt = TextReplaceTF(). \
setSource(
Directories.getFile(d, "f.txt")). \
setTarget(fp). \
addReplace("!!!VERSION!!!", "1.0").create()
te.add(pt)
# Create a task for GZip'ping the processed file.
# This task must depend on the processing task.
# Reuse the future file, this time as the source parameter. Since this task
# depends on the task that creates the file, the file will exist when this task
# is run.
te.add( \
GZipTF(). \
setSource(fp). \
setTarget(
FutureFile(d, "fp.txt.gz")), \
pt)
# te is a task executor.
# d is a
Directory where the file f.txt to be processed is.
# Note: in this example, we could have fed the result from TextReplaceTF right
# into GZipTF's source property instead since TextReplaceTF is a
Producer.
# A
FutureFile representing the result of the text processing task
# created below.
fp = Schmant::
FutureFile.new($d, "fp.txt")
# Create a task for processing the template file f.txt
pt = Schmant::TextReplaceTF.new.
setSource(Schmant::
Directories.getFile($d, "f.txt")).
setTarget(fp).
addReplace("!!!VERSION!!!", "1.0").create()
$te.add(pt)
# Create a task for GZip'ping the processed file
$te.add(
Schmant::GZipTF.new.
# Reuse the future file, this time as the source parameter. Since this task
# depends on the task creating the file, the file will exist when this task
# is run.
setSource(fp).
setTarget(
FutureFile.new($d, "fp.txt.gz")),
# This task must depend on the processing task
pt)
In the example below, an XML file is first preprocessed, then parsed and
then "statistics" is created for it. The XML parser uses an XML catalog for
resolving external entities. All tasks are run with a
TaskExecutor[].
Example 7.7. Computing statistics for an XML file
import java.io.File
import org.entityfs.util.*
import org.schmant.run.TaskExecutor
import org.schmant.support.*
import org.schmant.support.entityfs.*
import org.schmant.support.xml.*
import org.schmant.task.meta.RecursiveActionTF
import org.schmant.task.proxy.ReplaceSourceFileTF
import org.schmant.task.script.ScriptTF
import org.schmant.task.text.TextReplaceTF
import org.schmant.task.xml.catalog.AddSystemIdToCatalogTF
import org.schmant.task.xml.dom.DomParseXmlTF
// Create a task executor with two threads and start it
def te = new
TaskExecutor().
setNumberOfThreads(2).
start()
try {
// An XML catalog to use for resolving external entities
def cr = new
XmlCatalogResolver()
// Add all DTD files in the directory DTD to the catalog
def buildCatalogTask = new
RecursiveActionTF().
setSource(dtd).
setTaskFactory(
new
AddSystemIdToCatalogTF().
setXmlCatalog(cr)).create()
// Add the task to the executor
te.add buildCatalogTask
// Create a
FutureFile representing the file f. This is
// done because the preprocessing task below will invalidate f by deleting it.
// This is how EntityFS entities work; even though the preprocess task will
// put a new file in the same location as f, that new file is not f.
def ff = new
FutureFile(f)
// The XML document is in the
EFile f. Preprocess it.
// This makes the f variable invalid since the original file is replaced.
def preprocessTask = new
ReplaceSourceFileTF().
setSource(f).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0")).create()
// Add the task to the executor. This can be run in parallel with the XML
// catalog task defined above.
te.add preprocessTask
// Define the XML parsing task.
def parseTask = new
DomParseXmlTF().
setSource(ff).
setEntityResolver(cr).create()
// Schedule the task and inform the task executor that it depends on the
// preprocessing task and the XML catalog task.
te.add(parseTask, [preprocessTask, buildCatalogTask])
// Write the XML "statistics" to this file
def targetf = new
File(props.getStringValue("java.io.tmpdir"), "out.txt")
targetf.createNewFile()
// Add a closure that calculates statistics from the XML file. Adding the
// closure to the task executor will make it execute first when all of its
// dependencies have executed.
te.add(
{
// The closure does not have any arguments
//
// The closure has access to the variables in the surrounding scope.
// Convert the target
File targetf to an
EFile
def target =
SchmantFileSystems.getEntityForFile(targetf, false)
// Write the text to the target file
// The parse task is a
Producer of a
Document object.
Files.writeText(target, parseTask.get().toString())
},
// This depends on the parse task
parseTask)
// Wait for the task executor to finish
te.waitFor()
} finally {
// Make sure that the task executor is stopped.
te.shutdown()
}
// Create a task executor with two threads and start it
te = new
TaskExecutor().
setNumberOfThreads(2).
start();
try {
// An XML catalog to use for resolving external entities
cr = new
XmlCatalogResolver();
// Add all DTD files in the directory DTD to the catalog
buildCatalogTask = new
RecursiveActionTF().
setSource(dtd).
setTaskFactory(
new
AddSystemIdToCatalogTF().
setXmlCatalog(cr)).create();
// Add the task to the executor
te.add(buildCatalogTask);
// Create a
FutureFile representing the file f. This is done because
// the preprocessing task below will invalidate f by deleting it. This is how
// EntityFS entities work; even though the preprocess task will put a new file
// in the same location as f, that new file is not f.
ff = new
FutureFile(f);
// The XML document is in the
EFile f. Preprocess it.
// This makes the f variable invalid since the original file is replaced.
preprocessTask = new
ReplaceSourceFileTF().
setSource(f).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0")).create();
// Add the task to the executor. This can be run in parallel with the XML
// catalog task defined above.
te.add(preprocessTask);
// Define the XML parsing task.
parseTask = new
DomParseXmlTF().
setSource(ff).
setEntityResolver(cr).create();
// Schedule the task and inform the task executor that it depends on the
// preprocessing task and the XML catalog task.
te.add(parseTask, [preprocessTask, buildCatalogTask]);
// Write the XML "statistics" to this file
targetf = new
File(props.getStringValue("java.io.tmpdir"), "out.txt");
targetf.createNewFile();
// Add a closure that calculates statistics from the XML file. Adding the
// closure to the task executor will make it execute first when all of its
// dependencies have executed.
te.add(
// The closure does not have any arguments
function() {
// The closure has access to the variables in the surrounding scope.
// Convert the target
File targetf to an
EFile
var target =
SchmantFileSystems.getEntityForFile(targetf, false);
// Write the text to the target file
// The parse task is a
Producer of a
Document object.
Files.writeText(target, parseTask.get().toString());
},
// This depends on the parse task.
parseTask);
// Wait for the task executor to finish
te.waitFor();
} finally {
// Make sure that the task executor is stopped.
te.shutdown();
}
# Create a task executor with two threads and start it
te = Schmant::
TaskExecutor.new.
setNumberOfThreads(2).
start
begin
# An XML catalog to use for resolving external entities
cr = Schmant::
XmlCatalogResolver.new
# Add all DTD files in the directory DTD to the catalog
buildCatalogTask = Schmant::RecursiveActionTF.new.
setSource($dtd).
setTaskFactory(
Schmant::AddSystemIdToCatalogTF.new.
setXmlCatalog(cr)).create
# Add the task to the executor
te.add buildCatalogTask
# Create a
FutureFile representing the file f. This is done because
# the preprocessing task below will invalidate f by deleting it. This is how
# EntityFS entities work; even though the preprocess task will put a new file
# in the same location as f, that new file is not f.
ff = Schmant::
FutureFile.new $f
# The XML document is in the
EFile f. Preprocess it.
# This makes the f variable invalid since the original file is replaced.
preprocessTask = Schmant::ReplaceSourceFileTF.new.
setSource($f).
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", "1.0")).create
# Add the task to the executor. This can be run in parallel with the XML
# catalog task defined above.
te.add preprocessTask
# Define the XML parsing task.
parseTask = Schmant::DomParseXmlTF.new.
setSource(ff).
setEntityResolver(cr).create
# Schedule the task and inform the task executor that it depends on the
# preprocessing task and the XML catalog task.
te.add(parseTask, [preprocessTask, buildCatalogTask])
# Write the XML "statistics" to this file
targetf = Java::JavaIo::
File.new(
$props.getStringValue("java.io.tmpdir"),
"out.txt")
targetf.createNewFile
# Add a closure that calculates statistics from the XML file. Adding the
# closure to the task executor will make it execute first when all of its
# dependencies have executed.
#
# Create the closure using Proc.new. We cannot use a lambda expression here
# since our closure spans several statements.
te.add(
Proc.new { ||
# The closure has access to the variables in the surrounding scope.
# Convert the target
File targetf to an
EFile
target = Schmant::
SchmantFileSystems.getEntityForFile(targetf, false)
# Write the "statistics" to the target file
Schmant::
Files.writeText(target, parseTask.get.toString)
},
parseTask)
# Wait for the task executor to finish
te.waitFor
ensure
# Make sure that the task executor is stopped.
te.shutdown
end
# Create a task executor with two threads and start it
te =
TaskExecutor(). \
setNumberOfThreads(2). \
start()
try:
# An XML catalog to use for resolving external entities
cr =
XmlCatalogResolver()
# Add all DTD files in the directory DTD to the catalog
buildCatalogTask = RecursiveActionTF(). \
setSource(dtd). \
setTaskFactory(
AddSystemIdToCatalogTF(). \
setXmlCatalog(cr)).create()
# Add the task to the executor
te.add(buildCatalogTask)
# Create a
FutureFile representing the file f. This is done because
# the preprocessing task below will invalidate f by deleting it. This is
# how EntityFS entities work; even though the preprocess task will put a
# new file in the same location as f, that new file is not f.
ff =
FutureFile(f)
# The XML document is in the
EFile f. Preprocess it.
# This makes the f variable invalid since the original file is replaced.
preprocessTask = ReplaceSourceFileTF(). \
setSource(f). \
setTaskFactory(
TextReplaceTF(). \
addReplace("!!!VERSION!!!", "1.0")).create()
# Add the task to the executor. This can be run in parallel with the XML
# catalog task defined above.
te.add(preprocessTask)
# Define the XML parsing task.
parseTask = DomParseXmlTF(). \
setSource(ff). \
setEntityResolver(cr).create()
# Schedule the task and inform the task executor that it depends on the
# preprocessing task and the XML catalog task.
te.add(parseTask, [preprocessTask, buildCatalogTask])
# Write the XML "statistics" to this file
targetf =
File(props.getStringValue("java.io.tmpdir"), "out.txt")
targetf.createNewFile()
# This function will be used for creating the XML "statistics". Since it
# spans more than one statement, we cannot simply use a lambda for this
def calculateXmlStatistics():
# This function has access to the variables in the surrounding scope.
# Convert the target
File targetf to an
EFile.
target =
SchmantFileSystems.getEntityForFile(targetf, False)
# Write the text to the target file
# The parse task is a
Producer of a
Document object.
Files.writeText(target, parseTask.get().toString())
# Add the function to the task executor.
# It depends on the parse task
te.add(
calculateXmlStatistics,
parseTask)
# Wait for the task executor to finish
te.waitFor()
finally:
# Make sure that the task executor is stopped.
te.shutdown()
Java's File object works just like a FutureEntity
object because, unlike EntityFS entities, it does not require that the file or
directory that it references must exist.
A common scenario for a build script is to build and package a set of
several interdependent source code modules such as different
projects or modules in an IDE.
Schmant represents the collection of source code modules as a
ProjectRepository that contains a collection of
Project:s.
There are two ProjectRepository
implementations – EclipseWorkspace and
IntelliJWorkspace. A Project
may be a JavaProject or an
IntelliJJavaProject.
Building with the workspace builder task
The easiest way to build Java projects in a project repository is to use
the JavaWorkspaceBuilderTF. It can build all or some of the projects in
a ProjectRepository, and possibly also preprocess the source
code before building and/or postprocess the class files after building.
The example below shows how all Java projects
in an Eclipse workspace can be built.
Example 8.1. Build all projects in an Eclipse workspace using the workspace builder
task.
import org.schmant.project.eclipse.EclipseWorkspace
import org.schmant.task.project.JavaWorkspaceBuilderTF
// Create an
EclipseWorkspace object for the workspace in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
def ewos = new
EclipseWorkspace(wos)
// ...if the workspace had been an IntelliJ IDEA project instead
// def ewos = new
IntelliJWorkspace(
// wos,
// new
AbsoluteLocation("/MyProject.ipr"))
// Create the build task and run it
new
JavaWorkspaceBuilderTF().
setWorkspace(ewos).
// Put the classes in the directory bin
setTarget(bin).
run()
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
ewos = Schmant::
EclipseWorkspace.new($wos)
# ...if the workspace had been an IntelliJ IDEA project instead
# ewos =
IntelliJWorkspace.new(
# $wos,
# new
AbsoluteLocation("/MyProject.ipr"))
# Create the build task and run it
Schmant::JavaWorkspaceBuilderTF.new.
setWorkspace(ewos).
# Put the classes in the directory bin
setTarget($bin).
run
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
ewos =
EclipseWorkspace(wos)
# ...if the workspace had been an IntelliJ IDEA project instead
# ewos =
IntelliJWorkspace( \
# wos,
#
AbsoluteLocation("/MyProject.ipr"))
# Create the build task and run it. Put the compiled classes in the directory
# bin.
JavaWorkspaceBuilderTF(). \
setWorkspace(ewos). \
setTarget(bin). \
run()
The JavaWorkspaceBuilderTF automatically ignores all non-Java
projects in the workspace. To further narrow down the selection of which projects
to build, a Filter<Project> can
be used with the task. There are a number of filter implementations bundled with
Schmant:
Table 8.1. ProjectFilter implementations
All of Schmant's filter implementations also implement
ConvenientFilter which adds convenience methods for combining
different filters through logical operations such as AND
or OR
. All project filters also implement
the marker interface ProjectFilter, which
makes them easier to find in the API documentation.
The static methods in the
ProjectFilterUtil class can be used for applying
filters to collections of projects.
The functionality of JavaWorkspaceBuilderTF may not be
enough for a build script. In that case, the build script can build a collection
of projects manually. To do so, the script uses a ProjectDependencies
object to sort out the TaskDependency:s between projects, and
a TaskExecutor to schedule and run the build tasks. The
ProjectDependencies object serves two purposes: it keeps
track of a project's dependencies to other projects, and it is used to configure
the build task for a project with the location of the built class files from each
of the projects that it depends on.
The example below shows how all Java projects in an Eclipse workspace can
be built manually.
Example 8.2. Build all projects in an Eclipse workspace manually
import org.schmant.project.eclipse.EclipseWorkspace
import org.schmant.project.java.*
import org.schmant.run.TaskExecutor
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// Create an
EclipseWorkspace object for the workspace in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
def ewos = new
EclipseWorkspace(wos)
// Create a
JavaProjectDependencies object to keep track of dependencies
// between the projects in the workspace.
def projDeps = new
JavaProjectDependencies()
// A
TaskExecutor used for scheduling and running the compile tasks.
def te = new
TaskExecutor().
setNumberOfThreads(2).
start()
try
{
// Loop over all projects in the workspace. This assumes that all projects are
// Java projects. See the next example for how to deal with non-Java projects.
ewos.projects.each
{
proj ->
def javacTask = new
Jdk6JavacTF().
addSources(proj.sourceDirectories).
setTarget(bin).
addClasspathDecorator(
// This classpath decorator is used to handle dependencies to the other
// Java projects. It uses the
JavaProjectDependencies object created
// above.
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create()
// Register this project's dependency with the dependency object. By doing
// this, other projects that depend on this project will know when all their
// dependencies are met.
projDeps.registerDependency(proj, javacTask)
// Register where the classes built from this project will be put.
projDeps.registerClassDirectory(proj, bin)
// Schedule this project for building. Get all dependencies for building
// this project from the
JavaProjectDependencies object.
te.add(javacTask, projDeps.getDependencies(proj))
}
te.waitFor()
}
finally
{
te.shutdown()
}
// Create an
EclipseWorkspace object for the workspace in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
ewos = new
EclipseWorkspace(wos);
// Create a
JavaProjectDependencies object to keep track of dependencies
// between the projects in the workspace.
projDeps = new
JavaProjectDependencies();
// A
TaskExecutor used for scheduling and running the compile tasks.
te = new
TaskExecutor().
setNumberOfThreads(2).
start();
try {
// Get all projects from the workspace. This assumes that all projects are
// Java projects. See the next example for how to deal with non-Java projects.
itr = ewos.getProjects().iterator();
while(itr.hasNext()) {
proj = itr.next();
javacTask = new
Jdk6JavacTF().
addSources(proj.getSourceDirectories()).
setTarget(bin).
addClasspathDecorator(
// This classpath decorator is used to handle dependencies to the other
// Java projects. It uses the
JavaProjectDependencies object created
// above.
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create();
// Register this project's dependency with the dependency object. By doing
// this, other projects that depend on this project will know when all their
// dependencies are met.
projDeps.registerDependency(proj, javacTask);
// Register where the classes built from this project will be put.
projDeps.registerClassDirectory(proj, bin);
// Schedule this project for building. Get all dependencies for building
// this project from the
JavaProjectDependencies object.
te.add(javacTask, projDeps.getDependencies(proj));
}
te.waitFor();
} finally {
te.shutdown();
}
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
ewos = Schmant::
EclipseWorkspace.new($wos)
# Create a
JavaProjectDependencies object to keep track of dependencies
# between the projects in the workspace.
projDeps = Schmant::
JavaProjectDependencies.new
# A
TaskExecutor used for scheduling and running the compile tasks.
te = Schmant::
TaskExecutor.new.
setNumberOfThreads(2).
start
begin
# Get all projects from the workspace. This assumes that all projects are
# Java projects. See the next example for how to deal with non-Java projects.
ewos.projects.each do |proj|
javacTask = Schmant::
Jdk6JavacTF.new.
addSources(proj.sourceDirectories).
setTarget($bin).
addClasspathDecorator(
# This classpath decorator is used to handle dependencies to the other
# Java projects. It uses the
JavaProjectDependencies object created
# above.
Schmant::
JavaProjectClasspathDecorator.new.
setProject(proj).
setDependencies(projDeps)).create()
# Register this project's dependency with the dependency object. By doing
# this, other projects that depend on this project will know when all their
# dependencies are met.
projDeps.registerDependency(proj, javacTask)
# Register where the classes built from this project will be put.
projDeps.registerClassDirectory(proj, $bin)
# Schedule this project for building. Get all dependencies for building
# this project from the
JavaProjectDependencies object.
te.add(javacTask, projDeps.getDependencies(proj))
end
te.waitFor()
ensure
te.shutdown()
end
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
ewos =
EclipseWorkspace(wos)
# Create a
JavaProjectDependencies object to keep track of dependencies
# between the projects in the workspace.
projDeps =
JavaProjectDependencies()
# A
TaskExecutor used for scheduling and running the compile tasks.
te =
TaskExecutor(). \
setNumberOfThreads(2). \
start()
try:
# Get all projects from the workspace. This assumes that all projects are
# Java projects. See the next example for how to deal with non-Java projects.
itr = ewos.getProjects().iterator();
while itr.hasNext():
proj = itr.next()
# Create a compile task.
# The classpath decorator is used to handle dependencies to the other Java
# projects in the workspace. It uses the
JavaProjectDependencies object
# created above.
javacTask =
Jdk6JavacTF(). \
addSources(proj.getSourceDirectories()). \
setTarget(bin). \
addClasspathDecorator( \
JavaProjectClasspathDecorator(). \
setProject(proj). \
setDependencies(projDeps)).create()
# Register this project's dependency with the dependency object. By doing
# this, other projects that depend on this project will know when all their
# dependencies are met.
projDeps.registerDependency(proj, javacTask)
# Register where the classes built from this project will be put.
projDeps.registerClassDirectory(proj, bin)
# Schedule this project for building. Get all dependencies for building
# this project from the
JavaProjectDependencies object.
te.add(javacTask, projDeps.getDependencies(proj))
te.waitFor()
finally:
te.shutdown()
The example above made the assumption that all projects in the Eclipse
workspace were Java projects, but of course this is not always the case. Use
project filters like in the examples below to hide non-Java projects.
The EclipseWorkspace is used for representing
an Eclipse workspace. It parses workspace and project information from the workspace
and project configuration files, like a project's .project
and .classpath
files.
EclipseWorkspace supports Eclipse Java projects,
classpath variables and user libraries. The workspace object tries to parse the values of classpath
variables and user libraries from the workspace settings, if they are available.
If the variable definitions are not available, or if the parsed values should be overridden, new
values can be supplied in an EclipseWorkspaceSettings
object instead when creating the workspace object.
EclipseWorkspace tries to create
Project objects from all directories in the workspace
directory. If there are project directories located elsewhere, the
EclipseWorkspaceSettings object can be configured
with their locations.
The examples below shows how all non-test Java projects in an Eclipse
workspace can be built.
Example 8.3. Build a Jar file from the projects in an Eclipse workspace
import java.io.File
import org.schmant.project.eclipse.*
import org.schmant.project.filter.ProjectNameGlobFilter
import org.schmant.support.io.TempFileUtil
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.project.JavaWorkspaceBuilderTF
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// Create an
EclipseWorkspaceSettings object that is used to override
// information that is parsed from to the workspace configuration files.
def settings = new
EclipseWorkspaceSettings()
// Add a classpath variable to the settings object.
// The
EclipseWorkspace object tries to parse the values of classpath
// variables from the workspace metadata. If they are not defined there, or if
// a value there should be overridden, classpath variable values may be added
// manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE", new
File("lib/mylib.jar"))
def eWos = new
EclipseWorkspace(wos, settings)
// To create an Eclipse workspace object for the contents in the Schmant
// process' working directory, use
// def eWos = new
EclipseWorkspace(new
File("."), settings)
// Create a temporary directory for putting the class files in. This directory
// will be removed when the Schmant process exits.
def tmpDir =
TempFileUtil.createTempDirectory()
// To create a temporary directory in memory:
// def tmpDir =
SchmantFileSystems.createRamFileSystem()
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
setTarget(tmpDir).
// Don't build test projects. Negate the filter using the ~ operator.
setProjectFilter(~(new
ProjectNameGlobFilter("*_test"))).
run()
// Build the Jar file
// The target file targetFile may be a
File or a
FutureFile.
new
JarTF().
setSource(tmpDir).
setTarget(targetFile).run()
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// Create an
EclipseWorkspaceSettings object that is used to override
// information that is parsed from to the workspace configuration files.
settings = new
EclipseWorkspaceSettings();
// Add a classpath variable to the settings object.
// The
EclipseWorkspace object tries to parse the values of classpath
// variables from the workspace metadata. If they are not defined there, or if
// a value there should be overridden, classpath variable values may be added
// manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE", new
File("lib/mylib.jar"));
eWos = new
EclipseWorkspace(wos, settings);
// To create an Eclipse workspace object for the contents in the Schmant
// process' working directory, use
// eWos = new
EclipseWorkspace(new
File("."), settings);
// Create a temporary directory for putting the class files in. This directory
// will be removed when the Schmant process exits.
tmpDir =
TempFileUtil.createTempDirectory();
// To create a temporary directory in memory:
// var tmpDir =
SchmantFileSystems.createRamFileSystem();
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
setTarget(tmpDir).
// Don't build test projects.
setProjectFilter(new
ProjectNameGlobFilter("*_test").not()).
run();
// Build the Jar file
// The target file targetFile may be a
File or a
FutureFile.
new
JarTF().
setSource(tmpDir).
setTarget(targetFile).run();
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# Create an
EclipseWorkspaceSettings object that is used to override
# information that is parsed from to the workspace configuration files.
settings = Schmant::
EclipseWorkspaceSettings.new
# Add a classpath variable to the settings object.
# The
EclipseWorkspace object tries to parse the values of classpath
# variables from the workspace metadata. If they are not defined there, or if
# a value there should be overridden, classpath variable values may be added
# manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable(
"MY_VARIABLE",
Java::JavaIo::
File.new("lib/mylib.jar"))
eWos = Schmant::
EclipseWorkspace.new($wos, settings)
# To create an Eclipse workspace object for the contents in the Schmant
# process' working directory, use
# eWos = Schmant::
EclipseWorkspace.new(
# Java::JavaIo::
File.new("."),
# settings)
# Create a temporary directory for putting the class files in. This directory
# will be removed when the Schmant process exits.
tmpDir = Schmant::
TempFileUtil.createTempDirectory
# To create a temporary directory in memory:
# tmpDir = Schmant::
SchmantFileSystems.createRamFileSystem
Schmant::JavaWorkspaceBuilderTF.new.
setWorkspace(eWos).
setTarget(tmpDir).
# Don't build test projects.
setProjectFilter(Schmant::
ProjectNameGlobFilter.new("*_test").not).
run
# Build the Jar file
# The target file targetFile may be a
File or a
FutureFile.
Schmant::JarTF.new.
setSource(tmpDir).
setTarget($targetFile).run
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# Create an
EclipseWorkspaceSettings object that is used to override
# information that is parsed from to the workspace configuration files.
settings =
EclipseWorkspaceSettings()
# Add a classpath variable to the settings object.
# The
EclipseWorkspace object tries to parse the values of classpath
# variables from the workspace metadata. If they are not defined there, or if
# a value there should be overridden, classpath variable values may be added
# manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE",
File("lib/mylib.jar"))
eWos =
EclipseWorkspace(wos, settings)
# To create an Eclipse workspace object for the contents in the Schmant
# process' working directory, use
# eWos =
EclipseWorkspace(new
File("."), settings)
# Create a temporary directory for putting the class files in. This directory
# will be removed when the Schmant process exits.
tmpDir =
TempFileUtil.createTempDirectory()
# To create a temporary directory in memory:
# var tmpDir =
SchmantFileSystems.createRamFileSystem()
JavaWorkspaceBuilderTF(). \
setWorkspace(eWos). \
setTarget(tmpDir). \
setProjectFilter(
ProjectNameGlobFilter("*_test").not()). \
run()
# Build the Jar file
# The target file targetFile may be a
File or a
FutureFile.
JarTF(). \
setSource(tmpDir). \
setTarget(targetFile).run()
Example 8.4. Build a Jar file from the projects in an Eclipse workspace manually
import java.io.File
import org.entityfs.util.Directories
import org.schmant.project.eclipse.*
import org.schmant.project.filter.ProjectNameGlobFilter
import org.schmant.project.java.*
import org.schmant.run.TaskExecutor
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// Create an
EclipseWorkspaceSettings object that is used to override
// information that is parsed from the workspace configuration files.
def settings = new
EclipseWorkspaceSettings()
// Add a classpath variable to the settings object.
// The
EclipseWorkspace object tries to parse the values of classpath
// variables from the workspace metadata. If they are not defined there, or if
// a value there should be overridden, classpath variable values may be added
// manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE", new
File("lib/mylib.jar"))
def eWos = new
EclipseWorkspace(wos, settings)
// To create an Eclipse workspace object for the contents in the Schmant
// process' working directory, use
// def eWos = new
EclipseWorkspace(new
File("."), settings)
// Create a
JavaProjectDependencies object for keeping track of dependencies
// between different projects.
def projDeps = new
JavaProjectDependencies()
// This collection will contain all the directories with class files
def classDirs = []
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// def tmpDir =
TempFileUtil.createTempDirectory()
// Create a
TaskExecutor and start it
def te = new
TaskExecutor().
setNumberOfThreads(2).
start()
try
{
// Get all non-test Java projects from the workspace
// In Groovy, project filters may be combined using Groovy operators.
def javaProjects =
eWos.getProjects(
JavaProjectFilter.INSTANCE &
~(new
ProjectNameGlobFilter("*_test")))
// Loop over all non-test Java projects and create compile tasks for them
javaProjects.each
{
proj ->
// Create a temporary directory for compiled files
def classDir =
Directories.newDirectory(tmpDir, proj.name)
classDirs << classDir
// Create the compilation task
def javacTask = new
Jdk6JavacTF().
// A project may have several source directories
addSources(proj.sourceDirectories).
setTarget(classDir).
// Add a
JavaProjectClasspathDecorator that uses the
//
JavaProjectDependencies object to give the compile task its classpath
addClasspathDecorator(
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create()
// Register the compile task as the dependency that this project hinges on
projDeps.registerDependency(proj, javacTask)
// Also register the class directory so that it can be found by projects
// that depend on this project
projDeps.registerClassDirectory(proj, classDir)
// Add the compile task to the task executor. Use the projDeps object to
// get the dependencies
te.add(javacTask, projDeps.getDependencies(proj))
}
// Create a Jar file with all the compiled classes
// (Since this task depends on all the compile tasks, it might as well have
// been defined after the task executor has been shut down.)
te.add(
new
JarTF().
// targetFile is a
FutureFile
setTarget(targetFile).
addSources(classDirs),
// The
ProjectDependencies object is also a
TaskDependency
// object that is satisfied when all projects have been built.
projDeps)
// Wait for all tasks to complete
te.waitFor()
}
finally
{
te.shutdown()
}
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// Create an
EclipseWorkspaceSettings object that is used to override
// information that is parsed from the workspace configuration files.
settings = new
EclipseWorkspaceSettings();
// Add a classpath variable to the settings object.
// The
EclipseWorkspace object tries to parse the values of classpath
// variables from the workspace metadata. If they are not defined there, or if
// a value there should be overridden, classpath variable values may be added
// manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE", new
File("lib/mylib.jar"));
eWos = new
EclipseWorkspace(wos, settings);
// To create an Eclipse workspace object for the contents in the Schmant
// process' working directory, use
// var eWos = new
EclipseWorkspace(new
File("."), settings);
// Create a
JavaProjectDependencies object for keeping track of dependencies
// between different projects.
projDeps = new
JavaProjectDependencies();
// This collection will contain all the directories with class files
classDirs = []
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// var tmpDir =
TempFileUtil.createTempDirectory();
// Create a
TaskExecutor and start it
var te = new
TaskExecutor().
setNumberOfThreads(2).
start();
try {
// Get all non-test Java projects from the workspace
var javaProjects =
eWos.getProjects(
JavaProjectFilter.INSTANCE.and(
new
ProjectNameGlobFilter("*_test").not()));
// Iterate over all non-test Java projects and create compile tasks for them
itr = javaProjects.iterator();
while(itr.hasNext())
{
proj = itr.next();
// Create a temporary directory for compiled files
classDir =
Directories.newDirectory(tmpDir, proj.getName());
classDirs.push(classDir);
// Create the compilation task
javacTask = new
Jdk6JavacTF().
// A project may have several source directories
addSources(proj.getSourceDirectories()).
setTarget(classDir).
// Add a
JavaProjectClasspathDecorator that uses the
//
JavaProjectDependencies object to give the compile task its classpath
addClasspathDecorator(
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create();
// Register the compile task as the dependency that this project hinges on
projDeps.registerDependency(proj, javacTask);
// Also register the class directory so that it can be found by projects
// that depend on this project
projDeps.registerClassDirectory(proj, classDir);
// Add the compile task to the task executor. Use the projDeps object to
// get the dependencies
te.add(javacTask, projDeps.getDependencies(proj));
}
// Create a Jar file with all the compiled classes
// (Since this task depends on all the compile tasks, it might as well have
// been defined after the task executor has been shut down.)
te.add(
new
JarTF().
// targetFile is a
FutureFile
setTarget(targetFile).
addSources(classDirs),
// The
ProjectDependencies object is also a
TaskDependency
// object that is satisfied when all projects have been built.
projDeps);
// Wait for all tasks to complete
te.waitFor();
} finally {
te.shutdown();
}
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# Create an
EclipseWorkspaceSettings object that is used to override
# information that is parsed from the workspace configuration files.
settings = Schmant::
EclipseWorkspaceSettings.new
# Add a classpath variable to the settings object.
# The
EclipseWorkspace object tries to parse the values of classpath
# variables from the workspace metadata. If they are not defined there, or if
# a value there should be overridden, classpath variable values may be added
# manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable(
"MY_VARIABLE",
Java::JavaIo::
File.new("lib/mylib.jar"))
eWos = Schmant::
EclipseWorkspace.new($wos, settings)
# To create an Eclipse workspace object for the contents in the Schmant process'
# working directory, use
# eWos = Schmant::
EclipseWorkspace.new(java.io.File.new("."), settings);
# Create a
JavaProjectDependencies object for keeping track of dependencies
# between different projects.
projDeps = Schmant::
JavaProjectDependencies.new
# This array will contain all the directories with class files
classDirs = []
# The tmpDir variable points to a directory where temporary files are kept
# during the build. It can for instance be created by calling
# tmpDir = Schmant::
TempFileUtil.createTempDirectory
# Create a
TaskExecutor and start it
te =
TaskExecutor.new.
setNumberOfThreads(2).
start
begin
# Get all non-test Java projects from the workspace
javaProjects =
eWos.getProjects(
Schmant::
JavaProjectFilter::INSTANCE.and(
Schmant::
ProjectNameGlobFilter.new("*_test").not))
# Loop over all non-test Java projects and create compile tasks for them
javaProjects.each do |proj|
# Create a temporary directory for compiled files
classDir = Schmant::
Directories.newDirectory($tmpDir, proj.name)
classDirs << classDir
# Create the compilation task
javacTask = Schmant::Jdk6JavacTF.new.
# A project may have several source directories
addSources(proj.sourceDirectories).
setTarget(classDir).
# Add a
JavaProjectClasspathDecorator that uses the
#
JavaProjectDependencies object to give the compile task its classpath
addClasspathDecorator(
Schmant::
JavaProjectClasspathDecorator.new.
setProject(proj).
setDependencies(projDeps)).create
# Register the compile task as the dependency that this project hinges on
projDeps.registerDependency(proj, javacTask)
# Also register the class directory so that it can be found by projects
# that depend on this project
projDeps.registerClassDirectory(proj, classDir)
# Add the compile task to the task executor. Use the projDeps object to
# get the dependencies
te.add(javacTask, projDeps.getDependencies(proj))
end
# Create a Jar file with all the compiled classes
# (Since this task depends on all the compile tasks, it might as well have
# been defined after the task executor has been shut down.)
te.add(
Schmant::JarTF.new.
# targetFile is a
FutureFile
setTarget($targetFile).
addSources(classDirs),
# The
ProjectDependencies object is also a
TaskDependency
# object that is satisfied when all projects have been built.
projDeps)
# Wait for all tasks to complete
te.waitFor
ensure
te.shutdown
end
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# Create an
EclipseWorkspaceSettings object that is used to override
# information that is parsed from the workspace configuration files.
settings =
EclipseWorkspaceSettings()
# Add a classpath variable to the settings object.
# The
EclipseWorkspace object tries to parse the values of classpath
# variables from the workspace metadata. If they are not defined there, or if
# a value there should be overridden, classpath variable values may be added
# manually to the
EclipseWorkspaceSettings object.
settings.addClasspathVariable("MY_VARIABLE",
File("lib/mylib.jar"))
eWos =
EclipseWorkspace(wos, settings)
# To create an Eclipse workspace object for the contents in the Schmant process'
# working directory, use
# eWos =
EclipseWorkspace(
File("."), settings)
# Create a
JavaProjectDependencies object for keeping track of dependencies
# between different projects.
projDeps =
JavaProjectDependencies()
# This list will contain all the directories with class files
classDirs = []
# The tmpDir variable points to a directory where temporary files are kept
# during the build. It can for instance be created by calling
# tmpDir =
TempFileUtil.createTempDirectory()
# Create a
TaskExecutor and start it
te =
TaskExecutor(). \
setNumberOfThreads(2). \
start()
try:
# Get all non-test Java projects from the workspace
javaProjects = \
eWos.getProjects( \
JavaProjectFilter.INSTANCE.and( \
ProjectNameGlobFilter("*_test").not()))
# Iterate over all non-test Java projects and create compile tasks for them
itr = javaProjects.iterator()
while itr.hasNext():
proj = itr.next()
# Create a temporary directory for compiled files
classDir =
Directories.newDirectory(tmpDir, proj.getName())
classDirs.append(classDir)
# Create the compilation task
# Add a
JavaProjectClasspathDecorator that uses the
#
JavaProjectDependencies object to give the compile task its classpath
javacTask = Jdk6JavacTF(). \
addSources(proj.getSourceDirectories()). \
setTarget(classDir). \
addClasspathDecorator( \
JavaProjectClasspathDecorator(). \
setProject(proj). \
setDependencies(projDeps)).create()
# Register the compile task as the dependency that this project hinges on
projDeps.registerDependency(proj, javacTask)
# Also register the class directory so that it can be found by projects
# that depend on this project
projDeps.registerClassDirectory(proj, classDir)
# Add the compile task to the task executor. Use the projDeps object to
# get the dependencies
te.add(javacTask, projDeps.getDependencies(proj))
# Create a Jar file with all the compiled classes
# (Since this task depends on all the compile tasks, it might as well have
# been defined after the task executor has been shut down.)
#
# targetFile is a
FutureFile.
#
# The
ProjectDependencies object is also a
TaskDependency
# object that is satisfied when all projects have been built.
te.add(
JarTF(). \
setTarget(targetFile). \
addSources(classDirs), \
projDeps)
# Wait for all tasks to complete
te.waitFor()
finally:
te.shutdown()
The next example shows how the user library EriksLib
is defined and used when compiling a workspace.
Example 8.5. Build an Eclipse workspace using a user-defined library
import org.entityfs.util.Directories
import org.schmant.project.eclipse.*
import org.schmant.project.java.Library
import org.schmant.task.project.JavaWorkspaceBuilderTF
// Create an
EclipseWorkspaceSettings object that is used to provide information
// that cannot be parsed from its .project and .classpath files to the Eclipse
// workspace object.
def settings = new
EclipseWorkspaceSettings()
// The EriksLib library contains all Jar files in the lib directory.
def eriksLib = new
Library().
addEntries(
Directories.getAllFilesMatching(lib, "*.jar"))
// Add the user library to the settings object. The name used for the library is
// the name used in the Eclipse project's .classpath file.
settings.addLibrary("org.eclipse.jdt.USER_LIBRARY/EriksLib", eriksLib)
// Create an
EclipseWorkspace object for the workspace in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
def eWos = new
EclipseWorkspace(wos, settings)
// Build it
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
// This is a
File directory or a
Directory
setTarget(bin).
run()
// Create an
EclipseWorkspaceSettings object that is used to provide information
// that cannot be parsed from its .project and .classpath files to the Eclipse
// workspace object.
settings = new
EclipseWorkspaceSettings();
// The EriksLib library contains all Jar files in the lib directory.
eriksLib = new
Library().
addEntries(
Directories.getAllFilesMatching(lib, "*.jar"));
// Add the user library to the settings object. The name used for the library is
// the name used in the Eclipse project's .classpath file.
settings.addLibrary("org.eclipse.jdt.USER_LIBRARY/EriksLib", eriksLib);
// Create an
EclipseWorkspace object for the workspace in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
eWos = new
EclipseWorkspace(wos, settings);
// Build it
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
// This is a
File directory or a
Directory
setTarget(bin).
run();
# Create an
EclipseWorkspaceSettings object that is used to provide information
# that cannot be parsed from its .project and .classpath files to the Eclipse
# workspace object.
settings = Schmant::
EclipseWorkspaceSettings.new
# The EriksLib library contains all Jar files in the lib directory.
eriksLib = Schmant::
Library.new.
addEntries(Schmant::
Directories.getAllFilesMatching($lib, "*.jar"))
# Add the user library to the settings object. The name used for the library is
# the name used in the Eclipse project's .classpath file.
settings.addLibrary("org.eclipse.jdt.USER_LIBRARY/EriksLib", eriksLib)
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
eWos =
EclipseWorkspace.new($wos, settings)
# Build it
Schmant::JavaWorkspaceBuilderTF.new.
setWorkspace(eWos).
# This is a
File directory or a
Directory
setTarget($bin).
run
# Create an
EclipseWorkspaceSettings object that is used to provide information
# that cannot be parsed from its .project and .classpath files to the Eclipse
# workspace object.
settings =
EclipseWorkspaceSettings()
# The EriksLib library contains all Jar files in the lib directory.
eriksLib =
Library(). \
addEntries(
Directories.getAllFilesMatching(lib, "*.jar"))
# Add the user library to the settings object. The name used for the library is
# the name used in the Eclipse project's .classpath file.
settings.addLibrary("org.eclipse.jdt.USER_LIBRARY/EriksLib", eriksLib)
# Create an
EclipseWorkspace object for the workspace in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
eWos =
EclipseWorkspace(wos, settings)
# Build it
# bin is a
File directory or a
Directory
JavaWorkspaceBuilderTF(). \
setWorkspace(eWos). \
setTarget(bin). \
run()
In the next two examples, all source files are preprocessed before they
are compiled.
Example 8.6. Preprocess source files in Eclipse projects. Compile. Build Jar
import org.entityfs.util.filter.entity.*
import org.schmant.project.eclipse.EclipseWorkspace
import org.schmant.support.io.TempFileUtil
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.meta.RecursiveProcessTF
import org.schmant.task.project.JavaWorkspaceBuilderTF
import org.schmant.task.text.TextReplaceTF
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
def eWos = new
EclipseWorkspace(wos)
// An EntityFilter that hides Subversion .svn directories
def noSvnFilter = ~(
DirectoryFilter.FILTER &
new
EntityNameFilter(".svn"))
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// tmpDir =
TempFileUtil.createTempDirectory()
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
setTarget(tmpDir).
//
// Set a task factory for creating preprocess tasks. A task will be created
// from this factory for each source directory for each project. The source
// and target properties are set automatically.
setPreprocessTaskFactory(
//
// Recurse through the source directory.
new
RecursiveProcessTF().
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0").
// (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233"))).
run()
// Create a Jar file with all the compiled classes
new
JarTF().
// targetFile is a
File or a
FutureFile
setTarget(targetFile).
setSource(tmpDir).
run()
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
eWos = new
EclipseWorkspace(wos);
// An EntityFilter that hides Subversion .svn directories
noSvnFilter =
DirectoryFilter.FILTER.and(
new
EntityNameFilter(".svn")).not();
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// tmpDir =
TempFileUtil.createTempDirectory();
new
JavaWorkspaceBuilderTF().
setWorkspace(eWos).
setTarget(tmpDir).
//
// Set a task factory for creating preprocess tasks. A task will be created
// from this factory for each source directory for each project. The source
// and target properties are set automatically.
setPreprocessTaskFactory(
//
// Recurse through the source directory.
new
RecursiveProcessTF().
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0").
// (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233"))).
run();
// Create a Jar file with all the compiled classes
new
JarTF().
// targetFile is a
File or a
FutureFile
setTarget(targetFile).
setSource(tmpDir).
run();
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
eWos = Schmant::
EclipseWorkspace.new $wos
# An EntityFilter that hides Subversion .svn directories
noSvnFilter = Schmant::
DirectoryFilter::FILTER.and(
Schmant::
EntityNameFilter.new(".svn")).not
# The $tmpDir variable points to a directory where temporary files are kept
# during the build. It can for instance be created by calling
# tmpDir =
TempFileUtil.createTempDirectory
Schmant::JavaWorkspaceBuilderTF.new.
setWorkspace(eWos).
setTarget($tmpDir).
#
# Set a task factory for creating preprocess tasks. A task will be created
# from this factory for each source directory for each project. The source
# and target properties are set automatically.
setPreprocessTaskFactory(
#
# Recurse through the source directory.
Schmant::RecursiveProcessTF.new.
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", "1.0").
# (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233"))).
run
# Create a Jar file with all the compiled classes
Schmant::JarTF.new.
# targetFile is a
File or a
FutureFile
setTarget($targetFile).
setSource($tmpDir).run
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
eWos =
EclipseWorkspace(wos)
# An EntityFilter that hides Subversion .svn directories
noSvnFilter =
DirectoryFilter.FILTER.and(
EntityNameFilter(".svn")).not()
# The tmpDir variable points to a directory where temporary files are kept
# during the build. It can for instance be created by calling
# tmpDir =
TempFileUtil.createTempDirectory()
# Build the workspace
#
# Set a task factory for creating preprocess tasks. A task will be created from
# this factory for each source directory for each project. The source and target
# target properties are set automatically.
#
# Recurse through the source directory.
#
# The replace 232 -> 233 is for the documentation's unit tests. Pretend it's not
# there...
JavaWorkspaceBuilderTF(). \
setWorkspace(eWos). \
setTarget(tmpDir). \
setPreprocessTaskFactory(
RecursiveProcessTF(). \
setTaskFactory(
TextReplaceTF(). \
addReplace("!!!VERSION!!!", "1.0"). \
addReplace("232", "233"))). \
run()
# Create a Jar file with all the compiled classes
#
# targetFile is a
File or a
FutureFile
JarTF(). \
setTarget(targetFile). \
setSource(tmpDir). \
run()
Example 8.7. Preprocess source files in Eclipse projects. Compile manually. Build Jar
import org.entityfs.util.Directories
import org.entityfs.util.filter.entity.*
import org.schmant.arg.DirectoryAndFilter
import org.schmant.project.eclipse.*
import org.schmant.project.java.*
import org.schmant.run.*
import org.schmant.support.io.TempFileUtil
import org.schmant.task.jdk.jar.JarTF
import org.schmant.task.jdk.javac.jdk6.Jdk6JavacTF
import org.schmant.task.meta.RecursiveProcessTF
import org.schmant.task.text.TextReplaceTF
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// vars is a
Map of the values of classpath variables used in
// the workspace. These variables override the values parsed from the workspace
// metadata.
def settings = new
EclipseWorkspaceSettings().
addClasspathVariables(vars)
def eWos = new
EclipseWorkspace(wos, settings)
// Get all Java projects from the workspace
def javaProjects = eWos.getProjects().findAll{
proj ->
JavaProjectFilter.INSTANCE.matches(proj)}
// An
EntityFilter that hides Subversion .svn directories
def noSvnFilter = ~(
DirectoryFilter.FILTER &
new
EntityNameFilter(".svn"))
// Create a
JavaProjectDependencies object for keeping track of dependencies
// between different projects.
def projDeps = new
JavaProjectDependencies()
// This collection will contain all the directories with class files
def classDirs = []
// Create a
TaskExecutor and start it
def te = new
TaskExecutor().
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)).
start()
try
{
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// def tmpDir =
TempFileUtil.createTempDirectory()
// Create compile tasks for each Java project
javaProjects.each{
proj ->
def projName = proj.name
// Create a temporary directory to put the preprocessed source files in
def sourceDir =
Directories.newDirectory(tmpDir, projName + "_src")
// Create the preprocess task for all source files
def ppt = new
RecursiveProcessTF().
// Preprocess all files in the directory hierarchy. Ignore Subversion's
// .svn directories.
addSources(
DirectoryAndFilter.listWithFilter(
Directories.newViews(proj.sourceDirectories, noSvnFilter),
EFileFilter.FILTER)).
setTarget(sourceDir).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0").
// (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233")).
create()
te.add(ppt)
// Create a temporary directory for compiled files
def classDir =
Directories.newDirectory(tmpDir, projName)
classDirs.add(classDir)
// Create the compilation task
def javacTask = new
Jdk6JavacTF().
addSource(sourceDir).
setTarget(classDir).
// Add a
JavaProjectClasspathDecorator that uses the
//
JavaProjectDependencies object created above to give the compile
// task its classpath
addClasspathDecorator(
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create()
// Register the compile task as the dependency that this project hinges on.
// (If we would post process the compiled class files in any way, for
// instance Emma instrument them, the post process task would (probably) be
// the one that was registered here.)
projDeps.registerDependency(proj, javacTask)
// Also register the class directory so that it can be found by projects
// that depend on this project.
projDeps.registerClassDirectory(proj, classDir)
// Add the compile task to the task executor. Now the compile task hinges
// on both it's dependent projects and the preprocess task
te.add(
javacTask,
new
CompoundTaskDependency().
addAll(projDeps.getDependencies(proj)).
add(ppt))
}
// Create a Jar file with all the compiled classes
te.add(
new
JarTF().
// targetFile is a
File or a
FutureFile
setTarget(targetFile).
addSources(classDirs),
// This is also a dependency object for all compilation tasks.
projDeps)
// Wait for all tasks to complete
te.waitFor()
}
finally
{
te.shutdown()
}
// Create the Eclipse workspace object for the contents in the directory wos.
// wos may be a
File directory or an EntityFS
Directory.
// vars is a
Map of the values of classpath variables used in
// the workspace. These variables override the values parsed from the workspace
// metadata.
settings = new
EclipseWorkspaceSettings().
addClasspathVariables(vars);
eWos = new
EclipseWorkspace(wos, settings);
// Get all Java projects from the workspace
javaProjects = eWos.getProjects(
JavaProjectFilter.INSTANCE);
// An
EntityFilter that hides Subversion .svn directories
noSvnFilter =
DirectoryFilter.FILTER.and(
new
EntityNameFilter(".svn")).not();
// Create a
JavaProjectDependencies object for keeping track of dependencies
// between different projects.
projDeps = new
JavaProjectDependencies();
// This collection will contain all the directories with class files
classDirs = new
ArrayList();
// Create a
TaskExecutor and start it
te = new
TaskExecutor().
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)).
start();
try {
// The tmpDir variable points to a directory where temporary files are kept
// during the build. It can for instance be created by calling
// var tmpDir =
TempFileUtil.createTempDirectory();
// Iterate over all Java projects and create compile tasks for them
itr = javaProjects.iterator();
while(itr.hasNext()) {
proj = itr.next();
projName = proj.getName();
// Create a temporary directory to put the preprocessed source files in
sourceDir =
Directories.newDirectory(tmpDir, projName + "_src");
// Create the preprocess task for all source files
ppt = new
RecursiveProcessTF().
// Preprocess all files in the directory hierarchy. Ignore Subversion's
// .svn directories.
addSources(
DirectoryAndFilter.listWithFilter(
Directories.newViews(proj.getSourceDirectories(), noSvnFilter),
EFileFilter.FILTER)).
setTarget(sourceDir).
setTaskFactory(
new
TextReplaceTF().
addReplace("!!!VERSION!!!", "1.0").
// (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233")).
create();
te.add(ppt);
// Create a temporary directory for compiled files
classDir =
Directories.newDirectory(tmpDir, projName);
classDirs.add(classDir);
// Create the compilation task
javacTask = new
Jdk6JavacTF().
addSource(sourceDir).
setTarget(classDir).
// Add a
JavaProjectClasspathDecorator that uses the
//
JavaProjectDependencies object created above to give the compile
// task its classpath
addClasspathDecorator(
new
JavaProjectClasspathDecorator().
setProject(proj).
setDependencies(projDeps)).create();
// Register the compile task as the dependency that this project hinges on.
// (If we would post process the compiled class files in any way, for
// instance Emma instrument them, the post process task would (probably) be
// the one that was registered here.)
projDeps.registerDependency(proj, javacTask);
// Also register the class directory so that it can be found by projects
// that depend on this project.
projDeps.registerClassDirectory(proj, classDir);
// Add the compile task to the task executor. Now the compile task hinges
// on both it's dependent projects and the preprocess task
te.add(
javacTask,
new
CompoundTaskDependency().
addAll(projDeps.getDependencies(proj)).
add(ppt));
}
// Create a Jar file with all the compiled classes
te.add(
new
JarTF().
// targetFile is a
File or a
FutureFile
setTarget(targetFile).
addSources(classDirs),
// This is also a dependency object for all compilation tasks.
projDeps);
// Wait for all tasks to complete
te.waitFor();
} finally {
te.shutdown();
}
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# vars is a
Map of the values of classpath variables used in
# the workspace. These variables override the values parsed from the workspace
# metadata.
settings = Schmant::
EclipseWorkspaceSettings.new.
addClasspathVariables($vars)
eWos = Schmant::
EclipseWorkspace.new($wos, settings)
# Get all Java projects from the workspace
javaProjects = eWos.getProjects Schmant::
JavaProjectFilter::INSTANCE
# An
EntityFilter that hides Subversion .svn directories
noSvnFilter = Schmant::
DirectoryFilter::FILTER.and(
Schmant::
EntityNameFilter.new(".svn")).not
# Create a
JavaProjectDependencies object for keeping track of dependencies
# between different projects.
projDeps = Schmant::
JavaProjectDependencies.new
# This list will contain all the directories with class files
classDirs = []
# Create a
TaskExecutor and start it
te = Schmant::
TaskExecutor.new.
setNumberOfThreads($props.getIntValue("noOfBuildThreads", 2)).
start
begin
# The tmpDir variable points to a directory where temporary files are kept
# during the build. It can for instance be created by calling
# tmpDir =
TempFileUtil.createTempDirectory
# Iterate over all Java projects and create compile tasks for them
javaProjects.each do |proj|
projName = proj.name
# Create a temporary directory to put the preprocessed source files in
sourceDir = Schmant::
Directories.newDirectory($tmpDir, projName + "_src")
# Create the preprocess task for all source files
ppt = Schmant::RecursiveProcessTF.new.
# Preprocess all files in the directory hierarchy. Ignore Subversion's
# .svn directories.
addSources(
Schmant::
DirectoryAndFilter.listWithFilter(
Schmant::
Directories.newViews(proj.sourceDirectories, noSvnFilter),
Schmant::
EFileFilter::FILTER)).
setTarget(sourceDir).
setTaskFactory(
Schmant::TextReplaceTF.new.
addReplace("!!!VERSION!!!", "1.0").
# (For the documentation's unit tests. Pretend it's not here...)
addReplace("232", "233")).create
te.add ppt
# Create a temporary directory for compiled files
classDir = Schmant::
Directories.newDirectory($tmpDir, projName)
classDirs.push classDir
# Create the compilation task
javacTask = Schmant::Jdk6JavacTF.new.
addSource(sourceDir).
setTarget(classDir).
# Add a
JavaProjectClasspathDecorator that uses the
#
JavaProjectDependencies object created above to give the compile
# task its classpath
addClasspathDecorator(
Schmant::
JavaProjectClasspathDecorator.new.
setProject(proj).
setDependencies(projDeps)).create
# Register the compile task as the dependency that this project hinges on.
# (If we would post process the compiled class files in any way, for
# instance Emma instrument them, the post process task would (probably) be
# the one that was registered here.)
projDeps.registerDependency(proj, javacTask)
# Also register the class directory so that it can be found by projects
# that depend on this project.
projDeps.registerClassDirectory(proj, classDir)
# Add the compile task to the task executor. Now the compile task hinges
# on both it's dependent projects and the preprocess task
te.add(
javacTask,
Schmant::
CompoundTaskDependency.new.
addAll(projDeps.getDependencies(proj)).
add(ppt))
end
# Create a Jar file with all the compiled classes
te.add(
Schmant::JarTF.new.
# targetFile is a
File or a
FutureFile
setTarget($targetFile).
addSources(classDirs),
# This is also a dependency object for all compilation tasks.
projDeps)
# Wait for all tasks to complete
te.waitFor
ensure
te.shutdown
end
# Create the Eclipse workspace object for the contents in the directory wos.
# wos may be a
File directory or an EntityFS
Directory.
# vars is a
Map of the values of classpath variables used in
# the workspace. These variables override the values parsed from the workspace
# metadata.
settings =
EclipseWorkspaceSettings(). \
addClasspathVariables(vars)
eWos =
EclipseWorkspace(wos, settings)
# Get all Java projects from the workspace
javaProjects = eWos.getProjects(
JavaProjectFilter.INSTANCE)
# An
EntityFilter that hides Subversion .svn directories
noSvnFilter =
DirectoryFilter.FILTER.and(
EntityNameFilter(".svn")).not()
# Create a
JavaProjectDependencies object for keeping track of dependencies
# between different projects.
projDeps =
JavaProjectDependencies()
# This collection will contain all the directories with class files
classDirs = []
# Create a
TaskExecutor and start it
te =
TaskExecutor(). \
setNumberOfThreads(props.getIntValue("noOfBuildThreads", 2)). \
start()
try:
# The tmpDir variable points to a directory where temporary files are
# kept during the build. It can for instance be created by calling
# tmpDir =
TempFileUtil.createTempDirectory()
# Iterate over all Java projects and create compile tasks for them
itr = javaProjects.iterator()
while(itr.hasNext()):
proj = itr.next()
projName = proj.getName()
# Create a temporary directory to put the preprocessed source
# files in.
sourceDir =
Directories.newDirectory(tmpDir, projName + "_src")
# Create the preprocess task for all source files
#
# Preprocess all files in the directory hierarchy. Ignore
# Subversion's .svn directories.
#
# The replace 232 -> 233 is for the documentation's unit tests.
# Pretend that it's not there...
ppt = RecursiveProcessTF(). \
addSources(
DirectoryAndFilter.listWithFilter(
Directories.newViews(
proj.getSourceDirectories(), \
noSvnFilter), \
EFileFilter.FILTER)). \
setTarget(sourceDir). \
setTaskFactory(
TextReplaceTF(). \
addReplace("!!!VERSION!!!", "1.0"). \
addReplace("232", "233")). \
create()
te.add(ppt)
# Create a temporary directory for compiled files
classDir =
Directories.newDirectory(tmpDir, projName)
classDirs.append(classDir)
# Create the compilation task
#
# Add a
JavaProjectClasspathDecorator that uses
# the
JavaProjectDependencies object created above to give the
# compile task its classpath
javacTask = Jdk6JavacTF(). \
addSource(sourceDir). \
setTarget(classDir). \
addClasspathDecorator(
JavaProjectClasspathDecorator(). \
setProject(proj). \
setDependencies(projDeps)).create()
# Register the compile task as the dependency that this project
# hinges on. (If we would post process the compiled class files
# in any way, for instance Emma instrument them, the post
# process task would (probably) be the one that was registered
# here.)
projDeps.registerDependency(proj, javacTask)
# Also register the class directory so that it can be found by
# projects that depend on this project.
projDeps.registerClassDirectory(proj, classDir)
# Add the compile task to the task executor. Now the compile
# task hinges on both it's dependent projects and the preprocess
# task.
te.add(
javacTask, \
CompoundTaskDependency(). \
addAll(projDeps.getDependencies(proj)). \
add(ppt))
# End while
# Create a Jar file with all the compiled classes
#
# targetFile is a
File or a
FutureFile
#
# projDeps is also a dependency object for all compilation tasks.
te.add(
JarTF(). \
setTarget(targetFile). \
addSources(classDirs), \
projDeps)
# Wait for all tasks to complete
te.waitFor()
finally:
te.shutdown()
The EclipseWorkspace uses a plugin mechanism
for parsing project directories. Task packages may register new plugins that
teaches it to create new kinds of Project objects. See the
reference documentation for details.
IntelliJ IDEA project support is provided by the
IntelliJWorkspace class.
Note
There is a bit of name
confusion at work here. The equivalent of the Eclipse workspace is called a
project in IntelliJ and the equivalent of the Eclipse project is called a
module. But, to try to keep some semblance of consistency,
in Schmant and from now on in this manual, an IntelliJ project
is called a workspace and an IntelliJ module is called a project.
Sorry for the confusion.
The IntelliJWorkspace supports several types
of project dependencies. A project can have dependencies to module libraries,
project libraries, global libraries and application-supplied (IntelliJ-supplied)
libraries such as the Java EE libraries, as well as to single Jar files and
class file directories. If used by the workspace, information about global
and application-supplied libraries has to be provided in an
IntelliJWorkspaceSettings object when creating the
workspace object. All other necessary information about where to locate dependencies
is parsed from the workspace's .ipr
and
.iml
files by the
IntelliJWorkspace object.
An IntelliJ JavaProject also implements the
IntelliJJavaProject interface which adds a
getTestSourceDirectories
method for getting a project's
test sources.
The example below shows how to build an IntelliJ workspace with global
and application-supplied library dependencies.
Example 8.8. Build an IntelliJ workspace
import java.io.File
import org.entityfs.el.AbsoluteLocation
import org.entityfs.util.*
import org.schmant.project.intellij.*
import org.schmant.project.java.Library
import org.schmant.task.project.JavaWorkspaceBuilderTF
// Create an
IntelliJWorkspaceSettings object that is used to provide
// information that cannot be parsed from its .ipr and .iml files to the
// IntelliJ workspace object.
def settings = new
IntelliJWorkspaceSettings().
// Supply the IntelliJ IDEA installation directory. You don't need to do this
// if your workspace does not use any application-supplied libraries.
setApplicationHomeDirectory(
FileSystems.getEntityForDirectory(
new
File("/home/kalle/java/idea-7590"),
true));
// The GlobalLib library uses all Jar files in the lib and lib2 directories and
// the class files in the directory tree under the classes directory.
def globalLib = new
Library().
addEntries(
Directories.getAllFilesMatching(lib, "*.jar")).
addEntries(
Directories.getAllFilesMatching(lib2, "*.jar")).
addEntry(classes)
// Add the global library to the settings.
settings.addGlobalLibrary("GlobLib1", globalLib)
// Create an
IntelliJWorkspace object for the workspace in the directory wos.
// Supply both the global library and the path to the IntelliJ installation.
//
// If you don't want to use a proper IntelliJ installation for the application-
// supplied libraries, use any old directory that contains all required library
// files in the places where the workspace expects them. If a required file
// cannot be found, the workspace object throws an exception with a message that
// can be used to fix the problem.
//
// The application installation path or the global library map can be set to
// null if they are not used by the workspace.
def ijWos = new
IntelliJWorkspace(
wos,
// We have to tell the constructor where to find the workspace's .ipr file
new
AbsoluteLocation("/MyProject.ipr"),
settings)
// Build the workspace.
// The default is to build all Java files in the source and the test source
// directories. The dontBuildTestSources and dontBuildRegularSources properties
// may be set to avoid building either kind of sources.
new
JavaWorkspaceBuilderTF().
setWorkspace(ijWos).
// bin is a Java
File or an EntityFS
Directory. Class files built
// from both the regular and the test sources are put here.
setTarget(bin).
run()
// Create an
IntelliJWorkspaceSettings object that is used to provide
// information that cannot be parsed from its .ipr and .iml files to the
// IntelliJ workspace object.
settings = new
IntelliJWorkspaceSettings().
// Supply the IntelliJ IDEA installation directory. You don't need to do this
// if your workspace does not use any application-supplied libraries.
setApplicationHomeDirectory(
FileSystems.getEntityForDirectory(
new
File("/home/kalle/java/idea-7590"),
true));
// The GlobalLib library uses all Jar files in the lib and lib2 directories and
// the class files in the directory tree under the classes directory.
globalLib = new
Library().
addEntries(
Directories.getAllFilesMatching(lib, "*.jar")).
addEntries(
Directories.getAllFilesMatching(lib2, "*.jar")).
addEntry(classes);
// Add the global library to the settings.
settings.addGlobalLibrary("GlobLib1", globalLib);
// Create an
IntelliJWorkspace object for the workspace in the directory wos.
// Supply both the global library and the path to the IntelliJ installation.
//
// If you don't want to use a proper IntelliJ installation for the application-
// supplied libraries, use any old directory that contains all required library
// files in the places where the workspace expects them. If a required file
// cannot be found, the workspace object throws an exception with a message that
// can be used to fix the problem.
//
// The application installation path or the global library map can be set to
// null if they are not used by the workspace.
ijWos = new
IntelliJWorkspace(
wos,
// We have to tell the constructor where to find the workspace's .ipr file
new
AbsoluteLocation("/MyProject.ipr"),
settings);
// Build the workspace.
// The default is to build all Java files in the source and the test source
// directories. The dontBuildTestSources and dontBuildRegularSources properties
// may be set to avoid building either kind of sources.
new
JavaWorkspaceBuilderTF().
setWorkspace(ijWos).
// bin is a Java
File or an EntityFS
Directory. Class files built
// from both the regular and the test sources are put here.
setTarget(bin).
run();
# Create an
IntelliJWorkspaceSettings object that is used to provide
# information that cannot be parsed from its .ipr and .iml files to the
# IntelliJ workspace object.
settings = Schmant::
IntelliJWorkspaceSettings.new.
# Supply the IntelliJ IDEA installation directory. You don't need to do this
# if your workspace does not use any application-supplied libraries.
setApplicationHomeDirectory(
Schmant::
FileSystems.getEntityForDirectory(
Java::JavaIo::
File.new("/home/kalle/java/idea-7590"),
true))
# The GlobalLib library uses all Jar files in the lib and lib2 directories and
# the class files in the directory tree under the classes directory.
globalLib = Schmant::
Library.new.
addEntries(Schmant::
Directories.getAllFilesMatching($lib, "*.jar")).
addEntries(Schmant::
Directories.getAllFilesMatching($lib2, "*.jar")).
addEntry($classes)
# Add the global library to the settings.
settings.addGlobalLibrary("GlobLib1", globalLib)
# Create an
IntelliJWorkspace object for the workspace in the directory wos.
# Supply both the global library and the path to the IntelliJ installation.
#
# If you don't want to use a proper IntelliJ installation for the application-
# supplied libraries, use any old directory that contains all required library
# files in the places where the workspace expects them. If a required file
# cannot be found, the workspace object throws an exception with a message that
# can be used to fix the problem.
#
# The application installation path or the global library map can be set to
# null if they are not used by the workspace.
ijWos = Schmant::
IntelliJWorkspace.new(
$wos,
# We have to tell the constructor where to find the workspace's .ipr file
Schmant::
AbsoluteLocation.new("/MyProject.ipr"),
settings)
# Build the workspace.
# The default is to build all Java files in the source and the test source
# directories. The dontBuildTestSources and dontBuildRegularSources properties
# may be set to avoid building either kind of sources.
Schmant::JavaWorkspaceBuilderTF.new.
setWorkspace(ijWos).
# bin is a Java
File or an EntityFS
Directory. Class files built
# from both the regular and the test sources are put here.
setTarget($bin).
run
# Create an
IntelliJWorkspaceSettings object that is used to provide
# information that cannot be parsed from its .ipr and .iml files to the
# IntelliJ workspace object.
#
# The IntelliJ IDEA installation directory has to be supplied if the workspace
# does not use any application-supplied libraries.
settings =
IntelliJWorkspaceSettings(). \
setApplicationHomeDirectory( \
FileSystems.getEntityForDirectory( \
File("/home/kalle/java/idea-7590"), \
True))
# The GlobalLib library uses all Jar files in the lib and lib2 directories and
# the class files in the directory tree under the classes directory.
globalLib =
Library(). \
addEntries(
Directories.getAllFilesMatching(lib, "*.jar")). \
addEntries(
Directories.getAllFilesMatching(lib2, "*.jar")). \
addEntry(classes)
# Add the global library to the settings.
settings.addGlobalLibrary("GlobLib1", globalLib)
# Create an
IntelliJWorkspace object for the workspace in the directory wos.
# Supply both the global library and the path to the IntelliJ installation.
#
# If you don't want to use a proper IntelliJ installation for the application-
# supplied libraries, use any old directory that contains all required library
# files in the places where the workspace expects them. If a required file
# cannot be found, the workspace object throws an exception with a message that
# can be used to fix the problem.
#
# The application installation path or the global library map can be set to
# null if they are not used by the workspace.
ijWos =
IntelliJWorkspace( \
wos, \
AbsoluteLocation("/MyProject.ipr"), \
settings)
# Build the workspace.
# The default is to build all Java files in the source and the test source
# directories. The dontBuildTestSources and dontBuildRegularSources properties
# may be set to avoid building either kind of sources.
#
# bin is a Java
File or an EntityFS
Directory. Class files built from
# both the regular and the test sources are put here.
JavaWorkspaceBuilderTF(). \
setWorkspace(ijWos). \
setTarget(bin). \
run()
The IntelliJWorkspace uses a plugin mechanism
for parsing project directories. Task packages may register new plugins that
teaches it to create new kinds of Project objects. See the
reference documentation for details.
Appendix A. ArgumentInterpreter
Argument interpretation gives great flexibility in which kinds of arguments
that can be used for setting task properties. Most tasks use
ArgumentInterpreter together with an
ArgumentInterpretationStrategy to interpret some of their
properties. This appendix describes how the different strategies interpret
properties into the expected types. It also lists useful implementations of the
different argument types.
java.io.File (file or directory)
InterpretAsFileStrategy is used to interpret objects into
File:s (files and/or directories).
java.io.File
are used by tasks that use
modules that are not EntityFS-aware, i.e. mostly tasks that run external programs.
The files returned by the interpretation methods may or may not exist. Their
paths may be relative or absolute.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.1. Interpretation of an argument into a file or directory java.io.File
# | Type of input | Algorithm |
---|
1. | File | Return it. |
2. | CharSequence (a String, for instance) | The text is interpreted as a path name. A new File object is
created with the path name and returned. |
3. | EntityView
that supports the ECFileResolvable capability. | Return the Entity's backing File. |
4. | DirectoryRepresentation whose
directory supports the ECFileResolvable capability. | Return the directory's backing File. |
4. | NamedReadableFile | The contents of the file is copied to a temporary file with the
same name. The File object referencing the temporary file is returned. |
5. | ReadableFile | The contents of the file is copied to a temporary file with a generated
name. |
6. | FutureEntity (existing) | Return the File that backs the entity. This requires that
the entity supports the ECFileResolvable
capability. |
7. | FutureEntity (nonexisting) | Return a File representing the future entity's location,
based on the location of its base directory. This requires that the base directory
supports the ECFileResolvable
capability. |
InterpretAsUrlStrategy is used to interpret objects into
URL objects.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.2. Interpretation of an argument into a java.net.URL
InterpretAsFutureEntityStrategy is used to interpret
objects into FutureEntity objects.
The InterpretAsFutureEntityStrategy constructor takes an optional
source
value. It can be used as a hint to a
FutureEntityStrategy object to help it decide what to call the
entity it creates. It is up to each task that use future entities to decide if
it should provide a hint. A process task uses the
source
property as a hint.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.3. Interpretation of an argument into a FutureEntity
InterpretAsNamedReadableFileStrategy is used to interpret
objects into NamedReadableFile:s.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.4. Interpretation of an argument into a NamedReadableFile
# | Type of input | Algorithm |
---|
1. | NamedReadableFile | Return it. |
2. | RenamedFile | Run the file property through the
getReadableFile method. Return a
NamedReadableFileAdapter created using the result from the
method and the value of the name property. |
3. | File | If the file object references an existing file, return a
NamedReadableFileAdapter that wraps the file object. Otherwise,
the File object cannot be interpreted. |
4. | CharSequence (a String, for instance) | Interpret the supplied text as an URI. If the URI is absolute,
return a UrlReadableFile wrapped in a
NamedReadableFileAdapter. (This means that any valid URI can
be used as a file.) If the URI is relative, and thus a local file system path,
and it references an existing file, return a NamedReadableFile
object representing the file. If the relative URI references a nonexisting file,
it cannot be interpreted. |
5. | FutureEntity | If the future entity object references an existing
NamedReadableFile, return it. If not, the
FutureEntity cannot be interpreted. |
See CopyTF examples for how to use an URL as a NamedReadableFile.
InterpretAsReadableFileStrategy is used to interpret
objects into ReadableFile objects.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.5. Interpretation of an argument into a ReadableFile
# | Type of input | Algorithm |
---|
1. | ReadableFile | Return it. |
2. | byte[] | The array is wrapped in a ByteArrayReadableFile and
returned. |
3. | RenamedFile | Run the file property through the
getReadableFile method. Return a
NamedReadableFileAdapter created using the result from the
method and the value of the name property. |
4. | File | If the file object references an existing file, return a
FileReadableFile that wraps the file object. Otherwise, the
File cannot be interpreted. |
5. | CharSequence (a String, for instance) | Interpret the supplied text as an URI. If the URI is absolute,
return a UrlReadableFile wrapped in a
NamedReadableFileAdapter. This means that any valid URI can
be used as a file. If the URI is relative, and thus a local file system path,
and it references an existing file, return a NamedReadableFile
object representing the file. If the relative URI references a nonexisting file,
it cannot be interpreted. |
6. | FutureEntity | If the future entity object references an existing
NamedReadableFile, return it. If not, it cannot be interpreted. |
See CopyTF examples for how to use an URL as a ReadableFile.
InterpretAsWritableFileStrategy is used to interpret
objects into WritableFile objects representing existing files.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.6. Interpretation of an argument into an existing WritableFile
# | Type of input | Algorithm |
---|
1. | WritableFile | Return it. |
2. | FutureEntity | Get the file referenced by the future entity, possibly creating
it if it does not already exist. |
3. | File | If the file does not exist, create it. Return the file wrapped in a
ReadWritableFileAdapter. |
The InterpretAsNewWritableFileStrategy interprets an
object into a location for a new writable file, creates the file and then
returns it. If there already is a file at the target location, it uses an
OverwriteStrategy to decide what to do with it. If the
strategy decides to keep the old file, most tasks will fail.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.7. Interpretation of an argument into a new WritableFile
# | Type of input | Algorithm |
---|
1. | WritableFile | Return it. |
2. | FutureEntity | If there already is an entity at the location referenced by the future
entity, use the overwrite strategy to try to delete it. If successful, create a
new file and return it. |
3. | NewWritableFile | Return the new writable file.
|
4. | File | If there already is an entity at the location referenced by the file
object, use the overwrite strategy to try to delete it. If successful, create a
new file and return it. |
InterpretAsRandomlyAccessibleFileStrategy is used to
interpret objects into RandomlyAccessibleFile:s.
For some valid arguments, the returned file may be read only.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.8. Interpretation of an argument into a RandomlyAccessibleFile
InterpretAsEntityHolderStrategy is used to interpret
objects into EntityHolder objects.
An entity holder is a very limited Directory.
The returned directory is
wrapped in a DirectoryRepresentation object. It contains a
DirectoryView and an optional EntityFilter. If the
filter is set, the running task will only see the entities matching the
filter. If the task is recursive, it will still propagate down into all
subdirectories, even if they don't match the filter. This is how
using a DirectoryRepresentation
source
differs from using a DirectoryView source; when using a
DirectoryView source, only the subdirectories matching the filter will be
processed.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.9. Interpretation of an argument into an entity holder
Read only Entity (file or directory)
InterpretAsReadOnlyEntityStrategy is used to interpret
objects as read only EntityView:s.
By calling this method, the client says that it does not require a
entity that it can write to, only one it can read from. The returned entity is
not required to be read only, it might just as well be
read/write.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.10. Interpretation of an argument into a read only EntityView
# | Type of input | Algorithm |
---|
1. | EntityView | Return it. |
2. | DirectoryRepresentation | Return the directory. |
3. | FutureEntity | If the future entity references an existing entity, return it. Otherwise,
the object cannot be interpreted. |
4. | NamedReadableFile | Create a new file with the same name in a temporary directory. Copy the
contents of the supplied file to the new file and return it. |
5. | ReadableFile | Create a new temporary file with a generated name and copy the contents
of the supplied file to it. Return the temporary file. |
6. | CharSequence (a String, for instance) | Interpret the text as a file system path. If it
references an existing entity (file or directory), create a temporary entity
object based on the entity and return it. If not, the object cannot be
interpreted. |
7. | File | If the File object references an existing entity (file or
directory), create a temporary entity object based on the entity and return it.
If not, the object cannot be interpreted. |
InterpretAsReadOnlyDirectoryStrategy is used to interpret
objects as read only DirectoryView:s.
By calling this method, the client says that it does not require a
directory that it can write to, only one it can read from. The returned directory
is not required to be read only, it might just as well be read/write.
The returned directory is
wrapped in a DirectoryRepresentation object. It contains a
DirectoryView and an optional EntityFilter. If the
filter is set, the running task will only see the entities matching the
filter. If the task is recursive, it will still propagate down into all
subdirectories, even if they don't match the filter. This is how
using a DirectoryRepresentation
source
differs from using a DirectoryView source; when using a
DirectoryView source, only the subdirectories matching the filter will be
processed.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.11. Interpretation of an argument into a read only Directory
# | Type of input | Algorithm |
---|
1. | DirectoryRepresentation | Return it. |
2. | DirectoryAndFilter | Call this method with the DirectoryAndFilter's directory
to get the directory. Return the directory and the filter in a
DirectoryRepresentation object. |
3. | FutureEntity | If the future entity references an existing DirectoryView
(or any subclass), return it. Otherwise, the object cannot be
interpreted. |
4. | CharSequence (a String, for instance) | Interpret the text as a file path. If it references an
existing directory, return the directory wrapped in a
DirectoryRepresentation. Otherwise, the object cannot be
interpreted. |
5. | File | If the File object references an existing directory, create a
temporary entity object based on the entity and return it.
If not, the object cannot be interpreted. |
InterpretAsDirectoryStrategy is used to interpret
objects as read/write DirectoryView:s.
The returned directory is
wrapped in a DirectoryRepresentation object. It contains a
DirectoryView and an optional EntityFilter. If the
filter is set, the running task will only see the entities matching the
filter. If the task is recursive, it will still propagate down into all
subdirectories, even if they don't match the filter. This is how
using a DirectoryRepresentation
source
differs from using a DirectoryView source; when using a
DirectoryView source, only the subdirectories matching the filter will be
processed.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.12. Interpretation of an argument into a read/write Directory
Read/write Entity (file or directory)
InterpretAsEntityStrategy is used to interpret objects as
read/write EntityView:s.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.13. Interpretation of an argument into a read/write Entity
# | Type of input | Algorithm |
---|
1. | EntityView | Return it. |
2. | DirectoryRepresentation | Return the representation's directory. |
3. | FutureEntity | If the future entity references an existing entity, return it. Otherwise,
the object cannot be interpreted. |
4. | CharSequence (a String, for instance) | Interpret the text as a file system path. If it
references an existing entity (file or directory), create a temporary entity
object based on the entity and return it. If not, the object cannot be
interpreted. |
5. | File | If the File object references an existing entity (file or
directory), create a temporary entity object based on the entity and return it.
If not, the object cannot be interpreted. |
InterpretAsEFileStrategy is used to interpret objects as
read/write EFile:s.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.14. Interpretation of an argument into a read/write EFile
# | Type of input | Algorithm |
---|
1. | EFile | Return it. |
2. | FutureEntity | If the future entity references an existing EFile, return it.
Otherwise, the object cannot be interpreted. |
3. | CharSequence (a String, for instance) | Interpret the text as a file system path. If that path does not reference
an existing file, treat the text as an URI. If an existing file was referenced
by the text, create a temporary file entity object based on the file and return
it. If not, the object cannot be interpreted. |
4. | File | If the File object references an existing file, create a
temporary EFile object based on the File and return it.
If not, the object cannot be interpreted. |
InterpretAsXmlSourceStrategy is used to interpret objects
as XML Source objects that can be used for XSL
transformations.
First, ArgumentInterpreter
preprocesses the argument list as described above. Then the strategy object
tries to interpret each object in the preprocessed list by checking if each
object is of any of the types listed below, in the order that they are listed.
If it is, the algorithm listed at the matched type is used to create the
object tor return. If there are objects for which there are no matches, the
configuration of the ArgumentInterpretationStrategy decides
what will happen.
Table A.15. Interpretation of an argument into an XML Source
EntityView implementations
An EntityView is a file system entity, either a file or a
directory. It has a unique location in a FileSystem.
The tree view below lists the type's
inheritance hierarchy. The names of interfaces are written in italics.
EntityView -- view of generic file system entity
+ Entity -- generic file system entity
| + EFile -- file entity
| + Directory -- directory entity (no filters)
+ DirectoryView -- Directory with contents filtered by zero or more
| EntityFilter:s
+ Directory -- directory entity (no filters)
FutureEntityStrategy implementations
The FutureEntityStrategy defines a strategy that uses a
source object and creates a FutureEntity from it. Note that
you can use a closure instead of a future entity strategy.
The tree view below lists the type's
inheritance hierarchy. The names of interfaces are written in italics.
FutureEntityStrategy -- future entity strategy
+ FutureEntityIndexStrategy -- strategy that creates future entities on the
form [base_directory]/[prefix][index][suffix]
where index is incremented for each created
future entity
Appendix B. EntityFS cookbook
Schmant uses EntityFS extensively for working with files and
directories. This appendix gives some basic information about EntityFS programming
that might be useful for build script writers. More exhaustive information about
EntityFS programming can be found in the EntityFS Programmer's Guide.
EntityFS is an object-oriented file system API for Java. The difference in
philosophy between EntityFS and Java's built in file and directory support is
that Java's File object represents a path in the local file system,
while EntityFS entities represent the files and directories
themselves. From the application's perspective, an EntityFS
EFile entity, for instance, can be said to be
the file, while a Java File object is a pointer to the file. If the
EntityFS EFile is moved, the object is updated with the new location.
If it is deleted, the entity object is invalidated. If a new file is put in the
same location in the file system, the original EFile object is still
invalid.
The properties of entity objects are further discussed in
this article on the EntityFS site.
EntityFS file systems can be built on
the operating system's file system, on Zip or Jar files or in memory. See for
instance the section called “In-memory file systems”.
An EntityFS FileSystem contains
file and directory
entities. Entities are mostly used through
any of the utility classes.
Entities always live within the context of a FileSystem. It
has some global properties, such as strategies for entity
locking and for handling of file system events. It can also have
any number of FileSystemCapability:s and
EntityCapability:s that extend its functionality. For instance,
the ECFileResolvable entity capability makes files
and directories resolvable to Java's File objects.
A file system always contains a root Directory that can
be retrieved by calling its getRootDirectory()
method.
File systems are either created manually using a FileSystemBuilder
or through any of the utility methods of SchmantFileSystems.
SchmantFileSystems adapts the file system-creating
methods of EntityFS' FileSystems and also adds a few file
system-creation methods of its own.
FileSystem:s may be created to support entity locking. This
should be enabled for all read/write file systems accessed by parallel build
threads, since it prevents tasks from accidentally writing to the same files or
directories simultaneously. Many of the file systems returned by
SchmantFileSystems' methods have locking enabled.
Table B.1. FileSystemBuilder implementations
Implementation | Description |
---|
FSROFileSystemBuilder | Builds a read only file system that is backed by the operating system's
file system. The file system's root directory can be in any directory in the
backing file system. |
FSRWFileSystemBuilder | Builds a read/write file system that is backed by the operating system's
file system. The file system's root directory can be in any directory in the
backing file system. |
JarFileSystemBuilder | Builds a read only file system that is backed by a Jar file. The root
directory is in the root of the Jar file. |
RamFileSystemBuilder | Builds a read/write file system that stores its data in memory. |
ZipFileSystemBuilder | Builds a read only file system that is backed by a Zip file. The root
directory is in the root of the Zip file. |
Example B.1. Creating source and target file systems
import java.io.File
import org.entityfs.util.Directories
import org.entityfs.util.filter.entity.EntityNameFilter
import org.schmant.support.entityfs.SchmantFileSystems
import org.schmant.support.io.TempFileUtil
// Create a read only file system with its root directory in
// /home/me/myproject/src
srcd =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/src"), true)
// Get a view of the root directory that hides all .svn directories. When this
// view is used for getting child directories, all .svn directories will be
// hidden in them too, making all .svn directories in the file system invisible
// for all EntityFS-aware tasks.
// The ~ is used to negate the filter.
srcRoot = srcd.newView(~(new
EntityNameFilter(".svn")))
// Create a read/write file system in a temporary directory to use for putting
// built files in. Keep this directory when the script is done (set keep flag to
// true).
targetRoot =
TempFileUtil.createTempDirectory(
"myproject", null, true)
// Set a temporary directory on the target file system. Tasks that need to
// create temporary files use this directory for them.
targetRoot.fileSystem.setTemporaryFilesDirectory(
Directories.newDirectory(targetRoot, "tmp"))
// Create a read only file system with its root directory in
// /home/me/myproject/src
srcd =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/src"), true);
// Get a view of the root directory that hides all .svn directories. When this
// view is used for getting child directories, all .svn directories will be
// hidden in them too, making all .svn directories in the file system invisible
// for all EntityFS-aware tasks.
srcRoot = srcd.newView(new
EntityNameFilter(".svn").not());
// Create a read/write file system in a temporary directory to use for putting
// built files in. Keep this directory when the script is done (set keep flag to
// true).
targetRoot =
TempFileUtil.createTempDirectory(
"myproject", null, true);
// Set a temporary directory on the target file system. Tasks that need to
// create temporary files use this directory for them.
targetRoot.getFileSystem().setTemporaryFilesDirectory(
Directories.newDirectory(targetRoot, "tmp"));
// Create a read only file system with its root directory in
// /home/me/myproject/src
srcd =
SchmantFileSystems.getEntityForDirectory(
new
File("/home/me/myproject/src"), true);
// Get a view of the root directory that hides all .svn directories. When this
// view is used for getting child directories, all .svn directories will be
// hidden in them too, making all .svn directories in the file system invisible
// for all EntityFS-aware tasks.
srcRoot = srcd.newView(new
EntityNameFilter(".svn").not());
// Create a read/write file system in a temporary directory to use for putting
// built files in. Keep this directory when the script is done (set keep flag to
// true).
targetRoot =
TempFileUtil.createTempDirectory(
"myproject", null, true);
// Set a temporary directory on the target file system. Tasks that need to
// create temporary files use this directory for them.
targetRoot.getFileSystem().setTemporaryFilesDirectory(
Directories.newDirectory(targetRoot, "tmp"));
# Create a read only file system with its root directory in
# /home/me/myproject/src
srcd =
SchmantFileSystems.getEntityForDirectory( \
File("/home/me/myproject/src"), True)
# Get a view of the root directory that hides all .svn directories. When this
# view is used for getting child directories, all .svn directories will be
# hidden in them too, making all .svn directories in the file system invisible
# for all EntityFS-aware tasks.
srcRoot = srcd.newView(
EntityNameFilter(".svn").not())
# Create a read/write file system in a temporary directory to use for putting
# built files in. Keep this directory when the script is done (set keep flag to
# true).
targetRoot =
TempFileUtil.createTempDirectory( \
"myproject", None, True)
# Set a temporary directory on the target file system. Tasks that need to
# create temporary files use this directory for them.
targetRoot.getFileSystem().setTemporaryFilesDirectory( \
Directories.newDirectory(targetRoot, "tmp"))
Schmant implements a LogAdapter,
the SchmantReportLogAdapter, for making EntityFS log
into a Schmant Report. This log adapter can manually be set
on a file system fs
by calling
fs.getLogAdapterHolder().setLogAdapter(SchmantReportLogAdapter.INSTANCE)
.
If any of the methods of SchmantFileSystems or
TempFileUtil are used
for creating entities (and thus also file systems), the returned file systems
already have the Schmant log adapter set.
Example B.2. Using the SchmantReportLogAdapter
import org.entityfs.fs.FSRWFileSystemBuilder
import org.schmant.report.SchmantReportLogAdapter
// Create a new file system with its root directory in the directory d
// (a
File).
def fs = new
FSRWFileSystemBuilder().
setRoot(d).
create()
// Set the Schmant log adapter
fs.logAdapterHolder.setLogAdapter(
SchmantReportLogAdapter.INSTANCE)
// This is written to the current
Report
fs.logAdapter.logWarning("This is a warning message!")
// Create a new file system with its root directory in the directory d
// (a
File).
fs = new
FSRWFileSystemBuilder().
setRoot(d).
create();
// Set the Schmant log adapter
fs.getLogAdapterHolder().setLogAdapter(
SchmantReportLogAdapter.INSTANCE);
// This is written to the current
Report
fs.getLogAdapter().logWarning("This is a warning message!");
# Create a new file system with its root directory in the directory d
# (a
File).
fs = Schmant::
FSRWFileSystemBuilder.new.
setRoot($d).
create
# Set the Schmant log adapter
fs.logAdapterHolder.setLogAdapter(
Schmant::
SchmantReportLogAdapter::INSTANCE)
# This is written to the current
Report
fs.logAdapter.logWarning("This is a warning message!")
# Create a new file system with its root directory in the directory d
# (a
File).
fs =
FSRWFileSystemBuilder(). \
setRoot(d). \
create()
# Set the Schmant log adapter
fs.getLogAdapterHolder().setLogAdapter( \
SchmantReportLogAdapter.INSTANCE)
# This is written to the current
Report
fs.getLogAdapter().logWarning("This is a warning message!")
Entity:s live within the context of a FileSystem;
an entity can never leave it. Every entity has a unique location,
an AbsoluteLocation in its file system.
Entity objects are never instantiated directly by clients. Existing
entities are retrieved from their parent Directory object or, more
commonly, by using the utility methods of Directories.
New entities are also created by using the methods of their parent
Directory or by using Directories' methods.
All entities use read/write locking to protect them from concurrent
modification. Before using an entity method on an entity in a locking file
system, the entity has to be either locked
for reading or writing, depending on what the method does. All utility class methods
acquire the necessary locks before calling entity methods, which make them much
easier to use compared with calling entity methods directly.
A DirectoryView is a Directory viewed through
zero or more EntityFilter:s. If a child directory is fetched through
a directory view, it inherits the parent directory's view settings.
A common usage is to use directory views to hide all
.svn
directories in a file system, see
this example.
Often, most of the work against a FileSystem and its entities
is done through EntityFS' utility classes. They have high-level methods for
common operations, such as listing directory contents with filters or reading
contents of files.
Table B.2. EntityFS utility classes (useful subset)
EntityFilter implementations
The following table contains the entity Filter implementations
that EntityFS comes with.
Table B.3. EntityFilter implementations
All entity filters implement ConvenientFilter
so they have and
, or
,
xor
and not
methods to combine them with other filters.
See for instance this example
for how filters can be used.
The following is a collection of best practices for using EntityFS in
Schmant build scripts: