Antlion
Welcome
License
 
How-To Guides
Getting Started
Libraries
Artifacts
Subprojects
Repositories
Policy Strategies
Format Strings
Extending Antlion
FAQ
 
Tutorials
First Tutorial: Simple
 
Ant Tasks
<artifact>
<libraryDef>
<library>
<library-policy>
inner processors
inner repositories
<library-type>
<library-repository>
<library-urlrepository>
<library-mavenrepository>
<library-repositoryset>
<create-artifact>
<subprojects>
<run-subproject>
<replace-target>
 
Optional Tasks
About optional tasks
RegexpTokenFormatter
 
Prev   Next  
Policy Strategies

So you've got a repository for your libraries, and now you're wanting to decide on how to put all the meta-data associated with those files into your build scripts. With Antlion, the library policy declares how your library definitions will translate into Ant properties and references. Also, the policy contains transformations to simplify the information you give in a library definition.

Policies are one of the toughest things in Antlion to get working just how you want them. A slight change in a format string, or putting them in the wrong order, can cause the wrong JAR files to be picked up. Antlion provides a lot of power, and that can translate into headaches if you're not careful.

If you find yourself scratching your head at the generated results, then try turning on the debug logging, or set the Ant property antlion.enable-tracing to true. To set this property from the command-line, pass the argument -Dantlion.enable-tracing=true along with your normal arguments. When in this mode, Antlion reports each processor and repository's actions. It may be chatty, but it tells you exactly what's going on.


Referencing Your Repositories

The first thing in the responsibilities of the policy is enumerating how libraries can be referenced. Antlion allows you to come up with any scheme or naming that you find suitable (there are a few limitations, however, including type, artifact, and location).

By this point, you should already have setup your repository (if you need help, you can read these suggestions). That already tells you where all the files will be located. The next step is figuring out what you want to tell a <lib-entry> so that the right file can be found.

The Antlion project suggests using group, version, artifact, and type as the primary categorization names. But you can use whatever names you feel comforatable. Note that there are a few special names that cannot be changed (type being one of them). Below is a common example.

  <repository dir="${repository-dir}">
    <format text="[group]/[version]/[artifact]-[version].[type]" />
    <format text="[group]/[version]/[artifact].[type]" />
    <format text="[group]/[version]/[group]-[version].[type]" />
    <format text="[group]/[version]/[group].[type]" />
  </repository>
You'll note that the search order here is very critical, and so is making sure you type the attributes precisely in the <lib-entry>. Let's take XDoclet as an example. Version 1.2 beta 2 contains the jar files "xdoclet-1.2b2.jar" and "xdoclet-apache-module-1.2b2.jar". From this, you could use a library structure like:
  <library>
    <lib-entry group="xdoclet" version="1.2b2" />
    <lib-entry group="xdoclet" version="1.2b2"
      artifact="xdoclet-apache-module" />
  </library>
And it will work fine. However, let's say that the person setting up the repository wasn't thinking everything through (yes, this is based on personal experience) and instead wrote:
  <library>
    <lib-entry group="xdoclet" version="1.2b2" />
    <lib-entry group="xdoclet" version="1.2b2"
      artifact="apache-module" />
  </library>
The end result would be that the second <lib-entry> would not match the format [group]/[version]/[artifact]-[version].[type], but instead would match the format [group]/[version]/[group].[type], which the first <lib-entry> matched as well. This means anything generated from this library would include two copies of the same xdoclet jar file. There exist some processors to help detect these errors, but none can solve it easily.

The moral of the story is to take in how you setup your repository references. If you think you're being sneaky and creating lots of "short-cuts", they might just turn around and bite.


Transformations

Antlion provides a few processors which act to add implicit meta-data to a <lib-entry> or <library>, so that users don't have to enter every bit of data everywhere.

  • <attribute> defines a set of attributes that can be defined on a library group. If the attribute is defined on the library, then it will be the default value for the attribute of the same name in each contained library entry. This is useful as a short-hand where the entries in a library are all associated with one group and/or version.
  • <dynamic-attribute> allows for adding attributes to library entries based on existing Ant properties. For example, you can define Ant properties in the form "lib.[group].version", and assign library entries a default version attribute to the value of this property, based on that each entry's "group" attribute value.


Validators

Another group of processors perform tasks of validating that the library was setup correctly. Setting up a library can be a tricky task, and validating them by hand can be even trickier.

  • <version-check> can ensure that there are not two library entries covering the same artifact, but with different versions. For example, it fails the build if it finds a library containing both log4j-1.2.7.jar and log4j-1.2.8.jar. There are some caveats to this validator, as it has restrictions over how it discovers if two library entries refer to the same product.
  • <must-find> ensures that each library entry was discovered by a repository, and that the entry's corresponding file exists (except for artifacts).


Filters

In cases where too many or too few entries are listed for a library, the filter processors alter the list of entries.

  • <compatible-version> selects a single version of an entry when multiple versions of the same entry exist within a library.
  • <split> creates multiple entries based on a list of values in an entry's attribute.


Creators

Antlion can also do useful things with your libraries. These include:

  • <path> creates a referencable Ant <path>.
  • <fileset> creates a referencable Ant <fileset>.
  • <filelist> creates a referencable Ant <filelist>. It orders the entry by the order they appear in the library definition.
  • <property> creates an Ant property pointing to the absolute file location of each qualifying library entry.
  • <manifest-classpath> creates an Ant property for each library group that needs one, containing just the name (no paths) for each contained entry, separated by a space. This allows for auto-generating a MANIFEST.MF compatible "Class-Path" entry.


When Those Are Not Enough

Are the default processors not enough? You can create custom processors using one of two methods.

scripting

Anlion allows for you to have a processor written in a scripting language of your choice. If you're familiar with the Ant <script> task, this should be a piece of cake. See the first page for library dependency requirements for script support.

When you define your script processor, Antlion provides to your script object references beyond what the Ant <script> task provides:

  • the library to process. By default, this is given a reference name "library" to use in your script, just like you would reference any other Ant reference. You can change this by setting the <script> attribute libraryName. The library has these methods:
    • getInfo(): returns an Info object (see below) describing the attributes associated with the library itself.
    • getEntries(): returns a Java array of Entry objects (see below).
  • a logging helper. By default, this is given a reference name "logger" to use in your script, just like you would reference any other Ant reference. You can change this by setting the <script> attribute loggerName. The logger gives you access to these methods:
    • error(msg): reports an error message (a String argument), and quits the build with a failure.
    • warn(msg): reports a warning message (a String argument), and allows the build to continue.
    • trace(msg): creates a trace message (a String argument), which will be output dependent upon the current trace output level.
  • the Ant build project. This is actually provided by the Ant <script>, but is undocumented. You reference this as "project".
Each Entry object returned by getEntries() allows for the following methods:
  • setDynamicAttribute(name, value): sets a new attribute name/value pair on the entry.
  • getInfo(): returns the Info object for all the attributes in the element.
  • getAttributes(): returns an array of Strings, containing all of the attribute names for this element, converted to lower-case.
  • getAttributeName(name): returns the original attribute name, with the original capitalization, for the given case-insensitive name.
  • getAttribute(name, logger): returns the value for the given attribute.
And finally, the Info object provides these methods:
  • getAttributes(): returns an array of Strings, containing all of the attribute names for this element, converted to lower-case.
  • getAttributeName(name): returns the original attribute name, with the original capitalization, for the given case-insensitive name.
  • getAttribute(name, logger): returns the value for the given attribute.
  • createElement(name, value): adds a new name/value attribute pair to this info object. It is an error to add an attribute name more than once (case-insensitive).
  • defaultElement(name, value): if the Info object never has createElement() called for the name of this default attribute, then the value set here will be used. This allows for default values to elements. It is an error to add a default attribute name more than once (case-insensitve).

Here is an example on how to implement the scripting in a build file, using JavaScript (well, ECMAScript) as the language:

<project name="test-scripting" default="A">
  <target name="A">
    <!-- create the expected file -->
    <touch file="${basedir}/1" />
        
    <libraryDef>
      <policy>
        <property format="[x]-[y]" />
          <script language="javascript" priority="first"><![CDATA[
if (!project) throw new Error("project isn't defined?");
if (!library) throw new Error("library isn't defined?");
if (!logger) throw new Error("logger isn't defined?");

var i = library.getEntries();
echo("library entries size = "+i.length);
echo("entry[0] = ["+i[0]+"]");
for (var id = 0; id < i.length; ++id) {
  var e = i[id];
  if (!e) throw new Error("entry ["+e+"] is null?");
  for (var x in e) {
    logger.warn("entry["+id+"] contains ["+x+"]");
  }
  var y = e.getInfo().getAttribute("y", project);
  e.getInfo().createElement("x", y)
}
]]&gt;</script>
          <repository artifact="true" basedir="${basedir}" format="[x]" />
        </policy>
        <library>
          <lib-entry y="1" />
        </library>
    </libraryDef>
    <condition property="11-set">
      <isset property="1-1" />
    </condition>
    <fail unless="11-set">
Did not find or process artifact 1
    </fail>
  </target>
</project>

Through Java

Antlion provides the processors as standard Ant tasks, but with a funny name so that they aren't used in the wrong context. When a tag is referenced inside a policy, Antlion searches the registered Ant task list for names starting with (listed in order): ".antlion.", "library-", "antlion-", and "" (as-registered). The ".antlion." prefix is reserved for Antlion internal use. Therefore, if you want to add a processor named "dirset", you can register it using the standard Ant <taskdef> task. This process is described throughly here.


Custom Repositories

Along with the custom processors, you can develop your own custom repositories. At the moment, the only well-defined way to create a new repository is by writing a Java class that implements the Antlion interface net.sf.antlion.libraries.v3.Repository. These can be registered and referenced in the same way as library processors.

To ease this difficult task, you can extend net.sf.antlion.libraries.v3.AbstractFormatRepository, which will give your custom repository much of the same functionality as a standard repository. This class gives an Ant-based API to setting up what Antlion calls finders.

The repositories that Antlion provides all delegate the task of mapping between an expanded format string and the file itself to a finder. The finder knows how to search the repository space for the requested item. A variation also handles a local filesystem caching mechanism. Here are some classes to help you out:



Parent Policies

In some situations, you may wish to separate out your repositories so that you know which location you're pulling the libraries. You can aid in reducing cut-n-paste errors by adding in a limited hierarchy of policies.

Here's what happens when a policy defines another as a parent: all the processors and repositories defined in the parent are merged into the child's policy, with the parent's taking a lower priority. Thus, if the parent and child both define a repository, then the parent's repository will be searched after the child's repository. But, this also means that if a parent defines an <attribute> processor, and the child defines a <path> processor, that the <attribute> will execute before the <path>. If both child and parent define the same processor, then both will run.

Having multiple policies can quickly get you into trouble, but under the right circumstances, they can help keep things organized. Here are some guidelines to help you if you think you need a hierarchy of policies.

  • Always have a top-level policy which defines the general set of attributes that all policies should use. It should also define the creation policies that are expected to exist for all policies. This helps keep things consistent.
  • Don't get tricky. If the policies get complicated, then figuring out what happens during a build gets complicated.
  • Keep things shallow. Though there always exists exceptions, you shouldn't have a need to have more than a common top-level policy and two or three lower level policies that define repositories and possibly a few extra transformations.


Processor Priorities

The Antlion processors run on each library in an order declared by the processor priority, which is a numeric value that allows the list of processors to be sorted. Though our language normally uses the phrases "high priority" and "low priority", the Antlion priority number is structured such that a lower numbered processor will run before a higher numbered processor. Here, it seems better to use the phrases "earlier priority" and "later priority" to better match what Antlion does.

The priority number has been arbitrarily divided in separate regions of "time". Each slice is 100 units, and processors, by default, are assigned a number in the middle of that slice. The currently defined slices are:

NameStartEndDescription
Pre-repositories-100-1 processors that must run before the repositories can locate the files.
Repositories099 executes the repositories for file discovery.
Properties100199 creates Ant properties for the libraries or entries within the libraries.
Filesets200299 creates filesets and filelists for the libraries or entries.
Paths300399 creates paths for the libraries or entries.
Validate400499 performs validation checks on the libraries.
The built-in Antlion processors (and, indeed, any processor that extends net.sf.antlion.libraries.v3.processors.AbstractProcessor) have an attribute named subPriority which allows the fine-tuned adjustment of the default priority setting. There are no bounds on this sub-priority, so you could have a <properties> processor with a sub-priority of -400 to have it run at priority level -250, which is below any built-in processor.

Some of the built-in Antlion processors have a default sub-priority set, so that they will run cleanly with the other built-in processors. These sub-priorities can be overridden by manually setting the subPriority attribute.

By combining certain processors together with tightly controlled sub-priorities, you can create interesting results, such as:

  <attribute attributes="group" subPriority="-10" />
  <dynamic-attributes subPriority="-9">
    <attribute attrib="version" property="lib.[group].[name].version" />
  </dynamic-attributes>
  <attribute attributes="version" subPriority="-8" />
  <dynamic-attributes subPriority="-7">
    <attribute attrib="version" property="lib.[group].version" />
  </dynamic-attributes>
This example has logic that goes like this:
  • (-10) If the <library> has a group attribute, then give this to each contained <lib-entry> that doesn't define that attribute.
  • (-9) If the <lib-entry> defines both a group and name attribute, and does not have the version attribute set, then assign that <lib-entry> a version attribute from the Ant property ${lib.group.name.version}, if that property exists.
  • (-8) If the <lib-entry> does not define a version attribute, but the owning <library> does, then assign that attribute to the <lib-entry>.
  • (-7) Finally, if none of that was defined, then default the version number to the Ant property ${lib.group.version}, if it is set.


Prev   Next  
Document version $Revision: 1.18 $ $Date: 2005/12/15 16:05:26 $

SourceForge Logo
Copyright © 2004-2006, The Antlion Project