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()