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.
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.
svn co https://svn.sourceforge.net/svnroot/el4ant/trunk el4ant
In our example we name this folder INSTALL
INSTALL
diretory, run
ant -f bootstrap.xml -Donline=t
to download needed jar files,
compile plugins and configure test and example modules.
Here are the steps to follow in order to build a deliverable binary release of the build system.
bootstrap.xml
and etc/bootstrap.properties
if
the number version should be increased
develop
plugin is properly configured in plugins.xml
INSTALL
diretory, run
the command ant -f bootstrap.xml distclean configure
ant create.packages
dist/develop
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.
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
.
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.
The build.xml
file is generated by the project configuration (configure
in
bootstrap.xml
or in an already existing build.xml
).
The configuration step must be detailled:
plugin
,
module
and include
dist/project.properties
build.xml
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 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.
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.
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.
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.
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.
<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 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,
project.lib.directory
<el4ant:resolvecomponent type="jar" value="junit.jar"/>
junit.jar
, it will be looked at position
${project.lib.directory}/junit.jar
from the project base directory.
antlib/commons-logging.jar
, it will be looked at that exact
position from the project base directory.
The task el4ant:loadproject
(or include
as a synonym in project.xml
) is
responsible for doing the following things:
project.xml
.
include
statement.
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.
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.
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 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
).
This task does the following jobs:
onlinelib
plugin to
download lacking files.
javac
task.
el4ant.coreloader
.
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
.
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.
If a compilation has been done, the plugin.compiled
property is set to
true
. If not, the property is not set.
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.
<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 task el4ant:buildgen
is responsible to process all registered
BuildGenerator implementations to create the
build.xml
file. Only use at configuration.
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
.
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
.
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 task el4ant:projectproperties
is responsible to manage property generation
with the use of
PropertyGenerator implementations.
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
.
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.
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 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).
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.
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.
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.
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 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.
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.
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.
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
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
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
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.
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
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.
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.
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.
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
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.
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
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.
display
Displays the java command.
<el4ant:javacommand command="mycommand" action="display"/>
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.
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.
build.xml
.
build.xml
.
el4ant:buildplugin
task (BuildPluginTask
). If it works as a jar, it
works also in source format.
bootstrap.xml
or project.xml
). Check them in
dist/project.properties
.
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.
antlib
directory is reserved to contain plugins delivered by default
in the binary release. A project specific plugin should be designed in
another directory.
description
to keep the build.xml
projecthelp
smaller.
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.
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.
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.
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.
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
TBD: example with mytarget.module can use module attributes. Same idea for a module.eu target.
Depending on the information needed to generate the concerned properties, there are two possible ways to implement the generation.
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.
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
.
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"/>
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.
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.
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"/>
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.
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>
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.
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
.
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.