I don't know how to add dependency. My module needs Log4j. I added requirement to module info. I added also to gradle dependencies. I can run project, but I can't create custom runtime image.
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.5'
}
group 'eu.sample'
version '2.0'
repositories {
mavenCentral()
}
javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = "$moduleName/eu.sample.app.Main"
def lin_java_home = hasProperty('org.gradle.java.home') ? getProperty('org.gradle.java.home') : System.getenv('JAVA_HOME')
def lin_fx_jmods = hasProperty('linux.fx.mods') ? getProperty('linux.fx.mods') : System.getenv('PATH_TO_FX_MODS_LIN')
def win_java_home = hasProperty('windows.java.home') ? getProperty('windows.java.home') : System.getenv('JAVA_HOME_WIN')
def win_fx_jmods = hasProperty('windows.fx.mods') ? getProperty('windows.fx.mods') : System.getenv('PATH_TO_FX_MODS_WIN')
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1'
}
task jlink(type: Exec) {
dependsOn 'clean'
dependsOn 'jar'
workingDir 'build'
if (lin_java_home == null) {
throw new RuntimeException("java_home is not defined.")
}
if (lin_fx_jmods == null) {
throw new RuntimeException("fx_jmods is not defined.")
}
commandLine "${lin_java_home}/bin/jlink", '--module-path', "libs${File.pathSeparatorChar}${lin_fx_jmods}",
'--add-modules', "${moduleName}", '--output', "${moduleName}", '--strip-debug',
'--compress', '2', '--no-header-files', '--no-man-pages'
}
task jlinkWin(type: Exec) {
dependsOn 'clean'
dependsOn 'jar'
workingDir 'build'
if (win_java_home == null) {
throw new RuntimeException("java_home is not defined.")
}
if (win_fx_jmods == null) {
throw new RuntimeException("fx_jmods is not defined.")
}
commandLine "${lin_java_home}/bin/jlink", '--module-path',
"${win_java_home}/jmods${File.pathSeparatorChar}libs${File.pathSeparatorChar}${win_fx_jmods}",
'--add-modules', "${moduleName}", '--output', "${moduleName}", '--strip-debug',
'--compress', '2', '--no-header-files', '--no-man-pages'
}
When I fire task jlink I get:
Error: Module org.apache.logging.log4j not found, required by
app
I checked libs directory in build and there is not log4j jar. How to tell gradle to add dependencies to jlink task?
Problem
This is what you have in your jlink
task:
'--module-path', "libs${File.pathSeparatorChar}${fx_jmods}"
Whats means that you are adding the dependencies from:
libs
: basically the jar of your module helloFX
. This is a result of the jar
task, that only includes the classes and resources of your project, but not its dependencies.
fx_jmods
: That is the path to the JavaFX jmods.
But when you run, you get this error:
Error: Module org.apache.logging.log4j not found, required by app
The error means that the module-path
for the jlink
command is not complete, and it can't resolve all the required dependencies. As mentioned above, we are only including the module.jar and the JavaFX (jmods) jars, but not the log4j.jar
.
So we need to find a way to add that jar to the module-path.
There are a few possible solutions to include third party dependencies in your custom image.
Solution 1
We have to modify the jlink
task, to include the existing dependencies in our runtime configuration.
The most immediate solution is finding where the logj4 jars are stored by gradle, in the local .gradle repository.
'--module-path', "libs${File.pathSeparatorChar}${fx_jmods}: \
/Users/<user>/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.11.1/268..a10/log4j-api-2.11.1.jar: \
/Users/<user>/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.11.1/59..e4/log4j-core-2.11.1.jar"
While this solution works, it is not the most convenient, of course, as it depends on local paths from the user.
Solution 2
A better solution, can be done adding a task to copy the runtime dependencies, that are resolved directly by gradle, into the libs
folder like:
task libs(type: Copy) {
into 'build/libs/'
from configurations.runtime
}
and then calling this task from the jlink
task:
task jlink(type: Exec) {
dependsOn 'clean'
dependsOn 'jar'
dependsOn 'libs'
...
}
If you run ./gradlew jlink
and check the libs
folder you should find something like this:
build/libs/hellofx.jar
build/libs/javafx-base-11.0.1.jar
build/libs/javafx-base-11.0.1-$platform.jar
build/libs/javafx-graphics-11.0.1.jar
build/libs/javafx-graphics-11.0.1-$platform.jar
build/libs/javafx-controls-11.0.1.jar
build/libs/javafx-controls-11.0.1-$platform.jar
build/libs/javafx-fxml-11.0.1.jar
build/libs/javafx-fxml-11.0.1-$platform.jar
build/libs/log4j-api-2.11.1.jar
build/libs/log4j-core-2.11.1.jar
where $platform
is your running platform.
Notice that the libs
folder now contains all the dependencies that are required for the module path, and also that the JavaFX-*-$platform jars contain the native libraries, therefore, the jmods are not needed anymore in the module-path option. This will be enough:
'--module-path', "libs"
so your command line will be:
commandLine "${java_home}/bin/jlink", '--module-path', "libs",
'--add-modules', "${moduleName}", '--output', "${moduleName}", '--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'
You can run your jlink
task now successfully.
Solution 3
As suggested by @madhead comment, you can use another plugin for the jlink
task: the so called badass-jlink-plugin.
Modify your build to something like:
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.5'
id 'org.beryx.jlink' version '2.1.8'
}
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1'
}
javafx {
modules = ['javafx.controls', 'javafx.fxml']
}
mainClassName = "${moduleName}/eu.sample.app.Main"
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'helloFX'
}
}
Note also that the plugin has an option to create image for other platforms (see targetPlatform).
EDIT about log4j
As mentioned in the comments, the log4j
dependency doesn't play well with modules yet. There is an open issue at the Apache Issue tracker:
Currently based on the given automatic modules you can't use log4j-core in a project where you like to use jlink to produce a runtime image.
I've added this to my main class:
LogManager.getLogger(MainApp.class).info("hellofx!");
and I've added the log4j2.xml
file to src/main/resources
.
Running ./gradlew run
, works:
> Task :run
18:24:26.362 [JavaFX Application Thread] INFO eu.sample.app.Main - hellofx!
But, running jlink from Solution 2, creates the custom image, I can verify that the xml file is included, but when running from the image I get:
build/hellofx/bin/java -m hellofx/eu.sample.app.Main
ERROR StatusLogger Log4j2 could not find a logging implementation. \
Please add log4j-core to the classpath. Using SimpleLogger to log to the console...
And as mentioned, running jlink with the plugin from Solution 3 fails at the createMergedModule
task:
error: package org.apache.logging.log4j.spi is not visible
provides org.apache.logging.log4j.spi.Provider with org.apache.logging.log4j.core.impl.Log4jProvider;
[See EDIT(2), this has been fixed since version 2.1.9]
Alternative
At this point, a possible alternative could be use Slf4j instead. There is a sample listed from the plugin documentation that uses it successfully.
In summary this is required:
Gradle file:
dependencies {
compile 'org.slf4j:slf4j-api:1.8.0-beta2'
compile('ch.qos.logback:logback-classic:1.3.0-alpha4') {
exclude module: "activation"
}
}
Module-info:
requires org.slf4j;
requires ch.qos.logback.classic;
requires java.naming;
Main class:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(MainApp.class);
...
logger.info("hellofx!");
logging.properties
from here and logback.xml
from here, with your main class.
Both running ./gradlew
run or ./gradlew jlink
, and build/image/bin/HelloFX
work, and the message is logged to the console.
EDIT (2)
After reporting the issue to the badass-jlink-plugin's issue tracker, this was solved, and since version 2.1.19 it should work fine creating a custom image.
Since log4j
is multi-release jar, there is one thing required:
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.5'
id 'org.beryx.jlink' version '2.1.9'
}
...
jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'helloFX'
}
forceMerge('log4j-api') // <---- this is required for Log4j
}
See a full working sample here.