Building on ODA's Maven-ization
Mar 31, 2015, 8:30 PM
Over the weekend, I took a bit of time to apply some of my hard-won recent Maven knowledge to a project I wish I had more time to work with lately: the ODA. The development branches have been Maven-ized for half a year or so, but primarily just to the point of getting the compile to work. Now that I know more about it, I was able to go in and make great strides towards several important goals.
As a preliminary note: don't take my current implementations as gospel. There are parts that will no doubt change; for example, there are some intermittent timing issues currently with the final assembly. But the changes I did make have borne some early fruit.
Source Bundles
Over the releases, it's proven surprisingly fiddly to get parameter names, inline Javadoc, and attached source to work in Designer, leaving some builds no better off than the legacy API in those regards. The apparently-consistent fix for this is the use of "source" plugins: OSGi plugins that go alongside the normal one that just contain the source of each class. Those aren't too bad to generate manually from Eclipse, but the point of Maven is getting away from that sort of manual stuff.
Fortunately, Tycho (the OSGi toolkit for Maven) includes a plugin that allows you to generate these source bundles alongside the normal ones, by including this in the list of plugins executed during the build:
<plugin> <groupId>org.eclipse.tycho</groupId> <artifactId>tycho-source-plugin</artifactId> <version>${tycho-version}</version> <executions> <execution> <id>plugin-source</id> <goals> <goal>plugin-source</goal> </goals> </execution> </executions> </plugin>
Once you have that (which I added to the top-level project, so it cascades down), you can then add the plugins to the OSGi feature with the same name as the base plugin plus ".source". Eclipse will give a warning that the plugins don't exist (since they exist only during a Maven build), but you can ignore that.
Javadoc
Javadoc generation is an area where I suspect I'll make the most changes down the line, but I managed to wrangle it into a spot that mostly works for now.
Not every project in the tree needs Javadoc (for example, we don't need to include docs for third-party modules necessarily), but it's still useful to specify configuration. So I took the already-existing basic config in the parent pom and moved it to pluginManagement for the children:
<pluginManagement> <plugins> <plugin> <!-- javadoc configuration --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.9</version> <configuration> <failOnError>false</failOnError> <excludePackageNames>com.sun.*:com.ibm.commons.*:com.ibm.sbt.core.*:com.ibm.sbt.plugin.*:com.ibm.sbt.jslibrray.*:com.ibm.sbt.proxy.*:com.ibm.sbt.security.*:*.util.*:com.ibm.sbt.portlet.*:com.ibm.sbt.playground.*:demo.*:acme.*</excludePackageNames> </configuration> </plugin> </plugins> </pluginManagement>
Then, I added specific plugin references in the applicable child projects:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <executions> <execution> <id>generate-javadoc</id> <phase>package</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin>
With those, the build can generate Javadoc appropriate for consumption in the final assembly down the line.
Assembly
The final coordinating piece is referred to as the "assembly". The job of the Maven Assembly Plugin is to take your project components and output - built Jars, source files, documentation, etc. - and assembly them into an appropriate final format, usually a ZIP file.
The route I took is to add a distribution project to the tree whose sole job it is to wait until the other components are done and then assemble the results. The pom for this project primarily consists of telling Maven to run the assembly plugin to create an appropriately-named ZIP file using what's called an "assembly descriptor": an XML file that actually provides the instructions. There are a couple stock descriptors, but for something like this it's useful to write your own. It's quite a file (and also liable to change as I figure out the best practices), but is broken down into a couple logical segments.
First off, we have a rule telling it to include all files from the "src/main/resources" folder in the current (assembly) projet:
<fileSets> <fileSet> <directory>src/main/resources</directory> <includes> <include>**/*</include> </includes> <outputDirectory>/</outputDirectory> </fileSet> </fileSets>
This folder contains a README description of the result as well as the miscellaneous presentations and demo files the ODA has collected over time.
Next, in addition to the source bundles mentioned earlier, I want to include ZIP files of the important project sources in the distribution, for easy access (technically wasteful, but not by too much):
<moduleSet> <useAllReactorProjects>true</useAllReactorProjects> <includes> <include>org.openntf.domino:org.openntf.domino</include> <include>org.openntf.domino:org.openntf.domino.xsp</include> <include>org.openntf.domino:org.openntf.formula</include> <include>org.openntf.domino:org.openntf.junit4xpages</include> </includes> <binaries> <attachmentClassifier>src</attachmentClassifier> <outputDirectory>/source/</outputDirectory> <unpack>false</unpack> <outputFileNameMapping>${module.artifactId}.${module.extension}</outputFileNameMapping> </binaries> </moduleSet>
I use the "binaries" tag here instead of "sources" because I want to include the ZIP forms (hence unpack=false) - this is one part that may change, but it works for now.
Next, I gather the Javadocs generated earlier, but these I do want to unpack:
<moduleSet> <useAllReactorProjects>true</useAllReactorProjects> <includes> <include>org.openntf.domino:org.openntf.domino</include> <include>org.openntf.domino:org.openntf.domino.xsp</include> <include>org.openntf.domino:org.openntf.formula</include> </includes> <binaries> <attachmentClassifier>javadoc</attachmentClassifier> <outputDirectory>/apidocs/${module.artifactId}</outputDirectory> <unpack>true</unpack> </binaries> </moduleSet>
This results in an "apidocs" folder containing the Javadoc HTML for each of those three projects in subfolders.
Finally, I want to include the built and ZIP'd Update Site for use in Designer and Domino:
<moduleSet> <useAllReactorProjects>true</useAllReactorProjects> <includes> <include>org.openntf.domino:org.openntf.domino.updatesite</include> </includes> <binaries> <attachmentClassifier>assembly</attachmentClassifier> <outputDirectory>/</outputDirectory> <unpack>false</unpack> <includeDependencies>false</includeDependencies> <outputFileNameMapping>UpdateSite.zip</outputFileNameMapping> </binaries> <sources> <outputDirectory>/</outputDirectory> <includeModuleDirectory>false</includeModuleDirectory> <includes> <include>LICENSE</include> <include>NOTICE</include> </includes> </sources> </moduleSet>
While grabbing the Update Site, I also copy the all-important LICENSE and NOTICE files from this current project - these may be best moved to the resources folder above.
The result of all this is a nicely-packed ZIP containing everything a user should need to get started with the API:
Next Steps
So, as I mentioned, this work isn't complete, in large part because I'm still learning the ropes. I suspect that the way I'm gathering the sources in the assembly and generating and gathering the Javadoc are not quite right - and this shows in the way that slightly-different host configurations (like on a Bamboo build server or when doing a multi-threaded build) fail during packaging.
Additionally, it's somewhat wasteful to include the source plugins even for server distributions; I won't really lose sleep over it, but it'd still be ideal to continue the recent policy of providing ExtLib-style distinct Update Sites. I'm not sure if this will require creating multiple feature and update-site projects or if it can be accomplished with build profiles.
Finally, I would love to be able to get rid of the source-form third-party dependencies like Guava and Javolution. One of the main benefits of Maven is that you can very-easily consume dependencies by listing them in the config, but Tycho and Eclipse throw a wrench into that: when you configure a project to use Tycho, then Eclipse stops referencing the Maven dependencies. Moreover, even though I believe all of the dependencies we use contain OSGi metadata, which would satisfy a Tycho command-line build, both Eclipse and the requirement that we build an old-style (non-p2) Update Site prevent us from doing that simply. It's possible that the best route will be to have Maven download and copy in the Jar files of the dependencies, but even that has its own suite of issues.
But, in any event, it's satisfying seeing this come together - and nice for me personally to build on the work Nathan, Paul, and Roland-and-co. have been doing lately. Maven is a monster and still suffers from severe "how the F does this stuff work?" problems, but it does feel good to put it to work.