root/trunk/src/documentation/howto/writePlugins.html @ 392

Revision 392, 16.8 kB (checked in by tormp, 7 months ago)

fix minor typos and errata

  • Property svn:eol-style set to native
Line 
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2<!--
3   Licensed to the Apache Software Foundation (ASF) under one
4   or more contributor license agreements.  See the NOTICE file
5   distributed with this work for additional information
6   regarding copyright ownership.  The ASF licenses this file
7   to you under the Apache License, Version 2.0 (the
8   "License"); you may not use this file except in compliance
9   with the License.  You may obtain a copy of the License at
10
11     http://www.apache.org/licenses/LICENSE-2.0
12
13   Unless required by applicable law or agreed to in writing,
14   software distributed under the License is distributed on an
15   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   KIND, either express or implied.  See the License for the
17   specific language governing permissions and limitations
18   under the License.   
19-->
20<html>
21<head>
22        <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
23        <script type="text/javascript">var xookiConfig = {level: 1};</script>   
24        <script type="text/javascript" src="../xooki/xooki.js"></script>
25</head>
26<body>
27        <textarea id="xooki-source">
28<h1>How To write a module for easyant</h1>
29
30<p>A module in Easyant is a logical unit that provides additional pluggable functionality to your build set up. You may choose to use or ignore such a plugin when running the build. A module is composed, in the least, of a ant script associated with a ivy specs file.
31So let's write a Hello World plugin.</p>
32
33<h2>Generating plugin skeleton</h2>
34First we need to create a plugin structure. To ease plugin development easyant came with a skeleton for plugins.
35<code type="shell">> easyant skeleton:create -Dskeleton.org=org.apache.easyant.skeletons -Dskeleton.module=std-ant-plugin -Dskeleton.rev=0.1</code>
36It will then ask you a few questions
37<code type="shell">
38[input] Organisation name of YOUR project
39org.mycompany
40    [input] Module name of YOUR project
41myplugin
42    [input] Revision number of YOUR project [0.1]
430.1
44</code>
45That's all !
46We've a ready to use plugin structure.
47<code type="shell">
48<!--replace me by an image -->
49|-- module.ivy
50`-- src
51    |-- main
52    |   `-- resources
53    |       `-- myplugin.ant
54    `-- test
55        `-- antunit
56            |-- common
57            |   `-- test-utils.ant
58            `-- myplugin-test.xml
59
60</code>
61
62<h2>Ant script</h2>
63The skeleton has generated the plugin main script in src/main/resources/[MYPLUGIN].ant
64<code type="xml">
65<project name="org.mycompany;myplugin" 
66                xmlns:ivy="antlib:org.apache.ivy.ant" 
67        xmlns:ea="antlib:org.apache.easyant">
68       
69        <!-- Force compliance with easyant-core to 0.7 or higher -->
70        <!-- <ea:core-version requiredrevision="[0.7,+]" /> -->
71
72        <!-- Sample init target -->
73        <target name=":init" phase="validate">
74                <!-- you should remove this echo message -->
75                <echo level="debug">This is the init target of myplugin</echo>
76        </target>
77       
78        <!-- define a generic default target for this plugin -->
79        <target name="doit" depends="validate"/>
80</project>
81</code>
82By convention, projectname of the plugin should be formed like
83<code type="xml">
84[organisation]#[module]
85</code>
86Example:
87<code type="xml">
88org.mycompany#myplugin
89</code>
90
91<h3>Understanding Phases</h3>
92Phases are high-level build activities, like "package" or "documentation".  Plugins typically add low-level tasks to one or more phases. For example, a plugin might add a "build jar" task to the "package" phase, or a "generate javadoc" task to the "documentation" phase.  Less typically, a plugin can also define new phases for other plugins to use.
93In standard build types the project-lifecycle is defined by a plugin named <a href="../ref/plugins/phases-std.html">phases-std</a>.
94
95<h3>Pre conditions</h3>
96A build module should always check that a set of pre conditions is met in the validate phase (for static pre conditions) or at execution (for dynamic pre conditions).
97By convention, this target should be named ":init" and associated to the "validate" phase.
98
99Pre conditions, including for example - checking the existence of a file or a directory, could be performed inside this target. Additionally, this target is a great place to do global initializations that are needed for the rest of the build. This could include a taskdef initialization.
100Pre conditions can be performed by using <a href="../ref/anttasks/Parametertask.html">parameter task</a>.
101Example :
102<code type="xml">
103<target name=":init" phase="validate">
104        <!-- Our plugin need at least the existance of "validate" phase" -->
105        <ea:parameter phase="validate" />
106        <ea:parameter property="username" required="false" description="the username used to display en 'hello Username' by calling :hello target"/>
107</target>
108</code>
109
110<h3>Target Naming Conventions</h3>
111There is a conventional difference in the way public and private targets are named in Easyant. A <i>public target</i> is one that makes sense for the end user to be aware of, while a <i>private target</i> should be hidden from the end user.
112
113Conventionally, a public target should always have an associated 'description' attribute. Further, it's name should always begin with a ':'.
114
115Example :
116<code type="xml">
117<target name=":helloworld" depends="validate" description="display an hello world">
118        <echo>hello world !</echo>
119</target>
120
121<target name=":hello" depends="validate" depends="-check-username" description="display an hello to current user">
122        <echo mess="Hello ${username}"/>
123</target>
124</code>
125
126Whereas a private target name should begin with '-'.
127
128Example :
129<code type="xml">
130<!-- this target initialize username property if it's not already set -->
131<target name="-check-username" unless="username">
132        <echo>You can also add a "-Dusername=YOU" on the commandline to display a more personal hello message</echo>
133        <property name="username" value="${user.name}"/>
134</target>
135</code>
136
137<h3>The 'doit' Target</h3>
138Each module should have a target called <i>doit</i>. This is an important convention. This target should perform the essential purpose of the module when invoked independently.
139Example:
140<code type="xml">
141<target name="doit" depends=":helloworld"/>
142</code>
143
144<h3>What a build module should document</h3>
145<ul>
146<li>phases on which it relies</li>
147<li>parameters (properties, resource collections, paths).  For each parameter specify name, description, whether it is required, and optionally a default value.</li>
148<li>expected environment (files in a directory, a server up and running, ...)</li>
149<li>results produced</li>
150</ul>
151
152A build module should always check that the set of pre conditions is met in the validate phase (for static pre conditions) or at execution (for dynamic pre conditions).
153
154If ever what is considered static pre condition by a module is actually generated by another one, it is still possible to assign the build module validate phase to a phase triggered after the execution of the other build module (using phase mapping with the 'use' task).
155
156<h2>Publishing your plugin</h2>
157You can easily publish your plugin to an easyant repository using the standard phases <i>publish-shared (for snapshot)</i> or <i>release</i>
158<code type="shell">>  easyant publish-shared</code>
159<code type="shell">>  easyant release</code>
160
161By default plugins are published to a repository named <i>easyant-shared-modules</i> stored in $USER_HOME/.easyant/repository/easyant-shared-modules/.
162
163You can specify the repository name using one of the following property
164<ul>
165  <li>release.resolver</li>
166  <li>snapshot.resolver</li>
167</ul>
168<div id="note">Note: Repository must exist in easyant ivy instance. See <a href="../ref/ConfigureEasyantIvySettings.html">configure easyant ivy instance</a> man page for more informations.</div>
169
170<h2>Using your plugin in your project</h2>
171Considering that you published your plugin in a easyant repository, you could use it in your project.
172<code type="xml">
173<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
174        <info organisation="org.mycompany" module="myproject" 
175                        status="integration" revision="0.1">
176                <ea:build module="build-std-java" revision="0.2">
177                        <ea:plugin organisation="org.mycompany" module="myplugin" revision="0.1" as="myplugin"/>
178                </ea:build>     
179        </info>
180        <publications>
181                <artifact name="myplugin" type="ant"/>
182        </publications>
183</ivy-module>
184</code>
185And now running
186<code type="shell">> easyant -p </code>
187We should see myplugin's target.
188<code type="shell">
189Main targets:
190...
191 mygplugin:hello                   display an hello to current user
192 mygplugin:helloworld              display an hello world
193...
194</code>
195
196<h2>Getting further</h2>
197
198<h3>Adding additional files to your module</h3>
199Sometimes, we need to have a .properties files related to a given plugin.
200Sometimes it could be an additional file (an .xsl file for example).
201
202Before using it we must declare the new file in the plugin module descriptor.
203Open the module.ivy at the root level of plugin structure.
204<code type="xml">
205<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
206        <info organisation="org.mycompany" module="myplugin" 
207                        status="integration" revision="0.1">
208                <!-- here we use build-std-ant-plugin build type that provide everything we need for plugin development -->
209                <ea:build module="build-std-ant-plugin" revision="0.1"/>
210        </info>
211        <configurations>
212                <conf name="default" visibility="public" description="runtime dependencies artifact can be used with this conf"/>
213                <conf name="test" visibility="private" description="this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases."/>
214                <conf name="provided" visibility="public" description="this is much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive."/>
215        </configurations>
216        <publications>
217                <!--Defines the plugin main script -->
218                <artifact name="myplugin" type="ant"/>
219                <!--Defines a property file -->
220                <artifact name="myplugin" type="properties"/>
221                <artifact name="myfile" type="xsl"/>
222        </publications>
223</ivy-module>
224</code>
225Here we defined that our plugin is composed of 3 files :
226<ul>
227<li>myplugin.ant (if name argument is not specified the module name will be used)</li>
228<li>myplugin.properties</li>
229<li>myfile.xsl</li>
230</ul>
231
232Now we will see how we can use those files in our plugin script.
233Considering that a plugin must be generic and can be retrieved from different repository (filesystem, url, ftp, etc...) we should take care of how we reference those additional files in our script.
234To avoid any problems due to repository layout configuration, easyant gives you gives you access to properties containing the absolute path of a declared artifact. Those properties are composed with the following syntax :
235<code type="xml">
236[organisation].[module].[artifact].[type].file
237</code>
238Example:
239<code type="xml">
240org.mycompany.myplugin.myfile.xsl.file
241</code>
242
243The '.artifact' is optional when module name and artifact name are the same.
244<code type="xml">
245[organisation].[module].[type].file
246</code>
247Example:
248<code type="xml">
249org.mycompany#myplugin.properties.file
250</code>
251
252So loading a property file could be easy as :
253<code type="xml">
254<property file="${org.mycompany#myplugin.properties.file}" />
255</code>
256
257If you want to copy / use an additional file
258<code type="xml">
259<copy file="${org.mycompany.myplugin.myfile.xsl.file}" tofile="..."/>
260</code>
261
262<h3>Using third party libraries</h3>
263Most of the time when we write plugins we want to use third party ant tasks.
264
265<h4>Declaring dependencies in module.ivy</h4>
266First we need to declare the dependency in the plugin module.ivy.
267<code type="xml">
268<ivy-module version="2.0" xmlns:ea="http://www.easyant.org"> 
269        <info organisation="org.mycompany" module="myplugin" 
270                        status="integration" revision="0.1">
271                <ea:build module="build-std-ant-plugin" revision="0.1"/>
272        </info>
273        <configurations>
274                <conf name="default" visibility="public" description="runtime dependencies artifact can be used with this conf"/>
275                <conf name="test" visibility="private" description="this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases."/>
276                <conf name="provided" visibility="public" description="this is much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive."/>
277        </configurations>
278        <publications>
279                <artifact name="myplugin" type="ant"/>
280        </publications>
281
282        <dependencies>
283                <!-- your plugin dependencies goes here -->
284                <dependency org="foobar" name="amazingAntTask" rev="4.4" conf="default->default" />
285                <dependency org="foobar" name="myOtherAntTask" rev="4.4" conf="default->default" />
286        </dependencies>
287</ivy-module>
288</code>
289Here we depend on amazingAntTask and myOtherAntTask provided by foobar organisation.
290<h4>Using dependency in your plugin ant script?</h4>
291Easyant automatically creates a classpath specific for each plugin, this classpath contains all the required dependency <i>.jars</i>.
292
293The classpath is named
294<code tyep="xml">
295[organisation]#[module].classpath
296</code>
297Example:
298<code type="xml">
299org.mycompany#myplugin.classpath
300</code> 
301Since this classpath is auto-created you can use it to reference your taskdef.
302<code type="xml">
303<target name=":init" phase="validate">
304        <ea:parameter phase="validate"/>
305        ...
306        <taskdef resource="amazingAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
307        <taskdef resource="anotherAntTask.properties" classpathref="org.mycompany#myplugin.classpath" />
308</target>
309</code>
310
311<h3>Compatibilty with core revision</h3>
312A module can be dependent on features available in Easyant core. As such, it is possible for a module to be functional with particular versions of Easyant only.
313Easyant provides a way for modules to explicitly specify their dependency on core revisions.
314A module may use the ea:core-version task to specify such a dependency.
315A task may depend on:
316<ul>
317<li>static version (Example : 0.5)</li>
318<li>dynamic version (Example : latest.revision) even if we do not recommand to use it</li>
319<li>listed version (Example : (0.1,0.3,0.5) )</li>
320<li>range version (Example : [0.5,0.8] means from 0.5 to 0.8. Example2 : [0.5,+] means all version superior to 0.5)</li>
321</ul>
322<code type="xml">
323<project name="org.mycompany;myplugin" 
324                xmlns:ivy="antlib:org.apache.ivy.ant" 
325        xmlns:ea="antlib:org.apache.easyant">
326       
327        <!-- Force compliance with easyant-core to 0.7 or higher -->
328        <ea:core-version requiredrevision="[0.7,+]" />
329
330        <!-- Sample init target -->
331        <target name=":init" phase="validate">
332                <!-- you should  remove this echo message -->
333                <echo level="debug">This is the init target of myplugin</echo>
334        </target>
335
336        ...
337
338        <!-- define a generic default target for this plugin -->
339        <target name="doit" depends="validate"/>
340</project>
341</code>
342
343<h3>Writing plugin test case</h3>
344By default the skeleton has generated a antunit test file in src/test/antunit/[module]-test.ant.
345
346So in our case let's open "src/test/antunit/myplugin-test.xml"
347<code type="xml">
348<project name="org.mycompany;myplugin-test" xmlns:au="antlib:org.apache.ant.antunit">
349       
350        <!-- Mocking required phase --> 
351        <phase name="validate"/>
352               
353        <!-- Import your plugin -->     
354        <import file="../../main/resources/myplugin.ant"/>
355       
356        <!-- Defines a setUp / tearDown (before each test) that cleans the environment --> 
357        <target name="clean" description="remove stale build artifacts before / after each test">
358                <delete dir="${basedir}" includeemptydirs="true">
359                        <include name="**/target/**"/>
360                        <include name="**/lib/**"/>
361                </delete>
362        </target>
363       
364        <target name="setUp" depends="clean"/>
365        <target name="tearDown" depends="clean"/>
366       
367        <!-- init test case -->                 
368        <target name="testInit">
369                <antcall target=":init"/>
370                <au:assertLogContains level="debug" text="This is the init target of myplugin"/>
371        </target>
372       
373</project>   
374</code>
375Considering that our plugin relies on an externally defined phase (validate in our example) we must mock it in our test.
376Then we :
377<ul>
378  <li>import the plugin</li>
379  <li>define a generic tearDown, setUp method that cleans the target and lib directories.</li>
380  <li>define a test case for the init target that check that the output log contains "This is the init target of myplugin"</li>
381</ul>
382
383All targets prefixed by "test" will be executed as a test case (similar to junit 3 behavior).
384
385Now we will write a test case for our ":helloworld" target.
386<code type="xml">
387<target name="testHelloWorld">
388        <antcall target=":helloworld"/>
389        <au:assertLogContains text="hello world !"/>
390</target>
391</code>
392
393Tests can be executed by running :
394<code type="shell">> easyant test</code>
395
396You can access test-report at "target/antunit/html/index.html" or if you prefer the brut result "target/antunit/xml/TEST-src.test.antunit.myplugin-test_xml.xml".</textarea>
397<script type="text/javascript">xooki.postProcess();</script>
398</body>
399</html>
Note: See TracBrowser for help on using the browser.