Introduction
This documentation describes the build system implementation and how to develop
a new plugin for it. Readers must be comfortable with the build system concepts
and usage (
see the user's guide) and with
Ant development.
Concept
The build system can be described globally as an Ant
build.xml
file
generator. The XML project description
project.xml
declares the used plugins
(to include needed targets) and the module descriptions to generate properties
needed by the plugin targets and also additional targets like shortcuts.
Installation from sources
- Read
the getting started instruction to
install Ant 1.6.5 or Ant manual
- Get packaged sources from SourceForge and unpacked them in a folder,
or checkout it from Subversion with
svn co https://svn.sourceforge.net/svnroot/el4ant/trunk el4ant
In our example we name this folder INSTALL
- From a command line, in
INSTALL
diretory, run
ant -f bootstrap.xml -Donline=t
to download needed jar files,
compile plugins and configure test and example modules.
Release
Here are the steps to follow in order to build a deliverable binary release of
the build system.
- Install the build system from sources like it is described in the previous
(or use an export from Subversion)
- Change EL4Ant version in
bootstrap.xml
and etc/bootstrap.properties
if
the number version should be increased
- Check that the
develop
plugin is properly configured in plugins.xml
- From a command line, in
INSTALL
diretory, run
the command ant -f bootstrap.xml distclean configure
- Then, invoke
ant create.packages
- Release as zip files is available in
dist/develop
Build System Internals
Files
Project description
The project description file (
project.xml
) is an Ant build file which default
namespace is
el4ant
.
The
include
statement enables inclusion of project description parts from
other files. Beware that the order in which you include files is important.
<include file="plugins.xml"/>
Properties notation and Ant tasks may be invoked directly in the
project.xml
but it should only be used as the last possibility to solve a complex issue.
Bootstrap Ant build file
The
boostrap.xml
build file provides the following targets:
-
configure
configures the project from the project.xml
file.
-
distclean
cleans completely the project hierarchy (if fact files
declared by plugins configure
target)
Theses targets are configured with
etc/bootstrap.properties
.
Plugin Ant file
A plugin is a Ant build file with at least a
configure
target. This target is
invoked when the plugin is declared in the project description file.
Plugin Ant files can be stored in a jar file with specific manifest sections to
enable automatic plugin detection and of course ease delivery. The
el4ant:buildplugin
task processes the jar file creation.
Generated Ant build file
The
build.xml
file is generated by the project configuration (
configure
in
bootstrap.xml
or in an already existing
build.xml
).
Configuration process
The configuration step must be detailled:
- load plugin jar files from Ant ClassPath
- parse project XML files and execute each task on the fly for
plugin
,
module
and include
- execute property generators in
registration order and write
dist/project.properties
- execute build generators in registration
order and write
build.xml
Classes
The main build system concepts are Ant tasks (
module
,
plugin
,
hook
) or
types (
attribute
,
dependency
,
mapping
) in the
ch.elca.el4ant.model
package.
Note: it has eased the conception of the
project.xml
file, avoiding to
write a dedicated parser, even if it may be considered as tricky or uncleaned.
The ProjectRepository
The project repository is a singleton object that is only available at
configuration time. It contains the complete project model: registered plugins,
modules and hooks.
It also manages configuration listener implementations, component resolvers,
property generators and build generators.
To ease communication between components, additional objects can be stored
thanks to the
setUserData
and
getUserData
methods.
The ConfigurationListener interface
This interface enables to receive configuration notification events
(
ch.elca.el4ant.model.ConfigurationEvent
) when registered thanks to
the following methods of
ch.elca.el4ant.model.ProjectRepository
.
public void addConfigurationListener(ConfigurationListener listener);
public void removeConfigurationListener(ConfigurationListener listener);
The method
ConfigurationListener.componentConfiguring
is called before a
component begins its configuration (
execute
method of
Plugin
or
Module
)
and
ConfigurationListener.componentConfigured
when configuration succeeded.
If a component configuration failed, a
BuildException
is thrown and the Ant
process is aborted.
The
ConfigurationEvent
source is the concerned component (
Plugin
or
Module
).
This interface is used to enhance module configuration. If an attribute implies
needed attributes for other plugins, it is interesting to implement this
interface and manipulate module attributes before its configuration.
The PropertyGenerator interface
This interface enables an implementation to generate project properties needed
by Ant targets from the project model.
If an implementation needs properties from another implementation, dependencies
must be declared. The generator name is normally the plugin name itself. One
property generator per plugin is a good guideline.
Core implementations are in the
ch.elca.el4ant.propgen
package. A plugin
provides its own implementation in its dedicated package.
Its usage may be overturned to generate other objects like files for example.
The BuildGenerator interface
This interface enables an implementation to generate a part of the Ant build
file from any other source.
Current implementations are in the
ch.elca.el4ant.buildgen
package:
-
BaseGenerator
outputs the begin and the end of a source file with a
pause at a defined insert point. It is used to generate the build file
from the bootstrap.xml
file. It must be registered first to work
properly.
-
IncludeGenerator
includes a part of another Ant build file from a
defined insert point to the end line detected with
.
-
ShortcutGenerator
generates a target shortcut for available modules or
for available module execution units. This generation may be controled
with ifproperty
or unlessproperty
attributes that test the project
properties for a module or execution unit marker to know if the shortcut
must be generated or ignored.
When the build system generators are called, project properties are already
generated and they may be used safely from ProjectRepository.getProperties().
Its usage may be overturned to generate other objects like files for example.
Ant tasks
The extendedproperty task
The task
el4ant:extendedproperty
evaluates an expression, replacing property
notation at any level and setting an overwritable property with the evaluation
result. Unlike
property
, if the property already exists, its content is
replaced by the evaluation result. So that it can be used in loop or in
recursive call.
Example
<property name="set" value="tests"/>
<property name="set.description.tests" value="Modules in the set tests"/>
<el4ant:extendedproperty
name="mysetdescrition"
value="The set '${set}' is described as ${set.description.${set}}"/>
The value of
mysetdescription
is
The set 'tests' is described as Modules in the set tests
.
The component resolver task
The task
el4ant:resolvecomponent
(
ch.elca.el4ant.taskdefs.ResolveComponentTask
) enables to download
a lacking jar in a plugin
configure
target. The task attributes are:
-
type
is optional, its default value is jar
. Other possible value is
module
.
-
value
is, for a jar,
- a file name, consider to be located in
project.lib.directory
- or a file path relative to the project directory
- or an absolute path.
Example
<el4ant:resolvecomponent type="jar" value="junit.jar"/>
- If value is
junit.jar
, it will be looked at position
${project.lib.directory}/junit.jar
from the project base directory.
- If value is
antlib/commons-logging.jar
, it will be looked at that exact
position from the project base directory.
The ProjectLoader task
The task
el4ant:loadproject
(or
include
as a synonym in
project.xml
) is
responsible for doing the following things:
- Before any project loading, it looks for binary plugin jar files and loads
their plugin declaration names.
- It loads and executes the default project description file
project.xml
.
- It loads and executes included project description files with the
include
statement.
- It loads Ant plugin build files.
Plugin
is responsible for executing the
configure
target.
Use the
file
attribute to load a project description.
Internally, it uses a specific Ant ProjectHelper to load build files as
resources.
The AutoSelect task
This task
el4ant:autoselect
looks for the module corresponding to the path
where the build is launched. If a module path matches, the property
module
is
set (only in case it was not defined yet). If the module found declares only
one execution unit, then the property
eu
is set to its name.
Thus, it enables to launch
ant -find build.xml compile.module
from the
directory
my/module/java/ch
to compile the module
mymodule
for instance.
Note: be sure to use
-find build.xml
in your Ant command line or script.
The ReloadProject task
el4ant:reload
reloads Ant targets from a build file into the current Ant
project. The
file
attribute defines the build file to reload.
This task is used to reload the
build.xml
after an automatic reconfiguration.
The plugin builder task
The task
el4ant:buildplugin
(
ch.elca.el4ant.taskdefs.BuildPluginTask
) is
designed to ease the creation of the plugin jar. This task is only useful in a
plugin
load
target (or eventually
configure
).
Description
This task does the following jobs:
- check for third party jar dependencies and use
onlinelib
plugin to
download lacking files.
- compile Java classes with dependencies in the ClassPath with the
javac
task.
- package the plugin as a jar and load it in the build system ClassLoader
which reference name is
el4ant.coreloader
.
Attributes
The following attributes are available to configure these jobs:
-
srcdir
is the java source directory to compile. Optional.
-
destdir
is the java classes directory for compilation. Mandatory if
srcdir
is set.
-
jarfile
is the generated jar file. Mandatory. It should be in stored in
the buildsystem.dist.directory
.
Elements
-
fileset
declares files that must be included in the jar file. At least
one element is needed. See Ant documentation.
-
classpath
is the base original compilation classpath. It is appended to
the build system classloader. Optional.
-
plugin
declares the name of a plugin and the corresponding file in the
jar. Example
. Mandatory to
register a plugin in a packaged jar. Multiple plugins can be declared in a
single jar. The plugin name must be unique in the whole system.
-
manifest
is used in the packaged jar. It is optional. The main attribute
BuildSystemVersion
and plugin declaration section are added. See Ant
documentation.
Returned value
If a compilation has been done, the
plugin.compiled
property is set to
true
. If not, the property is not set.
Details
Compilation is done only if the plugin is in source format. The
buildplugin
task called from a binary release plugin XML file does no compilation.
If you do not need classes compilation, neither set
srcdir
,
destdir
.
Compilation uses the global build system compilation properties defined in
etc/bootstrap.properties
:
buildsystem.javac.deprecation
,
buildsystem.javac.debug
,
buildsystem.javac.encoding
and
buildsystem.javac.source
. See
javac
Ant task documentation for details.
The jar packaging uses the declared
fileset
,
manifest
and
plugin
elements. If no base manifest is declared, a default manifest is generated.
When the jar is packaged, it is automatically loaded in the build system
ClassLoader. So other tasks like
el4ant:projectproperties
can register a
newly compiled property generator easily.
To force loading of a plugin jar in the build system ClassLoader even when no
compilation is required, it is necessary to provide the
classpath
element
with a
pathelement
for the plugin location in
${buildsystem.lib.directory}
.
If the plugin provides new tasks, use
typedef
Ant task with
loaderref="el4ant.coreloader"
. If
typedef
is done in the
load
target, the
task is also available in the plugin caller. If in the
configure
target, new
task definition can only be used in the target itself. See
typedef
task
documentation and Antlib documentation for details about task definition.
Example
<property name="demo.java" value="${configure.plugin.dir}/java"/>
<property name="demo.classes" value="${configure.plugin.dir}/classes"/>
<el4ant:buildplugin
srcdir="${demo.java}"
destdir="${demo.classes}"
jarfile="${buildsystem.dist.directory}/buildsystem-demo.jar">
<classpath>
<!-- if not compiled, append el4ant.coreloader with plugin jar -->
<pathelement location="${buildsystem.lib.directory}/buildsystem-demo.jar"/>
<pathelement location="${buildsystem.lib.directory}/${buildsystem.core.jar}"/>
<pathelement location="${buildsystem.dist.directory}/${buildsystem.core.jar}"/>
</classpath>
<fileset dir="${demo.classes}"/>
<fileset dir="${demo.java}">
<exclude name="**/*.java"/>
</fileset>
<fileset dir="${configure.plugin.dir}">
<include name="demo.xml"/>
</fileset>
<manifest>
<attribute name="Implementation-Title" value="Demo Plugin"/>
<attribute name="Implementation-Vendor" value="ELCA Informatique SA"/>
<attribute name="Build-Date" value="${TODAY}"/>
<attribute name="Built-By" value="${user.name}"/>
</manifest>
<plugin name="demo" file="demo.xml"/>
</el4ant:buildplugin>
<typedef
resource="demo/antlib.xml"
uri="antlib:ch.elca.el4ant.demoplugin"
loaderref="el4ant.coreloader"/>
In that example, all features are used. The jar file contains the
demo.xml
declared as the plugin
demo
, plus compiled classes, plus resources from the
java directory. The declared manifest provides additional information compared
to the default generated manifest.
Then you can registered the property generator compiled in your plugin with
<el4ant:projectproperties
action="register"
class="demo.DemoPluginPropertyGenerator"/>
and call your newly registered task by
typedef
with
<demo:demotask argument="Demo Plugin configure target"
xmlns:demo="antlib:ch.elca.el4ant.demoplugin"/>
The task is declared in
demo/antlib.xml
file as
<?xml version="1.0"?>
<antlib>
<taskdef name="demotask" classname="demo.DemoTask"/>
</antlib>
The BuildGen task
The task
el4ant:buildgen
is responsible to process all registered
BuildGenerator implementations to create the
build.xml
file. Only use at configuration.
Attributes
-
action
is the action to apply on the build generators. Possible values
are source
, include
, shortcut
, output
.
-
ifattribute
and unlessattribute
are used to control shortcut
generation for the action shortcut
.
-
target
is the name of the Ant target for the action shortcut
.
-
file
is the build file to load, for the actions source
and include
or to generate for the action output
.
Elements
-
param
can be used to declare the Ant target parameters for the action
shortcut
. The name
attribute declares the parameter. Supported
parameters are module
and eu
.
Examples
el4ant:buildgen
is used in a plugin to include targets in the generated
build.xml
. See
tutorial section about it.
It is also used to
generate shortcuts according
to available modules.
The ProjectProperty task
The task
el4ant:projectproperties
is responsible to manage property generation
with the use of
PropertyGenerator implementations.
Attributes
-
action
is the action to apply on the property generators. Possible
values are register
, append
and output
.
-
class
is the PropertyGenerator implementation for the action
register
.
-
property
is the name of the property for the action append
.
-
value
is the value to append to the property for the action append
.
-
file
is the property file to generate for the action output
.
Details
The
register
action simply loads the property generator into the project
repository. There is no ClassPath to specify if the generator has been
compiled or loaded with the
el4ant:buildgen
task.
The
append
action registers a specific property generator to implement the
processing on the concerned property.
The
output
action executes property generators and writes the property file
to disk.
Examples
el4ant:projectproperties
is used in a plugin to register a new property
generator. See
tutorial section about it
An
append
action usage is available in the
tutorial section about packaging.
The hook task
The task
el4ant:hook
enables management and execution of hook chains. It
detects specific hook name patterns
[module]
and
[module].[eu]
to handle
generic hook chains (a chain per module or per module execution unit).
Attributes
-
action
is the action to apply on the hook. Possible values are create
,
append.last
, append.first
, remove
, call
. Default value is
append.last
.
-
name
is the hook chain name. Mandatory for any action.
-
module
is the module scope the action should use. In case of a
[module]
generic hook chain name, the action is limited to the defined
module.
-
eu
is the execution unit scope the action should use. In case of a
[module].[eu]
generic hook chain name, the action is limited to the
defined execution unit. If both module
and eu
scope are defined, the
action is limited to the defined module execution unit.
-
module.eu
is an alternate way to define module
and eu
scopes in a
single attribute. Use either module
and eu
attributes or module.eu
but not both at the same time.
-
target
is the Ant target name used by one of the append.last
,
append.first
or remove
action.
-
if
is the name of a property to enable the target at hook execution. If
the property is set when the hook is executed, the target in the chain is
enabled. Available with append.last
and append.first
action.
-
unless
is the name of a property to disable the target at hook
execution. If the property is set when the hook is executed, the target in
the chain is disabled. Available with append.last
and append.first
action.
Behavior at configuration
All actions except
call
are designed to be used at
configure
time.
A
hook
task invocation can be done in Ant build file in plugin
configure
target or directly in a project description file.
At the project description root level, the task is called exactly with defined
parameters, all attribute values are possible.
Inside an
module
element, the attribute
module
is set to the current module
name before execution.
Inside a
eu
element, the attributes
module
and
eu
are set to the current
module and execution unit names.
Behavior at execution
At
build.xml
execution, only the hook action
call
has effect. If the hook
name is generic, the corresponding scope (
module
or
module
plus
eu
) must
be set. The task executes the list of targets registered in the chain at
configuration.
Details
At configuration time, a hook chain must be created in the ProjectRepository
before being able to register targets on it. Each target registration (append
or removal) are stored as a PropertyGenerator instance that apply the action
at property generation.
After configuration, the list of available targets are stored in the
hook.list
property.
Registered targets on a hook chain is stored the
hook.targets.HOOKNAME
property. If a hook chain is empty, no corresponding property is generated.
For a generic hook name,
post.compile.[module]
for instance, a per module
chain is defined (if non empty) with the property
hook.targets.post.compile.mymodule
, for the module
mymodule
in our example.
At execution, the
hook
task uses the generated project properties
(
dist/project.properties
) to acheive
call
actions.
The javacommand task
The
javacommand
task enables the system to create a
Java
Ant command,
modify it and finally display and execute it in different steps. It is needed
to provide extensibility on a java command line with runtime hooks.
Attributes
-
action
is the action to process on the specified command. Mandatory.
-
command
is the Java command name on which action applies.
-
target
is the property name which should stores the result of an action.
Elements
All of them are Ant standard tasks or types. They are re-used as nested
elements by
javacommand
.
-
java
is the Java command template.
-
arg
is a command line argument.
-
jvmarg
is a JVM command line argument.
-
classpath
is a Java ClassPath definition.
-
sysproperty
is a Java system property definition.
Action create
Creates a new Java command. It nests a
java
element (mandatory) that is the
standard Ant
java
task. There is no restriction on the content of this
element.
<el4ant:javacommand command="mycommand" action="create">
<java className="test.Main" fork="true">
<arg value="arg1"/>
<arg value="arg2"/>
<sysproperty key="my.sys.prop1" value="v1"/>
<classpath path="/path"/>
</java>
</javacommand>
Result:
java -Dmy.sys.prop1="v1" -cp /path test.Main arg1 arg2
Action append
Appends application arguments, jvm arguments, system properties and/or a
ClassPath elements to an existing java command. The nested elements are
standard Ant elements and there is no restriction on their content.
Possible nested elements:
arg
,
sysproperty
,
jvmarg
,
classpath
.
<el4ant:javacommand command="mycommand" action="append">
<arg value="arg3"/>
<sysproperty key="my.sys.prop2" value="v2"/>
<jvmarg line="-verbose:gc"/>
</javacommand>
Result:
java -verbose:gc -Dmy.sys.prop1="v1" -Dmy.sys.prop2="v2" -cp /path test.Main arg1 arg2 arg3
Action prepend
Prepends (i.e. adds at the beginning) application arguments, jvm arguments,
system properties and/or classpath elements to an existing java command. The
nested elements are standard Ant elements and there is no restriction on their
content.
Possible nested elements:
arg
,
sysproperty
,
jvmarg
,
classpath
.
<el4ant:javacommand command="mycommand" action="prepend">
<arg value="arg0"/>
<sysproperty key="my.sys.prop0" value="v0"/>
</javacommand>
Result:
java -verbose:gc -Dmy.sys.prop0="v0" -Dmy.sys.prop1="v1" -Dmy.sys.prop2="v2" -cp /path test.Main arg0 arg1 arg2 arg3
Action getSysProperty
Gets the value of a system property defined on a java command line. The system
property to read is specified by a nested
sysproperty
element (mandatory) and
the result is stored in a Ant property whose name is specified in the
target
attribute (mandatory).
<el4ant:javacommand command="mycommand" action="getSysProperty" target="the.prop">
<sysproperty key="my.sys.prop1"/>
</javacommand>
Result:
v1
is stored in
the.prop
property.
Action setSysProperty
Sets the value of a system property defined on a java command line. The system
property to set is specified by a nested
sysproperty
element (mandatory).
<el4ant:javacommand command="mycommand" action="setSysProperty">
<sysproperty key="my.sys.prop1" value="newvalue"/>
</javacommand>
Result:
java -verbose:gc -Dmy.sys.prop0="v0" -Dmy.sys.prop1="newvalue" -Dmy.sys.prop2="v2" -cp /path test.Main arg0 arg1 arg2 arg3
Action getArguments
Gets the list of the application arguments defined on a java command line. The
result is stored in a property whose name is specified in the
target
attribute (mandatory).
<el4ant:javacommand command="mycommand" action="getArguments" target="the.args"/>
Result:
arg0 arg1 arg2 arg3
is stored in
the.args
property.
Action getArgument
Gets one of the application argument defined on a java command line. The result
is stored in a property whose name is specified in the
target
attribute
(mandatory). Two additional attributes containing a
pattern
and an
offset
must be specified. The argument to return is then selected as follows: all the
arguments are considered until one contains the
pattern
string. Some
arguments are then skipped; the number of arguments to skip is given by the
offset
. The argument to return is now found.
This action is useful for manipulating the application arguments. For example,
if a java command has the application arguments
-port 4321 -debug -logfile toto.txt
,
the port number can be retrieved with
pattern="port"
and
offset="1"
.
<el4ant:javacommand
command="mycommand"
action="getArgument"
target="the.arg"
pattern="arg1"
offset="1"/>
Result:
arg2
is stored in
the.arg
property.
Action getClassName
Gets the main class name. The result is stored in a property whose name is
specified in the
target
attribute (mandatory).
<el4ant:javacommand command="mycommand" action="getClassName" target="the.class"/>
Result:
test.Main
is stored in
the.class
property.
Action setClassName
This action sets the main class name. The new class name is specified in the
class
attribute (mandatory).
<el4ant:javacommand command="mycommand" action="setClassName" class="test.NewMain"/>
Result:
java -verbose:gc -Dmy.sys.prop0="v0" -Dmy.sys.prop1="newvalue" -Dmy.sys.prop2="v2" -cp /path test.NewMain arg0 arg1 arg2 arg3
Action getClassPath
Gets the class path. The result is stored in a property whose name is specified
in the
target
attribute (mandatory).
<el4ant:javacommand command="command.${eu}" action="getClassPath" target="the.path"/>
Result:
/path
is stored in
the.path
property.
Action setClassPath
This action sets the class path. The previous ClassPath is replaced. The new
ClassPath is specified in a nested
classpath
element (mandatory).
<el4ant:javacommand command="mycommand" action="setClassPath">
<classpath>
<pathelement path="/newpath"/>
</classpath>
</javacommand>
Result:
java -verbose:gc -Dmy.sys.prop0="v0" -Dmy.sys.prop1="newvalue" -Dmy.sys.prop2="v2" -cp /newpath test.NewMain arg0 arg1 arg2 arg3
Action execute
Executes the java command.
<el4ant:javacommand command="mycommand" action="execute"/>
Result: The command
java -verbose:gc -Dmy.sys.prop0="v0" -Dmy.sys.prop1="newvalue" -Dmy.sys.prop2="v2" -cp /newpath test.NewMain arg0 arg1 arg2 arg3
is executed.
Action display
Displays the java command.
<el4ant:javacommand command="mycommand" action="display"/>
Details
The JavaCommand task stores java commands in a internal storage. It is
technically possible to create and modify multiple commands in parallel and
decide to execute the resulting commands sequentially or parallely.
Plugin development
A plugin is a Ant XML build file package in a jar file with a specific manifest
section. It can be used directly in source version in a project to ease plugin
development.
Generalities
Available features
- Provide new Ant targets to be included in the generated
build.xml
.
- Generate properties based on plugin and module declarations to be used by
plugin targets.
- Generate target shortcuts to ease user experience of the
build.xml
.
- Manage plugin and module attributes to simplify their declaration by the
user.
- Provide new Ant tasks to achieve features.
- Plugin attributes are usable at configuration in the plugin build file.
Constraints
- A plugin must have a unique name.
- A plugin must be delivered as a single jar thanks to the
el4ant:buildplugin
task (BuildPluginTask
). If it works as a jar, it
works also in source format.
- Properties generated by a plugin must be relative to the project base
directory (
bootstrap.xml
or project.xml
). Check them in
dist/project.properties
.
- Plugin
load
and configure
targets must work even if the JVM working
directory is not the base directory of the project. So all file paths used
in tasks must be relative to the project base directory.
Guidelines
- The
antlib
directory is reserved to contain plugins delivered by default
in the binary release. A project specific plugin should be designed in
another directory.
- The target description should only be defined for high-level user
targets. Internal targets and expert user targets are described in
documentation but without Ant
description
to keep the build.xml
projecthelp
smaller.
- A two-space indentation in XML files is more comfortable because
lines are often long.
Tutorial
First steps to create a plugin
This section is written as a step by step tutorial to begin with the simplest
features and to finish with the most complex ones.
A plugin may be anywhere you want but it is wise to put it in
buildsystem/myplugin/plugin.xml
. You may want to collect many plugins in the
same directory, you're free to do it, just do not forget to document how to use
it.
Your
plugin.xml
file initial content should be:
<?xml version="1.0"?>
<!-- EL4Ant plugin definition -->
<project name="myplugin" xmlns:el4ant="antlib:ch.elca.el4ant">
<description>
EL4Ant myplugin.
Refer to the plugin documentation at YourWikiPluginDocumentationForInstance
</description>
<!-- Additional content comes here -->
</project>
To use it, you must add the
plugin
declaration in your
project.xml
:
<plugin name="myplugin" file="myplugin/plugin.xml"/>
Warning: this statement fails until a valid
configure
target is present in
the plugin file.
Create your own target
Your target is aimed to be included in the generated
build.xml
file. If your
target uses the project properties or additional tasks from the build system
itself or plugins, it must depend on the
init
target.
Write your Ant targets in the plugin file, at the end of the file. In this
tutorial, we just provide a simple target as an example. Targets to process
module and execution units will be described later. You must now provide
configuration statements to make it work.
<target
name="configure"
description="[C] Configure myplugin">
<!-- Declare the plugin include start point -->
<el4ant:buildgen action="include" target="myplugin.include.startpoint"/>
<!-- Additional plugin configuration comes here -->
</target>
<!-- Fake init target to load plugin file -->
<target name="init"/>
<!-- Targets following the next fake target are copied
into the generated build.xml file -->
<target name="myplugin.include.startpoint"/>
<!-- Targets from myplugin.xml -->
<target
name="myplugin.mytarget"
depends="init"
description="[MyPlugin] Description of mytarget">
<echo message="Do your job here"/>
</target>
<!-- End myplugin.xml -->
You can check your plugin correctness with
ant -f myplugin/plugin.xml -projecthelp
Now you can configure your project with
ant -f bootstrap.xml
to see how the
build.xml
looks like with
ant -projecthelp
.
Why are there comments in the plugin file name ? Just have a look at the
generated
build.xml
and ask yourself how you can debug it in case of
troubles.
Package your plugin
Your plugin is already usable but it should be packaged as a jar file to be
easily delivered and used by others.
Invoke the
el4ant:buildplugin
task in the
load
target to build the
plugin jar file.
<target
name="load"
description="[C] Load myplugin">
<!-- Create the plugin jar -->
<el4ant:buildplugin
jarfile="${buildsystem.dist.directory}/myplugin.jar">
<classpath>
<!-- if not compiled, append el4ant.coreloader with plugin jar -->
<pathelement location="${buildsystem.lib.directory}/myplugin.jar"/>
</classpath>
<fileset dir="${configure.plugin.dir}">
<include name="plugin.xml"/>
</fileset>
<!-- Optional element for the jar Manifest file -->
<manifest>
<attribute name="Implementation-Title" value="My Tutorial Plugin"/>
<attribute name="Implementation-Vendor" value="ELCA Informatique SA"/>
<attribute name="Build-Date" value="${TODAY}"/>
<attribute name="Built-By" value="${user.name}"/>
</manifest>
<plugin name="myplugin" file="plugin.xml"/>
</el4ant:buildplugin>
Take care to the fact that the plugin name can be different from the plugin Ant
XML file name.
Your plugin package can be found in
dist/buildsystem/myplugin.jar
(
buildsystem.dist.directory
is defined in
etc/bootstrap.properties
).
If you copy the plugin jar in the directory
antlib
(
buildsystem.lib.directory
from
etc/bootstrap.properties
), the plugin can
be used with the following declaration:
<plugin name="myplugin"/>
To go on developing your plugin, you must keep the previous plugin declaration
with both
name
and
file
attributes.
Configure your plugin with attributes
Attributes defined in the plugin declaration are directly copied in the
generated
dist/project.properties
.
<plugin name="myplugin" file="myplugin/plugin.xml">
<attribute name="myplugin.message" value="hello world !"/>
</plugin>
Your target can directly use them:
<target
name="myplugin.mytarget"
depends="init"
description="[MyPlugin] Description of mytarget">
<echo message="I just want to say: ${myplugin.message}"/>
</target>
If the attribute is not defined, you will get the message
I just want to say: ${myplugin.message}
To define a default value, you can use the Ant
property
task.
<target
name="myplugin.mytarget"
depends="init"
description="[MyPlugin] Description of mytarget">
<property name="myplugin.message" value="[sigh]"/>
<echo message="I just want to say: ${myplugin.message}"/>
</target>
As
property
does not change the value of an already defined property, you
keep the
plugin
declaration value if it is defined. If not, the default value
coded in your plugin target is used.
Moreover, plugin attributes are directly usable in the
configure
target as
Ant properties. So you can check them or set their default value there.
Generate target shortcuts
A target shortcut is a simple way to tell the user what can be done with the
current configuration. It consists in creating a target with the module
explicitly when it applies to it.
<el4ant:buildgen action="shortcut" target="mytarget.module">
<param name="module"/>
</el4ant:buildgen>
This declaration in a plugin will creates
mytarget.module.aModule
target in
the
build.xml
for all modules that invoke
mytarget.module -Dmodule=aModule
.
It is the same idea with module and execution unit parameters:
<el4ant:buildgen action="shortcut" target="mytarget.module.eu">
<param name="module"/>
<param name="eu"/>
</el4ant:buildgen>
If the target should do nothing or fail with some modules, the shortcut
generation can be controlled by setting an attribute for the concerned module
(or module execution unit) with
ifattribute
:
<el4ant:buildgen
action="shortcut"
target="mytarget.module"
ifattribute="myplugin.mytarget.enabled">
<param name="module"/>
</el4ant:buildgen>
Generation can be done only if an attribute is not set with
unlessattribute
:
<el4ant:buildgen
action="shortcut"
target="mytarget.module"
unlessattribute="myplugin.mytarget.disabled">
<param name="module"/>
</el4ant:buildgen>
Warning: your target should control that the given module (and execution unit)
exists and is well configured to acheive the job. Even if the shortcut for your
target is not generated, an invalid manual invocation is always possible:
ant clean.module -Dmodule=foo -Deu=bar
Configure your target behaviour with module and execution unit attributes
TBD: example with mytarget.module can use module attributes.
Same idea for a module.eu target.
Generate properties for your target
Depending on the information needed to generate the concerned properties, there
are two possible ways to implement the generation.
A simple property
Suppose an attribute is needed by the plugin
configure
target to check if a
directory or a file is available at configuration. In that case, the
configure
target checks the plugin attribute stored as a Ant property:
<antcontrib:if>
<not>
<isset property="plugin.directory"/>
</not>
<antcontrib:then>
<!-- Set default value -->
<property name="plugin.directory" value="dist/plugin"/>
<el4ant:projectproperties
property="plugin.directory"
value="${plugin.directory}"/>
</antcontrib:then>
</antcontrib:if>
<!-- Work with the plugin.directory property already in configure -->
<mkdir dir="${plugin.directory}"/>
This example code checks the
plugin.directory
attribute: if not defined, it
is immediately set to its default value, and a property generator is registered
to make that value persistent in the
dist/project.properties
.
Note: only the current plugin attributes are visible as Ant properties in the
plugin
configure
target.
Complex properties
If your property generation is more complex and must access to the project
repository to consult available plugins and modules, a Java class is needed.
The first step is to complete the
el4ant:buildplugin
parameters to compile that
new Java source. This part replaces the previous code described in
the packaging of a plugin in
load
target:
<!-- Create the plugin jar -->
<property name="myplugin.java" value="${configure.plugin.dir}/java"/>
<property name="myplugin.classes" value="${configure.plugin.dir}/classes"/>
<property
name="myplugin.jar"
value=""/>
<el4ant:buildplugin
srcdir="${myplugin.java}"
destdir="${myplugin.classes}"
jarfile="${buildsystem.dist.directory}/myplugin.jar">
<classpath>
<!-- if not compiled, append el4ant.coreloader with plugin jar -->
<pathelement location="${buildsystem.lib.directory}/myplugin.jar"/>
<pathelement location="${buildsystem.lib.directory}/${buildsystem.core.jar}"/>
<pathelement location="${buildsystem.dist.directory}/${buildsystem.core.jar}"/>
</classpath>
<fileset dir="${myplugin.classes}"/>
<fileset dir="${myplugin.java}">
<exclude name="**/*.java"/>
</fileset>
<fileset dir="${configure.plugin.dir}">
<include name="plugin.xml"/>
</fileset>
<!-- Optional element for the jar Manifest file -->
<manifest>
<attribute name="Implementation-Title" value="My Tutorial Plugin"/>
<attribute name="Implementation-Vendor" value="ELCA Informatique SA"/>
<attribute name="Build-Date" value="${TODAY}"/>
<attribute name="Built-By" value="${user.name}"/>
</manifest>
<plugin name="myplugin" file="plugin.xml"/>
</el4ant:buildplugin>
This new code compiles the Java sources available in the
java
plugin
directory to the
classes
directory. The generated content is included in the
plugin jar file, with non-Java sources from the
java
directory.
Then, it creates the
java
directory in your plugin directory, and creates a
Java source file which provides an implementation of the
ch.elca.el4ant.propgen.PropertyGenerator
. It is interesting to
inherit from
ch.elca.el4ant.propgen.AbstractPropertyGenerator
for
logging for instance.
Suppose that the class is named
myplugin.PropertyGenerator
. The configuration
code must register this implementation (after
el4ant:buildplugin
of course):
<el4ant:projectproperties
action="register"
class="myplugin.PropertyGenerator"/>
Let's run the configuration, if everything is OK, the new generated properties
are in
dist/project.properties
.
Clean your plugin files properly
In the previous section, the
el4ant:buildplugin
declaration has been completed
to compile sources. A
classes/
directory is now created. The following code
must be added to clean that directory when the target
distclean
is invoked.
<!-- Information for distclean -->
<antcontrib:if>
<istrue value="${plugin.compiled}"/>
<antcontrib:then>
<el4ant:projectproperties
action="append"
property="plugin.files"
value="${configure.plugin.dir}/classes"/>
</antcontrib:then>
</antcontrib:if>
The
plugin.files
properties is appended with the plugin
classes/
directory,
in case the plugin is in source format, so that it is properly cleaned up by
the
distclean
target. When the plugin is run in binary format (as a jar), the
property
plugin.compiled
is not set by
el4ant:buildplugin
.
If your plugin generated files elsewhere, it is advised that
distclean
deletes them too. For instance, a plugin that writes a report in
dist/mypluginreport
should use the following code in the
configure
target:
<el4ant:projectproperties
action="append"
property="plugin.files"
value="dist/mypluginreport"/>
Create your own task for your plugin target
If it is not already done, the
el4ant:buildplugin
task invoked in the
load
target must be parametered to compile Java sources, the way it is described in
the first part of
the complex properties generation section.
You can also clean up generated files by using the configuration explained in
the previous section.
Now, create an Ant task in the plugin
java
directory and a property file or
an Antlib XML file to declare it as
mytask
for instance.
If the task must be used immediately in the
configure
target, the task
loading can be done in the
configure
target directly:
<!-- Define tasks before usage -->
<typedef
resource="myplugin/antlib.xml"
uri="antlib:myplugin"
loaderref="el4ant.coreloader"/>
<!-- Now invoke the newly compiled task -->
<myplugin:mytask myparameter="x"
xmlns:myplugin="antlib:myplugin"/>
If the task must be used in user targets, it must be loaded when needed with
the following classical Ant code:
<typedef
resource="myplugin/antlib.xml"
uri="antlib:myplugin">
<classpath>
<pathelement path="${java.class.path}"/>
<pathelement location="${buildsystem.lib.directory}/myplugin.jar"/>
<pathelement location="${buildsystem.dist.directory}/myplugin.jar"/>
</classpath>
</typedef>
<myplugin:mytask myparameter="x"
xmlns:myplugin="antlib:myplugin"/>
Note: the ClassPath may be more complex if the task depends on other
classes. Also take care of the fact that the plugin jar may be generated from
source, so it is located in
buildsystem.dist.directory
, or if simply used as
a delivered jar, it is located in
buildsystem.lib.directory
.
If the Java sources need other classes, a
dependency
element per jar file
should be used in the
el4ant:buildplugin
task.
The plugin tasks are designed to be used by another plugin or directly in the
project XML description file. Tasks loaded in the
load
plugin target (with
the same
typedef
invocation as below) are automatically propagated in the
caller project task definitions. That types and tasks are usable in the caller
plugin
definition and after it.
Add flexibility in your plugin with hooks
To enable other plugins or users to enhance a plugin target, a hook can be
registered in the system
<target
name="myplugin.mytarget"
depends="init"
description="[MyPlugin] Description of mytarget">
<el4ant:hook action="call" name="pre.myplugin.mytarget"/>
<echo message="Do your job here"/>
<el4ant:hook action="call" name="post.myplugin.mytarget"/>
</target>
The hooks must be configured in the
configure
target with:
<el4ant:hook action="create" name="pre.myplugin.mytarget"/>
<el4ant:hook action="create" name="post.myplugin.mytarget"/>
Special hook name
.[module]
and
.[module].[eu]
are used to define a hook
chain per module or per module execution unit.
Add targets to other plugin targets with hooks
A plugin or a user has the possibility to insert a new Ant target in a hook
chain. This can be done in the plugin
configure
target or anywhere in an
included project description file. See the
hook task description
for more details.
<el4ant:hook
action="append.first"
name="post.myplugin.mytarget"
target="myplugin.myhook"/>
The removal of a target from a hook chain is also possible:
<el4ant:hook
action="remove"
name="post.myplugin.mytarget"
target="myplugin.myhook"/>
Advanced topic
Define common properties in an internal target
If you have to use common properties in your plugin
configure
target and in
some of your user targets, a common target that must be included in the
build.xml
can be used with a
depends
attribute in the
configure
target.
<target
name="configure"
description="[C] Configure myplugin"
depends="init.myplugin">
[...]
<!-- Use myplugin.classpath here -->
[...]
</target>
[...]
<!-- Targets from myplugin.xml -->
<target
name="init.myplugin">
<!-- Define common properties -->
<property name="myplugin.classpath" value="..."/>
</target>
<target
name="myplugin.mytarget"
depends="init,init.myplugin"
description="[MyPlugin] Description of mytarget">
<!-- Use myplugin.classpath also here -->
</target>
This mechanism is useful to share third-party jar file names and classpath when
the plugin uses third-party task. You can read checkstyle plugin source as an
example.
Define an initialization target
There are two ways to define a initialization target (to set properties or to
load a task). The first one is simple and efficient. It probably covers your
immediate needs.
<target
name="init.myplugin"
unless="init.myplugin.executed">
<!-- Define default values -->
<property name="myplugin.message" value="[sigh]"/>
<property name="init.myplugin.executed" value="true"/>
</target>
<target
name="myplugin.mytarget"
depends="init,init.myplugin"
description="[MyPlugin] Description of mytarget">
<echo message="I just want to say: ${myplugin.message}"/>
</target>
Of course, it is useless if there is only one target using the elements defined
in the initialization target.
The second way is only interesting if there are many targets using initialized
elements and targets are really often used. It consists in registering the
target in the
init
hook executed by the
init
target (that must be in the
depend
attribute of your user-level targets):
<target name="configure">
[...]
<!-- Register the init hook -->
<el4ant:hook action="append.last" name="init" target="init.myplugin"/>
[...]
</target>
[...]
<!-- Targets from myplugin.xml -->
<target
name="init.myplugin">
<!-- Define default values -->
<property name="myplugin.message" value="[sigh]"/>
</target>
Check for other plugins
When a plugin needs to register a hook into another plugin which is not
required for a normal usage, it is necessary to check for the plugin
configuration before invoking
el4ant:hook
.
For instance, if
myplugin
has to extend the
javadoc
target with the target
myplugin.pre.javadoc
in the hook
pre.javadoc
, the
configure
target must
check that the
javadoc
plugin is configured or else the command fails when
javadoc is not used.
<antcontrib:if>
<el4ant:ispluginconfigured name="javadoc"/>
<antcontrib:then>
<el4ant:hook
action="append.last"
name="pre.javadoc"
target="myplugin.pre.javadoc"/>
</antcontrib:then>
</antcontrib:if>
This conditional structure may be used also to manage properties according to
available plugins.
FAQ
What are differences between load
and configure
In a plugin, the
load
target is reserved to plugin compilation and packaging.
A plugin dependency can be declared in both
load
or
configure
target. Task
definitions from that depedency are published to caller if done in
load
but
remain private in
configure
.
Any other processing like property generation, hook declaration, build
generator registration, etc. should be done in
configure
.
How to debug Ant tasks
If you want to debug any code running in Ant, and of course EL4Ant tasks or
your own tasks, run Ant with the following environment variable set.
export ANT_OPTS=" -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=y -Djava.compiler=NONE"
Then you can connect your favorite debugger on port 8000.
ToDo
- Automated regression tests for core and plugins ! Not easy at all.