Duplicate class errors with Clover and JAXB or JAXB2 plugin
Symptoms
Project uses JAXB or JAXB2 plugin, which generates sources into src/main/java directory:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.0</version>
<configuration>
<generateDirectory>src/main/java</generateDirectory>
</configuration>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
When integrating Clover into a build, for example:
mvn clean clover:setup test clover:aggregate clover:clover
the build fails with a number of "duplicate class" errors:
[INFO] [compiler:compile {execution: default-compile}]
[DEBUG] Using compiler 'javac'.
[DEBUG] Source directories: [/bamboohome/xml-data/build-dir/MYPLAN-JOB1/mymodule/target/clover/src-instrumented
/bamboohome/xml-data/build-dir/MYPLAN-JOB1/mymodule/src/main/java]
...
[INFO] Compiling 123 source files to /bamboohome/xml-data/build-dir/MYPLAN-JOB1/mymodule/target/classes
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Compilation failure
/bamboohome/xml-data/build-dir/MYPLAN-JOB1/mymodule/target/clover/src-instrumented/com/acme/Foo.java:[16,7] duplicate class: com.acme.Foo
Cause
A build has a following sequence of events:
1. clover:setup is called - Clover instruments regular sources found in 'src/main/java' and changes the source root from 'src/main/java' to 'target/clover/src-instrumented'
2. jaxb2:generate is called - JAXB2 generates sources and adds 'src/main/java' directory as a source root again
3. javac gets two source roots - 'src/main/java' and 'target/clover/src-instrumented' - thus it gets "regular" sources twice (original and instrumented), what leads to the duplicate class error
Resolution
There are three solutions possible:
1. Use separate source root for generated sources
Thanks to this, the Clover-for-Maven Plugin will instrument regular sources found in "src/main/java" and redirect source root to the "target/clover/src-instrumented", while the JAXB/JAXB2 plugin will put generated sources into another source root. Clover can instrument or not these generated sources, depending on:
- the order in which JAXB's 'generate' and Clover's 'setup' goals are called
- the includesAllSourceRoots configuration setting for Clover
Example:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.0</version>
<configuration>
<generateDirectory>target/generated-sources/jaxb</generateDirectory>
</configuration>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
2. Call JAXB/JAXB2 'generate' goal directly from a command line
Thanks to this, source generation will happen before clover:setup is called and thus:
- the 'src/main/java' will contain both regular and generated sources
- the 'src/main/java' will be redirected to 'target/clover/src-instrumented'
- the javac will see only one source root
Example:
mvn clean jaxb2:generate clover:setup test clover:aggregate clover:clover
3. Reconfigure Clover so that the 'clover:setup' is called after a source generation phase
This requires configuring Clover in the POM (it means that the "Automatic Clover integration " feature in Bamboo cannot be used). Thanks to this, Clover will be able to "see" generated sources and could instrument them as well. Set the includesAllSourceRoots property to true (default is false) if you're interested in code coverage for JAXB generated sources. For instance, the 'jaxb2:generate' goal can be bound to the 'generate-sources' phase, while the 'clover:setup' goal to the 'process-sources' phase.
Example:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.8.0</version>
<configuration>
<generateDirectory>src/main/java</generateDirectory>
</configuration>
<executions>
<execution>
<id>generate</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.atlassian.maven.plugins</groupId>
<artifactId>clover-maven-plugin</artifactId> <!-- Before 4.1.1 it was maven-clover2-plugin -->
<version>4.1.1</version>
<configuration>
<!-- Instrument all source files, also generated by JAXB. Set to false if
you're not interested in such details (default is false) -->
<includesAllSourceRoots>true</includesAllSourceRoots>
</configuration>
<executions>
<execution>
<id>instrument</id>
<phase>process-sources</phase>
<goals>
<goal>setup</goal>
</goals>
</execution>
</executions>
</plugin>
mvn clean test clover:aggregate clover:clover
References
Code samples
Checkout sources of the clover-maven-plugin
from Bitbucket:
hg clone https://bitbucket.org/atlassian/maven-clover2-plugin
Open the src/it/jaxb directory. It contains an example how to configure JAXB and Clover in pom.xml.