Hacking Maven

We don’t use M2Eclipse to integrate Maven and Eclipse, partly because we had some bad experiences a couple of years ago when we were still using Eclipse 3.4 and partly because some of our developers use IBMs RAD to develop, and it doesn’t (or at least wasn’t) compatible. Our process is to update our local sources from SVN and then to run Maven’s eclipse:eclipse locally with the relevant workspace setting so that our .project and .classpath files are generated, based on the dependencies and other information in the POMs. It works well enough, but the plan is indeed to move to M2Eclipse at some stage soon.

We have parent POMs for each sub-system (each of which is made of several Eclipse projects). Some of us pull multiple sub-systems into our workspaces which is useful when you refactor interfaces between sub-systems, because Eclipse can refactor the code which calls the interfaces at the same time as you refactor the interfaces, saving lots of work. Maven generates .classpath files for Eclipse which reference everything in the workspace as a source projet rather than a JAR out of the local Maven repo. That is important because if Maven created JAR references and not project references, Eclipse’s refactoring wouldn’t adjust the code calling the refactored code.

10 days ago we switched from a build process based on continuum and archiva to jenkins and nexus. All of a sudden we lost some of the source references which were replaced with JAR references. If a project was referred to in a parent POM then it was referenced as a project in Eclipse, but if it was a sub-system built using a second parent POM, then Eclipse was referring to the project as a JAR from the local Maven repo. That was bad news for our refactoring! Here is an example of what used to happen:

    subsystem-a-project-1
    subsystem-a-project-2 references subsystem-a-project-1 as a project

    subsystem-b-project-1 references subsystem-a-project-1 as a project

Here is what was happening when we used Jenkins and Nexus:

    subsystem-a-project-1
    subsystem-a-project-2 references subsystem-a-project-1 as a project

    subsystem-b-project-1 references subsystem-a-project-1 as a JAR!!!

At the same time as moving to Jenkins and Nexus, we upgraded a few libraries and our organisational POM. We also moved our parent POMs from the root directory of the sub-system into a project for the parent (in preparation for using M2Eclipse which prefers having the parent POM in the workspace).

The question was, where do we start looking? Some people suggested it was because we’d moved the parent POM, others thought it was because we’d updated a version of a library on which eclipse:eclipse might have a dependency and others were of the opinion we had to return to Continuum and Archiva.

One of our sharp eyed developers noticed that we were getting the following output on the command line during the local Maven build:

    Artifact subsystem-a-project-1 already available as a workspace project, but with different version...

That was the single find which led to us solving the problem quickly. I downloaded the source of eclipse:eclipse from Maven central, under org/apache/maven/plugins/maven-eclipse-plugin. I created a dummy Java project in Eclipse. Next, I added the following environment variable to my command line from where I run Maven:

    set MAVEN_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=28000

That tells the JRE to open a debug port and wait until a debugger is connected before running the app. Running a Java program with those args makes it output "Listening for transport dt_socket at address: 28000". In Eclipse I went to the debug run configurations and added a remote connection at localhost on port 28000. It needs a project, so I added the dummy project I’d just created. I connected and Maven continued to run now that I was connected. The next step was to add the Maven Plugin source code which I’d downloaded from Maven Central. By right clicking on the debug view in the debug perspective (click on the tree), it is possible to add/update source attachments, and I added the sources JAR that I’d downloaded. The last bit was to find a suitable breakpoint. I extracted the sources from the downloaded ZIP and searched the files for the text which was being output by Maven which was hinting at the problem ("already available as a workspace project"). EclipsePlugin was the suspect class!

I added the eclipse:eclipse JAR to my dummy project so that I could open the class in Eclipse using ctrl+shift+t. Eclipse opened the class, but hadn’t worked out that the source from the source attachment belongs to that class. There is a button in the editor to attach the source by locating the JAR downloaded from Maven Central and Eclipse then showed the source code and I was able to add a breakpoint. By this time Maven had finished, so I restarted it and this time I hit the breakpoint, and the problem became quite clear. For some reason, rather than the version being a simple SNAPSHOT version, it contained a timestamp. The eclipse:eclipse plugin was of the opinion that I didn’t have the correct code in the workspace, and so rather than create a project reference, it created a JAR reference based on the JAR in the local Maven repo.

The internet is full of information about how Maven3 now uses unqiue timestamps for each build artefact deployed to the repo. There were some references saying you could disable it for Maven2 (which we still use), but when you move to Maven3 you can’t disable it. I thought about submitting a bug report to Codehaus (who supplies the eclipe:eclipse plugin), but it occurred to me that we were still referencing version 2.8 in our organisational POM and I’d spotted a 2.9 version when I was at Maven Central. So I updated the organisational POM to use version 2.9 of eclipse:eclipse and gave that a shot.

Hooray! 23:17 and I’d fixed our problem. Shame I had a 06:30 start the next day for a meeting :-/

This isn’t the first time that we’ve made the mistake of doing too much in a migration. We could have stuck with Continuum and Archiva when we updated our org-POM, and then step for step migrated to Nexus (still using Archiva) and then finally moved over to Nexus. Had we done that, we might not have had the problems we did; but the effort of a slow migration might also have been larger.

For me, the point to take away is that it easier to debug maven and go straight to the source of the problem, than it is to attempt to fix the problem by trail and error, as we were doing before I debugged Maven. Debugging Maven might sound like it’s advanced or complicated, but it’s damn fun – it’s real hacking and the feeling of solving a puzzle in this way makes the effort worth it. I fully recommend it if for no other reason that you get exposed to reading other peoples (open source) code.

Copyright © 2012, Ant Kutschera