Developing TeamCity plugins with groovy and spring. Explained, step-by-step example

By neokrates, written on April 22, 2010

article

  • Join date: 11-30-99
  • Posts: 224
View Counter:
  • 447 views
Rate it
Ad
Poll
  • Which features are most important for perfect CI tool?

    View Results

    Loading ... Loading ...
Feeds:
  • bodytext bodytext bodytext

Recently, I refactored one TeamCity plugin, called groovyPlug. Here is what I learned about developing with Groovy for TeamCity. This article aims to be a general guideline and step-by-step. There might be also mistakes, as I learned by doing and not from jetbrains guys. I would be happy to get your comments on this article.

Preconditions:

✔ TeamCity 4.X

✔ Java 1.6

✔ Groovy

✔ Eclipse

✔ Linux (Windows can be used as well, but you have to change some steps accordingly)

1

Location of TeamCity plugins

TeamCity stores its plugins in home directory, which is located per default ~/.BuildServer.

Here is the listing of home directory:

diuw@diuw-desktop: ls -All ~/.BuildServer 
config plugins system

1.1

TeamCity plugins directory, put your plugin here

Plugins is the directory where packaged plugins must be copied. TeamCity doesn’t deploy them automatically, each time you place new version there.
Instead, you will need to restart your web server, usually tomcat, for new version to be activated.
So, you will do something like:

cp myplugin.zip ~/.BuildServer/. 
$MY_TEAMCITY_TOMCAT_HOME/bin/wrapper restart

After that, there are two new items created under ~/.BuildServer:

~/.BuildServer/plugins/.unpacked/myplugin – the decompressed version of the plugin

and

~/.BuildServer/plugins/config/_myplugin_ – the LIVE version of your plugin.

1.2

TeamCity config directory, where you edit Groovy plugin directly

In this directory, the LIVE version of the plugin will be located.
That means that changes made to groovy files located under _myplugin_ will automatically take effect.

💡 Remark But for changes to automatically take effect one additional step is required. We need to cofiguare spring context which is located in META-INF/build-server-plugin-myplugin.xml. Later in the article is described how to do that…
 

Warning This feature should be used with caution. It allows rapid development process but should be avoided in production environment.

2

TeamCity Groovy plugin in detail

2.1

Plugin archive

I have a groovyPlug.zip archive, which is full TeamCity plugin.
It contains setver/groovyPlug.jar. This file has following structure:

.
|-- META-INF
| |-- MANIFEST.MF
| `-- build-server-plugin-groovyPlug.xml
`-- jetbrains
    `-- buildserver
        `-- groovyPlug
            |-- BuildResourcesLock.groovy
            |-- CustomSubstitutionProperties.class
            |-- DataUtil.class
            |-- GroovyBuildCleaner.groovy
            |-- GroovyBuildServerListener.groovy
            |-- GroovyExtensionsInitializer.class
            |-- GroovyPropertyProvider.groovy
            |-- GroovyReferencePropertiesProvider.groovy
            |-- GroovyScriptsPreparer.class
            |-- Lock.class
            |-- LockManager.class
            |-- LockType.class
            |-- LocksUtil.class
            |-- TakenLockInfo.class
            `-- Util.class

This is actually a spring enabled webapp! You will find the context in META-INF/build-server-plugin-groovyPlug.xml

Under jetbrains/buildserver/groovyPlug there are some Java classes and groovy sources as well. How TeamCity should apply them is defined in the spring context.

2.2

Coding TeamCity plugin

API for TeamCity plugins can be found here http://javadoc.jetbrains.net/teamcity/openapi/current/. Don’t forget that specifics depend on your TeamCity version.

And here is info particularly about this plugin http://confluence.jetbrains.net/display/TW/Groovy+plug

2.3

Using TeamCity plugin API

Here is an example of one groovy class which uses plugin API. It implements http://javadoc.jetbrains.net/teamcity/openapi/current/jetbrains/buildServer/serverSide/ParametersPreprocessor.html

package jetbrains.buildserver.groovyPlug

import com.intellij.openapi.diagnostic.Logger
import jetbrains.buildServer.serverSide.ParametersPreprocessor
import jetbrains.buildServer.serverSide.SBuild
import jetbrains.buildServer.serverSide.SRunningBuild

public class GroovyPropertyProvider implements ParametersPreprocessor {
  
  private static final Logger LOG = Logger.getInstance(GroovyPropertyProvider.class.getName());
      DataUtil dataProvider;
     GroovyPropertyProvider() {
    LOG.warn("GroovyPropertyProvider initialized." ;
  }
  
  public void fixRunBuildParameters(SRunningBuild build, Map<String, String> runParameters, Map<String, String> buildParams) {
    LOG.debug("GroovyPropertyProvider asked for properties for build (buildId=" + build.buildId + " " ;

    // .. Your code
  }
}

2.4

Registering groove class with TeamCity

Class must be registered in the context. In the META-INF/build-server-plugin-groovyPlug.xml we find:

  <bean class="jetbrains.buildserver.groovyPlug.GroovyExtensionsInitializer" init-method="init">
    <property name="propertyProvider" ref="groovyPropertyProvider"/>

2.5

Enabling changes to have immediate effect

The configuration is done in META-INF/build-server-plugin-groovyPlug.xml.

Step 1. Make TeamCity to copy class to the groovyPlug directory:

<bean id="scriptPreparer" 
      class="jetbrains.buildserver.groovyPlug.GroovyScriptsPreparer" 
      init-method="init">
	  
  <property name="sourceResourceDir" value="/jetbrains/buildserver/groovyPlug"/>
  <property name="targetDirUnderConfig" value="_ _groovyPlug_ _"/>
  <property name="fileNames" value="GroovyPropertyProvider.groovy"/>
  
</bean>

Step 2. Instruct TeamCity to create the bean and watch for updates of its source:

<lang:groovy id="groovyPropertyProvider" 
             refresh-check-delay="5000"
             script-source="file://${groovyplug.dir}/GroovyPropertyProvider.groovy">
			 
  <lang:property name="dataProvider" ref="dataUtil"/>
  <lang:property name="revisionExtractionHelper" ref="revisionExtractionHelperRef"/>
  
</lang:groovy>

💡 Remark:
jetbrains.buildserver.groovyPlug.GroovyExtensionsInitializer should refer to the groovyPropertyProvider beanId, otherwise it won’t work.

3

Extending TeamCity plugin

I had to make one new class, to fix our local revision generation issues. I have put it in RevisionExtractionLib.groovy

Steps to add RevisionExtractionLib class:

Step 1. Write the class logic. Whatever is in RevisionExtractionLib.groovy.

Step 2. Add RevisionExtractionLibI.java interface.
That is the way spring framework. It needs a precompiled java interface

In my case the code is:

package jetbrains.buildserver.groovyPlug;

import java.util.Map;
import jetbrains.buildServer.serverSide.SBuild;

public interface RevisionExtractionLibI {
  void addLastModificationsRevisions(Map<String, String> buildParametersToAdd, SBuild build, boolean setShellEnvironment); 
}

And I compiled it from command line with javac (needs jetbrains jar).

Step 3. Put java interface and groovy class in the right package.

They will be located in the same dirictory as original classes:

`-- jetbrains
    `-- buildserver
        `-- groovyPlug
     ...
            |-- RevisionExtractionLib.groovy
            |-- RevisionExtractionLibI.class
     ...

Step 4. Configure spring context.
In META-INF/build-server-plugin-groovyPlug.xml add:

1.

<bean id="scriptPreparer" 
      class="jetbrains.buildserver.groovyPlug.GroovyScriptsPreparer" 
      init-method="init">
	  
    <property name="sourceResourceDir" value="/jetbrains/buildserver/groovyPlug"/>
    <property name="targetDirUnderConfig" value="_ _groovyPlug_ _"/>
    <property name="fileNames" value="RevisionExtractionLib.groovy,GroovyPropertyProvider.groovy"/>
	
</bean>

2.

  <lang:groovy id="revisionExtractionLibRef" 
               refresh-check-delay="5000"
               script-source="file://${groovyplug.dir}/RevisionExtractionLib.groovy">
  </lang:groovy>

3.

If the class will be used in groovyPropertyProvider, then add:

  <lang:groovy id="groovyPropertyProvider" 
               refresh-check-delay="5000"
               script-source="file://${groovyplug.dir}/GroovyPropertyProvider.groovy">
			   
    <lang:property name="revisionExtractionLib" ref="revisionExtractionLibRef"/>
	
  </lang:groovy>

Step 5. Add groovy code to use your new class.
 

IMPORTANT. We refer our class by Java interface, not the groovy class itself!!!

In spring context we configured groovyPropertyProvider to expect revisionExtractionLib of type RevisionExtractionLib. In groovyPropertyProvider.groovy we add:

public class GroovyPropertyProvider implements ParametersPreprocessor {
  // ... Something
  RevisionExtractionLibI revisionExtractionLib;
  // ... Something
  public void fixRunBuildParameters(SRunningBuild build, Map<String, String> runParameters, Map<String, String> buildParams) {
    // ... Make use of revisionExtractionLib:
    revisionExtractionLib.addLastModificationsRevisions(buildParamsToAdd, build, true); 

4

Compiling with eclipse

Of course, to make this work, you need all jars in your classpath. I referred local jars, thats not perfect but it works.
Here is my .classpath just that you know what you need to compile:

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path=""/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
  <classpathentry exported="true" kind="con" path="GROOVY_SUPPORT"/>
  <classpathentry kind="lib" path="/home/diuw/Desktop/antlr-2.7.7.jar"/>
  <classpathentry kind="lib" path="/home/diuw/Desktop/asm-2.2.3.jar"/>
  <classpathentry kind="lib" path="/home/diuw/Desktop/groovy-1.6.4.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity/webapps/ROOT/WEB-INF/lib/common-api.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4.5.5/webapps/ROOT/WEB-INF/lib/server-api.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4.5.5/webapps/ROOT/WEB-INF/lib/runtime-util.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4.5.5/webapps/ROOT/WEB-INF/lib/util.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4.5.5/webapps/ROOT/WEB-INF/lib/annotations.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4BuildAgents/agent1/lib/agent-api.jar"/>
  <classpathentry kind="lib" path="/opt/dev/src/java/eclipse/ws1/groovyPlug/lib/groovyPlug.jar"/>
  <classpathentry kind="lib" path="/opt/dev/server/TeamCity4.5.5/buildAgent/lib/openapi.jar"/>
  <classpathentry kind="output" path="target"/>
</classpath>

5

Package, restart TeamCity, debug if needed

You will use jar and zip to package (something like this):

jar cf server/groovyPlug.jar jetbrains META-INF
zip -r ~/.BuildServer/plugins/groovyPlug.zip server

 
to create your very own groovyPlug.zip.

After restarting your TeamCity tomcat, watch the catalina.log for problems:

tail -F $MY_TEAMCITY_TOMCAT_HOME/log/catalina.log

Debug if you need to. Good luck! :-D

Be Sociable, Share!
 


TAGS

SOCIAL
Be Sociable, Share!


Leave a Reply