The old kingdom was crumbling. A new dynasty emerged, united by a new vision and a common creed.
Ant was created as the antithesis to Make. It was to be simple, cross platform<a href="#footnotes" />1</a> and maintainable. Although its lineage is clear in design terms — that is, the design of Ant is a reaction to that of Make — it should be noted that in practice they do not directly compete, but rather exist in parallel worlds; With some exceptions, the use of Ant is restricted to the Java world and the use of Make to the C/Unix world.
This perhaps unfortunate balkanisation of build tools seems to be inevitable; The fault lines which exist between different types of development are seemingly too big for a single tool to be competitive in every type of software build. The most obvious reason for this is that employing a tool familiar to developers and itself written on the target platform is the most straightforward means to mitigate the cost of supporting the build process. The other differentiating factor is differences between the tool chains. Make is fundamentally designed as a solution to the problem of working with file based tool chains such as that of C. This makes the Make design indispensable in that environment but anachronised in a more modern environment such as Java, where the tool chain and language design means it is neither necessary nor possible<a href="#footnotes" />2</a> to handle source files individually.
The disjunction between the domains of usage cases of Make and Ant, does not stop Make from being a useful point of comparison. Whereas in Make the job of performing specific actions (e.g. creating an archive, compiling) are delegated to command line programs, in Ant there are tasks. Tasks are in essence Java library code, typically highly configurable, which are callable from within an Ant build script.
Ant has quite an extensive library of available tasks, and it is not difficult to write new ones. However, should the requirement be a 'one off' custom task, as is often the case, then the overhead of maintaining it in an extra project can be considered onerous<a href="#footnotes" />3</a>. There is an alternative, which is to use the embedded scripting, although this has the disadvantage of requiring knowledge of another language (or potentially several, since there are a number of choices).
Tasks offer the following advantages over external programs:
The last point on XML configuration needs qualifying since critical discussion of Ant usually (somewhat unfairly) singles this out as the major weakness. XML is better at representing complex, hierarchical inputs to tasks than the command line equivalent of passing arguments to programs<a href="#footnotes" />4</a>. Another benefit is that XML lends itself to better, structured documentation (as demonstrated by the relative quality of the Ant documentation). Control flow, such as for loops and if-elses, when expressed in XML is of course far from ideal. However the main problem is that they are simply not there by default. Ant is inexpressive and deliberately so.
This conservative stance on control flow is a reaction to the issues invariably encountered using the perhaps overly expressive Make<a href="#footnotes" />5</a>. Ironically, it is this dogmatic pursuit of simplicity and inexpressiveness which frequently renders Ant builds unmaintainable. The difficulties it presents in properly configuring and modularising Ant builds has resulted in the bad practices of hard-coding and copy pasting, creating brittle and inflexible builds which need modifying to deal with even small changes.
Again we should mention the scripting in Ant which as well as being used to perform tasks, can also be used to orchestrate the build process, providing the control flow not possible in pure Ant. Here scripting is potentially at its most useful since getting the desired control and parametrisation of the build overall is the most challenging aspect of Ant. Unfortunately however, when interacting with the rest of the build it becomes evident that the scripting has been added as an afterthought. Interactions are done through the Ant Java API which, has simply not been designed for this purpose (and is not particularly elegant in any case), feels obtuse and too low level.
Ant by itself never improved the external dependency management situation over its predecessor Make. If anything the situation went backwards, since Make at least operated (mostly) in the context of the Unix environment where dependencies could be found in the file system and managed by the local package manager. In Ant the locating of dependencies is left as an exercise for the developer. Ad-hoc solutions, such as checking binary dependencies into source control, are bad practice but pragmatically can work satisfactorily in some circumstances. However, inevitably the situation will always arise where a given solution does not work well enough at which point complication and maintenance issues ensue.
These days there is the option to use Apache Ivy for dependency management in Ant. It is far from perfect and requires a lot of expertise to apply it effectively<a href="#footnotes" />6</a>. Still, when it is put to good use, Ivy is a large improvement as it makes it possible to have a complex build become independent of its local environment (i.e can be run on any machine without any specific configuration), a property which vastly improves the ease of use and reliability of builds.
An IDE needs to understand the dependency relationships between projects and external libraries. Using Ant, it is essentially not possible to avoid duplication of this information between the build and the IDE configuration. The problem being that there is no notion of a project in Ant, or any other means to reason about transitive dependencies. In short, Ant is too low level.
Ant predates the modern IDE, so it is perhaps unsurprising that it does not support them very well<a href="#footnotes" />7</a>. Nevertheless, this has become a major conceptual deficiency, now that the IDE is such a fundamental part of Java development.
Time has shown that Ant succeeds in being simple-ish<a href="#footnotes" />8</a> and cross platform, but that it did not achieve its main goal of maintainability in larger, more complex builds. Ant handles complexity<a href="#footnotes" />9</a> well only for small cases, and requires significant expertise and discipline in order to scale to larger builds. However what is even more detrimental than this is that even optimal use it is not possible to get a satisfactory coexistence between an Ant build process and the IDE setup.
Ant describes itself as declarative, because of the way tasks are described in XML. But Ant is also sometimes described as imperative since ultimately it is more or less a programming environment. Within targets tasks are executed in a sequential manner, and targets themselves ultimately are organised into a sequence for execution (even if they are idempotent).
Perhaps this confusion reflects more on the limitations of the categories imperative and declarative than anything else. Where there is a growing consensus however is that build systems need a declarative representation of projects. Moving to this point of view has resulted in Ant being reclassified as not declarative.