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.

Revision: r1.19 - 28 Jun 2006 - 10:26 - YvesMartin
EL4Ant > DeveloperGuide
Copyright © 2004 by ELCA. All material on this collaboration platform should not be disclosed outside of ELCA.
Ideas, requests, problems regarding TWiki? Send feedback.