The EL4Ant build system is based on Ant and some basic concepts to ease the description and modularization of a project to build.
EL4Ant is a Ant build file generator. This generation is controlled by a project description file which declares plugins used and modules.
The build file generation is called the configuration step, it corresponds to
the configure
Ant target invocation on bootstrap.xml
or build.xml
.
After configuration, the generated build.xml
is used as a normal Ant build
file.
Ant is the well-known Java build tool from the Apache project http://ant.apache.org. It is the main component of the build system. It is included without any changes.
execute
.
What you should know about Ant before using it:
build.xml
build file by default. You can load another
file name with the -f
option.
ant -projecthelp
.
ant target1 target2
. It is faster because only one
Java virtual machine is started to process all targets.
To know more about Ant, please consult the Ant manual.
The build system provides an infrastructure to easily extend its behavior with
plugins. Even core targets (compile
, start
) are defined in plugins, so they
have to be declared in your project description to get a usable build system.
Plugins are read by the configuration process initiated by
ant -f bootstrap.xml
.
The results of the configuration are a project specific build.xml
and
properties project.properties
.
A plugin may depend on other plugins. If dependencies or configuration constraints are not fullfiled in a plugin description, the configuration may abort.
A module is the minimal project component. It enables you to split your project into features-set or services.
It is defined by its relative path (for instance mysystem/core
) and a set of
specific child directories defined by plugins (for instance java
to compile,
docs
for javadoc and sql
for the SQL scripts). The module's path may
contain as many subdirectories as needed by your project structure.
module_root/ java/ docs/ sql/ [...]
In the project description, a module is declared with:
<module path="mysystem/core"> [...] </module>
A symbolic module name is associated with a module. If not specified, it is
build from the path, removing the /
characters. In the previous example, the
module name is mysystemcore
. The module name can also be set explicitly with
the name
attribute in the module
element.
If the module definition is in a separate xml file (for instance mymodule.xml
)
and placed directly in module path (for instance mysystem/core
) the attribute
path can be omitted. Of course this separate xml file must be referenced by
using directive include
(for instance
<include file="mysystem/core/mymodule.xml"/>
) in project xml file.
An execution unit (eu) is a logical part of a module. It may be used optionally to provide internal structure to a module. Each execution unit of a module has its own deliverables (typically jar files) and runtime command definition.
An execution unit defines:
A module will be split into jars according to the execution unit definitions. Each execution unit will have its own runtime specification: ClassPath, main class, arguments, system properties.
The declaration of execution units in a module is optional. In fact, a module
without eu
element has a virtual default execution unit called 0
, you may
see that notation in generated properties, targets or hooks.
The main aim of execution units is to select classes to be deployed on
different tiers - for instance client
, server
and batch
. As a result
runtime commands are configurable per execution unit. The execution unit
definition is orthogonal to the module definition.
Examples are provided in project description section.
A dependency enables to declare a module to module or module to jar class
use. Dependencies are implicitly transitive: if a module A
depends on B
and
module B
depends on C
, you can consider A
automatically depends on C
.
In fact, a dependency applies to execution units. A dependency may be limited to a specified execution unit; the configuration resolves implicit dependencies with the following patterns:
An attribute is a configuration entry used by plugins. It may be defined in a plugin declaration for a global scope, in a module declaration or in an execution unit declaration to limit its scope to the corresponding object.
Consult the plugins documentation to know which attribute names are available for each scope.
A hook is an entry point in the build file target execution. Hooks allow to add specific behaviors during the execution of build system targets. Hooks are declared in plugins and hook targets registered on a hook chain are called within the target.
The hook invocation may be conditionned by a user property which acts as a switch.
Most of the time, a pre and a post hook is defined before and after an important target processing. Specific properties may be available to configure hook target implementation behavior.
Consult the plugins documentation to know which hook chains are available and which hook targets are registered by default. Most hooks are designed for plugin to plugin interaction.
Each module defines what sets it is contained in (sample sets could be tests
,
framework
, or consoleApplication
). To define the sets a module is part of,
the set
attribute is used. A set classifies modules in a logical group to
apply a target on them.
The sets for a module can be declared with multiple set
attributes or a
single attribute with a list of set names separated by comas.
If a module does not declare a set
attribute, it will be present in all sets
from its module dependencies. If module A
is in sets S1
and S2
, and
module B
in set S3
, then a module C
that depends on A
and B
without
explicitly declaring its sets will be in sets S1
, S2
and S3
.
If a module does not declare a set
and has no dependencies, then it will be
registered in the set default
.
If a module declares the noset
attribute, then it is not registered in any
set.
The following graph shows the complete structure of the project.xml
file:
+ <plugin [name] [file]> | + <attribute name [value | CDATA]> | + <module [name] [path]> | + <eu name> | | + <attribute name [value | CDATA]> | | + <hook name target [action="append.(first|last)"]> | + <dependency [jar | module] [eu] [inheritEuList]> | | + {if module} <mapping target [eu]> | + <attribute name [value | CDATA]> | + <hook name target [eu] [action="append.(first|last)"]> | + <hook name target [module] [eu] [action="append.(first|last)"]>
include
statement is analyzed, its content is registered
immediately, so it has an impact on the registration order.
Project description examples are described later.
Attribute elements are used to configure the plugin behavior and Ant targets. The attribute scope depends on where it is set:
plugin
element,
module
element,
eu
element.
Please refer to each plugin documentation to know available attribute names, expected values and scope(s).
In the normal case, a plugin declaration only uses the name
attribute. The
corresponding build file packaged in the plugin jar
file is processed.
If the given name
does not match any available plugins in the EL4Ant lib
directory, the plugin can only be used to store attributes. Such a plugin is
called virtual.
When developing a plugin, the file
attribute must be provided. See
for more details about plugin development.
A module declaration must set its directory path
. A symbolic name
can be
set. A module can declare its dependencies, its execution units, its attributes
and its hook targets.
Module execution units are optional. When no execution unit is specified, execution unit attibutes and hooks must be defined on the module level.
A module may depend on a third party jar or on another module.
A dependency to a jar
applies by default to all module execution units. It
can be specific to a module execution unit with the eu
scope. The jar file
may only be the file name which is then expected to be in lib/
. It may be a
path relative to the project directory or an absolute path.
A dependency to a module
uses the target module name (explicitly declared or
built from the path).
eu
scope is not set,
inheritEuList=true
is used, the target module execution unit list
is declared in the current module.
mapping
is used).
eu
scope is set,
eu
depends on the
single module jar.
eu
depends on the same
eu
of the target module (except if a mapping
is used).
The mapping
element helps to declare explicitly execution unit mappings.
An eu
element declares an execution unit for the module it is used in. Its
name
is mandatory. An execution unit can declare its attributes and its hook
targets.
Attributes and hook target registrations are limited to the execution unit where they are declared.
It enables to create more complex dependencies from module to module, mainly
when the module execution units are not the same. Multiple mapping
elements
may be used in the same dependency
element.
dependency
eu
scope is not set,
mapping target="targeteu"
makes dependencies for each current module
execution unit to the target module targeteu
.
mapping eu="oneeu" target="targeteu"
makes a dependency of the
current module oneeu
to the target module targeteu
.
dependency
eu
scope is set,
mapping target="targeteu"
makes a dependency of the current module
eu
to the target module targeteu
.
It enables to plug a dedicated target in a plugin hook chain.
If the hook name contains the [module]
pattern, then a hook chain is
available per module. If the hook name contains the [module].[eu]
pattern,
then a hook chain is available per module execution unit.
If the hook element is used to register a target in a chain at the project level,
[module]
hook
[module].[eu]
hook
If the hook element is used to register a target in a chain at the module level,
[module]
hook
[module].[eu]
hook
If the hook element is used to register a target in a chain at the execution unit level,
[module]
hook
[module].[eu]
hook
A target invocation in a hook chain can be controlled by the if
and unless
attributes which expect a property name to check. With if
, the target is
invoked when the property is defined and different from false
or no
. With
unless
, the target is invoked when the property is not defined or equals to
false
or no
. Property switching is designed to be used on the Ant command
line with -Dpropertyname=t
for instance.
The documentation of plugins describes the declared hook chains in the user targets and targets designed to be registered in a hook chain.
Here is a simple project description:
<?xml version="1.0"?> <!-- EL4Ant Project definition --> <ant:project name="helloworld" xmlns="antlib:ch.elca.el4ant" xmlns:ant="antlib:org.apache.tools.ant"> <plugin name="compile"/> <plugin name="runtime"/> <plugin name="show"/> <module name="helloworldcommon" path="helloworld/common"> <dependency jar="log4j-1.2.8.jar"/> <attribute name="set" value="helloworld"/> </module> <module name="helloworldapp" path="helloworld/app"> <dependency module="helloworldcommon"/> <attribute name="runtime.runnable" value="true"/> <attribute name="runtime.class" value="helloworld.Main"/> </module> <module name="helloworldtests" path="helloworld/tests"> <dependency module="helloworldcommon"/> <attribute name="junit.runnable" value="true"/> <attribute name="set" value="tests"/> </module> </ant:project>
This description does not use execution units. Execution unit attributes are so declared at the module level.
Consider this short project description:
mytool
without execution unit. The jar mytool.jar
will be
generated with the whole module content.
myservice
interface
: The jar myservice-interface.jar
contains
common classes and the service interface.
implementation
: The jar myservice-implementation.jar
contains common classes and the service interface and
implementation. Only this eu depends on the mytool
module.
myapp
client
: The jar myapp-client.jar
contains client
code deployed with Java WebStart. The client
eu depends on
myservice.interface
to be able to remotely call myservice
.
server
: The jar myapp-server.jar
contains a server
component deployed in a EJB container to provide a remote myservice
interface implementation. The server
eu depends on
myservice.implementation
.
batch
: The jar myapp-batch.jar
provides automation
to call myservice
with parameters provided in a file transmitted by
FTP.
<?xml version="1.0"?> <!-- EL4Ant Project definition --> <ant:project name="example" xmlns="antlib:ch.elca.el4ant" xmlns:ant="antlib:org.apache.tools.ant"> <plugin name="compile"/> <plugin name="runtime"/> <plugin name="show"/> <module name="mytool" path="modules/tool"> <dependency jar="log4j-1.2.8.jar"/> </module> <module name="myservice" path="modules/services/food"> <eu name="interface"> <attribute name="compile.jar.excludes" value="**/implementation/**"/> </eu> <eu name="implementation"> </eu> <dependency eu="implementation" module="mytool"/> </module> <module name="myapp" path="applications/food"> <eu name="client"> <attribute name="compile.jar.includes" value="**/client/**"/> <!-- TBD: Attributes to generate Java WebStart package --> </eu> <dependency eu="client" module="myservice"> <!-- myapp.client depends on myservice.interface --> <mapping target="interface"/> <dependency> <eu name="server"> <attribute name="compile.jar.includes" value="**/server/**"/> <!-- TBD: Attributes to generate EJB specific artefacts --> </eu> <dependency eu="server" module="myservice"> <!-- myapp.server depends on myservice.implementation --> <mapping target="implementation"/> <dependency> <eu name="batch"> <attribute name="compile.jar.includes" value="**/batch/**"/> <attribute name="runtime.runnable" value="true"/> <attribute name="runtime.class" value="app.Batch"/> </eu> <dependency eu="batch" module="myservice"> <!-- myapp.batch depends on myservice.interface --> <mapping target="interface"/> <dependency> </module> </ant:project>
The hook concept enables to plug additional behavior in a plugin target. The
most interesting hook chain is runtime.[module].[eu]
which is used to tune a
Java application command line to run.
A target registration in a hook is permanent except when if
or unless
attributes are used.
<?xml version="1.0"?> <!-- EL4Ant Project definition --> <ant:project name="helloworld" xmlns="antlib:ch.elca.el4ant" xmlns:ant="antlib:org.apache.tools.ant"> <plugin name="compile"/> <plugin name="runtime"/> <plugin name="show"/> <module name="helloworldcommon" path="helloworld/common"> <dependency jar="log4j-1.2.8.jar"/> <attribute name="set" value="helloworld"/> </module> <module name="helloworldapp" path="helloworld/app"> <dependency module="helloworldcommon"/> <attribute name="runtime.runnable" value="true"/> <attribute name="runtime.class" value="helloworld.Main"/> <hook name="runtime.[module].[eu]" target="runtime.hook.jvm.verbose" if="jvmverbose"/> </module> <module name="helloworldtests" path="helloworld/tests"> <dependency module="helloworldcommon"/> <attribute name="junit.runnable" value="true"/> <attribute name="set" value="tests"/> <hook name="runtime.[module].[eu]" target="runtime.hook.jvm.verbose" if="jvmverbose"/> </module> </ant:project>
In the previous example, the Java command executed by target
start.module.eu.helloworldapp
and start.module.eu.helloworldtests
will be
turned in verbose mode (JVM option -verbose
) when the property jvmverbose
is set.
ant start.module.eu.helloworldapp -Djvmverbose=
Plugin documentation details the available hook and their usage in most use cases.
When a target of the build.xml
is invoked, EL4Ant checks that the project
configuration is up-to-date compared to the project description files. If not,
a new configuration is automatically done before the listed targets are invoked
- if possible, because they may have been removed from the project.
The autoselect feature enables you to invoke a target with its generic name from a module directory. EL4Ant automatically deduces the module the target should apply on according to the invocation directory.
Suppose a module mymodule
which directory path is myproject/mymodule
has
been declared in the project. The target ant compile.module
invoked from a
sub-directory of myproject/mymodule/
automatically defines the module
property to mymodule
and compile.module
compiles only mymodule
.
If the automatically selected module has only one execution unit, it is selected too, so generic target relative to an execution unit can be used.
Note: this feature only make sense if Ant is used with -find build.xml
. For
comfort, it is advised to set the option in the ant script directly.
By default, generated shortcuts do not have descriptions to prevent them to be
shown by ant -projecthelp
. If you want some targets to be visible, you can
make them visible with the property project.visible.shortcuts
in
etc/bootstrap.properties
:
## Visible shortcuts regexp list (use java.util.regex.Pattern regexp) project.visible.shortcuts=start.*\.module\.eu.*,compile\.rec\.module\.helloworld.*
The previous line adds a description to matching shortcuts, so they are shown
by the command ant -projecthelp
.
For each plugin, the documentation explains:
compile.module.mymodule
is generated to invoke
compile.module
if the module mymodule
is compilable.
Use ant -v -projecthelp
to list all generated targets. See
generation filter configuration for shortcuts.
This plugin compiles Java sources from each module's java/
directory to the
corresponding classes/
directory.
<plugin name="compile"/>
Most important targets are compile
and jars
.
A module without specific attributes is expected to have a java/
directory
(default of compile.sources.directory
). Java sources are compiled in a
dedicated classes/
directory (by default in dist/classes
).
A module without sources to compile, but only with resources to include in jar
files and in ClassPath, must set the compile.nosources
module attribute.
A module without sources nor resources to include in ClassPath must set the
compile.noclasses
module attribute. It implies automatically
compile.nosources
. Such a module may exist to provide files for other modules
(for instance webapp/
resources to be included in a WAR).
A manifest file per execution unit is generated in the classes directory as
META-INF/Manifest-eu.mf
. This manifest provides the Class-Path
attribute
with third-party jar file names and project jar dependencies. As a result,
META-INF/Manifest-*.mf
files are excluded when packaging execution unit jar
files.
compile
compiles all modules.
compile.module
compiles a specific module and copy resources to
classes/
. Shortcuts are generated.
compile.rec.module
compiles a specific module and recursively its
dependencies. Shortcuts are generated.
jars
compiles and builds all module jar files.
jars.rec.module
compiles and builds a specific module's jar files and
recursively its dependencies. Shortcuts are generated.
clean
cleans all modules.
clean.module
cleans a module. Shortcuts are generated.
clean.rec.module
cleans a module and its dependencies.
Shortcuts are generated.
compile.clean.hook
is the default target registered in clean.[module]
hook chain. It is responsible to delete classes
directory and generated
jars files.
jars.manifest.module.eu
generates the META-INF/Manifest-eu.mf
file per
execution unit with its Class-Path
attribute. It is registered in the
pre.jar.[module].[eu]
hook chain.
The compile
and jars
targets checks if the source directory java/
is
present in the module directory. If not, nothing is done.
compile.resources.includes
is the global value for the includes
argument for the Ant fileset
used in copy
task from java/
to
classes/
directories. Default is **/*
.
compile.resources.excludes
is the global value for the excludes
argument for the Ant fileset
used in copy
task from java/
to
classes/
directories. Default is
**/.cvsignore,**/*.java,**/doc-file/*
.
compile.jar.includes
is the global value for the includes
argument for
the Ant fileset
used in jar
task on the classes/
directory. Default
is **/*
.
compile.jar.excludes
is the global value for the excludes
argument for
the Ant fileset
used in jar
task on the classes/
directory. Default
is empty.
compile.sources.directory
is the directory name where the module Java
source files are located. This property applies to all modules. Default is
java
.
compile.classes.directory
is the directory path where Class files must
be generated. Argument 0 is the declared module path and argument 1 is the
module name. Default is dist/classes/{1}
. You may want generated classes
in {0}/classes
for instance.
compile.resources.includes
is the module value for the includes
argument for the Ant fileset
used in copy
task from java/
to
classes/
directories. Default is the same global attribute's value.
compile.resources.excludes
is the module value for the excludes
argument for the Ant fileset
used in copy
task from java/
to
classes/
directories. Default is the same global attribute's value.
compile.nosources
is a flag to mark that the java/
directory does not
exists. Compiled jar files (execution units) are used if they
exist. Default value is false
.
compile.noclasses
is a flag to mark that classes/
and jar files must
not be generated. Default value is false
.
compile.classes
is the specific location of the module classes/
directory. If not set, it is generated from plugin
compile.classes.directory
pattern.
compile.jar.includes
is the execution unit value for the includes
argument for the Ant fileset
used in jar
task on the classes/
directory. Default is the same global attribute's value.
compile.jar.excludes
is the execution unit value for the excludes
argument for the Ant fileset
used in jar
task on the classes/
directory. Default is the same global attribute's value.
compile.jar
is an optional attribute to set the jar file name. Its
default value is modulename-eu.jar
. If the name is a single file name
without directories, then it is relative to dist/lib
. If the file path
is relative, it is relative to the project directory.
pre.compile.[module]
and post.compile.[module]
called unconditionnaly
in compile.module
. If the module is compilable (when compile.nosources
flag is not set), pre.javac.[module]
and post.javac.[module]
are
called around the javac task invocation. The following properties are set
to be used in targets registered on that hook chains:
module
is the current module name
module.src
is the module java/
directory
module.classes
is the module classes/
directory
module.classpath
is the module compilation ClassPath
module.src.exists
is set if the java/
directory is available
module.resources.includes
is the module includes
argument for the
Ant fileset
used in copy
task from java/
to classes/
directories
module.resources.excludes
is the module excludes
argument for the
Ant fileset
used in copy
task from java/
to classes/
directories
pre.jar.[module].[eu]
and post.jar.[module].[eu]
called in
jars.rec.module
. The following properties are set:
module
is the current module name
eu
is the current execution unit name
jarfile
is the target jar file to create or update
module.src
is the module java/
directory
module.classes
is the module classes/
directory
module.src.exists
is set if the java/
directory is available
module.classes.includes
is the module includes
argument for the Ant
fileset
used in jar
task on the classes/
directory
module.classes.excludes
is the module excludes
argument for the Ant
fileset
used in jar
task on the classes/
directory
clean.[module]
is used to register cleaning task per module according to
generated files. Default chain contains compile.clean.hook
.
Hooks are called even if the source directory java/
is not present.
This plugin provides execution targets to run a java command corresponding to a module execution unit.
<plugin name="runtime"/>
compile
start.module.eu
executes the java command for a module execution unit
with generated jars in ClassPath. Shortcuts are generated if the module
or execution unit runtime.runnable
is set.
startdev.module.eu
executes the java command for a module execution unit
with classes/
directories in ClassPath. Shortcuts are generated if
the module or execution unit runtime.runnable
is set.
runtime.default.command.creator
must be a valid
java command creator. Default value is
runtime.command.creator.basic
.
runtime.runnable
declares that all execution units are runnable if the
attribute value is true
. By default a module is not runnable.
If no execution unit is declared,
runtime.directory
is the execution directory. Default is dist/runtime
.
runtime.command.creator
can define the module specific java command
creator. See command creators section.
runtime.command.creator.basic
, runtime.class
must be
set to a class name with public static void main(String[])
method.
runtime.runnable
declares that the concerned execution unit is runnable
if the attribute value is true
(or not defined). By default an execution
unit is not runnable.
runtime.directory
is the execution directory. Default is dist/runtime
.
runtime.command.creator
can define the execution unit specific java
command creator. See
command creators section.
runtime.command.creator.basic
, runtime.class
must be set to
a class name with public static void main(String[])
method.
Command creators are targets used to create the original java command before
hook targets registered in runtime.[module].[eu]
are called to modify it.
The creator definition with runtime.command.creator
has a specific
behavior. If an execution unit does not specify a creator, the first creator
defined in its dependencies is used. If no creator definition is found, then
the plugin default setting is used. Only one creator per execution unit can be
defined.
runtime.command.creator.basic
creates a basic java command, using the
execution unit attribute runtime.class
(or the module attribute if no
eu)
A plugin may provide alternate runtime command creators but its usage is hidden.
When running a execution unit with the runtime.command.creator.basic
(default
behavior), the runtime.class
is used as main class.
You can pass arguments to the main class different ways:
runtime.arguments
attribute set in the module execution unit
-Druntime.arguments="arg1 arg2 arg3"
set in the Ant command line
-Druntime.append.arguments="arg4 arg5"
can be used to
append arguments to the current list (from runtime.arguments
property)
runtime.[module].[eu]
hook is designed to enable the modification of the
java command created by the module runtime.command.creator
. The name of
the current java command is command.${module}.${eu}
. Those hook targets
are called recursively in module dependencies by the start targets.
pre.start.[module].[eu]
targets are called before java command creation.
post.start.[module].[eu]
targets are called after java command execution
ends.
Registered targets in these three hooks can work with the following properties:
module
is the caller module name
eu
is the caller execution unit name
module.eu.classpath
is the module execution unit class with jars or
module classes according to the caller target (start
or startdev
)
module.runtime.creator
is the module creator target.
module.eu.dependencies
is the module.eu dependency list for the current
execution unit.
When the runtime.command.creator.basic
is used, runtime.[module].[eu]
and
post.start.[module].[eu]
targets can work with the following property:
module.eu.class
is the current execution unit runtime.class
main class
to run.
This plugin shows the project model information on the console with Ant logging
INFO level at the end of the project configuration (execution of
ant -f bootstrap.xml
or ant configure
when already configured) with the
[Model]
task prefix.
Currently displayed information: Modules, execution units, dependencies to jars and to module with eu mappings, sets with modules registered in them.
<plugin name="show"/>
This plugin downloads lacking jar files from a list of repositories according to its file name.
<plugin name="onlinelib"> <attribute name="onlinelib.repository" value="http://el4.elca-services.ch/el4ant/jars/"/> <attribute name="onlinelib.repository" value="http://el4.elca-services.ch/el4ant/jar-sources/"/> </plugin>
You can easily create your own repositories of files using all supported
protocol by wget
: http://
, ftp://
or even file://
if local. Expected
files must be available in a the refered directory.
When the plugin is declared, download of lacking files is effective, and if a file is optional, the trial is done at each configuration, which slows the configuration step.
Here is a way to control when the download can be done. Use the following declaration in your project description:
<antcontrib:if xmlns:antcontrib="antlib:net.sf.antcontrib"> <ant:isset property="online"/> <antcontrib:then> <plugin name="onlinelib" file="buildsystem/core/onlinelib.xml"> <attribute name="onlinelib.repository" value="http://el4.elca-services.ch/el4ant/jars/"/> <attribute name="onlinelib.repository" value="http://el4.elca-services.ch/el4ant/jar-sources/"/> </plugin> </antcontrib:then> <antcontrib:else> <ant:echo level="info">You can enable online plugin with -Donline=t at configuration to download files.</ant:echo> </antcontrib:else> </antcontrib:if>
So the plugin onlinelib
is only declared when the system property online
is
set on the command line.
ant -f bootstrap.xml configure -Donline=t
onlinelib.repository
declares a repository URL. Mind the ending /
, for
instance http://el4ant.sourceforge.net/repository/jars/
. No default
value. The attribute may be used many times to declare more than one
repository.