Chapter 4. Build scripts and tasks

Table of Contents

A real-world example
Tasks and task factories
Interpreted arguments
Future entities
Producers
Recursive 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

info("Hello World!");

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

Groovy

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

JavaScript

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

JRuby

# 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

Jython

# 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

Groovy

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

JavaScript

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

JRuby

# 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

Jython

# 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

Groovy

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

JavaScript

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

JRuby

# 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

Jython

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

There are two ways of representing the target of a generator task – using a File or using a FutureEntity such as a FutureFile or a FutureDirectory. Both are references to the location where the generator task will put its result.

After the generator task has been run, the created entity can be retrieved from the FutureEntity object. This makes it possible to use future entities for representing source entities that do not yet exist when using a TaskExecutor for running tasks, see the section called “Task executors and future entities” for an example.

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.

Groovy

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

JavaScript

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

JRuby

# 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

Jython

# 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.

Groovy

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

JavaScript

// 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); }

JRuby

# 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

Jython

# 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

Groovy

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

JavaScript

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

JRuby

# 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

Jython

# 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.

Groovy

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

JavaScript

// 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(); }

JRuby

# 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

Jython

# 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.

Groovy

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

JavaScript

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

JRuby

# 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

Jython

# 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.


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.

Groovy

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

JavaScript

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

JRuby

# 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

Jython

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