Chapter 5. Environment and resources

Table of Contents

Reports and logging
Schmant and EntityFS
Properties and arguments
Utility classes
Temporary files and directories
Including other script files

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.


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:

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

Groovy

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

JavaScript

// 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. itr = new FilteringIterator( Directories.getDepthLastIterator(d), new EFileNameExtensionFilter("xml").and( new EntityRecentModificationFilter(6, TimeUnit.HOURS).and( new SuperParentOrFilter( new EntityNameGlobFilter("here*"))))); while(itr.hasNext()) { Packages.java.lang.System.out.println(Files.readTextFile(itr.next())); }

JRuby

# 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. itr = Schmant::FilteringIterator.new( Schmant::Directories.getDepthLastIterator($d), Schmant::EFileNameExtensionFilter.new("xml").and( Schmant::EntityRecentModificationFilter.new( 6, Java::JavaUtilConcurrent::TimeUnit::HOURS).and( Schmant::SuperParentOrFilter.new( Schmant::EntityNameGlobFilter.new("here*"))))) while itr.hasNext do Java::JavaLang::java.lang.System.out.println(Schmant::Files.readTextFile(itr.next)) end

Jython

# 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. # # java.util.concurrent classes are not imported automatically from java.util.concurrent import TimeUnit itr = FilteringIterator( \ Directories.getDepthLastIterator(d), \ EFileNameExtensionFilter("xml").and( \ EntityRecentModificationFilter(6, TimeUnit.HOURS).and( \ SuperParentOrFilter( \ 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

Groovy

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.

JavaScript

// Create an EclipseWorkspace from the contents of directory wos // Hide all .svn directories. wWos = new EclipseWorkspace( wos.newView(new EntityNameFilter(".svn").not())); // Now the .svn directories in the workspace will be invisible.

JRuby

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

Jython

# Create an EclipseWorkspace from the contents of directory wos # Hide all .svn directories. wWos = EclipseWorkspace( \ wos.newView(EntityNameFilter(".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

Groovy

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

JavaScript

// 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

JRuby

# 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

Jython

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

propsProperties

Java system properties and properties set using the -p argument when launching Schmant.

argsList<String>

Script arguments given after the script file on the command line.

org_schmant_scriptFileEFile

The 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

Groovy

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)

JavaScript

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

JRuby

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

Jython

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

ArgumentInterpreter

A singleton instance of the ArgumentInterpreter class is used by many tasks to interpret untyped arguments. See the section called “Interpreted arguments”. ArgumentInterpreter is seldom used directly by Schmant scripts.

DirectoryAndFilter

This is not a utility class per se, but it has the static listWithFilter method for creating a list of DirectoryAndFilter objects from a collection of directories and an EntityFilter. See for instance the task reference documentation for RecursiveProcessTF for examples.

JdkUtil

Utility methods for working with JRE and JDK installations.

Example 5.5. Using JdkUtil

Groovy

import org.schmant.support.* // Only do this if we're on Java 7 or newer if (JdkUtil.getCurrentJdkVersion().isSameOrNewerThan(new JdkVersion("1.7"))) { // ... do stuff } else { warn("This requires Java 7 or newer") }

JavaScript

// Only do this if we're on Java 7 or newer if (JdkUtil.getCurrentJdkVersion().isSameOrNewerThan(new JdkVersion("1.7"))) { // ... do stuff } else { warn("This requires Java 7 or newer"); }

JRuby

# Only do this if we're on Java 7 or newer if Schmant::JdkUtil.getCurrentJdkVersion().isSameOrNewerThan(Schmant::JdkVersion.new("1.7")) # ... do stuff else warn "This requires Java 7 or newer" end

Jython

# Only do this if we're on Java 7 or newer if JdkUtil.getCurrentJdkVersion().isSameOrNewerThan(JdkVersion("1.7")): debug("... do stuff") else: warn("This requires Java 7 or newer")

ProjectFilterUtil

Utilities for using project filters. See Chapter 8, Projects.

ReportManager

Holds a reference to each thread's Report object.

SchmantFileSystems

Utility methods for creating EntityFS FileSystem:s that a Schmant script may use. The created file systems have the SchmantReportLogAdapter log adapter set.

The SchmantFileSystems class also has the makeFileBacked and makeRandomlyAccessible for making EFile entities File-backed and FCRandomAccess:ible, respectively.

SchmantUtil

Various utility methods.

TempFileUtil

Utility methods for creating temporary files and directories. See the section called “Temporary files and directories”.

XmlCatalogResolver

XML catalog that can be used for resolving entities when parsing or XSL transforming XML files. See the XsltTF task factory documentation for examples.

In addition to Schmant's own utility classes, many of EntityFS' utility classes may be useful. See Appendix B, EntityFS cookbook.

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.

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.