I thought I’d share the current workflow I have been using at work and why I think it’s been a pretty decent scheme so far. For this post I’m only covering jar deployment but I could see it easily working the same for gems or node.js npm packages.
Just for a bit of background, here’s some of the tooling we’re using:
- Gradle for build scripts
- Artifactory serving up artifacts
- Github for Revision Control
- Jenkins for Continuous Deployment
- Git Flow for high level git operations based on Vincent Driessen’s Git Branching Model
For this example, let’s create a jar that is really just a simple wrapper to Joiner.join in Google Guava. Yes this is pretty lame and has no use in the real world but we don’t want to detract from the point of this post, setting up a pragmatic build system.
So to start, we create a new project and create the various files needed for our project. Create a directory for the module (for this project we’re just calling it module-example). cd into and type “git flow init”. You’ll be prompted for branch names but I always just accept the defaults. If you type git branch you’ll see that you have two branches, develop and master, with develop checked out.
Now it’s time to create our initial project structure. I create build.gradle, settings.gradle, and gradle.properties with the following contents:
All this does is specify what gradle plugins we’ll be using, dependencies, and where to resolve our dependencies from. The javadocJar and sourcesJar tasks are just to produce javadoc and sources jars as part of the build task.
I’ll explain the maven plugin in better detail later.
This just specifies the groupId and the initial integration version. Nuff said.
Unfortunately gradle uses the folder name as the module name, which will be workspace be default in jenkins. You can change this in jenkins, but I prefer setting the module name explicitly to ensure the right name is always used regardless of the dir name.
This is my .gitignore to ignore eclipse and gradle generated artifacts. I’ve learned that lots of problems get caused by committing IDE files and you also don’t want to commit your compiled jars/classes/test-reports/etc. from gradle.
With everything done, I do a “git add -A” and “git commit -am ‘initial commit’” to start tracking files. Finally, I do a “git push –all” to push all branches to github. You can see my initial project structure here.
Creating the First Feature
To start with our first feature we use git flow to create a new feature branch.
git flow feature start the-combiner
This creates and checks out a new feature branch from develop. Now I just create a simple class named Combiner that looks like the following:
With the feature finished we add the untracked file, commit and type “git flow feature finish” to merge our changes back into develop. We also push the development branch back to github.
Now Would Be a Good Time to Setup Jenkins
I’m going to assume Jenkins is already setup. You’ll need these plugins installed:
- Jenkins Artifactory Plugin
- Jenkins Gradle plugin
- Jenkins GIT plugin
I then setup a Jenkins job called “module-example snapshot”. This checks out any pushes to the develop branch, runs the gradle build task on it (which runs tests and produces artifacts on successful test passes) and then pushes a snapshot release to our in house artifactory server. This means any push to develop will trigger a build that releases a snapshot jar of that module that others could use for their development. For this I use the following artifactory plugin settings:
Next, I create a job named “module-example release” which does the same as above except it watches and checks out the master branch and publishes a release artifact rather than a snapshot.
Finally, I have a job named “module-example post-release” that checks develop out and bumps the version number up to the next integration release (1.0.1-SNAPSHOT) and pushes it back to the develop branch. I have not found an easy way to do this in jenkins so I simply have a task like the following in my scripts to bump the snapshot version:
If we push to develop now, we should see a new jar with the version 1.0.0-SNAPSHOT show up in our artifactory snapshots repository.
Let’s do a Release
Doing a release with git flow is pretty easy. We’re ready to deploy version 1.0.0 of our new module so we type
git flow release start 1.0.0
We now edit our gradle.properties file and take the -SNAPSHOT off the version number and do any other pre-release activities that might be needed.
Once we’re ready we run
git flow release finish 1.0.0 && git push --all
And sit back and watch the magic happen… 1.0.0 of our module is released to our artifactory server for others to use and when we fetch the latest upstream the next version is 1.0.1-SNAPSHOT in the develop branch.
Although the initial setup requires a little investment up front, the flow of actually working and releasing pays off in huge dividends IMHO. Our build script knows next to nothing about what SCM we use and where artifacts get published to… it’s only concerned about what it is ment for: building. Releasing is just a matter of running two commands and pushing master. Another nicety is that master ALWAYS reflects something that could be released… there’s no confusion on whether or not what is in master could be released.
In the near future I’ll follow up more with our evolving process of deploying JEE web applications using this process.