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.