- 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
-
|
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)
}
]]></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:
Name | Start | End | Description |
Pre-repositories | -100 | -1 |
processors that must run before the
repositories can locate the files. |
Repositories | 0 | 99 |
executes the repositories for file discovery. |
Properties | 100 | 199 |
creates Ant properties for the libraries or entries within the
libraries. |
Filesets | 200 | 299 |
creates filesets and filelists for the libraries or entries. |
Paths | 300 | 399 |
creates paths for the libraries or entries. |
Validate | 400 | 499 |
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.
|
|
|