diff --git a/gradle-plugin-jlink/README.md b/gradle-plugin-jlink/README.md index cd87beb..270a6d2 100644 --- a/gradle-plugin-jlink/README.md +++ b/gradle-plugin-jlink/README.md @@ -1,18 +1,33 @@ # JLink Gradle plugin 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. It uses the java toolchain to locate the `jlink` executable and creates a java runtime for your module path based Java application. This 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) -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 plugins { diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkExtension.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkExtension.java index 0da8027..5edac2e 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkExtension.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkExtension.java @@ -39,8 +39,6 @@ public class JLinkExtension { private final ListProperty includeLocales; - private final Property moduleVersion; - public enum Endian { LITTLE, BIG, @@ -62,7 +60,6 @@ public class JLinkExtension { noManPages = project.getObjects().property(Boolean.class); endian = project.getObjects().property(Endian.class); includeLocales = project.getObjects().listProperty(String.class); - moduleVersion = project.getObjects().property(String.class); } public void setJavaCompiler(JavaCompiler javaCompiler) { @@ -177,11 +174,4 @@ public class JLinkExtension { return includeLocales; } - public void setModuleVersion(String moduleVersion) { - this.moduleVersion.set(moduleVersion); - } - - public Property getModuleVersion() { - return moduleVersion; - } } diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkPlugin.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkPlugin.java index 46f0945..ba0e561 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkPlugin.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkPlugin.java @@ -29,8 +29,20 @@ public abstract class JLinkPlugin implements Plugin { throw new GradleException("this plugin is only available for Linux"); } project.getPluginManager().apply(JavaLibraryPlugin.class); + + TaskProvider jarTask = project.getTasks().named("jar", Jar.class); + Objects.requireNonNull(jarTask, "jar task must be present"); + JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain(); 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.getJavaCompiler().convention(service.compilerFor(toolchain)); Provider modulePathProvider = jLinkExtension.getJavaCompiler().map(it -> { @@ -51,16 +63,13 @@ public abstract class JLinkPlugin implements Plugin { jLinkExtension.getLauncher().unsetConvention(); jLinkExtension.getCompress().convention(6); // zip-6, 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.getNoManPages().convention(true); // same as jpackage default jLinkExtension.getEndian().convention(JLinkExtension.Endian.NATIVE); jLinkExtension.getIncludeLocales().unsetConvention(); - jLinkExtension.getModuleVersion().convention(project.getVersion().toString()); 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.getAddModules().convention(List.of("java.base")); jPackageExtension.getModule().unsetConvention(); @@ -69,16 +78,20 @@ public abstract class JLinkPlugin implements Plugin { jPackageExtension.getAppName().convention(project.getName()); jPackageExtension.getAppVersion().convention(project.getVersion().toString()); - // get the jar task - TaskProvider jarTask = project.getTasks().named("jar", Jar.class); - Objects.requireNonNull(jarTask, "jar task required to be present"); + // install the jmod task TaskProvider jModTask = project.getTasks().register("jmod", JModTask.class); project.getTasks().withType(JModTask.class).forEach(it -> { it.setGroup("build"); it.dependsOn(jarTask); + it.jarFile.set(jarTask.get().getArchiveFile()); 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 TaskProvider jDepsTask = project.getTasks().register("jdeps", JDepsTask.class); project.getTasks().withType(JDepsTask.class).forEach(it -> { @@ -87,7 +100,7 @@ public abstract class JLinkPlugin implements Plugin { it.javaCompiler.set(jLinkExtension.getJavaCompiler()); it.modulePath.set(jLinkExtension.getModulePath()); }); - Objects.requireNonNull(jModTask, "jmod task required to be present"); + // depend jlink on jdeps task TaskProvider jLinkTask = project.getTasks().register("jlink", JLinkTask.class); project.getTasks().withType(JLinkTask.class).forEach(it -> { @@ -112,8 +125,8 @@ public abstract class JLinkPlugin implements Plugin { project.getTasks().withType(JPackageTask.class).forEach(it -> { it.setGroup("build"); it.dependsOn(jLinkTask); - it.javaCompiler.set(jPackageExtension.getJavaCompiler()); - it.modulePath.set(jPackageExtension.getModulePath()); + it.javaCompiler.set(jLinkExtension.getJavaCompiler()); + it.modulePath.set(jLinkExtension.getModulePath()); it.jPackageOutputDirectory.set(jPackageExtension.getjPackageOutputDirectory()); it.addModules.set(jPackageExtension.getAddModules()); it.module.set(jPackageExtension.getModule()); diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModExtension.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModExtension.java new file mode 100644 index 0000000..88ff873 --- /dev/null +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModExtension.java @@ -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 moduleVersion; + + private final Property compress; + + private final ListProperty cmds; + + private final ListProperty 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 getModuleVersion() { + return moduleVersion; + } + + public void setCompress(Integer compress) { + this.compress.set(compress); + } + + public Property getCompress() { + return compress; + } + + public void setCmds(List cmds) { + this.cmds.set(cmds); + } + + public ListProperty getCmds() { + return cmds; + } + + public void setConfigs(List configs) { + this.configs.set(configs); + } + + public ListProperty getConfigs() { + return configs; + } +} diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModTask.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModTask.java index bc72235..a60bf20 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModTask.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JModTask.java @@ -8,26 +8,25 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; 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.tasks.AbstractExecTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; 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.JavaToolchainService; -import org.gradle.jvm.toolchain.JavaToolchainSpec; import javax.inject.Inject; +import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -46,23 +45,42 @@ public class JModTask extends AbstractExecTask { DirectoryProperty jModDirectory; @Input + @Optional Property moduleVersion; + @Input + @Optional + Property compress; + + @Input + @Optional + ListProperty cmds; + + @Input + @Optional + ListProperty configs; + + @Input + @Optional + ListProperty headerFiles; + + @Input + @Optional + ListProperty libs; + @Inject public JModTask() { super(JModTask.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(); - TaskProvider jarTask = getProject().getTasks().named("jar", Jar.class); - Objects.requireNonNull(jarTask); - jarFile.convention(jarTask.get().getArchiveFile()); jModDirectory = getProject().getObjects().directoryProperty(); - jModDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jmods")); moduleVersion = getProject().getObjects().property(String.class); 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 @@ -75,26 +93,16 @@ public class JModTask extends AbstractExecTask { getLogger().log(LogLevel.ERROR, e.getMessage(), e); throw new RuntimeException(e); } - List args = List.of( - "create", - "--class-path", - jarFile.get().getAsFile().getAbsolutePath(), - jModDirectory.get().file(getProject().getName() + ".jmod").getAsFile().getAbsolutePath() - ); - setArgs(args); - getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); + String jarPath = jarFile.get().getAsFile().getAbsolutePath(); + setArgs(createArgs(jarPath, getProject().getName() + ".jmod")); + getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs()); super.exec(); Configuration runtimeConfiguration = getProject().getConfigurations().getByName("runtimeClasspath"); Objects.requireNonNull(runtimeConfiguration, "runtimeClasspath configuration must be present"); for (ResolvedArtifact artifact : collectArtifacts(runtimeConfiguration)) { - args = List.of( - "create", - "--class-path", - artifact.getFile().getAbsolutePath(), - jModDirectory.get().file(artifact.getName() + ".jmod").getAsFile().getAbsolutePath() - ); - setArgs(args); - getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); + String artifactPath = artifact.getFile().getAbsolutePath(); + setArgs(createArgs(artifactPath, artifact.getName())); + getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs()); super.exec(); } } @@ -131,6 +139,46 @@ public class JModTask extends AbstractExecTask { return moduleVersion; } + public void setCompress(Integer compress) { + this.compress.set(compress); + } + + public Property getCompress() { + return compress; + } + + public void setCmds(List cmds) { + this.cmds.set(cmds); + } + + public ListProperty getCmds() { + return cmds; + } + + public void setConfigs(List configs) { + this.configs.set(configs); + } + + public ListProperty getConfigs() { + return configs; + } + + public void setHeaderFiles(List headerFiles) { + this.headerFiles.set(headerFiles); + } + + public ListProperty getHeaderFiles() { + return headerFiles; + } + + public void setLibs(List libs) { + this.libs.set(libs); + } + + public ListProperty getLibs() { + return libs; + } + private static void createDirectory(Path path) throws IOException { if (Files.exists(path)) { Files.walkFileTree(path, new SimpleFileVisitor<>() { @@ -168,4 +216,37 @@ public class JModTask extends AbstractExecTask { private static Stream of(T node, Function> childrenFn) { return Stream.concat(Stream.of(node), childrenFn.apply(node).stream().flatMap(n -> of(n, childrenFn))); } + + private List createArgs(String jarFile, String jmodName) { + List 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; + } } diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageExtension.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageExtension.java index bd1acc3..2d60aaa 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageExtension.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageExtension.java @@ -10,10 +10,6 @@ import org.gradle.jvm.toolchain.JavaCompiler; public class JPackageExtension { - private final Property javaCompiler; - - private final DirectoryProperty modulePath; - private final DirectoryProperty jPackageOutputDirectory; private final ListProperty addModules; @@ -29,8 +25,6 @@ public class JPackageExtension { private final Property appVersion; public JPackageExtension(Project project) { - javaCompiler = project.getObjects().property(JavaCompiler.class); - modulePath = project.getObjects().directoryProperty(); jPackageOutputDirectory = project.getObjects().directoryProperty(); addModules = project.getObjects().listProperty(String.class); module = project.getObjects().property(String.class); @@ -40,22 +34,6 @@ public class JPackageExtension { appVersion = project.getObjects().property(String.class); } - public void setJavaCompiler(JavaCompiler javaCompiler) { - this.javaCompiler.set(javaCompiler); - } - - public Property getJavaCompiler() { - return javaCompiler; - } - - public void setModulePath(Directory modulePath) { - this.modulePath.set(modulePath); - } - - public DirectoryProperty getModulePath() { - return modulePath; - } - public void setjPackageOutputDirectory(Directory jPackageOutputDirectory) { this.jPackageOutputDirectory.set(jPackageOutputDirectory); } diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageTask.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageTask.java index 64eec43..bec0d4c 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageTask.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JPackageTask.java @@ -147,8 +147,7 @@ public class JPackageTask extends AbstractExecTask { args.add(icon.get()); } setArgs(args); - System.err.println("executing " + getExecutable() + " with " + args); - getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); + getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + getArgs()); super.exec(); } @@ -200,10 +199,6 @@ public class JPackageTask extends AbstractExecTask { return appImage; } - /*public void setInput(DirectoryProperty input) { - this.input = input; - }*/ - public DirectoryProperty getInput() { return input; }