Is there a way to add maven dependencies while using the maven-jlink-plugin?
This has not much to do with the plugin I believe. Module joda.time
in your case seems to be an automatic module.
The jlink
tool does not support linking of automatic modules because they can rely on the arbitrary content of the classpath, which goes against the idea of a self-contained Java runtime.
So there are two ways to fix this probably :-
(you don't own the jar) Temporarily go ahead create a
module-info.java
[you could usejdeps
tool for it] and update the jar[usingjar
tool] with the corresponding compiled class as in projects under Java 9.(you own the dependency) Permanently migrate the jar to Java 9 itself, where it would consist of the
module-info.class
by itself after being compiled and packaged.
Maven javafx:jlink. How to specifiy the module-path used?
Specifically to answer your question:
How to specify the module-path used?
You could run jlink from the command-line instead of via Maven.
That way you can specify any module path you wish.
From the man page for jlink:
jlink [options] --module-path modulepath --add-modules module [, module...]
modulepath
The path where the jlink tool discovers observable modules. These modules can be modular JAR files, JMOD files, or exploded modules.
module
The names of the modules to add to the runtime image. The jlink tool adds these modules and their transitive dependencies.
If you wish to continue using the openjfx maven plugin jlink command, you can do that.
The code which configures the module path passed to jlink by the maven plugin is in git.
if (modulepathElements != null && !modulepathElements.isEmpty()) {
commandArguments.add(" --module-path");
String modulePath = StringUtils.join(modulepathElements.iterator(), File.pathSeparator);
if (jmodsPath != null && ! jmodsPath.isEmpty()) {
getLog().debug("Including jmods from local path: " + jmodsPath);
modulePath = jmodsPath + File.pathSeparator + modulePath;
}
commandArguments.add(modulePath);
commandArguments.add(" --add-modules");
if (moduleDescriptor != null) {
commandArguments.add(" " + moduleDescriptor.name());
} else {
throw new MojoExecutionException("jlink requires a module descriptor");
}
}
This is based on the javafx:jlink options:
jmodsPath: When using a local JavaFX SDK, sets the path to the local JavaFX jmods
So if you put your module on the path specified by that option, it should be found.
Here are some other options.
Package your app as a non-modular app using jpackage
I think mipa's suggestion of using JPackageScriptFX is probably the best approach:
- github.com/dlemmermann/JPackageScriptFX
Create a new jar from binaries
- Unjar the JBibTex.jar.
- Add in the compiled module-info to unjared directory.
- Create a new jar, e.g. JBibTexModule.jar.
- Install that to your maven repository via a mvn install command.
- Add a maven dependency on JBibTexModule instead of JBibTex.
- Add any needed statements to your main app module-info file to use the library.
Then, I believe you are done, and it would build, link, and run ok.
Create a new jar from source
The jbibtex library is also in github, so you could create an issue or pull-request on the project to ask it to be modularized.
Also, you could:
- Clone the repository.
- Add the module-info.java.
- Build the jar file.
- Install it locally in your local maven repository.
- Then depend on the local version you have created.
Jlink - including a directory that contains a custom python script in a JavaFX application
Problems
The src/main/resources
directory only exists in your project sources. It does not exist in the build output, and it definitely does not exist in your deployment location. In other words, using:
var pyPath = Paths.get("src/main/resources/script/main.py").toAbsolutePath().toString();
Will only work when your working directory is your project directory. It's also reading the "wrong" main.py
resource, as the "correct" one will be in your target
directory. Additionally, resources are not files. You must access resources using the resource-lookup API. For example:
var pyPath = getClass().getResource("/script/main.py").toString();
And note src/main/resources
is not included in the resource name.
Executing the Script
But even after you correctly access the resource you still have a problem. Your script is a resource, which means it will be embedded in a JAR file or custom run-time image when deployed. I strongly doubt Python will know how to read and execute such a resource. This means you need to find a way to make the Python script a regular file on the host computer.
Potential Solutions
I can think of at least three approaches that may solve the problems described above. As I only have Windows, I can't promise the below examples will work on other platforms, or otherwise be easily translated for other platforms.
My examples don't include JavaFX, as I don't believe that's necessary to demonstrate how to include a Python script that is executed at runtime.
Here are some common aspects between all solutions.
module-info.java:
module com.example {}
main.py:
print("Hello from Python!")
1. Extract the Script at Runtime
One approach is to extract the Python script to a known location on the host computer at runtime. This is likely the most versatile solution, as it doesn't depend much on the way you deploy your application (jar, jlink, jpackage, etc.).
This example extracts the script to a temporary file, but you can use another location, such as an application-specific directory under the user's home directory. You can also code it to extract only if not already extracted, or only once per application instance.
I think this is the solution I would use, at least to start with.
Project structure:
| pom.xml
|
\---src
\---main
+---java
| | module-info.java
| |
| \---com
| \---example
| \---app
| Main.java
|
\---resources
\---scripts
main.py
Main.java:
package com.example.app;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
Path target = Files.createTempDirectory("sample-1.0").resolve("main.py");
try {
// extract resource to temp file
try (InputStream in = Main.class.getResourceAsStream("/scripts/main.py")) {
Files.copy(in, target);
}
String executable = "python";
String script = target.toString();
System.out.printf("COMMAND: %s %s%n", executable, script); // log command
new ProcessBuilder(executable, script).inheritIO().start().waitFor();
} finally {
// cleanup for demo
Files.deleteIfExists(target);
Files.deleteIfExists(target.getParent());
}
}
}
pom.xml:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>sample</artifactId>
<version>1.0</version>
<name>sample</name>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>18</maven.compiler.release>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.app.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jlink-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>default-cli</id>
<phase>package</phase>
<goals>
<goal>jlink</goal>
</goals>
</execution>
</executions>
<configuration>
<classifier>win</classifier>
</configuration>
</plugin>
<plugin>
<groupId>org.panteleyev</groupId>
<artifactId>jpackage-maven-plugin</artifactId>
<version>1.5.2</version>
<executions>
<execution>
<id>default-cli</id>
<phase>package</phase>
<goals>
<goal>jpackage</goal>
</goals>
</execution>
</executions>
<configuration>
<type>APP_IMAGE</type>
<runtimeImage>${project.build.directory}/maven-jlink/classifiers/win</runtimeImage>
<module>com.example/com.example.app.Main</module>
<destination>${project.build.directory}/image</destination>
<winConsole>true</winConsole>
</configuration>
</plugin>
</plugins>
</build>
</project>
Build the project with:
mvn package
And then execute the built application image:
> .\target\image\sample\sample.exe
COMMAND: python C:\Users\***\AppData\Local\Temp\sample-1.015076039373849618085\main.py
Hello from Python!
2. Place the Script in the "Application Directory"
Disclaimer: I don't know if doing this is a smart or even supported approach.
This solution makes use of --input
to place the script in the "application directory" of the application image. You can then get a reference to this directory by setting a system property via --java-options
and $APPDIR
. Note I tried to get this to work with the class-path, so as to not need the $APPDIR
system property, but everything I tried resulted in Class#getResource(String)
returning null
.
The application directory is the app
directory shown in this documentation.
As this solution places the Python script with the rest of the application image, which means it's placed in the installation location, you may be more likely to run into file permission issues.
Given the way I coded Main.java
, this demo must be executed only after packaging with jpackage
. I suspect there's a more robust way to implement this solution.
Project structure:
| pom.xml
|
+---lib
| \---scripts
| main.py
|
\---src
\---main
\---java
| module-info.java
|
\---com
\---example
\---app
Main.java
Main.java:
package com.example.app;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
String executable = "python";
String script = Path.of(System.getProperty("app.dir"), "scripts", "main.py").toString();
System.out.printf("COMMAND: %s %s%n", executable, script); // log command
new ProcessBuilder(executable, script).inheritIO().start().waitFor();
}
}
pom.xml:
(this is only the <configuration>
of the org.panteleyev:jpackage-maven-plugin
plugin, as everything else in the POM is unchanged from the first solution)
<configuration>
<type>APP_IMAGE</type>
<runtimeImage>${project.build.directory}/maven-jlink/classifiers/win</runtimeImage>
<input>lib</input>
<javaOptions>
<javaOption>-Dapp.dir=$APPDIR</javaOption>
</javaOptions>
<module>com.example/com.example.app.Main</module>
<destination>${project.build.directory}/image</destination>
<winConsole>true</winConsole>
</configuration>
Build the project with:
mvn package
And then execute the built application image:
> .\target\image\sample\sample.exe
COMMAND: python C:\Users\***\Desktop\sample\target\image\sample\app\scripts\main.py
Hello from Python!
3. Add the Script as Additional "App Content"
Disclaimer: Same as the disclaimer for the second solution.
This would make use of the --app-content
argument when invoking jpackage
. Unfortunately, I could not figure out how to configure this using Maven, at least not with the org.panteleyev:jpackage-maven-plugin
plugin. But essentially this solution would have been the same as the second solution above, but with --input
removed and --app-content lib/scripts
added. And a slight change to how the script Path
is resolved in code.
The --app-content
argument seems to put whatever directories/files are specified in the root of the generated application image. I'm not sure of a nice convenient way to get this directory, as the application image structure is slightly different depending on the platform. And as far as I can tell, there's no equivalent $APPDIR
for the image's root directory.
JavaFX Maven with moditect and Java 11
I have added a complete example for OpenJDK11 with OpenJFX 11 and auto generated modul-info.class with moditect plugin (Click here!)
Try this:
Note: The moduleName property must be the same as in the module info file
<?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>
<groupId>md</groupId>
<artifactId>test33</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>test33</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mainClass>md.test33.MainApp</mainClass>
<moduleName>md</moduleName>
</properties>
<organization>
<!-- Used as the 'Vendor' for JNLP generation -->
<name>Your Organisation</name>
</organization>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>11-ea+25</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- sets up the version of Java you are running and complines the Code -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release> <!-- or <release>10</release>-->
</configuration>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2.1</version> <!-- Use newer version of ASM -->
</dependency>
</dependencies>
</plugin>
<!-- used to make the program run -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>--module-path</argument>
<argument>
${project.build.directory}/modules
</argument>
<argument>--module</argument>
<argument>${moduleName}/${mainClass}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<outputDirectory>
${project.build.directory}/modules
</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<version>1.0.0.Beta1</version>
<executions>
<execution>
<id>create-runtime-image</id>
<phase>package</phase>
<goals>
<goal>create-runtime-image</goal>
</goals>
<configuration>
<modulePath>
<path>${project.build.directory}/modules</path>
</modulePath>
<modules>
<module>${moduleName}</module>
<!-- <module>javafx.controls</module>
<module>javafx.graphics</module> -->
</modules>
<launcher>
<name>test33</name>
<module>${moduleName}/${mainClass}</module>
</launcher>
<compression>2</compression>
<stripDebug>true</stripDebug>
<outputDirectory>${project.build.directory}/jlink-image</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/modules</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
modul-info.java
module md {
requires javafx.fxml;
requires javafx.controls;
requires javafx.graphics;
requires javafx.base;
opens md.test33;
}
Error: automatic module cannot be used with jlink: - Maven with JavaFX
The jlink
requires all dependencies to be modular. After generation, it generates a custom JRE image including the required modules. The ini4j
seems non-modular.
For non-modular dependencies, you can go with the old Classpath approach after getting the custom JRE which has been generated without non-modular ones.
Briefly, run jlink excluding the non-modulars than add the jar files of non-modulars to the generated JRE image. The modules method and Classpath method can be combined this way.
A bit of fiddling with maven plugins should do this automatically.
Example for ini4j
- Define some properties for convenience.
pom.xml
<properties>
<jlink-image-name>JUSBPlotter</jlink-image-name>
<ini4j-jar-name>ini4j.jar</ini4j-jar-name>
</properties>
- Disable
ini4j
frommodule-info.java
(It should be enable during development, only do this when you want to package the project)
module org.openjfx.JUSBPlotter {
requires javafx.controls;
requires javafx.fxml;
requires com.fazecast.jSerialComm;
//requires ini4j;
requires org.apache.commons.io;
opens org.openjfx.JUSBPlotter to javafx.fxml;
exports org.openjfx.JUSBPlotter;
}
- Configure
maven-dependency-plugin
to copy the jar file ofini4j
into thelib/
folder in jlink image.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
</execution>
</executions>
<configuration>
<artifactItems>
<!-- Copy ini4j jar into the jlink image -->
<artifactItem>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.4</version>
<type>jar</type>
<destFileName>${ini4j-jar-name}</destFileName>
</artifactItem>
</artifactItems>
<!-- Set output directory to lib folder in jlink image -->
<outputDirectory>${project.build.directory}/${jlink-image-name}/lib</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</plugin>
- Configure
jlink
launcher option in thejavafx-maven-plugin
in order to add the jar file of non-modularini4j
to the Classpath.
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<stripDebug>true</stripDebug>
<compress>2</compress>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<launcher>JUSBPlotter</launcher>
<jlinkImageName>JUSBPlotter</jlinkImageName>
<mainClass>org.openjfx.JUSBPlotter.Main</mainClass>
<!-- ini4j jar file will be copied to the {image-folder}/lib/ folder. The launcher script should have this option to add it to the classpath -->
<options>-cp ../lib/${init4j-jar-name}</options>
</configuration>
</plugin>
Run:
mvn clean javafx:jlink
mvn package
cd target/JUSBPlotter/bin
./JUSBPlotter
maven-dependeny-plugin
will copy the jar file when you run mvn package
. But the jlink image must be already generated. So run the mvn javafx:jlink
first. Then run mvn package
.
Refer here to see how I applied for sqlite-jdbc
in my project.
Related Topics
How to Test Abstract Class in Java with Junit
Alternative to Deprecated Getcelltype
Why Do Constructors in Java Not Have a Return Type
Maximum Size of a Method in Java 7 and 8
Java 8: How to Work with Exception Throwing Methods in Streams
Java Array with More Than 4Gb Elements
Why String.Replaceall() in Java Requires 4 Slashes "\\\\" in Regex to Actually Replace "\"
How to Compile Jrxml to Get Jasper
Difference Between Webdriver.Get() and Webdriver.Navigate()
If Parenthesis Has a Higher Precedence Then Why Is Increment Operator Solved First
Remove Duplicates from Arraylists
Concurrent Threads Adding to Arraylist at Same Time - What Happens
Eclipse Error ... Cannot Be Resolved to a Type