|
Serban Iordache |
Adapted from: Marc Reinhold (https://twitter.com/mreinhold/status/882644292036026368)
Adapted from: Marc Reinhold (https://twitter.com/mreinhold/status/882644292036026368)
Adapted from: Marc Reinhold (https://twitter.com/mreinhold/status/882644292036026368)
package org.example.jpms;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Product {
private int id;
private String name;
private double price; // Using double for money is actually a bad idea
public Product() {
this(-1, "", 0);
}
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return name + ": " + price;
}
}
package org.example.jpms;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class XMLPrinter {
public static void main(String[] args) throws Exception {
Product product = new Product(100, "pizza", 3.25);
JAXBContext jaxbContext = JAXBContext.newInstance(Product.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(product, System.out);
}
}
package org.example.jpms;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class ProductTest {
@Test
public void testToString() {
Product p = new Product(33, "spaghetti", 2.15);
Assertions.assertEquals("spaghetti: 2.15", p.toString());
}
}
plugins {
id 'application'
}
repositories {
jcenter()
}
mainClassName = 'org.example.jpms.XMLPrinter'
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
Module name | Package | Maven artifact |
---|---|---|
java.activation |
javax.activation.* |
com.sun.activation:javax.activation:1.2.0 |
java.corba |
javax.activity.* |
org.jboss.openjdk-orb:openjdk-orb:8.1.2.Final |
java.transaction |
javax.transaction.* |
javax.transaction:javax.transaction-api:1.3 |
java.xml.bind |
javax.xml.bind.* |
org.glassfish.jaxb:jaxb-runtime:2.3.2 |
java.xml.ws |
javax.xml.ws.* |
com.sun.xml.ws:jaxws-ri:2.3.2 |
java.xml.ws.annotation |
javax.annotation.* |
javax.annotation:javax.annotation-api:1.3.2 |
plugins {
id 'application'
}
repositories {
jcenter()
}
mainClassName = 'org.example.jpms.XMLPrinter'
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
plugins {
id 'application'
}
repositories {
jcenter()
}
mainClassName = 'org.example.jpms.XMLPrinter'
dependencies {
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
module org.example.mylib {
requires java.desktop;
requires transitive java.sql;
}
module org.example.mylib {
requires java.desktop;
requires transitive java.sql;
exports org.example.mylib.core;
exports org.example.mylib.impl to org.example.gui;
}
module org.example.mylib {
requires java.desktop;
requires transitive java.sql;
exports org.example.mylib.core;
exports org.example.mylib.impl to org.example.gui;
opens org.example.mylib.model;
opens org.example.mylib.impl to java.xml.bind;
}
module org.example.mylib {
requires java.desktop;
requires transitive java.sql;
exports org.example.mylib.core;
exports org.example.mylib.impl to org.example.gui;
opens org.example.mylib.model;
opens org.example.mylib.impl to java.xml.bind;
uses javax.annotation.processing.Processor;
provides org.example.mylib.Solver with org.example.mylib.impl.MySolver;
}
Options for the javac
and java
tools:
--add-exports module/package=other-module(,other-module)*
--add-reads module=other-module(,other-module)*
--add-opens module/package=target-module(,target-module)*
Specifying the classpath and the module-path with the javac and java tools:
classpath | module-path |
---|---|
|
|
You can mix --class-path
and --module-path
.
All code on the classpath is part of the unnamed module, which:
Non-modular JARs found on the module-path are turned into automatic modules.
A module descriptor is generated on the fly
requires transitive
all other resolved modulesPackages are not allowed to span different modules.
Dealing with split packages using the javac
and java
tools:--patch-module module=file(:file)*
Merges all types from a list of artifacts into the given module.
java
--module-path ...
--add-modules ...
--patch-module java.xml.ws.annotation=/path/to/my/lib/jsr305-3.0.2.jar
--module org.example.hello/org.example.hello.HelloWorld
module org.example.jpms {
requires java.xml.bind;
opens org.example.jpms to java.xml.bind;
}
package org.example.jpms;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class XMLPrinter {
public static void main(String[] args) throws Exception {
Product product = new Product(100, "pizza", 3.25);
JAXBContext jaxbContext = JAXBContext.newInstance(Product.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(product, System.out);
System.out.printf("\n\nRunning module: %s\n",
XMLPrinter.class.getModule().getName());
}
}
package org.example.jpms;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class ProductTest {
@Test
public void testToString() {
Product p = new Product(33, "spaghetti", 2.15);
Assertions.assertEquals("spaghetti: 2.15", p.toString());
System.out.printf("\n\nTesting module: %s\n",
ProductTest.class.getModule().getName());
}
}
compileJava {
doFirst {
options.compilerArgs = ['--module-path', classpath.asPath]
classpath = files()
}
}
run {
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--add-modules', moduleName
]
classpath = files()
}
}
compileTestJava {
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'org.junit.jupiter.api',
'--add-reads', "$moduleName=org.junit.jupiter.api",
'--patch-module',
"$moduleName=" + files(sourceSets.test.java.srcDirs).asPath,
]
classpath = files()
}
}
test {
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--add-modules', 'ALL-MODULE-PATH',
'--add-reads', "$moduleName=org.junit.jupiter.api",
'--patch-module',
"$moduleName=" + files(sourceSets.test.java.outputDir).asPath,
'--add-opens',
"$moduleName/org.example.jpms=org.junit.platform.commons"
]
classpath = files()
}
}
import java.util.regex.Matcher
startScripts {
doFirst {
defaultJvmOpts = [
'--module-path', 'LIB_DIR_PLACEHOLDER',
'--add-modules', moduleName,
]
}
doLast{
def bashFile = new File(outputDir, applicationName)
bashFile.text = bashFile.text.replaceFirst('LIB_DIR_PLACEHOLDER',
Matcher.quoteReplacement('$APP_HOME/lib'))
def batFile = new File(outputDir, applicationName + ".bat")
batFile.text = batFile.text.replaceFirst('LIB_DIR_PLACEHOLDER',
Matcher.quoteReplacement('%APP_HOME%\\lib'))
}
}
plugins {
id 'application'
id "org.javamodularity.moduleplugin" version "1.5.0"
}
repositories {
jcenter()
}
mainClassName = 'org.example.jpms/org.example.jpms.XMLPrinter'
dependencies {
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
1. compile all source files except module-info.java
--release
option2. compile module-info.java
--release
option--module-path
option3. create a JAR containing the class files produced in steps 1 and 2.
Maven: configure the pom.xml as shown in the documentation.
Gradle: use the org.javamodularity.moduleplugin
plugin.
plugins {
id 'java'
id "org.javamodularity.moduleplugin" version "1.5.0"
}
modularity.mixedJavaRelease 8
repositories {
jcenter()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
assembles and optimizes a set of modules and their dependencies into a custom runtime image
public class HelloWorldVerticle extends AbstractVerticle {
@Override
public void start(Future<Void> future) {
vertx.createHttpServer()
.requestHandler(r -> {
String name = Optional.ofNullable( r.getParam( "name" ))
.orElse( "nameless stranger" );
r.response().end ( "Hello, " + name + "!" );
})
.listen( 8080, result -> {
if ( result.succeeded() ) { future.complete(); }
else { future.fail(result.cause()); }
} );
}
}
package com.example;
import io.vertx.core.Vertx;
public class HelloWorldServer {
public static void main(final String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle( HelloWorldVerticle.class.getName() );
}
}
module com.example {
requires vertx.core;
exports com.example;
}
plugins {
id 'application'
id "org.javamodularity.moduleplugin" version "1.5.0"
}
repositories { jcenter() }
dependencies {
implementation "io.vertx:vertx-core:3.5.0"
}
mainClassName = "com.example/com.example.HelloWorldServer"
run.jvmArgs = ["--add-modules", "jdk.unsupported",
"--add-reads", "netty.common=jdk.unsupported"]
The ModiTect approach:
for each non-modularized artifact in the dependency graph:
ModiTect can be used with both Maven and Gradle.
<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.moditect</groupId>
<artifactId>moditect-integrationtest-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>moditect-integrationtest-vertx</artifactId>
<packaging>jar</packaging>
<name>ModiTect Integration Test - Vert.x</name>
<properties>
<io.vertx.version>3.5.0</io.vertx.version>
<io.netty.version>4.1.15.Final</io.netty.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${io.vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>9</release>
<compilerArgs>
<compilerArg>--module-path</compilerArg>
<compilerArg>${project.build.directory}/modules</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<executions>
<execution>
<id>add-module-info-to-dependencies</id>
<phase>package</phase>
<configuration>
<outputDirectory>
${project.build.directory}/modules
</outputDirectory>
<modules>
<module>
<artifact>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</artifact>
<moduleInfo>
<name>com.fasterxml.jackson.core</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</artifact>
<moduleInfo>
<name>com.fasterxml.jackson.annotations</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</artifact>
<moduleInfo>
<name>com.fasterxml.jackson.databind</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.common</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.buffer</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.codec</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-resolver</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.resolver</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.transport</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-codec-dns</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.codec.dns</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http2</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.codec.http2</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.resolver.dns</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
<version>${io.netty.version}</version>
</artifact>
<moduleInfo>
<name>io.netty.channel.unix</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</artifact>
<moduleInfoSource>
module io.netty.channel.epoll {
requires io.netty.buffer;
requires io.netty.channel.unix;
requires io.netty.common;
requires io.netty.transport;
exports io.netty.channel.epoll;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-kqueue</artifactId>
</artifact>
<moduleInfoSource>
module io.netty.channel.kqueue {
exports io.netty.channel.kqueue;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</artifact>
<moduleInfoSource>
module io.netty.handler {
exports io.netty.handler.flow;
exports io.netty.handler.flush;
exports io.netty.handler.ipfilter;
exports io.netty.handler.logging;
exports io.netty.handler.ssl;
exports io.netty.handler.ssl.ocsp;
exports io.netty.handler.ssl.util;
exports io.netty.handler.stream;
exports io.netty.handler.timeout;
exports io.netty.handler.traffic;
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-codec-socks</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.codec.socks</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-handler-proxy</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.handler.proxy</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</artifact>
<moduleInfo>
<name>io.netty.codec.http</name>
</moduleInfo>
</module>
<module>
<artifact>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</artifact>
<moduleInfo>
<name>io.vertx.core</name>
<requires>
static log4j.api;
static log4j;
static slf4j.api;
*;
</requires>
<exports>
!*impl*;
*;
</exports>
<uses>
io.vertx.core.spi.VertxFactory;
io.vertx.core.spi.VerticleFactory;
io.vertx.core.spi.FutureFactory;
io.vertx.core.spi.BufferFactory;
</uses>
</moduleInfo>
</module>
</modules>
<module>
<mainClass>com.example.HelloWorldServer</mainClass>
<moduleInfo>
<name>com.example</name>
<exports>
com.example to io.vertx.core;
</exports>
</moduleInfo>
</module>
</configuration>
<goals>
<goal>add-module-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<!-- Builds a modular runtime image -->
<profile>
<id>jlink</id>
<build>
<plugins>
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<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>com.example</module>
</modules>
<launcher>
<name>helloWorld</name>
<module>com.example</module>
</launcher>
<compression>2</compression>
<stripDebug>true</stripDebug>
<outputDirectory>
${project.build.directory}/jlink-image
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
maven {
url 'https://jitpack.io'
}
}
dependencies {
classpath "org.moditect:moditect-gradle-plugin:1.0.0-beta2"
}
}
plugins{
id "java"
id "application"
}
apply plugin: "org.moditect.gradleplugin"
repositories {
mavenCentral()
}
group = "org.moditect"
version = "2.0.0"
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_11
ext {
moduleName = 'com.example'
vertxVersion = '3.5.0'
nettyVersion = '4.1.15.Final'
jacksonVersion ='2.9.0'
}
mainClassName = 'com.example.HelloWorldServer'
jar {
manifest {
attributes("Automatic-Module-Name": moduleName)
}
}
dependencies{
compile "io.vertx:vertx-core:$vertxVersion"
}
moditect {
addMainModuleInfo {
version = project.version
overwriteExistingFiles = false
jdepsExtraArgs = ['-q']
module {
mainClass = mainClassName
moduleInfo {
name = moduleName
exports = 'com.example to io.vertx.core;'
}
}
}
addDependenciesModuleInfo {
jdepsExtraArgs = ['-q']
modules {
module {
artifact "com.fasterxml.jackson.core:jackson-core:$jacksonVersion"
moduleInfo {
name = 'com.fasterxml.jackson.core'
}
}
module {
artifact "com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion"
moduleInfo {
name = 'com.fasterxml.jackson.annotations'
}
}
module {
artifact "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
moduleInfo {
name = 'com.fasterxml.jackson.databind'
}
}
module {
artifact "io.netty:netty-common:$nettyVersion"
moduleInfo {
name = 'io.netty.common'
}
}
module {
artifact "io.netty:netty-buffer:$nettyVersion"
moduleInfo {
name = 'io.netty.buffer'
}
}
module {
artifact "io.netty:netty-codec:$nettyVersion"
moduleInfo {
name = 'io.netty.codec'
}
}
module {
artifact "io.netty:netty-resolver:$nettyVersion"
moduleInfo {
name = 'io.netty.resolver'
}
}
module {
artifact "io.netty:netty-transport:$nettyVersion"
moduleInfo {
name = 'io.netty.transport'
}
}
module {
artifact "io.netty:netty-codec-dns:$nettyVersion"
moduleInfo {
name = 'io.netty.codec.dns'
}
}
module {
artifact "io.netty:netty-codec-http2:$nettyVersion"
moduleInfo {
name = 'io.netty.codec.http2'
}
}
module {
artifact "io.netty:netty-resolver-dns:$nettyVersion"
moduleInfo {
name = 'io.netty.resolver.dns'
}
}
module {
artifact "io.netty:netty-transport-native-unix-common:$nettyVersion"
moduleInfo {
name = 'io.netty.channel.unix'
}
}
module {
artifact "io.netty:netty-transport-native-epoll:$nettyVersion"
moduleInfo {
name = 'io.netty.channel.epoll'
}
}
module {
artifact "io.netty:netty-transport-native-kqueue:$nettyVersion"
moduleInfo {
name = 'io.netty.channel.kqueue'
}
}
module {
artifact "io.netty:netty-handler:$nettyVersion"
moduleInfoSource = '''
module io.netty.handler {
exports io.netty.handler.flow;
exports io.netty.handler.flush;
exports io.netty.handler.ipfilter;
exports io.netty.handler.logging;
exports io.netty.handler.ssl;
exports io.netty.handler.ssl.ocsp;
exports io.netty.handler.ssl.util;
exports io.netty.handler.stream;
exports io.netty.handler.timeout;
exports io.netty.handler.traffic;
}
'''
}
module {
artifact "io.netty:netty-codec-socks:$nettyVersion"
moduleInfo {
name = 'io.netty.codec.socks'
}
}
module {
artifact "io.netty:netty-handler-proxy:$nettyVersion"
moduleInfo {
name = 'io.netty.handler.proxy'
}
}
module {
artifact "io.netty:netty-codec-http:$nettyVersion"
moduleInfo {
name = 'io.netty.codec.http'
}
}
module {
artifact "io.vertx:vertx-core:$vertxVersion"
moduleInfo {
name = 'io.vertx.core'
requires = '''
static log4j.api;
static log4j;
static slf4j.api;
*;
'''
exports = '''
!*impl*;
*;
'''
uses = '''
io.vertx.core.spi.VertxFactory;
io.vertx.core.spi.VerticleFactory;
io.vertx.core.spi.FutureFactory;
io.vertx.core.spi.BufferFactory;
'''
}
}
}
}
createRuntimeImage {
outputDirectory = file("$buildDir/jlink-image")
modules = ['com.example']
launcher {
name = 'helloWorld'
module = 'com.example'
}
compression = 2
stripDebug = true
}
}
The "badass" approach
This approach is taken by the badass-jlink Gradle plugin.
plugins {
id 'application'
id "org.javamodularity.moduleplugin" version "1.5.0"
id 'org.beryx.jlink' version '2.10.3'
}
repositories { jcenter() }
dependencies {
implementation "io.vertx:vertx-core:3.5.0"
}
mainClassName = "com.example/com.example.HelloWorldServer"
run.jvmArgs = ["--add-modules", "jdk.unsupported",
"--add-reads", "netty.common=jdk.unsupported"]
jlink {
options = ['--strip-debug', '--compress', '2',
'--no-header-files', '--no-man-pages']
mergedModule {
additive = true
uses 'io.vertx.core.spi.VertxFactory'
uses 'io.vertx.core.spi.VerticleFactory'
uses 'io.vertx.core.spi.FutureFactory'
uses 'io.vertx.core.spi.BufferFactory'
}
}
jpackage
javapackager
tool (removed in OpenJDK 11)The badass-jlink plugin provides a jpackage
task for creating a platform-specific application installer.
public class HelloFX extends Application {
@Override
public void start(Stage stage) {
Label label = new Label("Hello, jPrime!");
label.setFont(Font.font(48));
StackPane pane = new StackPane(label);
Scene scene = new Scene(pane, 480, 320);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
module org.example.jpms.hellofx {
requires javafx.controls;
opens org.example.jpms.hellofx to javafx.graphics;
}
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.7'
id 'org.beryx.jlink' version '2.10.3'
}
repositories { jcenter() }
javafx {
modules = ['javafx.controls']
}
mainClassName = "org.example.jpms.hellofx/org.example.jpms.hellofx.HelloFX"
jlink {
options = ['--strip-debug', '--compress', '2',
'--no-header-files', '--no-man-pages']
launcher { name = 'helloFX' }
jpackage {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
installerOptions = ['--win-per-user-install', '--win-dir-chooser']
}
}
}
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.7'
id 'org.beryx.jlink' version '2.10.3'
}
repositories { jcenter() }
javafx {
modules = ['javafx.controls']
}
mainClassName = "org.example.jpms.hellofx/org.example.jpms.hellofx.HelloFX"
jlink {
options = ['--strip-debug', '--compress', '2',
'--no-header-files', '--no-man-pages']
launcher { name = 'helloFX' }
jpackage {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
installerOptions = ['--win-per-user-install', '--win-dir-chooser']
}
}
}
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.7'
id 'org.beryx.jlink' version '2.10.3'
}
repositories { jcenter() }
javafx {
modules = ['javafx.controls']
}
mainClassName = "org.example.jpms.hellofx/org.example.jpms.hellofx.HelloFX"
jlink {
options = ['--strip-debug', '--compress', '2',
'--no-header-files', '--no-man-pages']
launcher { name = 'helloFX' }
jpackage {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
installerOptions = ['--win-per-user-install', '--win-dir-chooser']
}
}
}
Approach:
This approach is taken by the badass-runtime Gradle plugin.
plugins {
id 'application'
id "org.beryx.runtime" version "1.1.6"
}
repositories {
jcenter()
}
dependencies {
implementation "io.vertx:vertx-core:3.5.0"
}
mainClassName = "com.example.HelloWorldServer"
runtime {
options = ['--strip-debug', '--compress', '2',
'--no-header-files', '--no-man-pages']
modules = ['java.naming', 'java.compiler', 'java.logging', 'jdk.unsupported']
}
Code and slides of this talk: https://github.com/beryx/talk-jprime-2019
Gradle
Maven