Skip to content

JavaCPP and JPMS (Java Platform Module System)

HGuillemet edited this page Mar 7, 2022 · 3 revisions

TL;DR

  • Add Maven dependencies towards the needed presets using the -plaform artifacts.
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv-platform</artifactId>
            <version>4.5.1-1.5.5</version>
        </dependency>
  • requires the preset main (non-native) modules in your module-info.java.
requires org.bytedeco.opencv;
  • Build the project specifying the proper value for -Djavacpp.platform.
mvn -Djavacpp.platform=linux-x86_64 ...
  • Run the application with a module path obtained from the Maven dependencies and command line option --add-modules ALL-MODULE-PATH.
java -p ... --add-modules ALL-MODULE-PATH  -m ... 
  • Build the runtime image with Apache maven-jlink-plugin with the launcher option --add-modules=ALL-SYSTEM.

  • Package your runtime image using jpackage.

Class path and module path

When running a JPMS application, jar files are either loaded from the module path or the class path. However, classes contained in a jar loaded from the class path become member of the unnamed module, and classes from named modules cannot read classes from the unnamed module. So the class path is usually not suitable for containing Java classes. Classes in named modules can locate resources in the class path though. So JavaCPP native jar files, which contain native libraries as resources and no Java classes, could be located in the class path, but since only named module can be bundled in a jlink image, we recommend to load all JavaCPP jar files, be them native or java , from the module path.

Module graph

To load a resource or a Java class in a jar file located on the module path, the module must be brought into the module graph. The module graph is constructed by resolving module dependencies starting from some root module. The root module is usually the application module, specified by the -m launcher option, but other modules can be added as root modules using the --add-modules option. The module dependencies are specified in the module descriptor (module-info.java) as requires clauses.

Maven dependencies

Apart from the module graph, Maven and other build tools have their own dependency system to define the artifacts that are necessary to build your application.

Each preset has one main artifact, containing the Java classes, and a series of native sub-artifacts, called classifiers, one for each combination of platform and extension that are supported by the preset. Additionally, each preset uses a special artifact, suffixed by -platform, and optionally suffixed by an extension, like -gpu. The platform artifact is an empty shell, that only contains a pom.xml declaring Maven dependencies towards the platform artifacts of other presets, and towards all existing native classifiers of this preset. The native classifier dependencies can however be limited to one or several platforms using the Maven property javacpp.platform. This property affects all -platform artifacts in the dependency graph of your application. See this page for more details.

So by declaring a dependency towards the platform artifacts of the presets your application uses and by specifying the javacpp.platform property (and possibly the javacpp.platform.extension property) on maven command line, Maven is able to construct the list of all necessary and sufficient artifacts.

You must avoid declaring a Maven dependency directly towards the main preset artifact. Suppose you depend on preset A. If you declare dependency to A. You must also declare a dependency to A with native classifier (A and A with classifier share the same dependencies and do not automatically depend on each other). Furthermore if A depends on preset B, B main artifact will be automatically brought into the dependency graph, but not B artifact with the native classifier. If you declare a dependency towards artifact A-platform instead and specify javacpp.platform, all 4 artifacts will be brought into the dependency graph.

JavaCPP modules

All JavaCPP and presets jar files are named modules. The main JavaCPP module, bundled in javacpp-1.5.5.jar is called org.bytedeco.javacpp. The presets module are called after the presets name, for instance org.bytedeco.opencv. The native modules have the same name, but with up to three suffixes appended :

  • one for the system, eg linux,
  • one for the architecture, eg x86_64,
  • one for the optional extension, eg gpu. Thus opencv-4.5.2-1.5.6-linux-x86_64-gpu.jar contains module org.bytedeco.opencv.linux.x86_64.gpu JavaCPP module itself also has native counterparts, called for instance org.bytedeco.javacpp.linux.x86_64. The platform artifacts are also modules, named for instance org.bytedeco.opencv.platform but these are normally not needed in the module graph.

Some dependencies are declared in main module descriptors, for instance org.bytedeco.opencv requires org.bytedeco.openblas. All preset main modules also require org.bytedeco.javacpp transitively, which means an application already requiring OpenCV preset doesn’t need to explicitly declare a dependency towards JavaCPP in order to read classes defined in package org.bytedeco.javacpp.

However, native module descriptors do not declare any dependencies. The main reason for this is that a module depending on an another module which exists in several version with different extensions (eg, gpu or cpu) does not know at its building time which of these versions will be needed. All native modules required by an application must then be added to the module graph, either using the application module descriptor, or using an --add-modules launch option.

Running your JPMS application

Assuming your build system has gathered all dependencies for compiling your application, you now need to build the module path for runtime. In most cases, the module path is the same for building and running, so you would like to use the path built from Maven dependencies as a runtime module path. You have several options:

  • Use an IDE to run your application. The IDE automatically reuses the building module path for running, but you must ensure that it imports the dependencies from Maven using the proper javacpp.platform property. In IDEA IntelliJ, this can be configured from the Build Tools / Maven / Importing settings panel by specifying, eg, -Djavacpp.platform=linux-x86_64, in « VM Option for Importer ».
  • Ask Maven to run your application using the exec:java goal of the Exec Maven plugin. The project's dependencies will be used as module path.
  • Use the JavaFX Maven plugin and its javafx:run goal. By default, this plugin builds a module path with the maven dependencies containing modules transitively required by your application, and move all remaining dependencies to the class path.
  • Write your own launch script and specify the -p command line option from the list of maven dependencies that can you retrieve with the dependency plugin: mvn dependency:build-classpath -Djavacpp.platform=linux-x86_64.

In addition to specifying the module path, you must bring any JavaCPP native modules listed in the module path to the module graph. You can explicitly list the required module in your module-info.java, but it’s easier to rely again on the maven dependency mechanism and the javacpp.platform property and add all the module path to the module graph with option --add-modules ALL-MODULE-PATH. This is not required if the native artifacts are listed in the class path.

Building a JLink image

To build a JLink image, we recommend the Apache Maven JLink plugin. It uses the Maven dependencies to construct the list of modules to include in the image. You simply need to include in the launcher script the command line option --add-modules=ALL-SYSTEM to bring into the module graph all the modules included in the image. This can be configured as an option of the JLink plugin. A sample project using this mechanism is available here.

Packaging

JPackage can then be used to build a platform-specific package from your JLink runtime image. Note that jpackage can call jlink itself, but you won't benefit from the Apache JLink plugin feature that automatically populates the image from Maven depenencies. So you must configure jpackage to skip this step and build a package image from the jlink image and then a package from the package image. Akman JPackage Maven plugin provides a practical Maven interface to jpackage. This issue has been filed. If accepted, the JPackage maven plugin could be used also for the runtime image creation.