Ana was looking for some guest posts on her blog, so here’s my two cents how I went about writing a plugin for a project called Jenkins, which is almost certainly the number one open source continuous integration server platform used in the world today. My blog can be seen at http://chemmo.wordpress.com
I wrote a plugin for Jenkins some time ago, and I just got around to preparing it for publishing on the official repository, so I thought I’d write about some of the steps involved with getting it up and running. I have been using it for some time and I’m pretty happy with it, works well etc, but maybe others will find it useful and have some other ideas for it so I thought I’d keep with the spirit of open source and release it into to the wild. Its actually very simple but also very powerful.
I’m not going to go into the detail of what Jenkins is or does, there are plenty of sites for that (ahoy wikipedia/jenkins wiki!); the plugin extends Jenkins by adding a new build step type, allowing the user to enter scala code into a text box, along with one or more directories containing jar files which will be added to the classpath at runtime. There are also options for debugging – you can specify a port number in order to attach to the running process, and whether or not to suspend the process until your debug session has been attached.
When the job is executed, the code entered into the text box will be compiled and if successful, run as if it was the mainline. Under the hood, this is simply calling scala with the code in the text box whacked into a generated .scala file and the filename passed as a command-line parameter. The official jenkins wiki was a good starting point, along with its long stream of comments.
Here is the file structure for the project in its completed state:
Lets go over a few of these…
pox.xml – this is the Maven definition, containing dependency information and other essential project meta data. This is fairly trivial, although Maven itself probably gave me the most trouble out of all of the components!
src/main/java – all of your java files should be placed here and subdirectories (i.e. namespaces).
src/main/resources – all resource files, should be here – these are essential in order for your ui components to appear correctly in a Jenkins job. In particular, the config.jelly file – I struggled with this for some time as the documentation is quite poor – I reverted to finding other plugins which were doing what I needed and downloading their source files to see how they were doing it. And not every plugin had the same approach! The help files here give each ui component (text boxes, drop down boxes, checkboxes, etc) a help section (expanded by clicking the “?” next to it).
Maven gave me the most trouble for this project, mainly because I was unfamiliar with it and the dependency management which you have very little control over. The main issue I had was due to the fact I was behind a corporate firewall so was frustrated when dependencies were not resolving. Now that I have successfully set up it up on my home machine and pulled in dependencies with ease I am only just starting to warm to Maven! The most important element of the pom.xml is choosing the correct jenkins plugin version. My pom.xml is currently working with 1.447 – the source code is on github if you want to take a closer look.
Onto the source code, it will probably be worthwhile having the Java class open whilst reading this. There are a few key functions which you must have in order for the plugin to work, I’ll list the function signatures here and go over them briefly:
public ScalaBuilder(String libDir, String src, String pathToScala,
boolean debug, String port, boolean suspend)
public String getLibDir()
public void setLibDir(String libDir)
public Builder newInstance(StaplerRequest req, JSONObject formData)
public boolean perform(AbstractBuild build, Launcher launcher,BuildListener listener)
First and foremost, you need your class – mine extends Builder because it is a new build type. Obviously this needs a constructor, which simply sets the member values to whatever has been passed in. The constructor is called by a second class within the file which is an extension of a BuildStepDescriptor, which has code to perform some (optional) validation and the method which actually executes the build step. I have a single validation method to check the directories provided in the library directory text box actually exist.
You’ll also need getter and setter methods for each of the fields within your jelly config (i.e. your form data), if you miss any of these out you’ll be scratching your head for a while! The newInstance method is used to convert the JSON object provided by the form data into the parameters required by your main class constructor. Pretty simple really!
The perform function is the meat of the build step – in my case, it generates the classpath from the library folders provided and a set of command line options (whether to debug, which port, etc). Finally it creates the .scala file from the appropriate text and then runs scala itself with the filename passed in. It creates a new Shell object and calls perform with the command line string, which will return the exit code which scala produces.
One other notable issue I had was with scala on Linux: I found the build would not fail even if the code from scala returned a non-zero exit code. It reported correctly if the code failed to compile, but not from completed execution. This was because of a known issue with scala , where the exit code was swallowed. The issue is described here, and the solution provided – which actually requires a modification of the scala launch shell script.
An excellent feature of Maven with Jenkins was the ability to run an instance locally to test your changes. Instead of having to deploy your packaged plugin to your actual Jenkins (and potentially restarting if it isn’t a new install of the plugin), you can simply enter the following commands:
> set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
> mvn hpi:run
If you’re doing this on Linux, replace the set with export. This will then run an instance at http://localhost:8080/ where you can try out your changes.
Well that’s about it. Let me know if you have any questions or need me to expand on something and I’ll try my best to help!