Avoiding re-building prerequisites in Ant

2019-05-05 14:51发布

问题:

I have an existing Ant project and would like to speed up the build process by avoiding re-building components that are already up to date.

Ant permits you to specify that one target depends on another, but by default every prerequisite is always rebuilt, even if it is already up to date. (This is a key difference between Ant and make. By default, make only re-builds a target when needed -- that is, if some prerequisite is newer.)

<uptodate property="mytarget.uptodate">  // in set.mytarget.uptodate task
  ...
</uptodate>
<!-- The prerequisites are executed before the "unless" is checked. -->
<target name="mytarget" depends="set.mytarget.uptodate" unless="mytarget.uptodate">
  ...
</target>

To make Ant re-build prerequisites only if necessary, there seem to be two general approaches within Ant.

The first approach is to use the uptodate task to set a property. Then, your task can test the property and build only if the property is (not) set.

<uptodate property="mytarget.uptodate">  // in set.mytarget.uptodate task
  ...
</uptodate>
<!-- The prerequisites are executed before the "unless" is checked. -->
<target name="mytarget" depends="set.mytarget.uptodate" unless="mytarget.uptodate">
  ...
</target>

An alternate first approach is to use the outofdate task from ant contrib. It's nicer in that it is just one target without a separate property being defined; by contrast, outofdate requires separate targets to set and to test the property.

The second approach is to create a <fileset> using the <modified> selector. It calculates MD5 hashes for files and selects files whose MD5 differs from earlier stored values. It's optional to set

 <param name="cache.cachefile"     value="cache.properties"/>

inside the selector; it defaults to "cache.properties". Here is an example that copies all files from src to dest whose content has changed:

    <copy todir="dest">
        <fileset dir="src">
            <modified/>
        </fileset>
    </copy>

Neither of these is very satisfactory, since it requires me to write Ant code for a process (avoiding re-building) that ought to be automatic.

There is also Ivy, but I can't tell from its documentation whether it provides this feature. The key use case in the Ivy documentation seems to be downloading subprojects from the Internet rather than avoiding wasted work by staging the parts of a single project. Maven provides similar functionality, with the same use case highlighted in its documentation. (Moving an existing non-trivila project to Maven is said to be a nightmare; by contrast, starting greenfield development with Maven is more palatable.)

Is there a better way?

回答1:

This conditional compilation of a large build is a feature of make that I initally missed in ANT. Rather than use target dependencies, I'd suggest dividing your large project into smaller modules, each publishing to a common shared repository.

Ivy can then be used to control the component versions used by the main module of the project.

<ivy-module version="2.0">
    <info organisation="com.myspotontheweb" module="multi_module_project"/>
    <publications>
        <artifact name="main" type="jar"/>
    </publications>
    <dependencies>
        <dependency org="com.myspotontheweb" name="component1" rev="latest.integration"/>
        <dependency org="com.myspotontheweb" name="component2" rev="latest.integration"/>
        <dependency org="com.myspotontheweb" name="component3" rev="latest.integration"/>
        <dependency org="com.myspotontheweb" name="component4" rev="latest.integration"/>
    </dependencies>
</ivy-module>

The ivy:retrieve task will only download/copy one of the sub-modules if they have changed (published from their build files)

It all sounds more complicated but maybe you're already sub-dividing the project within your build file.... For example, if your ANT uptodate task is being made dependent on one the build artifacts.