This page describes the major features in Mutant which are significantly different from those of Ant1. These are covered from a user perspective. Other pages describe the differences from the perspectives of a task developer and an Ant core developer.
When Mutant is installed, the most immediately obvious difference will be in the directory layout, particularly the lib directory. Where Ant1's lib directory contained ant.jar, optional.jar and the bundled XML jars, Mutant's lib directory contain a number of subdirectories.
In the root directory, there are a number of jars. These jars are the startup jars
| init.jar | a set of low level utility routines required at startup |
| start.jar | the Mutant launcher class |
| ant.jar | old Ant1 entry point |
The subdirectories have the following functions
| parser | The XML parser jars. This is not available to task libraries unless they explicitly indicate that it is required. |
| antcore | Mutant's core classes. These classes are not available to tasks. |
| common | classes which are available to both the core and all task libraries. |
| syslibs/antlibs | Task libraries. The distinction between the two is discussed below. |
| frontend | Ant Frontends. Different frontends may be plugged in by placing jars here. |
The directory a jar is in will control the visibility of the jar's classes and resources. This is closely related to the classloader hiearchy used by Mutant.
Mutant supports the concept of Ant Libraries. These are collections of components (tasks and types) and their supporting classes packaged into a Jar. In Mutant each library has a globally unique identifier. This identifier uses the same conventions as Java package naming - i.e. a reverse DNS name.
The jar does not need to be exclusively for Ant. For example, it may be the jar for a tool which wishes to provide some Ant tasks for using the tool. It could be the main jar of an appication server that bundles Ant tasks for deployment. The jar does not even need to be installed in Mutant's antlib directory - Mutant can be configured to look in jars in other locations for Ant Libraries.
Whatever the jar contains and wherever it is located, Mutant looks for an XML based descriptor in the jar at META-INF/antlib.xml. This XML file describes the components in the jar that will be available to Mutant.
Here is an example of the descriptor.
As a user you generally won't need to worry about this decriptor. The library developer will have developed it to describe their library. Mutant uses the descriptor to understand what Ant related components the library provides.
Library management in Mutant is split into two phases - a loading phase and an import phase. The loading phase is where Mutant loads the antlib.xml descriptor from the library. After loading, Mutant knows where the library is located and what components it provides. In general Mutant will not make the components available to build scripts automatically. A build script needs to notify Mutant what components it is using from each library. This is known as importing.
Mutant loads libraries from two locations within the Mutant directory structure when Mutant starts up - the lib/syslibs and lib/antlibs directories. The distinction between these two locations is explained in the configuration discussion below. All jars in these directories will be search for library descriptors. All libraries providing descriptors will be available for importing into builds
In addition to the automatically loaded libraries, it is possible to
explicitly request additional libraries to be loaded using the
<loadlib> task. Individual libraries may be loaded or a set
of libraries loaded from a directory. Libraries may also be loaded from remote
locations by specifying a URL. The loadlib task can request that all components
in the loaded library also be imported at the time of loading. Examples of the
loadlib task follow
In general the loading of libraries from absolute locations would be a configuration task. These would be specified in the Mutant configuration file and would not be used in a build file. The loading of project libraries from relative locations may be something that you would see in a build file.
Mutant is designed to allow tasks to be defined simply by dropping a jar in a directory or configuring Mutant to look in particular places for jars. This will allow tasks and types developed by many different developers to be used. With many developers developing tasks, however, inevitably some tasks will end up with the same name.
This is very similar to the situation faced by Java with Java class names. Many Java classes have the same name. When a Java programmer uses a class they must import that class with an import statement. This tells the Java compiler which particular class is being referenced by the classname. Effectively the import statement creates a mapping from a name used in the Java source file and a class in the global package namespace.
Mutant provides an import task to specify, in a manner similar to Java's import statement, which components are being used. When a component is imported the library is identified by its unique id. By default the component is imported into the frame using the name it is known by within the library. When this would conflict with a name that has already been imported, the import task allows the component to be given a new name, or alias, by which it will be referenced. It is also possible to import all the components in a library. The folowing example shows the various methods by which the import task can be used to import components.
By using library identifiers, import operations are not tied to a particular library location. The separation of the loading and importing into separate phases allows environment-dependent locations to be specified as configuration information and location independent importing to be specified in the build file. This is similar to the case of Java imports. Java classes are imported by their global name without needing to known where the classfile is that contains that class. That information is provided externally in the CLASSPATH variable.
Many libraries will have dependencies on external jars. For example, a task might make use of regular-expression libraries such as jakarta-regexp or a task may provide a wrapper around some external tool. In these cases the task will usually need to have the required classes available in the classloader from which the task itself was loaded. It is possible to avoid these direct dependencies using techniques such as reflection and explicitly loading the required classes through additional classloaders but the code is often harder to write and understand.
It is not always possible or desirable to bundle the required classes in the
task's jar nor is it desirable that the system classpath contains the required
classes. In fact it is often preferable to run Mutant with an empty classpath.
Buildfiles which do not assume that the system classpath contains particular
classes are generally more portable. Mutant provides a task,
<libpath>, to allow additional paths to be assodicated with a
library. As with the loadlib task, the libpath task may be used to associate a
single file, all jars in a directory or remote jars with a library.
The above example associates all the jars in
/home/conor/jakarta-ant/lib/optional/ with the library whose unique id is
ant.ant1compat. A path must be associated with a library before any components
are imported from the library. Mutant associates the paths with the library and
at the time a component is requested from the library, Mutant will form the
classloader that will be used. The additional paths are added to the definition
of the classloader. Once a component is loaded, further paths will not have any
effect. For this reason, and since the additonal paths are likely to be absolute
paths, the <libpath> task is normally used in Mutant's
configuration phase.
Mutant will automatically import all components of any library whose unique identifier begins with "ant." when the ;library is loaded. This is mainly a convenience to provide a minimum set of tasks with which to construct build files.
Mutant provides a mechanism for including build file fragments into a build file. This following example illustrates how this works
The include mechanism can be used to include a complete project file or as
shown in the example, a fragment. When including a project, any attributes on
the included <project> element are ignored and the contents
of the project inserted into the including project. In all cases the included
files must be a well formed XML file containing a root element.
This area of Mutant is subject to change.
At present include elements are processed at parse-time. As such they can only occur at the top-level of the build file. They cannot occur in a target. The use of the namespace to qualify the include element is intended to convey the fact that this is not part of the build structure.
An alternative approach would be to define include as a regular task. When an include is processed, the top-level tasks of the included project would be executed immediately and the targets added to the current project structure.
.Mutant allows build file writers to reference properties and targets in other projects by creating a project reference. Project references are introduced using the ref task.
The above example creates two project references - one to the build in main.xml and one to the build in test.xml. When a reference is created, it must be given a label. This label will be used to refer to items within the referenced project (see below). Note that the ref task is a regular task and the reference is only created when the ref task is executed. A ref task may be placed at the top level of a build to effectively create static references. Alternatively a reference may be created dynamically by putting the ref task in a target.
The label given to a project reference is used when accessing items within the project. So, to access the build.dir property in the project referenced by the main label, you would use main:build.dir. Similarly the compile target would be referred to as main:compile. Since the referenced projects may also create their own project references, labels may be concatenated to access items at arbitrary depths. For example, if main.xml referenced another project under the label "sub", a property debug could be referenced as main:sub:debug. The following example shows various items in the referenced project being used.
When a project is referenced, the top level tasks of the referenced project are run to allow the project to initialize itself. Mutant allows the referring build to set a property in a referenced project before the reference is created. When the reference is eventually created these properties are set before initialization occurs. In normal Ant fashion, these overriding properties take precedence. In particular properties in the referenced projects may be set from the command line as this example shows
The example above shows a target in the referenced project being used as a dependency and also as a target in an antcall. Since refs are dynamic, Mutant will only evaluate such dependencies when required. If the alt target were never to be run, the dependency on main:fubar would never be checked.
The import task allows components defined in a referenced project to be brought into the main build. For example, the following will bring the definition of tool from the test project in the build. The imported component may also be aliased to a new name.
As discussed above Mutant provides a number of tasks to manage libraries and it is appropriate to run many of these tasks as part of a user or system-wide configuration rather than incorporating them into the build file. Mutant provides a configuration system to support running these tasks at an appropriate time. A Mutant configuration file looks as follows
The antconfig element is the root element of the configuration file. It supports three attributes
| allow-unset-properties | controls whether Mutant will fail a build which refers to a property which has not been set. This defaults to the Ant1 behaviour (true) |
| allow-remote-library | controls whether Mutant uses components defined in remote libraries |
| allow-remote-project | controls whether Mutant can run a project or reference a project which is located remotely. |
The configuration provides two collections of configuration tasks, global-tasks and project-tasks. Global-tasks are run once and are run in the context of the main project - i.e. the build file identified on the command line (defaults to build.xml/build.ant), whilst project-tasks are run as part of the initialization of every project including referenced projects and antcall projects. Looking at the above example, the global-tasks associate a path with the ant.ant1compat library while the per-project tasks import the antopt.monitor library into every project that Mutant processes.
The tasks that can be run in the configuration phase are regular tasks but not all tasks are automatically available. This is the difference between the syslibs and antlibs directories. The global tasks are run after the syslibs libraries have been loaded but prior to the antlibs libraries being loaded. The syslibs library tasks are therefore available in the configuration phase. This arrangement is to allow the configuration tasks to setup library paths for the libraries contained in the antlibs directory, especially libraries in the Ant namespace which are automatically imported at the time they are loaded.
If it is required to use tasks from libraries installed in the antlibs directory, the configuration tasks may explicitly load the library and import the required tasks. The following example shows the loading and use of the echo task in the configuration tasks. Note that the ant.home property has been set at the time the configuration tasks are started.
When Mutant starts up it will load configurations from two locations - The file .ant/conf/antconfig.xml in the user's home directory and the file conf/antconfig.xml in the Mutant home directory. In addition, config files can be specified on the command line using a -config argument. This allows project specific configurations to be used.
Mutant allows any task or datatype to be placed outside of a target. All such components are processed when the project is initialized and before any targets are processed. In fact Mutant does not require that a project contain any targets. In this case the only operations performed are the top level tasks at project initialization. The following shows a simple example
Normally when a task supports a nested element, Ant automatically determines the type of the nested element and creates an instance of this type. This instance is then configured and passed to the task. Mutant extends this scheme by allowing a build file writer to specify the type of nested element to use rather than relying on the core to determine it. Mutant will process attributes and further nested elements based on the specified type. For example:
In this example the nested element is actually a classfileset which supports the <root> nested element. The actual type is specified using the notation from XML Schema. Mutant predclares the XML Schema namespace under the xsi prefix. You may explicitly declare this in the build file's XML and use a different prefix if you wish. For example, this is equivalent to the above
In Ant1, the default build file name is build.xml. In Mutant, the default build file is build.ant. If build.xnt cannot be found, Mutant will then look for build.xml. This allows you to support both Ant1 and Ant2 users. The build.xml file can contain an Ant1 build file. The build.ant file can include or reference this build and make use of Mutant's capabilities such as library management, library path settings, etc.