Teamcity & CVS & Maven: release on server
If you use Maven 2 and Teamcity integration server, you might want to perform releases on server. Although it's not so complicated, some things must fit one into another and you might spend a lot of time till you find out how to configure pom.xml and build configuration. For those of you, who need to setup it, this article could come quite handy.
Let's assume that you have maven release process setuped up for your localhost. If you do not, you can look at following articles, to get it running (this is the most difficult part, that I want to avoid analyzing in this post):
- Maven 2 release plugin and access to CVS over SSH with private key (only in Czech)
- Simple HOWTO for Maven release process
- Maven release plugin documentation
So let's concentrate on what's needed to setup in Teamcity. Create new configuration according to these steps:
General Settings Tab
Fill basic data but focus on Artifact paths. Relative paths where built artifacts will be placed should be inserted in this field. You can find it out if you run "mvn release:prepare release:perform" on you localhost. Then, when everything goes well, newly created artifacts should end up somewhere in target/checkout directory.
This will enable you quick download of built artifacts from Overview page.
VCS Settings Tab
Some settings in this tab are important to be set right. At first you need to select VCS checkout mode to Automatically on agent (if supported by VCS roots) option. It means, that agent running this build will perform checkout (not export) so Maven will be able to create a tag from it. You can verify that by going to teamcity agent intallation directory, work subdirectory and finding checkout directory for this build (of course you must to run this build config at least once) - you'll see VCS subdirectiores among project files and folders (in case of CVS you'll find CVS directory there). Maven is extensively working with VCS during release process, so it is vital to have it like that.
Click on the Edit link in VCS Root or Create and attach new VCS Root.
VCS Root Tab
There are two places you should concentrate on. The first one is CVS Root where you should insert the same CVS Root as you have in your pom.xml. String should be exactly the same including protocol type (ssh, ext or so). For example if you have in pom.xml this declaration:
<scm> <connection>scm:cvs:ext:anonymous@mycvsserver:/CVSRoot/groupFolder:projectX</connection> </scm>
You should have "cvs:ext:anonymous@mycvsserver:/CVSRoot/groupFolder" in CVS Root (to tell the truth, username could differ, but process is sensible to other values difference). Field Module name could differ as well - so you can have "projectX" in pom.xml (as in example above) and fill subfolder "projectX/subProjectY" in Teamcity.
If you setup CVS Root wrong, you'll be rewarded with this exception:
java.lang.IllegalArgumentException: Unrecognized CVS Root: :ssh:anonymous@mycvsserver:/CVSRoot/groupFolder at org.netbeans.lib.cvsclient.connection.ConnectionFactory.getConnection(ConnectionFactory.java:88) at org.apache.maven.scm.provider.cvslib.cvsjava.util.CvsConnection.connect(CvsConnection.java:158) at org.apache.maven.scm.provider.cvslib.cvsjava.util.CvsConnection.processCommand(CvsConnection.java:475) at org.apache.maven.scm.provider.cvslib.cvsjava.command.checkin.CvsJavaCheckInCommand.executeCvsCommand(CvsJavaCheckInCommand.java:55) at org.apache.maven.scm.provider.cvslib.command.checkin.AbstractCvsCheckInCommand.executeCheckInCommand(AbstractCvsCheckInCommand.java:89) at org.apache.maven.scm.command.checkin.AbstractCheckInCommand.executeCommand(AbstractCheckInCommand.java:53) at org.apache.maven.scm.command.AbstractCommand.execute(AbstractCommand.java:58) at org.apache.maven.scm.provider.cvslib.AbstractCvsScmProvider.executeCommand(AbstractCvsScmProvider.java:521) at org.apache.maven.scm.provider.cvslib.AbstractCvsScmProvider.checkin(AbstractCvsScmProvider.java:585) at org.apache.maven.scm.provider.AbstractScmProvider.checkIn(AbstractScmProvider.java:365) at org.apache.maven.shared.release.phase.ScmCommitPhase.checkin(ScmCommitPhase.java:124) at org.apache.maven.shared.release.phase.ScmCommitPhase.execute(ScmCommitPhase.java:109) at org.apache.maven.shared.release.DefaultReleaseManager.prepare(DefaultReleaseManager.java:194) at org.apache.maven.shared.release.DefaultReleaseManager.prepare(DefaultReleaseManager.java:131) at org.apache.maven.shared.release.DefaultReleaseManager.prepare(DefaultReleaseManager.java:94) at org.apache.maven.plugins.release.PrepareReleaseMojo.execute(PrepareReleaseMojo.java:136) at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:451) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:558) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeStandaloneGoal(DefaultLifecycleExecutor.java:512) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:482) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:330) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:227) at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:142)
Last thing is to select Checkout HEAD revision in Checkout option. Maven makes tag and checkout by tag inside release process, so we need to select checkout of newest source files from HEAD (or alternatively from some branch if you need).
Build Runner Tab
This is the last important tab. There you should fill "clean install release:prepare release:perform" in Goals field. I found out, that sometimes in multiproject environment release:prepare fails to run because it doesn't find SNAPSHOT artifacts in repository when building dependent subprojects that has them defined in parent declaration. Specifying "clean install" in this field will prevent failure of this type.
Next you should fill --batch-mode in Additional Maven command line parameters. This means, that maven will not ask you to enter or confirm release version number, next SNAPSHOT number and tag name. It will use computed defaults without asking (TeamCity does not offer any means to interact with the build process after it has been started). The only way how to affect on these vaules is to set proper SNAPSHOT version in pom.xml an commit it before starting release build in TeamCity.
Finally you might want to enter Java memory extension for Maven build. Default memory limit is 64MB and when uploading created artifacts to remote repository it could be easily reached (it seems that maven holds entire artifact in memory while uploading).
If you rely on performRelease property, you should be aware, that this property is set by Maven only for release:perform stage - not for release:prepare. So when you have profile activated by this property, that defines "modules" inclusion in build, Maven will activate this profile only for the last goal. Pom.xml's versions are renumbered in release:prepare stage, so in this case you'd have some projects skipped from renumbering. Setting -DperformRelease property in JVM command line parameters will ensure, that profiles will be activated consistently thorough whole build process.
Build Runner Tab
When everything goes well, your release build should end with success and you should see menu with built artifacts on the overview screen of TeamCity (artifacts are of course also in your company repository).
Although it's not a rocket science, it takes some time to get all this running. I've spent six hours to tune this, so if my advices could save some of your time, purpose of this article would be fulfilled.