Thursday, March 08, 2007

Add Groovy to your Eclipse Plugin Projects

I have begun some basic work on the Grails Eclipse plugin that I outlined in a proposal in a previous post. One of the features I want to highlight will be the fact that I want the plugin to be almost exclusively written in Groovy instead of Java.

As I start work on the plugin, I want to highlight a feature that I will hopefully add to the Groovy Eclipse Plugin in the real near future, Groovy PDE Build support. If you install the Groovy Eclipse Plugin ( I think the version currently in Head/Trunk is better than the one on the update site ), you will find that your development for the most part goes smoothly, except for when you start using Groovy to write your Eclipse Plugins. The issue is that the PDE Builder doesn't know, and probably doesn't want to know, about the Groovy code and its need to be compiled with the Groovy compiler. So how do you deal with this?

One way is to parallel the work of the Eclipse PDE Build team and write your own scripts that can interpret the build.properties file to export your plugin yourself. This can make you proud and think you are a real programmer, except that why in the heck do you want to repeat something that has already been done?

Another way is to generate the build.xml file, click the custom build option on the plugin and maintain the ant build file manually. Do I need to go into the obvious problems and pain with this option? If you like pain, reading ant XML scripts, pain, deciphering the incredibly ( but probably necessary ) complex PDE build process, pain, keeping your Ant XML file synchronized to changes in your project manually, and yet more pain, go ahead. I wont stop you, for the rest of us I am going to cover the next, and I believe, better option.

One side note, I am very familiar with the pain that I described above, I wrote a series of Groovy scripts to build our products, as per the first option. Pretty cool and better than the second option, but it really was a source of pain and, as I will now show you, unnecessary work.

The final option I give you, with an example, is what I think to be the best option. In fact, I wish I knew of it before on previous Eclipse based projects. As of Eclipse 3.2, you have the ability to specify a custom callbacks XML file.

To add Groovy PDE Build support to your plugin project, first you must install the Groovy Eclipse plugin and enable the groovy nature for your project. Deployment of the groovy libraries into your plugin for installation is another topic for another time.

Afterwards goto your Eclipse installation directory and open up the plugins directory. Goto the org.eclipse.pde.build plugin installation directory and look in the templates/plugins subdirectory. You will see there a template called customBuildCallbacks.xml, import this into your project.

Now open up your build.properties file and add a property called customBuildCallbacks like the following example:

customBuildCallbacks = customBuildCallbacks.xml

This means that the build will now reference a callback Ant Build script in my project called customBuildCallbacks.xml in the root of my project. You could prepend a project relative path to the file name to place the file somewhere else if you choose.

The next step I like to do is to right click the manifest.mf(plugin.xml) file and ask the PDE to generate a build.xml file. Now I am not going to keep the build.xml file around very long nor its javaCompiler args file. I am going to use it to extract the targets that will be called in the callback ant script and to find out the name of the classpath reference that will be used.

Below is an example file from the plugin I am currently working on.

<subant antfile="${customBuildCallbacks}" target="pre.@dot" failonerror="false" buildpath=".">
<property name="source.folder1" value="src/"/>
<property name="target.folder" value="${temp.folder}/@dot.bin"/>
<reference refid="@dot.classpath"/>
</subant>
<!-- compile the source code -->
<javac destdir="${temp.folder}/@dot.bin" failonerror="${javacFailOnError}" verbose="${javacVerbose}" debug="${javacDebugInfo}" includeAntRuntime="no" bootclasspath="${bundleBootClasspath}" source="${bundleJavacSource}" target="${bundleJavacTarget}" >
<compilerarg line="${compilerArg}" compiler="${build.compiler}"/>
<classpath refid="@dot.classpath" />
<src path="src/" />
<compilerarg value="@${basedir}/javaCompiler...args" compiler="org.eclipse.jdt.core.JDTCompilerAdapter"/>
<compilerarg line="-log '${temp.folder}/@dot.bin${logExtension}'" compiler="org.eclipse.jdt.core.JDTCompilerAdapter"/>
</javac>
<!-- Copy necessary resources -->
<copy todir="${temp.folder}/@dot.bin" failonerror="true" overwrite="false">
<fileset dir="src/" excludes="**/*.java, **/package.htm*" />
</copy>
<subant antfile="${customBuildCallbacks}" target="post.compile.@dot" failonerror="false" buildpath=".">
<property name="source.folder1" value="src/"/>
<property name="target.folder" value="${temp.folder}/@dot.bin"/>
<reference refid="@dot.classpath"/>
</subant>


There are two subant tasks that are of interest one before the javac task and one after. You can overload one or both in your callbacks ant script to allow the groovy builder to run before, after or before and after the java compiler. The important thing here is that the target name and classpath reference in your callbacks script must match.



Here below is an example from my current plugin project.



<target name="pre.@dot">
</target>

<!-- ===================================================================== -->
<!-- Steps to do during the compilation target <name>, after the compile -->
<!-- but before jaring. Substitute "name" with the name of the compilation-->
<!-- target, eg @dot -->
<!-- Available parameters : -->
<!-- source.foldern : n = 1 ... N, the source folders -->
<!-- target.folder : where the results of the compilation go -->
<!-- <name>.classpath : name = name of the compilation target. A -->
<!-- reference to the classpath structure. -->
<!-- ===================================================================== -->
<target name="post.compile.@dot">
<path id="org.codehaus.groovy.eclipse.libs">
<fileset dir="../org.codehaus.grails.eclipse/grails-home/lib">
<include name="**/*.jar"/>
</fileset>
</path>
<taskdef name="groovy"
classname="org.codehaus.groovy.ant.Groovy"
classpathref="org.codehaus.groovy.eclipse.libs"/>
<taskdef name="groovyc"
classname="org.codehaus.groovy.ant.Groovyc"
classpathref="org.codehaus.groovy.eclipse.libs"/>
<delete>
<fileset dir="${target.folder}" includes="**/*.groovy"/>
</delete>
<groovy classpathref="@dot.classpath">
def classpathString = task.classpath.list().toList().findAll{ new File( "$it" ).exists() }.join( File.pathSeparator )
ant.groovyc( srcdir: properties.'source.folder1',
destdir: properties.'target.folder',
stacktrace:'true',
classpath: classpathString )
</groovy>
</target>



The important things to notice are that the targets are called pre.@dot and post.compile.@dot which were derived from the generated ant build script and could be derived from your build.properties file. The next thing to notice is the classpath called 'org.codehaus.groovy.eclipse.libs' that points to a directory and slurps in all available jar files. This is done to locate the groovy library and therefore the groovy and groovyc ant tasks. You must set the path, either relative to the workspace or hardcoded from your file system. The last thing to notice is the use of the sourceN.folder properties, if you have more than one source folder that is being compiled into your plugin, then you must make allowance for it. If you are not sure, go back to the generated build.xml, it will tell you since it will set those properties before making the subant call to your customBuildCallbacks.xml script.


Conclusion


What is the point of all this? Well simply put, once you have made these modifications, you can write groovy code in your plugin and have the Eclipse PDE Builder/Exporter compile your groovy code along with your bundle with no muss or fuss. This functionality works if you are exporting a plugin, or as part of a feature or as part of an update site. Cool eh?



One last point, since most of this is rather rote cut and paste, I am looking to enhance the Groovy Eclipse Plugin to handle this for you. Hopefully it should be part of the next official release on the update site.

4 comments:

Erik Vonderheid said...

Nice post. I think I will try this in the near future.

Pauli said...

Thank you very much for these tips. With your help I was able to get exportation of RCP plugin products written in Scala to work. See the link.

Mickael Istria said...

Hi,

I just stumbled on your blog post, and it answers one of the question I answered myself a few hours ago!
However I am wondering whether you have made progress with this issue, and had the opportunity to contribute this to Groovy-Eclipse or another extension, or found another way to compile Groovy in PDE build, an easier one ? ;)

James E. Ervin, IV said...

Thanks for the compliment Mickael. I am no longer active working on the plugin, but Andrew Eisenberg and his collegue at SpringSource have been making gangbuster progress on the plugin recently. I am not sure of their progress on Groovy PDE, but being Eclipse guys I wouldn't think it would be hard to nudge them forward in this area.