Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Ant Patterns: Targets, Macros, Reuse

Posted on July 12, 2007 By Luis Fernandez

Ant Patterns keep popping up in every Java shop I visit. We all have a build.xml that grew from a quick copy into a small city. Some of us run CruiseControl on an old server in the corner. Subversion repos are busy. And while a shiny phone just stole everyone’s attention, our builds still need love. This post is from the trenches. It is about targets, macros, and reuse in Apache Ant, told from a practitioner who breaks builds for a living and then fixes them with stronger glue.

Definitions

Target: a named step in Ant. It can depend on other targets and it can run tasks. Think of it as a verb like compile or test or package.

Depends: a list that says which targets must run before the current one. This creates a simple graph. No loops please.

Property: a constant that gets set once. Good for directories, versions, and flags that never change during the run.

Fileset and Patternset: selectors for files. Use includes and excludes to keep your target focused and safe.

macrodef: a way to define your own task with parameters, attributes, and nested elements. Perfect when you repeat the same few tasks with tiny tweaks.

presetdef: a pre configured version of an existing task. Think of it as a shortcut with defaults baked in.

import: include another build file into the current one. Handy for common targets and shared rules across modules.

antcall: run a target as a separate call with its own property scope. Use with care. It is not free.

Examples

First a build core with a few targets and a macro that compiles any module in a consistent way.

<project name="app" default="package" basedir=".">

  <property name="src.dir" value="src"/>
  <property name="build.dir" value="build"/>
  <property name="classes.dir" value="${build.dir}/classes"/>
  <property name="lib.dir" value="lib"/>

  <path id="compile.classpath">
    <fileset dir="${lib.dir}" includes="**/*.jar"/>
  </path>

  <target name="clean">
    <delete dir="${build.dir}"/>
  </target>

  <target name="prepare">
    <mkdir dir="${classes.dir}"/>
  </target>

  <macrodef name="compile.module">
    <attribute name="src"/>
    <attribute name="out"/>
    <sequential>
      <mkdir dir="@{out}"/>
      <javac srcdir="@{src}" destdir="@{out}" includeantruntime="false">
        <classpath refid="compile.classpath"/>
      </javac>
    </sequential>
  </macrodef>

  <target name="compile" depends="prepare">
    <compile.module src="${src.dir}" out="${classes.dir}"/>
  </target>

  <target name="jar" depends="compile">
    <mkdir dir="${build.dir}/dist"/>
    <jar destfile="${build.dir}/dist/app.jar" basedir="${classes.dir}"/>
  </target>

  <target name="package" depends="jar"/>

</project>

The macro compile.module lets you apply the same recipe to more than one source tree. If you later split code into core and plugin, you add two lines with different src and out values and the world keeps turning.

Now let us share common tasks across several projects without copy paste. Use import and presetdef for a clean base.

<!-- file: common.xml -->
<project name="common">
  <property name="reports.dir" value="build/reports"/>

  <presetdef name="copy.resources">
    <copy overwrite="true" failonerror="true">
      <fileset>
        <patternset>
          <include name="**/*.properties"/>
          <include name="**/*.xml"/>
        </patternset>
      </fileset>
    </copy>
  </presetdef>

  <macrodef name="junit.report">
    <attribute name="input"/>
    <sequential>
      <mkdir dir="${reports.dir}"/>
      <junitreport todir="${reports.dir}">
        <fileset dir="@{input}" includes="TEST-*.xml"/>
        <report format="frames"/>
      </junitreport>
    </sequential>
  </macrodef>
</project>
<!-- file: build.xml -->
<project name="app" default="test" basedir=".">
  <import file="common.xml"/>

  <target name="resources">
    <copy.resources todir="build/resources">
      <fileset dir="resources"/>
    </copy.resources>
  </target>

  <target name="test" depends="compile resources">
    <junit printsummary="yes" haltonfailure="no">
      <classpath>
        <path refid="compile.classpath"/>
        <pathelement location="${classes.dir}"/>
      </classpath>
      <batchtest todir="build/test-results">
        <fileset dir="test" includes="**/*Test.java"/>
      </batchtest>
    </junit>

    <junit.report input="build/test-results"/>
  </target>
</project>

Now test reports and resource copying live in one place. New projects import the same rules and you tweak the shared file once when the team needs a change.

Counterexamples

Here are a few traps I keep finding in real build files. They work for a while and then bite.

<target name="compile-core">
  <javac srcdir="core/src" destdir="build/core-classes">
    <classpath refid="compile.classpath"/>
  </javac>
</target>

<target name="compile-plugin">
  <javac srcdir="plugin/src" destdir="build/plugin-classes">
    <classpath refid="compile.classpath"/>
  </javac>
</target>

This is a copy paste special. One day someone tweaks warnings or encoding in one target and forgets the other. Missing consistency is the real bug here. A macro would fix it.

<target name="package">
  <antcall target="compile"/>
  <antcall target="jar"/>
</target>

Using antcall like this slows the build and resets properties in ways that surprise people. A clean chain with depends is faster and clearer.

<target name="clean">
  <delete dir="." />
</target>

Yes I have seen that. Always scope your filesets and directories. A small typo turns a quick cleanup into a long day.

Decision rubric for Ant targets, macros, and reuse

Use this checklist when you touch your build.xml. It keeps the build honest.

  • Is it a step with a verb name like compile test package clean publish Then create a target.
  • Do you repeat the same set of tasks with small variations like dirs or include patterns Wrap that in a macrodef with attributes.
  • Do you want a tuned version of a single task with default flags Use presetdef and keep your targets tidy.
  • Do multiple projects need the same logic Put it in a shared file and import it. Keep only project specific wiring in each local build file.
  • Are you calling targets for order Prefer depends. Only use antcall when you need a new scope or different properties.
  • Can you name the target without a conjunction If you need and or then in the name you are packing too much into one target. Split it.
  • Do file picks feel fuzzy Tighten your fileset and patternset. Explicit include lines beat broad globs.
  • Is a property changing mid build Ant properties are immutable. If you need variation use attributes in macros or pass new properties from the command line.
  • Does the build need cross module steps Consider subant or a top level aggregator that imports common parts and wires modules with depends.
  • Can a new teammate read the build file in one pass If not add short comments and extract common parts. Readability is a feature.

Lesson learned

Ant rewards you when targets are verbs, macros are recipes, and reuse is honest. The moment you stop copy paste and start writing small named pieces, the build turns from mystery to map. You gain speed without hunting flaky rules. You gain trust because the same inputs give the same outputs.

Keep your Ant Patterns simple. Use targets for flow, macrodef for repeated structure, presetdef for tuned defaults, and import for sharing. Avoid antcall unless you need a new scope. Be picky with filesets. Write names that read like plain speech. Your future self will thank you when a late night build breaks and the fix takes minutes instead of hours.

Now go patch that build before the next commit storm hits. And yes the phone is shiny, but shipping on time is shinier.

Productivity & Workflow Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes