Mutant Proposal

User Features

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.

Directory Layout

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.

Ant Libraries

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.

Ant library descriptor

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.

<antlib libid="ant.system"
        home="http://jakarta.apache.org/ant">

  <taskdef name="libpath" classname="org.apache.ant.antlib.system.LibPath"/>
  <taskdef name="loadlib" classname="org.apache.ant.antlib.system.LoadLib"/>
  <taskdef name="import" classname="org.apache.ant.antlib.system.Import"/>

  <converter classname="org.apache.ant.antlib.system.FileConverter"/>
  <converter classname="org.apache.ant.antlib.system.URLConverter"/>
  <converter classname="org.apache.ant.antlib.system.PrimitiveConverter"/>

  <aspect classname="org.apache.ant.antlib.system.AntAspect"/>
</antlib>

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.

Loading libraries

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

  <!-- load a library from a specific file and import all components -->
  <loadlib file="/opt/tools/toollib.jar" importall="true"/>

  <!-- load all libraries from a directory -->
  <loadlib dir="/opt/appserver/lib/"/>

  <!-- load libraries from a remote server -->
  <loadlib url="http://jakarta.apache.org/ant/testtasks.jar"/>

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.

Importing components

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.

  <!-- import the runtool task from the com.foo.tools library -->
  <import libraryId="com.foo.tools" name="runtool"/>

  <!-- import the runtool task from the com.bar.tools library and
       alias it since the runtool is already defined -->
  <import libraryId="com.bar.tools" name="runtool" alias="runbartool"/>

  <!-- import all of the tasks from the com.fubar.tools library -->
  <import libraryId="com.fubar.tools"/>

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.

Library classpath management

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.

  <libpath libraryid="ant.ant1compat"
           dir="/home/conor/jakarta-ant/lib/optional/"/>

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.

Automatic Imports

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.

Includes

Mutant provides a mechanism for including build file fragments into a build file. This following example illustrates how this works

build.ant
==========
<project default="main" xmlns:ant="http://jakarta.apache.org/ant">
  <ant:include fragment="fragment.ant"/>
</project>

fragment.ant
============
<fragment>
  <target name="main">
    <echo message="main target"/>
  </target>
</fragment>

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.

.
Project References
Creating references

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.

  <!-- create a reference to the main build -->
  <ref project="main.xml" name="main"/>

  <ref project="test.xml" name="test">
    <property name=build.dir" value="build/test"/>
  </ref>

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.

Referencing project items

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.

  <!-- Specify the build.dir in the main project prior to the reference
       being created -->
  <property name="main:build.dir" value="build/main"/>

  <!-- create the reference to the main build -->
  <ref project="main.xml" name="main"/>

  <!-- create the reference to the test build -->
  <ref project="test.xml" name="test">
    <property name="build.dir" value="build/test"/>
  </ref>

  <!-- our main target calls the fubar target in the main build -->
  <target name="main">
    <antcall target="main:fubar"/>
  </target>

  <!-- Our alt target depends on the fubar target in the main build -->
  <target name="alt" depends="main:fubar">
    <echo message="main's debug flag is ${main:debug}"/>
  </target>

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

  mutant -Dmain:build.dir=temp

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.

  <!-- import a task definition from another project -->
  <import ref="test:tool"/>
Configuration

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

<antconfig allow-unset-properties="true">
  <global-tasks>
    <libpath libraryid="ant.ant1compat"
             file="/home/conor/dev/jakarta-ant/lib/optional/"/>
  </global-tasks>
  <project-tasks>
    <import libraryid="antopt.monitor"/>
  </project-tasks>
</antconfig>

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.

<antconfig allow-unset-properties="true">
  <global-tasks>
    <loadlib file="${ant.home}/lib/antlibs/ant1compat.jar" importall="true"/>
    <echo message="Starting the build"/>
  </global-tasks>
  <project-tasks>
    <echo message="Starting new project"/>
  </project-tasks>
</antconfig>

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.

Targetless builds

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

<project>
  <echo message="Welcome to Mutant"/>
</project>
Extensibility

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:

    <copy todir="dest">
      <fileset xsi:type="classfileset" dir="../../bin/ant1compat/">
        <root classname="org.apache.tools.ant.Project"/>
      </fileset>
    </copy>

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

<?xml version="1.0"?>
<project xmlns:schema="http://www.w3.org/2001/XMLSchema-instance"
         name="test" default="main">
  <target name="main">
    <delete dir="dest"/>
    <mkdir dir="dest"/>
    <copy todir="dest">
      <fileset schema:type="classfileset" dir="../../bin/ant1compat/">
        <root classname="org.apache.tools.ant.Project"/>
      </fileset>
    </copy>
  </target>
</project>
Default build file

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.


Copyright © 2002, Apache Software Foundation