Gradle Multi Project with Java 9 and Java 8 - gradle

I would like to migrate a gradle multi project to java 9. Instead of trying a big bang I would like to go step by step. My idea is to start from top (main module) and then work my way down. This implies, that I will have a main module in Java 9 while others are still in Java 8.
If my info is correct a good approach is to have the modules in java 8 integrated as automatic modules. It works fine with 3rd party libs as jar's, but I do not know how to teach gradle to do it with my own java 8 modules.
Imagine I got a Main Java 9 Module 'java9' and a Java 8 Library Module 'java8'
What I did so far:
'java8' build.gradle
apply plugin: 'java'
ext.moduleName = 'java8module.main'
jar {
inputs.property("moduleName", moduleName)
manifest {
attributes('Automatic-Module-Name': moduleName)
}
}
This gives me a java8 jar with a Module Name I decided for in the MANIFEST.MF
'java9' build.gradle
apply plugin: 'application'
mainClassName = 'com.thoughtblaze.java9.main.Main'
ext.moduleName = 'java9main.main'
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
]
classpath = files()
}
}
dependencies {
compile project (':java8')
}
'java9' module-info
module java9main.main {
requires java9module.main ;
requires java8module.main ;
}
The problem now is:
'java9' does not recognize java8 as a required module and therefore I cant access any class of the java8 module.
What do I miss here ?

Related

Not able to extend a Gradle task

I'm new with Gradle (we're switching from SBT) and using it to build apps made with Play Framework.
I need to add some filtering on the resources before Gradle processes them (I would like to inject some build properties into the configuration to make them available from the code).
I've managed to "extend" the java processResources task, but, for some reason, I cannot do the same with play processPlayBinaryPlayResources.
processPlayBinaryPlayResources {
filter ReplaceTokens, tokens: [
"applicationVersion": version
]
}
Even this doesn't work :
def playVersion = "2.6.20"
def scalaVersion = "2.12"
def javaVersion = "1.8"
apply plugin: "java"
apply plugin: "idea"
apply plugin: "play"
model {
components {
play {
platform play: playVersion, scala: scalaVersion, java: javaVersion
injectedRoutesGenerator = true
}
}
}
processPlayBinaryPlayResources {
doLast {
println("ok")
}
}
dependencies {
compile "io.vavr:vavr:0.9.2"
compile "org.imgscalr:imgscalr-lib:4.2"
compile "com.typesafe.play:play-guice_${scalaVersion}:2.6.20"
compile "com.typesafe.akka:akka-http_${scalaVersion}:10.1.5"
compile "com.typesafe.play:filters-helpers_${scalaVersion}:2.6.20"
compile "ch.qos.logback:logback-classic:1.2.3"
}
It yields :
> Could not find method processPlayBinaryPlayResources() for arguments [build_6grwx7eowye82rdqpu4qlinur$_run_closure2#582d9dbd] on root project 'myproject' of type org.gradle.api.Project.
Any idea why ?
Your assumption of finding a task processPlayBinaryPlayResources is based on that, that the java plugin automatically adds a processResources task for all source set as process<sourceset_name>Resources . This happens only when a source set is added using java pugins sourceSets method which, in this case PlayBinaryPlay is not. The play plugin uses its own DSL to configure source sets.
Therefore when you try extending processPlayBinaryPlayResources it does not happen as no such tasks by that name exists in the first place and hence while delegating it to Project, you end up with this
Could not find method processPlayBinaryPlayResources() for arguments [build_6grwx7eowye82rdqpu4qlinur$_run_closure2#582d9dbd] on root project 'myproject' of type org.gradle.api.Project.
Lastly, I would like to add that the processPlayBinaryPlayResources task is not added by the play plugin.

JavaFX 11 : Create a jar file with Gradle

I am trying to upgrade a JavaFX project from the 8 Java version to the 11 version. It works when I use the "run" Gradle task (I followed the Openjfx tutorial), but when I build (with the "jar" Gradle task) and execute (with "java -jar") a jar file, the message "Error: JavaFX runtime components are missing, and are required to run this application" appears.
Here is my build.gradle file :
group 'Project'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.11
repositories {
mavenCentral()
}
def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
platform = 'win'
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'mac'
}
dependencies {
compile "org.openjfx:javafx-base:11:${platform}"
compile "org.openjfx:javafx-graphics:11:${platform}"
compile "org.openjfx:javafx-controls:11:${platform}"
compile "org.openjfx:javafx-fxml:11:${platform}"
}
task run(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = "project.Main"
}
jar {
manifest {
attributes 'Main-Class': 'project.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
compileJava {
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls,javafx.fxml'
]
}
}
run {
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls,javafx.fxml'
]
}
}
Do you know what I should do ?
With Java/JavaFX 11, the shadow/fat jar won't work.
As you can read here:
This error comes from sun.launcher.LauncherHelper in the java.base module. The reason for this is that the Main app extends Application and has a main method. If that is the case, the LauncherHelper will check for the javafx.graphics module to be present as a named module:
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
If that module is not present, the launch is aborted.
Hence, having the JavaFX libraries as jars on the classpath is not allowed
in this case.
What's more, every JavaFX 11 jar has a module-info.class file, at the root level.
When you bundle all the jars content into one single fat jar, what happens to those files with same name and same location? Even if the fat jar keeps all of them, how does that identify as a single module?
There is a request to support this, but it hasn't been addressed yet: http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs
Provide a means to create an executable modular “uber-JAR” that contains more than one module, preserving module identities and boundaries, so that an entire application can be shipped as a single artifact.
The shadow plugin still does make sense to bundle all your other dependencies into one jar, but after all you will have to run something like:
java --module-path <path-to>/javafx-sdk-11/lib \
--add modules=javafx.controls -jar my-project-ALL-1.0-SNAPSHOT.jar
This means that, after all, you will have to install the JavaFX SDK (per platform) to run that jar which was using JavaFX dependencies from maven central.
As an alternative you can try to use jlink to create a lightweight JRE, but your app needs to be modular.
Also you could use the Javapackager to generate an installer for each platform. See http://openjdk.java.net/jeps/343 that will produce a packager for Java 12.
Finally, there is an experimental version of the Javapackager that works with Java 11/JavaFX 11: http://mail.openjdk.java.net/pipermail/openjfx-dev/2018-September/022500.html
EDIT
Since the Java launcher checks if the main class extends javafx.application.Application, and in that case it requires the JavaFX runtime available as modules (not as jars), a possible workaround to make it work, should be adding a new Main class that will be the main class of your project, and that class will be the one that calls your JavaFX Application class.
If you have a javafx11 package with the Application class:
public class HelloFX extends Application {
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
Scene scene = new Scene(new StackPane(l), 400, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Then you have to add this class to that package:
public class Main {
public static void main(String[] args) {
HelloFX.main(args);
}
}
And in your build file:
mainClassName='javafx11.Main'
jar {
manifest {
attributes 'Main-Class': 'javafx11.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Now you can run:
./gradlew run
or
./gradlew jar
java -jar build/libs/javafx11-1.0-SNAPSHOT.jar
The final goal is to have the JavaFX modules as named modules on the module path, and this looks like a quick/ugly workaround to test your application. For distribution I'd still suggest the above mentioned solutions.
With the latest versions of JavaFX, you can use two Gradle plugins to easily distribute your project (javafxplugin and jlink).
With these plugins, you can:
Create a distributable zip file that contains all the needed jar files: it requires a JRE to be executed (with the bash or batch script)
Create a native application with Jlink for a given OS: a JRE is not needed to execute it, as Jlink includes a "light" JRE (including only the needed java modules and dependencies) in the distribution folder
I made an example on bitbucket, if you want an example.
[Edit: For the latest versions of JavaFX, please check my second answer]
If someone is interested, I found a way to create jar files for a JavaFX11 project (with Java 9 modules). I tested it on Windows only (if the application is also for Linux, I think we have to do the same steps but on Linux to get JavaFX jars for Linux).
I have a "Project.main" module (created by IDEA, when I created a Gradle project) :
src
+-- main
| +-- java
| +-- main
| +-- Main.java (from the "main" package, extends Application)
| +-- module-info.java
build.gradle
settings.gradle
...
The module-info.java file :
module Project.main {
requires javafx.controls;
exports main;
}
The build.gradle file :
plugins {
id 'java'
}
group 'Project'
version '1.0'
ext.moduleName = 'Project.main'
sourceCompatibility = 1.11
repositories {
mavenCentral()
}
def currentOS = org.gradle.internal.os.OperatingSystem.current()
def platform
if (currentOS.isWindows()) {
platform = 'win'
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'mac'
}
dependencies {
compile "org.openjfx:javafx-base:11:${platform}"
compile "org.openjfx:javafx-graphics:11:${platform}"
compile "org.openjfx:javafx-controls:11:${platform}"
}
task run(type: JavaExec) {
classpath sourceSets.main.runtimeClasspath
main = "main.Main"
}
jar {
inputs.property("moduleName", moduleName)
manifest {
attributes('Automatic-Module-Name': moduleName)
}
}
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'javafx.controls'
]
classpath = files()
}
}
task createJar(type: Copy) {
dependsOn 'jar'
into "$buildDir/libs"
from configurations.runtime
}
The settings.gradle file :
rootProject.name = 'Project'
And the Gradle commands :
#Run the main class
gradle run
#Create the jars files (including the JavaFX jars) in the "build/libs" folder
gradle createJar
#Run the jar file
cd build/libs
java --module-path "." --module "Project.main/main.Main"

Use Gradle to generate Java classes from a Swagger definition into a standalone JAR

I am fairly new to both Gradle and Swagger code generator plugin for it (concretely the one that is linked from Swagger's website, i.e. https://github.com/thebignet/swagger-codegen-gradle-plugin), so I'm not sure whether my problem is with Gradle in general or with that particular plugin.
I've created a simple multi-module Spring Boot application (but the fact that I'm using Spring Boot or even Spring doesn't matter much). It's a console application; i.e. it doesn't start a webserver. In fact, it's actually a REST client consuming someone else's interface.
The application consists of four modules: spc-parent (which is just an envelope for the rest) containing spc-boot, spc-service, and spc-integration-model. Spc-boot contains just the starting point of the application, spc-service now contains a single Spring service, and spc-integration-model is meant to contain classes needed to consume the REST interface. The resulting structure will be much more complicated but I've tried to create a sort of a minimal example.
The problem lies within the spc-integration-model module. It consists of a single source file, petstore.json, and a build.gradle copied from https://github.com/thebignet/swagger-codegen-gradle-plugin (and only slightly modified). There are actually two problems (but they may have the same underlying cause).
When running gradle build (from spc-parent) for the very first time, it fails. Java sources are generated from petstore.json but they don't get compiled, which is why the service in spc-service doesn't see needed classes. However, running gradle build a second time fixes this (generated Java sources get compiled which makes it possible to compile spc-service, too).
The created JAR of spc-integration-model never contains anything besides Manifest.
My goal here is to persuade Gradle to compile the generated classes right away during the first build and also to put them into the JAR.
Now for some concrete Gradle tasks. The most interesting is spc-integration-model's build.gradle:
plugins {
id 'org.detoeuf.swagger-codegen' version '1.7.4'
id 'java'
}
apply plugin: 'org.detoeuf.swagger-codegen'
repositories {
mavenCentral()
jcenter()
}
swagger {
inputSpec = 'http://petstore.swagger.io/v2/swagger.json'
outputDir = file('build/swagger')
lang = 'java'
additionalProperties = [
'apiPackage' : 'ondra.spc.integration.client.api',
'dateLibrary' : 'java8',
'hideGenerationTimestamp': 'true',
'invokerPackage' : 'ondra.spc.integration.client',
'library' : 'resttemplate',
'modelNameSuffix' : 'Dto',
'modelPackage' : 'ondra.spc.integration.client.model'
]
importMappings = [
'Dog': 'io.swagger.petstore.client.model.Dog'
]
}
sourceSets {
swagger {
java {
srcDir file("${project.buildDir.path}/swagger/src/main/java")
}
}
}
classes.dependsOn('swagger')
ext {
spring_boot_version = springBootVersion
jackson_version = jacksonVersion
junit_version = jUnitVersion
swagger_annotations_version = swaggerAnnotationsVersion
swagger_codegen_version = swaggerCodegenVersion
}
dependencies {
swaggerCompile "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
swaggerCompile "io.swagger:swagger-annotations:$swagger_annotations_version"
compile sourceSets.swagger.output
compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
compile "io.swagger:swagger-codegen:$swagger_codegen_version"
testCompile "junit:junit:$junit_version"
}
(Now that I'm re-reading my question I see that the local version of petstore.json is actually not used and an online version is used instead but let's leave that aside.)
The rest should be quite straightforward. spc-service:
dependencies {
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile project (":spc-integration-model")
}
spc-boot:
dependencies {
compile "org.springframework.boot:spring-boot:$springBootVersion"
compile "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion"
compile "org.springframework:spring-web:$springVersion"
runtime "org.hibernate.validator:hibernate-validator:$hibernateVersion"
runtime "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile project (":spc-service")
testCompile("org.junit.jupiter:junit-jupiter-api:$jUnitVersion")
testRuntime("org.junit.jupiter:junit-jupiter-engine:$jUnitVersion")
}
test {
useJUnitPlatform()
}
spc-parent:
subprojects {
apply plugin: 'java'
group 'ondra'
version '1.0-SNAPSHOT'
buildscript {
ext {
hibernateVersion = '6.0.9.Final'
jacksonVersion = '2.9.4'
springBootVersion = '2.0.0.RELEASE'
springVersion = '5.0.5.RELEASE'
swaggerAnnotationsVersion = '1.5.16'
swaggerCodegenVersion = '2.2.3'
jUnitVersion = '5.1.1'
}
repositories {
mavenCentral()
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
}
And spc-parent's settings.gradle:
rootProject.name = 'spc-parent'
include 'spc-boot'
include 'spc-service'
include 'spc-integration-model'
I've also put the whole application into a single archive: https://drive.google.com/open?id=1cOYIcaxnhik548w0wEGswgD2g4udATdD

Unable to resolve library using Gradle. Resolved using Grape

I'm fairly new to Groovy and I'm trying to wrap my head around Gradle. If I import the org.jvnet.hudson.plugins through Grapes it works perfectly and the dependency is resolved. But if I try to retrieve the dependency using Gradle the dependency is not resolved.
The package org.eclipse.hudson:hudson-core:3.2.1 works with both Gradle and Grape.
A dependency that is not resolved using Gradle
compile 'org.jvnet.hudson.plugins:checkstyle:3.42'
A dependency which is resolved using Grape
#Grab('org.jvnet.hudson.plugins:checkstyle:3.42')
A dependency which is resolved using Gradle
compile 'org.eclipse.hudson:hudson-core:3.2.1'
Error during Gradle build
line 3, column 1.
import hudson.plugins.checkstyle.CheckStyleResultAction;
^
The build.gradle
apply plugin: 'groovy'
repositories {
mavenCentral()
maven {
url "http://repo.jenkins-ci.org/releases/"
}
}
configurations {
ivy
}
sourceSets {
main {
groovy {
srcDirs = ['src/']
}
}
test {
groovy {
srcDirs = ['test/']
}
}
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.11'
compile "org.apache.ivy:ivy:2.4.0"
ivy "org.apache.ivy:ivy:2.3.0"
// Works
compile 'org.eclipse.hudson:hudson-core:3.2.1'
// Does not work
compile 'org.jvnet.hudson.plugins:checkstyle:3.42'
}
tasks.withType(GroovyCompile) {
groovyClasspath += configurations.ivy
}
You're probably not actually downloading the jar you think you are. Looks like the default artifact that comes back from the org.jvnet.hudson.plugins:checkstyle:3.42 dependency is actually a file named checkstyle-3.42.hpi.
To get the jar which contains the classes instead, use:
compile group: 'org.jvnet.hudson.plugins', name: 'checkstyle', version:'3.42', ext: 'jar'
Then that class will be found on your classpath (and you'll be on to locating the next missing dependency).

Gradle Setting Manifest Class-Path on Jars in Ear

Set Up
I'm using Gradle and have a multi-project build using Java EE with IBM WebSphere Application Server. The project directory structure looks like this:
--/build.gradle
--/defaults.gradle
--/settings.gradle
--/common-ejb
--/common-ejb/build.gradle
--/logging
--/logging/build.gradle
--/project1
--/project1/build.gradle
--/project1-ejb
--/project1-ejb/build.gradle
--/project2
--/project2/build.gradle
--/project2-ejb
--/project2-ejb/build.gradle
project1 and project2 are individual ears that get deployed. They both reuse a number of EJBs from common-ejb and share some other library dependencies that aren't relevant for this question.
After performing the build: project1.ear looks like:
--/lib/log4j.jar
--/lib/logging.jar
--/META-INF/application.xml
--/META-INF/MANIFEST.MF
--/common-ejb.jar
--/project1-ejb.jar
Gradle properly creates the application.xml to load EJBs from both projects. Unfortunately, project1-ejb.jar will fail to load due to dependencies on common-ejb.jar. The project1-ejb.jar/META-INF/MANIFEST.MF needs to have the Class-Path set with common-ejb.jar since it's not in the lib/ directory.
I was able to set it by explicitly defining it as done below. Gradle knows the dependencies for the Class-Path, so it should be able do this automatically. Is there a way to set this up?
Gradle Files
Not including project2, but you can guess what it looks like.
--/build.gradle
apply from: 'defaults.gradle'
defaultTasks 'clean', 'build'
--/defaults.gradle
defaultTasks 'build'
repositories {
mavenCentral()
}
--/settings.gradle
include 'common-ejb'
include 'project1'
include 'project1-ejb'
include 'logging'
--/logging/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'log4j:log4j:1.2.+'
}
--/common-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':logging')
}
--/project1-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':common-ejb')
compile project(':logging')
}
// THIS IS THE WORKAROUND, I don't want to explicitly modify the Class-Path for each EJB based on the EAR the EJB is going to be included in.
jar {
manifest {
attributes("Class-Path": project(':common-ejb').jar.archiveName)
}
}
--/project1/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'ear'
apply plugin: 'java'
dependencies {
deploy project(':project1-ejb')
deploy project(':common-ejb')
earlib project(':logging')
}
Using some information from and modifying code from a question about getting all dependencies of a project from the Gradle forums.
Essentially, you want to take the EAR's deploy dependencies and see if any deploy dependencies depend on each other. If they do, you set the Class-Path to include the referenced jars.
Remove the manifest lines from project1-ejb and project2-ejb. Add the following to your defaults.gradle:
def getAllDependentProjects(project) {
if ( !project.configurations.hasProperty("runtime") ) {
return []
}
def projectDependencies = project.configurations.runtime.getAllDependencies().withType(ProjectDependency)
def dependentProjects = projectDependencies*.dependencyProject
if (dependentProjects.size > 0) {
dependentProjects.each { dependentProjects += getAllDependentProjects(it) }
}
return dependentProjects.unique()
}
gradle.projectsEvaluated {
if (plugins.hasPlugin('ear')) {
def deployProjectDependencies = configurations.deploy.getAllDependencies().withType(ProjectDependency)*.dependencyProject
deployProjectDependencies.each {
def cur = it
def cur_deps = getAllDependentProjects(cur)
def depJars = []
deployProjectDependencies.each {
def search = it
if ( cur_deps.contains(search)) {
depJars += search.jar.archiveName
}
}
depJars = depJars.unique()
if ( depJars.size() > 0 ) {
logger.info("META-INF Dependencies for deploy dependency " + cur.name + ": " + depJars)
cur.jar.manifest.attributes(
'Class-Path': depJars.join(' ')
)
}
}
}
}
This will have the desired affect. Directly after the configuration step and before build, the EAR projects will reevaluate their dependencies to see if any are cross-referenced. There may be a more efficient way, but this gets the job done.

Resources