Share Scala class in test folder with Java tests in Maven - java

I have a Maven project with mixed Java and Scala code. I want to use an auxiliary class located in the scala test folder for Java tests. The file tree is like below, omitting packages:
+ test/
+ java/...
- SomeTest.java
+ scala/...
- Aux.scala
- OtherTest.scala
I want to import code from Aux.scala for use in the SomeTest.java class. It works fine in my IDE, where all folders are marked as test folders. However when building this project in Maven I get an import error from the Java compiler.
How can I configure Maven to use the Scala test code for Java tests?

In order to resolve dependencies on Scala classes in the Java test-compile phase, you have to bind the testCompile goal of the scala-maven-plugin to the process-test-resources phase. That way, the Scala classes are already compiled when you compile the Java test classes.
The following snippet should do the trick:
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.1.4</version>
<executions>
<!-- Run scala compiler in the process-test-resources phase, so that dependencies on
scala classes can be resolved later in the (Java) test-compile phase -->
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
The complete build element of my pom.xml for a mixed Java/Scala project looks the following:
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.1.4</version>
<executions>
<!-- Run scala compiler in the process-test-resources phase, so that dependencies on
scala classes can be resolved later in the (Java) test-compile phase -->
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<stdout>W</stdout> <!-- Skip coloring output -->
</configuration>
<executions>
<execution>
<id>scala-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<suffixes>(?<!(IT|Integration))(Test|Suite|Case)</suffixes>
</configuration>
</execution>
<execution>
<id>integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<suffixes>(IT|Integration)(Test|Suite|Case)</suffixes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Related

COMPILATION ERROR : package my.package.root.util does not exist

I have an Eclipse Java project for which I am trying to execute the unit tests using Maven.
I have my unit tests as below so that it respects the expected hierarchy
src/test/java/StringUtilsTests.java
However, my unit test references the source code located in:
src/my/package/root/util/StringUtils.java
just because it has always been like this and I don't want to change my folder hierarchy for the tests.
Therefore, I use the build-helper-maven-plugin to add this source folder, as below
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/my/package/root/util</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
But even with that, I get the below error:
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /C:/Users/frederic/git/myproject/src/test/java/StringUtilsTests.java:[22,32] package package my.package.root.util does not exist
Below is my reference to the maven-surefire-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<skipTests>false</skipTests>
</configuration>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
And it's even worse if I don't put my test in
src/test/java/StringUtilsTests.java
but instead here for example:
src/tests/my/package/root/util/StringUtilsTests.java
The maven-surefire-plugin will show
No tests to run
Do you need to run them as Unit-Tests or can you run them as Integration-Tests? I think Integration tests would work out-of-the-box like this? Simply change the class names to end with IT MyClassIT.java for these tests or finetune your .pom and change the goal to integration-test-phase or verification-phase? I think that should work since it will be executed later on in the build cycle.
Otherwise try to change this line with a wildcard:
<source>src/my/package/root/util/*</source>
Based on this article, adding the below elements to my pom.xml fixed my issue:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<executions>
<execution>
<id>compiletests</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<testSourceDirectory>${project.basedir}/src-test</testSourceDirectory>
And VERY important was to add this dependency
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>

Create and install de-lomboked source jar in maven

Disclaimer: I have solved this problem and am documenting the solution for the world to know.
How do I create and install a *-sources.jar containing "delomboked" source code in maven?
By default, The maven-source-plugin creates a sources jar without delomboking the source files, which causes projects that depend on the library binaries to complain about mismatching source files.
TL;DR (explained beneath)
Add the following plugins configuration to your plugins configuration in the project.build element of your pom.xml
<project>
...
<build>
<plugins>
...
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.0.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-to-lombok-build</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>generate-delomboked-sources-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar"
basedir="${project.build.directory}/delombok"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
<executions>
<execution>
<id>install-source-jar</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>install</phase>
<configuration>
<file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<classifier>sources</classifier>
<generatePom>true</generatePom>
<pomFile>${project.basedir}/pom.xml</pomFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Explanation
lombok-maven-plugin will enable you to delombok the source code (${project.basedir}/src/main/java) and place it in the target directory (${project.build.directory}/delombok). Usually this will place the code in the ${project.build.directory}/generated-sources/delombok folder, but because Intellij automatically considers this additional source-code, duplicate code errors will occur when developing your library, in order to stop this, just specify a non-default target directory (in this case just outside of the generated-sources dir).
maven-resources-plugin is necessary in order to also copy resources from the standard ${project.basedir}/src/main/resources directory. If there are any other non-standard resource directories in your project, you should configure them in the resources section for this plugin.
maven-antrun-plugin is used instead of the maven-source-plugin because you cannot specify a custom source directory in the latter. The jar task points to our custom "generated-sources" and produces the standard-named sources jar.
maven-install-plugin install-file goal is used because you cannot attach jars using the install goal. We can hack a solution by manually installing a file using the install-file goal with a classifier of sources.
I hope this helps others who are on struggle street like I was with this problem.
The following solution is based on the one offered above but improves it by using the build-helper plugin to attach the generated delomboked source jar instead of using install-file. This has the benefit that the normal maven install and deploy phases correctly handle the generated file just as they would if the sources plugin had been used.
<project>
...
<build>
<plugins>
...
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.12.0</version>
<executions>
<execution>
<id>delombok-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>generate-delomboked-sources-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar"
basedir="${project.build.directory}/delombok"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>attach-delomboked-sources-jar</id>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
<type>jar</type>
<classifier>sources</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Would like to point out that a profile can also be used (to get around the build source directory being un-customizable). The solution is described at https://sudonull.com/post/1197-Lombok-sourcesjar-and-convenient-debug
Add the following to the pom.xml
properties:
<origSourceDir>${project.basedir}/src/main/java</origSourceDir>
<sourceDir>${origSourceDir}</sourceDir>
<delombokedSourceDir>${project.build.directory}/delombok</delombokedSourceDir>
</properties>
Profile and build section changes:
<profiles>
<profile>
<id>build</id>
<properties>
<sourceDir>${delombokedSourceDir}</sourceDir>
</properties>
</profile>
</profiles>
<build>
<sourceDirectory>${sourceDir}</sourceDirectory>
<plugins>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<id>delombok</id>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>${origSourceDir}</sourceDirectory>
<outputDirectory>${delombokedSourceDir}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
Execute with mvn clean install -Pbuild
This should solve the "Library source does not match the bytecode for class" error in IntelliJ and allow seamless debugging in most cases.
Ref: "Delombok plugin + profile in maven"
Both of the answers are flawed for multi-module projects or pure pom projects, because there are no sources, so you will have to create an empty directory, and it'll produce an empty .jar.
There is a simple (but a bit more complex) way to achieve functionality you want: make your own Maven Plugin.
Sounds overly complicated, but we can re-use maven-sources-plugin and with MOJO's inheritance update necessary parts:
import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.source.SourceJarNoForkMojo;
import org.apache.maven.project.MavenProject;
/**
* This goal bundles all the sources into a jar archive, but uses delomboked sources.
*/
#Mojo(name = "jar-no-fork", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true)
public class EnhancedSourceJarNoForkMojo extends SourceJarNoForkMojo {
#Parameter(property = "<some-prefix>.useDelombokSources", defaultValue = "true")
protected boolean useDelombokSources;
#Parameter(property = "<some-prefix>.delombokSourcesLocation", defaultValue = "delombok")
protected String delombokSourcesLocation;
#Override
protected List<String> getSources(MavenProject p) {
// if user doesn't want delomboked sources, use default algorithm
List<String> sources = super.getSources(p);
if (!useDelombokSources) {
return sources;
}
// typically, sources' list will contain: [src/main/java, target/generated_sources].
// replace src/main/java if it's present with delombok-generated sources
String target = p.getBuild().getDirectory();
return super.getSources(p)
.stream()
.map(s -> s.endsWith("java") ? String.format("%s/%s", target, delombokSourcesLocation) : s)
.collect(Collectors.toList());
}
}
Gist with pom.xml routine is available here.

Exclude *target* directory from the maven-checkstyle-plugin scan

I am using Apache Maven Checkstyle plugin in my pom.xml.
I am trying to exclude the target directory from the check style scan but no luck so far. Here is the pom code i am trying.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>checkstyle-check</id>
<phase>test</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<configLocation>checkstyles.xml</configLocation>
<failsOnError>true</failsOnError>
<failOnViolation>true</failOnViolation>
<consoleOutput>true</consoleOutput>
<includes>**\/*.java,**\/*.groovy</includes>
<excludes>**WHAT GOES HERE TO EXCLUDE THE TARGET DIRECTORY**</excludes>
</configuration>
</plugin>
In Apache Maven Checkstyle Plugin version 3 to specify the location of the source directories we have to use sourceDirectories parameter. Then we can specify only directories of application/library and test sources to be used for Checkstyle:
<sourceDirectories>
<sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
<sourceDirectory>${project.build.testSourceDirectory}</sourceDirectory>
</sourceDirectories>
Now only src/main/java and src/test/java will be analysed.
Here is my full working example:
<!-- Apache Maven Checkstyle Plugin (checks Java code adheres to a coding standard) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectories>
<sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
<sourceDirectory>${project.build.testSourceDirectory}</sourceDirectory>
</sourceDirectories>
<!-- relates to https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml -->
<configLocation>/src/main/resources/checkstyle.xml</configLocation>
</configuration>
</plugin>
<excludes>**/generated/**/*</excludes>
This will remove the generated files from the plugin.

Compile generated maven project in generated-sources directory

I am generating a complete maven project (with its own pom.xml) with swagger codegen maven plugin. It outputs the project to generated-sources/swagger/ directory. However java sources in this directory are compiled against dependencies that are residing in my generator project's pom.xml, not against the one which is generated.
Is such configuration possible? I have already read about maven antlr4 and build helper plugins, but they do not seem useful for this purpose.
Use openapi-generator-maven-plugin to generate the source. Than the maven-invoker-plugin to build and test the generated source.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>${openapi-generator-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>swagger.yaml</inputSpec>
<generatorName>java</generatorName>
<skipValidateSpec>true</skipValidateSpec>
<output>${project.build.directory}/generated-sources/openapi</output>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>${maven-invoker-plugin.version}</version>
<configuration>
<pom>${project.build.directory}/generated-sources/openapi/pom.xml</pom>
</configuration>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

how to specify maven compiler plugin order

I'm working on a mixed java and kotlin project by using maven.
The problem I'm facing right now is, maven-compiler-plugin runs before compiling kotlin-maven-plugin.
[INFO] --- maven-compiler-plugin:3.0:compile (default-compile) #annotation ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 11 source files to /Users/hannes/workspace/tikxml/annotation/target/classes
[INFO] --- kotlin-maven-plugin:1.0.0-beta-4583:compile (compile) # annotation
[INFO] Kotlin Compiler version 1.0.0-beta-4583
In my java source code I'm referencing classes written in kotlin. But javac runs before kotlinc. Hence, maven interrupts with compiler errors.
My pom (parent pom, I use submodules) looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
...
<modules>
<module>core</module>
<module>annotation</module>
<module>processor</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
<kotlin.version>1.0.0-beta-4583</kotlin.version>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/main/java</source>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/test/java</source>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
The Kotlin documentation on Using Maven suggests explicitly binding the kotlin-maven-plugin execution to the process-sources phase. Since maven-compiler-plugin is bound to the compile phase, binding kotlin-maven-plugin to the process-sources phase makes it run first.
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals> <goal>test-compile</goal> </goals>
</execution>
</executions>
</plugin>
Although it's more verbose, you can compile both Java and Kotlin sources in the compile phase with the following configuration:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>kotlin-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>kotlin-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5</version>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
Normally, plugins are executed in the order they are declared in the POM, but the executions of default-compile and default-testCompile are special because they're built-in, so they always go first. The above configuration gets around that by disabling the default compile executions, and declaring maven-compiler-plugin with new compilation executions after the kotlin-maven-plugin. In this way, you can get all compilation to properly occur during the compile phase of the build lifecycle.
A simple but effective solution would be to change the two phases (the <phase> element) applied to the executions of the Kotlin Maven Plugin, from compile to process-sources and from test-compile to process-test-sources.
You want the Kotlin part to be executed before the Java one. Maven by default will invoke the Maven Compiler Plugin as part of the compile phase for source code, and test-compile for test code. Moving the Kotlin part to their previous phases would then make the flow as you wished.
If you wanted the opposite (first Java then Kotlin), then you could have moved the Kotlin part to the next phases (as an example: from compile to process-classes and from test-compile to process-test-classes).
For further details about Maven phases, check the official documentation.
In case you are open to using a different build system, dependent projects are easy to set up with Kobalt:
val p1 = javaProject { ... }
val p2 = kotlinProject(p1) { ... }
This makes project p2 depend on p1. You can also mix Java and Kotlin projects.

Categories

Resources