more options and extension for jmod command

This commit is contained in:
Jörg Prante 2024-11-07 17:48:36 +01:00
parent c814515f95
commit ac147ef72e
7 changed files with 223 additions and 81 deletions

View file

@ -1,18 +1,33 @@
# JLink Gradle plugin # JLink Gradle plugin
This project defines a Gradle plugin that enable the use of This project defines a Gradle plugin that enable the use of
[jlink](https://docs.oracle.com/javase/9/tools/jlink.htm) in Gradle builds. [jmod](https://docs.oracle.com/en/java/javase/21/docs/specs/man/jmod.html),
[jdeps](https://docs.oracle.com/en/java/javase/21/docs/specs/man/jdeps.html),
[jlink](ttps://docs.oracle.com/en/java/javase/21/docs/specs/man/jlink.html) and
[jpackage](ttps://docs.oracle.com/en/java/javase/21/docs/specs/man/jpackage.html) and
in Gradle builds on Linux.
In general, the plugin enables developers to create Java Runtimes with jlink. In general, the plugin enables developers to create Java Runtimes with jlink.
It uses the java toolchain to locate the `jlink` executable and creates It uses the java toolchain to locate the `jlink` executable and creates
a java runtime for your module path based Java application. This a java runtime for your module path based Java application. This
modularization allows small, secure and tailored application distributions. modularization allows small, secure and tailored application distributions.
## Jlink plugin usage ## Plugin usage
This Gradle plugin integrates with the [Gradle Java Library plugin](https://docs.gradle.org/current/userguide/java_library_plugin.html) This Gradle plugin integrates with the [Gradle Java Library plugin](https://docs.gradle.org/current/userguide/java_library_plugin.html)
to create distributions automatically using a jlink runtime rather than a user provided JDK runtime. to create distributions automatically using a modular runtime rather than a user provided JDK runtime.
It applies the `org.xbib.gradle.plugin.jlink` plugin which configures a `jlink` task that executes the jlink tool. For this, it runs `jmod`, `jdeps`, `jlink` or `jpackage` in order to check and very module
dependencies on the Gradle runtime dependencies in an automatic way with minimum parameters to specify
`jmod` downloads all runtime depency jars and transforms them into the jmod file specification,
so `jdeps`
`jlink` creates a directory with a modular runtime (which can be passed to container builds)
while `jpackage` relies on Linux packaging tools to create an `RPM` or `DEB` package for
distribution.
Here is a minimal example to build a
```groovy ```groovy
plugins { plugins {

View file

@ -39,8 +39,6 @@ public class JLinkExtension {
private final ListProperty<String> includeLocales; private final ListProperty<String> includeLocales;
private final Property<String> moduleVersion;
public enum Endian { public enum Endian {
LITTLE, LITTLE,
BIG, BIG,
@ -62,7 +60,6 @@ public class JLinkExtension {
noManPages = project.getObjects().property(Boolean.class); noManPages = project.getObjects().property(Boolean.class);
endian = project.getObjects().property(Endian.class); endian = project.getObjects().property(Endian.class);
includeLocales = project.getObjects().listProperty(String.class); includeLocales = project.getObjects().listProperty(String.class);
moduleVersion = project.getObjects().property(String.class);
} }
public void setJavaCompiler(JavaCompiler javaCompiler) { public void setJavaCompiler(JavaCompiler javaCompiler) {
@ -177,11 +174,4 @@ public class JLinkExtension {
return includeLocales; return includeLocales;
} }
public void setModuleVersion(String moduleVersion) {
this.moduleVersion.set(moduleVersion);
}
public Property<String> getModuleVersion() {
return moduleVersion;
}
} }

View file

@ -29,8 +29,20 @@ public abstract class JLinkPlugin implements Plugin<Project> {
throw new GradleException("this plugin is only available for Linux"); throw new GradleException("this plugin is only available for Linux");
} }
project.getPluginManager().apply(JavaLibraryPlugin.class); project.getPluginManager().apply(JavaLibraryPlugin.class);
TaskProvider<Jar> jarTask = project.getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask, "jar task must be present");
JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain(); JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain();
JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class); JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class);
JModExtension jModExtension = project.getExtensions().create("jmod", JModExtension.class);
jModExtension.getjModDirectory().convention(project.getLayout().getBuildDirectory().dir("jmods"));
jModExtension.getModuleVersion().convention(project.getVersion().toString());
jModExtension.getCompress().convention(6); // zip-6, default
jModExtension.getCmds().unsetConvention();
jModExtension.getConfigs().unsetConvention();
JLinkExtension jLinkExtension = project.getExtensions().create("jlink", JLinkExtension.class); JLinkExtension jLinkExtension = project.getExtensions().create("jlink", JLinkExtension.class);
jLinkExtension.getJavaCompiler().convention(service.compilerFor(toolchain)); jLinkExtension.getJavaCompiler().convention(service.compilerFor(toolchain));
Provider<Directory> modulePathProvider = jLinkExtension.getJavaCompiler().map(it -> { Provider<Directory> modulePathProvider = jLinkExtension.getJavaCompiler().map(it -> {
@ -51,16 +63,13 @@ public abstract class JLinkPlugin implements Plugin<Project> {
jLinkExtension.getLauncher().unsetConvention(); jLinkExtension.getLauncher().unsetConvention();
jLinkExtension.getCompress().convention(6); // zip-6, default jLinkExtension.getCompress().convention(6); // zip-6, default
jLinkExtension.getStripDebug().convention(true); // same as jpackage default jLinkExtension.getStripDebug().convention(true); // same as jpackage default
jLinkExtension.getStripNativeCommands().convention(false); jLinkExtension.getStripNativeCommands().convention(false); // keep commands
jLinkExtension.getNoHeaderFiles().convention(true); // same as jpackage default jLinkExtension.getNoHeaderFiles().convention(true); // same as jpackage default
jLinkExtension.getNoManPages().convention(true); // same as jpackage default jLinkExtension.getNoManPages().convention(true); // same as jpackage default
jLinkExtension.getEndian().convention(JLinkExtension.Endian.NATIVE); jLinkExtension.getEndian().convention(JLinkExtension.Endian.NATIVE);
jLinkExtension.getIncludeLocales().unsetConvention(); jLinkExtension.getIncludeLocales().unsetConvention();
jLinkExtension.getModuleVersion().convention(project.getVersion().toString());
JPackageExtension jPackageExtension = project.getExtensions().create("jpackage", JPackageExtension.class); JPackageExtension jPackageExtension = project.getExtensions().create("jpackage", JPackageExtension.class);
jPackageExtension.getJavaCompiler().convention(service.compilerFor(toolchain));
jPackageExtension.getModulePath().convention(modulePathProvider);
jPackageExtension.getjPackageOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("jpackage")); jPackageExtension.getjPackageOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("jpackage"));
jPackageExtension.getAddModules().convention(List.of("java.base")); jPackageExtension.getAddModules().convention(List.of("java.base"));
jPackageExtension.getModule().unsetConvention(); jPackageExtension.getModule().unsetConvention();
@ -69,16 +78,20 @@ public abstract class JLinkPlugin implements Plugin<Project> {
jPackageExtension.getAppName().convention(project.getName()); jPackageExtension.getAppName().convention(project.getName());
jPackageExtension.getAppVersion().convention(project.getVersion().toString()); jPackageExtension.getAppVersion().convention(project.getVersion().toString());
// get the jar task // install the jmod task
TaskProvider<Jar> jarTask = project.getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask, "jar task required to be present");
TaskProvider<JModTask> jModTask = project.getTasks().register("jmod", JModTask.class); TaskProvider<JModTask> jModTask = project.getTasks().register("jmod", JModTask.class);
project.getTasks().withType(JModTask.class).forEach(it -> { project.getTasks().withType(JModTask.class).forEach(it -> {
it.setGroup("build"); it.setGroup("build");
it.dependsOn(jarTask); it.dependsOn(jarTask);
it.jarFile.set(jarTask.get().getArchiveFile());
it.javaCompiler.set(jLinkExtension.getJavaCompiler()); it.javaCompiler.set(jLinkExtension.getJavaCompiler());
it.moduleVersion.set(jLinkExtension.getModuleVersion()); it.jModDirectory.set(jModExtension.getjModDirectory());
it.moduleVersion.set(jModExtension.getModuleVersion());
it.compress.set(jModExtension.getCompress());
it.cmds.set(jModExtension.getCmds());
it.configs.set(jModExtension.getConfigs());
}); });
// install the jdeps task // install the jdeps task
TaskProvider<JDepsTask> jDepsTask = project.getTasks().register("jdeps", JDepsTask.class); TaskProvider<JDepsTask> jDepsTask = project.getTasks().register("jdeps", JDepsTask.class);
project.getTasks().withType(JDepsTask.class).forEach(it -> { project.getTasks().withType(JDepsTask.class).forEach(it -> {
@ -87,7 +100,7 @@ public abstract class JLinkPlugin implements Plugin<Project> {
it.javaCompiler.set(jLinkExtension.getJavaCompiler()); it.javaCompiler.set(jLinkExtension.getJavaCompiler());
it.modulePath.set(jLinkExtension.getModulePath()); it.modulePath.set(jLinkExtension.getModulePath());
}); });
Objects.requireNonNull(jModTask, "jmod task required to be present");
// depend jlink on jdeps task // depend jlink on jdeps task
TaskProvider<JLinkTask> jLinkTask = project.getTasks().register("jlink", JLinkTask.class); TaskProvider<JLinkTask> jLinkTask = project.getTasks().register("jlink", JLinkTask.class);
project.getTasks().withType(JLinkTask.class).forEach(it -> { project.getTasks().withType(JLinkTask.class).forEach(it -> {
@ -112,8 +125,8 @@ public abstract class JLinkPlugin implements Plugin<Project> {
project.getTasks().withType(JPackageTask.class).forEach(it -> { project.getTasks().withType(JPackageTask.class).forEach(it -> {
it.setGroup("build"); it.setGroup("build");
it.dependsOn(jLinkTask); it.dependsOn(jLinkTask);
it.javaCompiler.set(jPackageExtension.getJavaCompiler()); it.javaCompiler.set(jLinkExtension.getJavaCompiler());
it.modulePath.set(jPackageExtension.getModulePath()); it.modulePath.set(jLinkExtension.getModulePath());
it.jPackageOutputDirectory.set(jPackageExtension.getjPackageOutputDirectory()); it.jPackageOutputDirectory.set(jPackageExtension.getjPackageOutputDirectory());
it.addModules.set(jPackageExtension.getAddModules()); it.addModules.set(jPackageExtension.getAddModules());
it.module.set(jPackageExtension.getModule()); it.module.set(jPackageExtension.getModule());

View file

@ -0,0 +1,70 @@
package org.xbib.gradle.plugin.jlink;
import org.gradle.api.Project;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import java.util.List;
public class JModExtension {
private final DirectoryProperty jModDirectory;
private final Property<String> moduleVersion;
private final Property<Integer> compress;
private final ListProperty<String> cmds;
private final ListProperty<String> configs;
public JModExtension(Project project) {
jModDirectory = project.getObjects().directoryProperty();
moduleVersion = project.getObjects().property(String.class);
compress = project.getObjects().property(Integer.class);
cmds = project.getObjects().listProperty(String.class);
configs = project.getObjects().listProperty(String.class);
}
public void setjModDirectory(Directory jModDirectory) {
this.jModDirectory.set(jModDirectory);
}
public DirectoryProperty getjModDirectory() {
return jModDirectory;
}
public void setModuleVersion(String moduleVersion) {
this.moduleVersion.set(moduleVersion);
}
public Property<String> getModuleVersion() {
return moduleVersion;
}
public void setCompress(Integer compress) {
this.compress.set(compress);
}
public Property<Integer> getCompress() {
return compress;
}
public void setCmds(List<String> cmds) {
this.cmds.set(cmds);
}
public ListProperty<String> getCmds() {
return cmds;
}
public void setConfigs(List<String> configs) {
this.configs.set(configs);
}
public ListProperty<String> getConfigs() {
return configs;
}
}

View file

@ -8,26 +8,25 @@ import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.tasks.AbstractExecTask; import org.gradle.api.tasks.AbstractExecTask;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaCompiler; import org.gradle.jvm.toolchain.JavaCompiler;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -46,23 +45,42 @@ public class JModTask extends AbstractExecTask<JModTask> {
DirectoryProperty jModDirectory; DirectoryProperty jModDirectory;
@Input @Input
@Optional
Property<String> moduleVersion; Property<String> moduleVersion;
@Input
@Optional
Property<Integer> compress;
@Input
@Optional
ListProperty<String> cmds;
@Input
@Optional
ListProperty<String> configs;
@Input
@Optional
ListProperty<String> headerFiles;
@Input
@Optional
ListProperty<String> libs;
@Inject @Inject
public JModTask() { public JModTask() {
super(JModTask.class); super(JModTask.class);
javaCompiler = getProject().getObjects().property(JavaCompiler.class); javaCompiler = getProject().getObjects().property(JavaCompiler.class);
JavaToolchainSpec toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain();
JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class);
javaCompiler.convention(service.compilerFor(toolchain));
jarFile = getProject().getObjects().fileProperty(); jarFile = getProject().getObjects().fileProperty();
TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask);
jarFile.convention(jarTask.get().getArchiveFile());
jModDirectory = getProject().getObjects().directoryProperty(); jModDirectory = getProject().getObjects().directoryProperty();
jModDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jmods"));
moduleVersion = getProject().getObjects().property(String.class); moduleVersion = getProject().getObjects().property(String.class);
moduleVersion.convention(getProject().getVersion().toString()); moduleVersion.convention(getProject().getVersion().toString());
compress = getProject().getObjects().property(Integer.class);
cmds = getProject().getObjects().listProperty(String.class);
configs = getProject().getObjects().listProperty(String.class);
headerFiles = getProject().getObjects().listProperty(String.class);
libs = getProject().getObjects().listProperty(String.class);
} }
@Override @Override
@ -75,26 +93,16 @@ public class JModTask extends AbstractExecTask<JModTask> {
getLogger().log(LogLevel.ERROR, e.getMessage(), e); getLogger().log(LogLevel.ERROR, e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
List<String> args = List.of( String jarPath = jarFile.get().getAsFile().getAbsolutePath();
"create", setArgs(createArgs(jarPath, getProject().getName() + ".jmod"));
"--class-path", getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs());
jarFile.get().getAsFile().getAbsolutePath(),
jModDirectory.get().file(getProject().getName() + ".jmod").getAsFile().getAbsolutePath()
);
setArgs(args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
Configuration runtimeConfiguration = getProject().getConfigurations().getByName("runtimeClasspath"); Configuration runtimeConfiguration = getProject().getConfigurations().getByName("runtimeClasspath");
Objects.requireNonNull(runtimeConfiguration, "runtimeClasspath configuration must be present"); Objects.requireNonNull(runtimeConfiguration, "runtimeClasspath configuration must be present");
for (ResolvedArtifact artifact : collectArtifacts(runtimeConfiguration)) { for (ResolvedArtifact artifact : collectArtifacts(runtimeConfiguration)) {
args = List.of( String artifactPath = artifact.getFile().getAbsolutePath();
"create", setArgs(createArgs(artifactPath, artifact.getName()));
"--class-path", getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs());
artifact.getFile().getAbsolutePath(),
jModDirectory.get().file(artifact.getName() + ".jmod").getAsFile().getAbsolutePath()
);
setArgs(args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
} }
} }
@ -131,6 +139,46 @@ public class JModTask extends AbstractExecTask<JModTask> {
return moduleVersion; return moduleVersion;
} }
public void setCompress(Integer compress) {
this.compress.set(compress);
}
public Property<Integer> getCompress() {
return compress;
}
public void setCmds(List<String> cmds) {
this.cmds.set(cmds);
}
public ListProperty<String> getCmds() {
return cmds;
}
public void setConfigs(List<String> configs) {
this.configs.set(configs);
}
public ListProperty<String> getConfigs() {
return configs;
}
public void setHeaderFiles(List<String> headerFiles) {
this.headerFiles.set(headerFiles);
}
public ListProperty<String> getHeaderFiles() {
return headerFiles;
}
public void setLibs(List<String> libs) {
this.libs.set(libs);
}
public ListProperty<String> getLibs() {
return libs;
}
private static void createDirectory(Path path) throws IOException { private static void createDirectory(Path path) throws IOException {
if (Files.exists(path)) { if (Files.exists(path)) {
Files.walkFileTree(path, new SimpleFileVisitor<>() { Files.walkFileTree(path, new SimpleFileVisitor<>() {
@ -168,4 +216,37 @@ public class JModTask extends AbstractExecTask<JModTask> {
private static <T> Stream<T> of(T node, Function<T, Collection<T>> childrenFn) { private static <T> Stream<T> of(T node, Function<T, Collection<T>> childrenFn) {
return Stream.concat(Stream.of(node), childrenFn.apply(node).stream().flatMap(n -> of(n, childrenFn))); return Stream.concat(Stream.of(node), childrenFn.apply(node).stream().flatMap(n -> of(n, childrenFn)));
} }
private List<String> createArgs(String jarFile, String jmodName) {
List<String> args = new ArrayList<>();
args.add("create");
args.add("--class-path");
args.add(jarFile);
args.add(jModDirectory.get().file(jmodName).getAsFile().getAbsolutePath());
if (moduleVersion.isPresent()) {
args.add("--module-version");
args.add(moduleVersion.get());
}
if (compress.isPresent()) {
args.add("--compress");
args.add("zip-" + compress.get());
}
if (cmds.isPresent() &&!cmds.get().isEmpty()) {
args.add("--cmds");
args.add(String.join(File.pathSeparator, cmds.get()));
}
if (configs.isPresent() && !configs.get().isEmpty()) {
args.add("--config");
args.add(String.join(File.pathSeparator, configs.get()));
}
if (headerFiles.isPresent() && !headerFiles.get().isEmpty()) {
args.add("--header-files");
args.add(String.join(File.pathSeparator, headerFiles.get()));
}
if (libs.isPresent() && !libs.get().isEmpty()) {
args.add("--libs");
args.add(String.join(File.pathSeparator, libs.get()));
}
return args;
}
} }

View file

@ -10,10 +10,6 @@ import org.gradle.jvm.toolchain.JavaCompiler;
public class JPackageExtension { public class JPackageExtension {
private final Property<JavaCompiler> javaCompiler;
private final DirectoryProperty modulePath;
private final DirectoryProperty jPackageOutputDirectory; private final DirectoryProperty jPackageOutputDirectory;
private final ListProperty<String> addModules; private final ListProperty<String> addModules;
@ -29,8 +25,6 @@ public class JPackageExtension {
private final Property<String> appVersion; private final Property<String> appVersion;
public JPackageExtension(Project project) { public JPackageExtension(Project project) {
javaCompiler = project.getObjects().property(JavaCompiler.class);
modulePath = project.getObjects().directoryProperty();
jPackageOutputDirectory = project.getObjects().directoryProperty(); jPackageOutputDirectory = project.getObjects().directoryProperty();
addModules = project.getObjects().listProperty(String.class); addModules = project.getObjects().listProperty(String.class);
module = project.getObjects().property(String.class); module = project.getObjects().property(String.class);
@ -40,22 +34,6 @@ public class JPackageExtension {
appVersion = project.getObjects().property(String.class); appVersion = project.getObjects().property(String.class);
} }
public void setJavaCompiler(JavaCompiler javaCompiler) {
this.javaCompiler.set(javaCompiler);
}
public Property<JavaCompiler> getJavaCompiler() {
return javaCompiler;
}
public void setModulePath(Directory modulePath) {
this.modulePath.set(modulePath);
}
public DirectoryProperty getModulePath() {
return modulePath;
}
public void setjPackageOutputDirectory(Directory jPackageOutputDirectory) { public void setjPackageOutputDirectory(Directory jPackageOutputDirectory) {
this.jPackageOutputDirectory.set(jPackageOutputDirectory); this.jPackageOutputDirectory.set(jPackageOutputDirectory);
} }

View file

@ -147,8 +147,7 @@ public class JPackageTask extends AbstractExecTask<JPackageTask> {
args.add(icon.get()); args.add(icon.get());
} }
setArgs(args); setArgs(args);
System.err.println("executing " + getExecutable() + " with " + args); getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs());
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
} }
@ -200,10 +199,6 @@ public class JPackageTask extends AbstractExecTask<JPackageTask> {
return appImage; return appImage;
} }
/*public void setInput(DirectoryProperty input) {
this.input = input;
}*/
public DirectoryProperty getInput() { public DirectoryProperty getInput() {
return input; return input;
} }