Mutant Proposal

Goals

This page describes the key goals that have shaped the development of Mutant.

The first section identifies a set of issues with Ant1 that have cropped up as Ant1 has evolved. The design implications of each issue are then summarized as a Mutant requirement. I do not want to suggest that these problems are unsolvable within the Ant1 design. Already I believe we have seen the Ant2 proposals influencing people trying to extend Ant1. These issues may be solvable, although at the risk of backward compatability impacts or further complication of the Ant1 codebase.

The second section covers a set of additional requirements that have emerged as the whole concept of Ant2 has developed. Many of these came from the discussions on the Ant-Dev mailing list.

The realisation of these requirements as they impact a user is on the next page. Th implications for task developers and Ant developers are discussed in the following sections.

Ant1 Issues
Unrestricted core access

The interface between the Ant core and tasks is not controlled. It allows tasks almost complete access to the internal data structures of the core. This is poor encapsulation. Whilst most tasks do not need and do not use this access, its existence nonetheless prevents changes being made to the core without raising concerns about impacting backward compatability.

The uncontrolled nature of the task-core interface also makes it difficult to reuse tasks and types in a different context without almost completely duplicating the Ant core

A tightly defined interface between the core and the components (tasks and types) will allow the core implementation to be changed without the risk of impacting tasks. It will also allow components to be reused in other contexts.

Lack of embedding support

Ant1 does not provide strong support for embedding Ant in other systems, particularly a GUI or IDE. The development of Antidote highlighted this difficultly. Antidote was forced to perform its own XML parsing so it could build its own model of the build. There is no communication medium for a GUI to communicate with the core. Without this capability any GUI will be limited to simply editing the XML representation of the build.

The definition of a project model, a Java-based object-model of a build description would decouple Ant from the XML representation. This allows other representations to be used. Such an object model also forms the basis for communications between systems which want to embed Ant. An IDE could manipulate this project object model directly and pass to the core for processing without needing to convert to and from an external representations such as XML.

Configuration done at Parse-Time

The original implementation of Ant1 performed all task configuration at the time the XML description of the build was parsed (parse-time). This approach could not handle dynamically created tasks very well since in some instances the type of the task to be configured was not known at parse-time. This limitation was overcome with the introduction of the UnknownElement and RuntimeConfigurable classes. While these work well, they are confusing and at times surprising to most Ant developers. Also some operations still occur at parse-time, notably creation of nested elements of known types. This could be overcome by going to a fully dynamic model but the fear of backward incompatability constrains this change from occuring.

Execution-time configuration of tasks allows for the latest information to be used for configuration. It is also necessary to support the concept of the project model. The project model cannot contain execution-time information as it does in Ant1

The decoupling of the parsing and exection phases, communicating through the medium of the project model will allow the core to be modularized. The parsing and execution phases can be separated and one replaced without impacting the other.

Multiple execution

In Ant1, a task, once configured, may be used more than once - i.e. its execute method may be called more than once. This occurs when the Ant command line specifies the evaluation of two targets. If those targets have overlapping dependencies, the tasks in those overlapping dependencies will be executed twice. This arrangement requires task writers to preserve the state configured by Ant during the execution of the task so that the next execution has the correct configuration.

Many task writers are not aware of this requirement. Frequently the task's state is changed during the execution method, which can lead to mysterious falures during subsequent executions. This need to preserve the configuration state makes writing tasks much harder.

Once a task is configured, it should be executed once. If the execution of multiple targets is required, a new task instance should be created and configured from the same project model. If reuse of task instances is desired each instance must be reinitialized and reconfigured before use.

No automatic deployment of tasks

Ant1 has a set of well-known tasks (and types). A well-known task is one for which a mapping between the taskname and its implementing class is predefined in Ant. This mapping is provided by the two defaults.properties files in Ant's code. These well-known tasks do not need to be taskdef'd to make them available in a build file. Conversely tasks which are not in this list need to be explicitly taskdef'd.

Note that this distinction is not the same as the core/optional distinction found in Ant1. An Ant1 core task is notionally supported by Ant without requiring any additional external libraries - it just requires the classes available in the 1.1 JDK, in Ant and in the libraries Ant uses such as the XML parser. An optional task is a task which requires JDK 1.2+ features or the support of an external library, such as jakarta-regexp.

The problem with this system is that there is no namespace management. If a new task is added to Ant, it may invalidate buildfiles which are already using that taskname via a taskdef. Actually the taskdef may continue to work or it may not - it will depend on the nested elements that the task definitions support (see above discussion of regarding cofiguration at parse-time)

Tasks need to be deployed in libraries by either placing them in well known directories of an Ant install or by telling Ant where to look for them. Removing the central management of defined task names allows tasks libraries to be developed and maintained independently of Ant much more easily.

Once centralised management of the task namespace is removed, there is, however, the possibility of name collision. A mechanism is required to allow a build file writer to select which particular tasks are assigned to which tasknames.

Top level tasks

In Ant1 certain tasks may appear outside of any target. This is implemented as a set of hardcoded conditions in the XML parsing phase. The execution and configuration of these tasks does not involve the use of RuntimeConfigurable and UnknownElement.

This hardcoding is not very inituitive. It is confusing to users who do not know why only some tasks may be run outside of a target. Also as the list of tasks that may be useful as a top-level task grows, further special cases must be added to the code making the code harder to maintain.

The number of special names should be kept to a minimum. Special cases should be avoided as they are not inituitive.

Build file inclusion is cumbersome

In Ant1, the inclusion of a set of build file definitions or targets is achieved through the use of XML entities. This is just cumbersome.

A simplified include mechanism is required to replace the entity based inclusion of Ant1.

Classpath management

Probably the greatest issue facing Ant1 today involves the management of the classpath and the associated visibility of classes. Most optional tasks in Ant1 require the supporting classes for the task to be available on the system classpath. For example, the JUnit task is only usable if the JUnit jar is on the classpath. If it is not, Ant will complain about being unable to create the junit task.

The usual solution when a user runs into this problem is to put the required jar into the ANT_HOME/lib directory. This just adds the required jar to the system classpath prior to starting Ant albeit without requiring the user to explicitly set the classpath.

There are a number of issues with this approach. The classes on the classpath share the same classloader as Ant's classes and Ant's support libraries - particularly the XML parser. If a task wishes to use a specific XML parser, it may conflict with Ant's own parser. If two tasks require different versions of a supporting jar, these will conflict.

Many tasks make use of factory objects (dynamic class instantiation, dynamic resource loading, etc). When the factory is in the system classpath it will not be able to load resources available in a taskdef'd task's custom classpath due to the delegating nature of classloaders. Such errors can be very confusing to users. More recent code is likely to attempt use of a context classloader but this is not set by Ant1 currently.

Do not require jars to be added to ANT_HOME/lib to enable tasks. Allow users to specify the location of support jars on a per-library basis.

Extensibility

The earliest versions Ant1 did not explicitly support any datatypes. The only datatype, property, was actually created as a side-effect of running the <property> task. If it were to be implemented today, property would probably be known as a string datatype, perhaps associated with a <load-properties> task. This is also the reason property is one of those tasks which is allowed to exist outside of a target.

As Ant1 evolved new types such as <path> and <fileset> were added. The concept of datatypes has continued to evolve to the point where users can now define new datatypes. It would be expected that in addition to new types there will be many sub-types created, such as new types of fileset. Ant1, however, does not strongly support such type extensibility. When a subtype is created by extending an existing type, Ant1 requires it to be either used by reference or all tasks which accept the base type need to be modified to support the new type. Use by reference is not always possible. It will depend on whether the base type has been coded to support references.

Support polymorphic behaviour, allowing a build file to pass a subtype to a task which is defined to accept the base type. Allow task interfaces to be defined in terms of interfaces.

Move reference processing to the core to make it more regular removing the burden of type developers to explicitly support references.

Project object does too much

In Ant1 the Project object takes on too many roles including all of the following:

  • Holds the project model built when parsing the build file
  • Holds the run-time state of the build through the properties and current task definitions
  • Provides context for task execution. All tasks have a reference to their project instance and use it to access core functions
  • Provides the implementation of common task operations
  • Build event management
  • Message logging
  • Global filter definitions
  • Java Version
  • Property replacement
  • Message Levels
  • Utility functions such as boolean conversion and path translation
  • Integration point for embedding Ant

As a class, Project is not cohesive. Reuse of the Ant core functionality is difficulty as all of these concerns bring in many other support classes.

Separate the various roles of Project into more cohesive classes.

Other Goals

In addition to the issues which arise from Ant1 limitations, there are a number of requirements which have emerged in the mailing list discussions about Ant2. This isn't a complete list - just the ones I picked up for Mutant.

Limited project reuse

In Ant1 the only way to reuse build file definitions is to use the <ant> task. While useful, it is relatively coarse-grained. It can also be tedious if you are trying to extend an existing project definition - that is, adding new targets while retaining a user's ability to access the existing targets.

In addition to the extension of projects, it should be possible to compose projects creating dependencies between the targets of the different projects, and to access the data and definitions of one project by a controlling project

Allow a project to extend and control other projects

Ant1 Compatibility - Zero Friction

While it has always been accepted that there will be come backward compatibility breaks in moving from Ant1 to Ant2, these should be minimized. If the changeover from Ant1 to Ant2 is difficult, it may never happen. On the other hand, the openness of the Ant1 interface means that some tasks will not work as expected - the most difficult being the <script> task.

Achieve a practical level of compatability without degrading the integrity of the core's interfaces. A practical level of compatability is intentionally a vague measure but it would require the majority of projects in Gump to be built without noticeable differences from Ant1

XML Configuration

In Ant1 configuration of Ant is achieved either through the execution of OS dependent scripts by the launcher scripts or though properties files which have the limited capability to set properties.

Provide an XML based configuration system with rich capabilities to configure the operation of Ant.

Aspects

In Ant1 there are many things which are common to a group of tasks but adding the capability to each task is repetitive and tedious. An example would be the failonerror attribute which controls whether a task will cause the build to stop when it fails. This started on just one task but has since been added to many other tasks as users want more fine control over when their builds stop. Aspects have been discussed as a mechanism for providing a single implementation of a concept or control that can then be applied to tasks independently.

Investigate the use of an Aspect approach to provide common functionality across tasks with a single implementation


Copyright © 2002, Apache Software Foundation