more options and better tests for the jlink plugin

This commit is contained in:
Jörg Prante 2024-11-03 23:37:21 +01:00
parent 942e765c78
commit 79e53ba85f
13 changed files with 404 additions and 88 deletions

0
gradle-plugin-jacc/README.md Executable file → Normal file
View file

0
gradle-plugin-jflex/README.md Executable file → Normal file
View file

View file

@ -0,0 +1,40 @@
# 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.
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
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.
It applies the `org.xbib.gradle.plugin.jlink` plugin which configures a `jlink` task that executes the jlink tool.
```groovy
plugins {
id 'org.xbib.gradle.plugin.jlink' version "8.8.0"
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
jlink {
modules.set(List.of("org.example.app"))
launcher.set("app=org.example.app/org.example.app.Main")
stripDebug.set(true)
}
```
In addition, the plugin configures a `jmod` task to download the dependent artifacts on the
runtime class path and create jmod files for the `jlink` task. Also, a `jdeps` task is automatically
configured to show the dependent modules.
The plugin is currently only available under Linux.

View file

@ -0,0 +1,165 @@
package org.xbib.gradle.plugin.jlink;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.inject.Inject;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.AbstractExecTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
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;
public class JDepsTask extends AbstractExecTask<JDepsTask> {
@Nested
Property<JavaCompiler> javaCompiler;
@InputDirectory
@Optional
DirectoryProperty modulePath;
@Input
Property<Boolean> printModuleDeps;
@Input
Property<Boolean> recursive;
@Input
Property<Boolean> verbose;
@Inject
public JDepsTask() {
super(JDepsTask.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));
Provider<Directory> modulePathProvider = javaCompiler.map(it -> {
Directory jmods = it.getMetadata().getInstallationPath().dir("jmods");
if (jmods.getAsFile().exists()) {
getLogger().log(LogLevel.INFO, "using jmods directory found in installation for module path");
return jmods;
} else {
getLogger().log(LogLevel.WARN, "directory " + jmods + " does not exist! Is jmods package installed?");
return null;
}
});
modulePath = getProject().getObjects().directoryProperty();
modulePath.convention(modulePathProvider);
printModuleDeps = getProject().getObjects().property(Boolean.class);
printModuleDeps.convention(true);
recursive = getProject().getObjects().property(Boolean.class);
recursive.convention(true);
verbose = getProject().getObjects().property(Boolean.class);
verbose.convention(false);
}
@Override
public void exec() {
setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jdeps"));
TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask, "jar task must be present");
Configuration runtimeConfiguration = getProject().getConfigurations().getByName("runtimeClasspath");
Objects.requireNonNull(runtimeConfiguration, "runtimeClasspath configuration must be present");
List<ResolvedArtifact> resolvedArtifacts = collectArtifacts(runtimeConfiguration);
List<String> jarFiles = resolvedArtifacts.stream()
.map(a -> a.getFile().getAbsolutePath())
.toList();
List<String> args = new ArrayList<>();
args.add("--module-path");
args.add(modulePath.get().getAsFile().getAbsolutePath());
args.add("--module-path");
args.add(String.join(";", jarFiles)); // jdeps does not understand jmod files
args.add("--class-path");
args.add(String.join(";", jarFiles));
if (printModuleDeps.get()) {
args.add("--print-module-deps");
}
if (recursive.get()) {
args.add("--recursive");
}
if (verbose.get()) {
args.add("-v"); // verbose, list deps for each class
}
args.add(jarTask.get().getArchiveFile().get().getAsFile().getAbsolutePath());
setArgs(args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec();
}
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 setPrintModuleDeps(Boolean printModuleDeps) {
this.printModuleDeps.set(printModuleDeps);
}
public Property<Boolean> getPrintModuleDeps() {
return printModuleDeps;
}
public void setRecursive(Boolean recursive) {
this.recursive.set(recursive);
}
public Property<Boolean> getRecursive() {
return recursive;
}
public void setVerbose(Boolean verbose) {
this.verbose.set(verbose);
}
public Property<Boolean> getVerbose() {
return verbose;
}
private static List<ResolvedArtifact> collectArtifacts(Configuration configuration) {
return configuration.getResolvedConfiguration()
.getFirstLevelModuleDependencies()
.stream()
.flatMap(JDepsTask::of)
.flatMap(dependency -> dependency.getModuleArtifacts().stream())
.distinct()
.toList();
}
private static Stream<ResolvedDependency> of(ResolvedDependency node) {
return of(node, ResolvedDependency::getChildren);
}
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)));
}
}

View file

@ -1,15 +1,26 @@
package org.xbib.gradle.plugin.jlink; package org.xbib.gradle.plugin.jlink;
import org.gradle.api.Project; 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.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import java.util.List; import java.util.List;
import org.gradle.jvm.toolchain.JavaCompiler;
public class JLinkExtension { public class JLinkExtension {
private final Property<JavaCompiler> javaCompiler;
private final DirectoryProperty modulePath;
private final DirectoryProperty outputDirectory;
private final ListProperty<String> addModules; private final ListProperty<String> addModules;
private final ListProperty<String> limitModules;
private final Property<Boolean> bindServices; private final Property<Boolean> bindServices;
private final Property<String> launcher; private final Property<String> launcher;
@ -24,6 +35,10 @@ public class JLinkExtension {
private final Property<Endian> endian; private final Property<Endian> endian;
private final ListProperty<String> includeLocales;
private final Property<String> moduleVersion;
public enum Endian { public enum Endian {
LITTLE, LITTLE,
BIG, BIG,
@ -31,14 +46,44 @@ public class JLinkExtension {
} }
public JLinkExtension(Project project) { public JLinkExtension(Project project) {
this.addModules = project.getObjects().listProperty(String.class); javaCompiler = project.getObjects().property(JavaCompiler.class);
this.bindServices = project.getObjects().property(Boolean.class); modulePath = project.getObjects().directoryProperty();
this.launcher = project.getObjects().property(String.class); outputDirectory = project.getObjects().directoryProperty();
this.compress = project.getObjects().property(Integer.class); addModules = project.getObjects().listProperty(String.class);
this.stripDebug = project.getObjects().property(Boolean.class); limitModules = project.getObjects().listProperty(String.class);
this.noHeaderFiles = project.getObjects().property(Boolean.class); bindServices = project.getObjects().property(Boolean.class);
this.noManPages = project.getObjects().property(Boolean.class); launcher = project.getObjects().property(String.class);
this.endian = project.getObjects().property(Endian.class); compress = project.getObjects().property(Integer.class);
stripDebug = project.getObjects().property(Boolean.class);
noHeaderFiles = project.getObjects().property(Boolean.class);
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) {
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 setOutputDirectory(Directory outputDirectory) {
this.outputDirectory.set(outputDirectory);
}
public DirectoryProperty getOutputDirectory() {
return outputDirectory;
} }
public void setAddModules(List<String> addModules) { public void setAddModules(List<String> addModules) {
@ -49,6 +94,14 @@ public class JLinkExtension {
return addModules; return addModules;
} }
public void setLimitModules(List<String> limitModules) {
this.limitModules.set(limitModules);
}
public ListProperty<String> getLimitModules() {
return limitModules;
}
public void setBindServices(Boolean bindServices) { public void setBindServices(Boolean bindServices) {
this.bindServices.set(bindServices); this.bindServices.set(bindServices);
} }
@ -104,4 +157,20 @@ public class JLinkExtension {
public Property<Endian> getEndian() { public Property<Endian> getEndian() {
return endian; return endian;
} }
public void setIncludeLocales(List<String> includeLocales) {
this.includeLocales.set(includeLocales);
}
public ListProperty<String> getIncludeLocales() {
return includeLocales;
}
public void setModuleVersion(String moduleVersion) {
this.moduleVersion.set(moduleVersion);
}
public Property<String> getModuleVersion() {
return moduleVersion;
}
} }

View file

@ -1,45 +1,87 @@
package org.xbib.gradle.plugin.jlink; package org.xbib.gradle.plugin.jlink;
import org.gradle.api.GradleException;
import org.gradle.api.NonNullApi;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.attributes.Usage; import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.file.Directory;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.TaskProvider;
import org.gradle.internal.os.OperatingSystem;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function; import org.gradle.jvm.toolchain.JavaToolchainService;
import java.util.stream.Stream; import org.gradle.jvm.toolchain.JavaToolchainSpec;
@NonNullApi
public abstract class JLinkPlugin implements Plugin<Project> { public abstract class JLinkPlugin implements Plugin<Project> {
@Override @Override
public void apply(Project project) { public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class); if (!OperatingSystem.current().isLinux()) {
throw new GradleException("this plugin is only available for Linux");
}
project.getPluginManager().apply(JavaLibraryPlugin.class);
JLinkExtension extension = project.getExtensions().create("jlink", JLinkExtension.class); JLinkExtension extension = project.getExtensions().create("jlink", JLinkExtension.class);
JavaToolchainSpec toolchain = project.getExtensions().getByType(JavaPluginExtension.class).getToolchain();
JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class);
extension.getJavaCompiler().convention(service.compilerFor(toolchain));
Provider<Directory> modulePathProvider = extension.getJavaCompiler().map(it -> {
Directory jmods = it.getMetadata().getInstallationPath().dir("jmods");
if (jmods.getAsFile().exists()) {
project.getLogger().log(LogLevel.INFO, "using jmods directory found in installation for module path");
return jmods;
} else {
project.getLogger().log(LogLevel.WARN, "directory " + jmods + " does not exist! Is jmods package installed?");
return null;
}
});
extension.getModulePath().convention(modulePathProvider);
extension.getOutputDirectory().convention(project.getLayout().getBuildDirectory().dir("jlink"));
extension.getAddModules().convention(List.of("java.base")); extension.getAddModules().convention(List.of("java.base"));
extension.getLimitModules().convention(List.of());
extension.getBindServices().convention(false); extension.getBindServices().convention(false);
extension.getLauncher().unsetConvention(); extension.getLauncher().unsetConvention();
extension.getCompress().convention(6); // zip-6, default extension.getCompress().convention(6); // zip-6, default
extension.getStripDebug().convention(true); extension.getStripDebug().convention(false);
extension.getNoHeaderFiles().convention(true); extension.getNoHeaderFiles().convention(true);
extension.getNoManPages().convention(true); extension.getNoManPages().convention(true);
extension.getEndian().convention(JLinkExtension.Endian.NATIVE); extension.getEndian().convention(JLinkExtension.Endian.NATIVE);
extension.getIncludeLocales().unsetConvention();
extension.getModuleVersion().convention(project.getVersion().toString());
// get the jar task
TaskProvider<Jar> jarTask = project.getTasks().named("jar", Jar.class); TaskProvider<Jar> jarTask = project.getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask); 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.dependsOn(jarTask); it.dependsOn(jarTask);
it.javaCompiler.set(extension.getJavaCompiler());
it.moduleVersion.set(extension.getModuleVersion());
}); });
Objects.requireNonNull(jModTask); // install the jdeps task
TaskProvider<JLinkTask> jLinkTask = project.getTasks().register("jlink", JLinkTask.class); TaskProvider<JDepsTask> jDepsTask = project.getTasks().register("jdeps", JDepsTask.class);
project.getTasks().withType(JLinkTask.class).forEach(it -> { project.getTasks().withType(JDepsTask.class).forEach(it -> {
it.dependsOn(jModTask); it.dependsOn(jModTask);
it.modulePath.set(extension.getModulePath());
});
Objects.requireNonNull(jModTask, "jmod task required to be present");
// depend jlink on jdeps and jmod task
TaskProvider<JLinkTask> jLinkTask = project.getTasks().register("jlink", JLinkTask.class);
Objects.requireNonNull(jLinkTask, "jlink task required to be present");
project.getTasks().withType(JLinkTask.class).forEach(it -> {
it.dependsOn(jDepsTask);
it.javaCompiler.set(extension.getJavaCompiler());
it.modulePath.set(extension.getModulePath());
it.outputDirectory.set(extension.getOutputDirectory());
it.addModules.set(extension.getAddModules()); it.addModules.set(extension.getAddModules());
it.limitModules.set(extension.getLimitModules());
it.bindServices.set(extension.getBindServices()); it.bindServices.set(extension.getBindServices());
it.launcher.set(extension.getLauncher()); it.launcher.set(extension.getLauncher());
it.compress.set(extension.getCompress()); it.compress.set(extension.getCompress());

View file

@ -1,12 +1,11 @@
package org.xbib.gradle.plugin.jlink; package org.xbib.gradle.plugin.jlink;
import java.util.Objects;
import org.gradle.api.file.Directory; import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.DirectoryProperty;
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.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
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.InputDirectory; import org.gradle.api.tasks.InputDirectory;
@ -15,8 +14,6 @@ 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.api.tasks.TaskProvider;
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.File;
@ -35,6 +32,12 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
@Nested @Nested
Property<JavaCompiler> javaCompiler; Property<JavaCompiler> javaCompiler;
@InputDirectory
DirectoryProperty modulePath;
@OutputDirectory
DirectoryProperty outputDirectory;
@Input @Input
ListProperty<String> addModules; ListProperty<String> addModules;
@ -46,12 +49,6 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
@Optional @Optional
Property<Boolean> bindServices; Property<Boolean> bindServices;
@InputDirectory
DirectoryProperty modulePath;
@OutputDirectory
DirectoryProperty outputDirectory;
@Input @Input
@Optional @Optional
Property<String> launcher; Property<String> launcher;
@ -84,11 +81,11 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
public JLinkTask() { public JLinkTask() {
super(JLinkTask.class); super(JLinkTask.class);
javaCompiler = getProject().getObjects().property(JavaCompiler.class); javaCompiler = getProject().getObjects().property(JavaCompiler.class);
modulePath = getProject().getObjects().directoryProperty();
outputDirectory = getProject().getObjects().directoryProperty();
addModules = getProject().getObjects().listProperty(String.class); addModules = getProject().getObjects().listProperty(String.class);
limitModules = getProject().getObjects().listProperty(String.class); limitModules = getProject().getObjects().listProperty(String.class);
bindServices = getProject().getObjects().property(Boolean.class); bindServices = getProject().getObjects().property(Boolean.class);
modulePath = getProject().getObjects().directoryProperty();
outputDirectory = getProject().getObjects().directoryProperty();
launcher = getProject().getObjects().property(String.class); launcher = getProject().getObjects().property(String.class);
compress = getProject().getObjects().property(Integer.class); compress = getProject().getObjects().property(Integer.class);
stripDebug = getProject().getObjects().property(Boolean.class); stripDebug = getProject().getObjects().property(Boolean.class);
@ -96,32 +93,11 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
noManPages = getProject().getObjects().property(Boolean.class); noManPages = getProject().getObjects().property(Boolean.class);
endian = getProject().getObjects().property(JLinkExtension.Endian.class); endian = getProject().getObjects().property(JLinkExtension.Endian.class);
includeLocales = getProject().getObjects().listProperty(String.class); includeLocales = getProject().getObjects().listProperty(String.class);
JavaToolchainSpec toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain();
JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class);
javaCompiler.convention(service.compilerFor(toolchain));
addModules.convention(List.of("java.base"));
limitModules.unsetConvention();
bindServices.convention(false);
Provider<Directory> modulePathProvider = javaCompiler.map(it -> {
Directory jmods = it.getMetadata().getInstallationPath().dir("jmods");
if (jmods.getAsFile().exists()) {
getLogger().log(LogLevel.INFO, "using jmods directory found in installation for module path");
return jmods;
} else {
getLogger().log(LogLevel.WARN, "directory " + jmods + " does not exist! Is jmods package installed?");
return null;
}
});
modulePath.convention(modulePathProvider);
outputDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jlink"));
launcher.unsetConvention();
includeLocales.unsetConvention();
} }
@Override @Override
public void exec() { public void exec() {
setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jlink")); setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jlink"));
TaskProvider<JModTask> jmodTask = getProject().getTasks().named("jmod", JModTask.class);
File jlinkOutput = outputDirectory.get().getAsFile(); File jlinkOutput = outputDirectory.get().getAsFile();
try { try {
createDirectory(jlinkOutput.toPath()); createDirectory(jlinkOutput.toPath());
@ -129,13 +105,7 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
List<String> args = new ArrayList<>(); List<String> args = new ArrayList<>();
args.add("--module-path"); if (limitModules.isPresent() && !limitModules.get().isEmpty()) {
args.add(modulePath.get().getAsFile().getAbsolutePath());
args.add("--module-path");
args.add(jmodTask.get().jmodDirectory.getAsFile().get().getAbsolutePath());
args.add("--add-modules");
args.add(String.join(",", addModules.get()));
if (limitModules.isPresent()) {
args.add("--limit-modules"); args.add("--limit-modules");
args.add(String.join(",", limitModules.get())); args.add(String.join(",", limitModules.get()));
} }
@ -152,6 +122,9 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
} }
if (stripDebug.get()) { if (stripDebug.get()) {
args.add("--strip-debug"); args.add("--strip-debug");
//args.add("--strip-native-debug-symbols=keep-debuginfo-files");
args.add("--strip-native-debug-symbols=exclude-debuginfo-files");
args.add("--strip-java-debug-attributes");
} }
if (noHeaderFiles.get()) { if (noHeaderFiles.get()) {
args.add("--no-header-files"); args.add("--no-header-files");
@ -163,12 +136,19 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
args.add("--endian"); args.add("--endian");
args.add(endian.get().toString().toLowerCase(Locale.ROOT)); args.add(endian.get().toString().toLowerCase(Locale.ROOT));
} }
if (includeLocales.isPresent()) { if (includeLocales.isPresent() && !includeLocales.get().isEmpty()) {
args.add("--include-locales"); args.add("--include-locales");
args.add(String.join(",", includeLocales.get())); args.add(String.join(",", includeLocales.get()));
} }
args.add("--module-path");
args.add(modulePath.get().getAsFile().getAbsolutePath());
args.add("--module-path");
TaskProvider<JModTask> jmodTask = getProject().getTasks().named("jmod", JModTask.class);
Objects.requireNonNull(jmodTask, "jmod task must be present");
args.add(jmodTask.get().jModDirectory.getAsFile().get().getAbsolutePath());
args.add("--add-modules");
args.add(String.join(",", addModules.get()));
setArgs(args); setArgs(args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
} }

View file

@ -11,6 +11,7 @@ import org.gradle.api.logging.LogLevel;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
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.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.OutputDirectory;
@ -42,7 +43,10 @@ public class JModTask extends AbstractExecTask<JModTask> {
RegularFileProperty jarFile; RegularFileProperty jarFile;
@OutputDirectory @OutputDirectory
DirectoryProperty jmodDirectory; DirectoryProperty jModDirectory;
@Input
Property<String> moduleVersion;
@Inject @Inject
public JModTask() { public JModTask() {
@ -55,16 +59,18 @@ public class JModTask extends AbstractExecTask<JModTask> {
TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class); TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask); Objects.requireNonNull(jarTask);
jarFile.convention(jarTask.get().getArchiveFile()); jarFile.convention(jarTask.get().getArchiveFile());
jmodDirectory = getProject().getObjects().directoryProperty(); jModDirectory = getProject().getObjects().directoryProperty();
jmodDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jmods")); jModDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jmods"));
moduleVersion = getProject().getObjects().property(String.class);
moduleVersion.convention(getProject().getVersion().toString());
} }
@Override @Override
public void exec() { public void exec() {
setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jmod")); setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jmod"));
try { try {
System.err.println("creating directory " + jmodDirectory.getAsFile().get().toPath()); getLogger().log(LogLevel.INFO, "creating directory " + jModDirectory.getAsFile().get().toPath());
createDirectory(jmodDirectory.getAsFile().get().toPath()); createDirectory(jModDirectory.getAsFile().get().toPath());
} catch (IOException e) { } catch (IOException e) {
getLogger().log(LogLevel.ERROR, e.getMessage(), e); getLogger().log(LogLevel.ERROR, e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
@ -73,21 +79,21 @@ public class JModTask extends AbstractExecTask<JModTask> {
"create", "create",
"--class-path", "--class-path",
jarFile.get().getAsFile().getAbsolutePath(), jarFile.get().getAsFile().getAbsolutePath(),
jmodDirectory.get().file(getProject().getName() + ".jmod").getAsFile().getAbsolutePath() jModDirectory.get().file(getProject().getName() + ".jmod").getAsFile().getAbsolutePath()
); );
setArgs(args); setArgs(args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
for (ResolvedArtifact artifact : collectArtifacts(getProject().getConfigurations().getByName("runtimeClasspath"))) { Configuration runtimeConfiguration = getProject().getConfigurations().getByName("runtimeClasspath");
Objects.requireNonNull(runtimeConfiguration, "runtimeClasspath configuration must be present");
for (ResolvedArtifact artifact : collectArtifacts(runtimeConfiguration)) {
args = List.of( args = List.of(
"create", "create",
"--class-path", "--class-path",
artifact.getFile().getAbsolutePath(), artifact.getFile().getAbsolutePath(),
jmodDirectory.get().file(artifact.getName() + ".jmod").getAsFile().getAbsolutePath() jModDirectory.get().file(artifact.getName() + ".jmod").getAsFile().getAbsolutePath()
); );
setArgs(args); setArgs(args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args); getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec(); super.exec();
} }
@ -109,12 +115,20 @@ public class JModTask extends AbstractExecTask<JModTask> {
return jarFile; return jarFile;
} }
public void setJmodDirectory(Directory jmodDirectory) { public void setjModDirectory(Directory jModDirectory) {
this.jmodDirectory.set(jmodDirectory); this.jModDirectory.set(jModDirectory);
} }
public DirectoryProperty getJmodDirectory() { public DirectoryProperty getjModDirectory() {
return jmodDirectory; return jModDirectory;
}
public void setModuleVersion(String moduleVersion) {
this.moduleVersion.set(moduleVersion);
}
public Property<String> getModuleVersion() {
return moduleVersion;
} }
private static void createDirectory(Path path) throws IOException { private static void createDirectory(Path path) throws IOException {
@ -134,7 +148,7 @@ public class JModTask extends AbstractExecTask<JModTask> {
}); });
Files.deleteIfExists(path); Files.deleteIfExists(path);
} }
Files.createDirectory(path); Files.createDirectories(path);
} }
private static List<ResolvedArtifact> collectArtifacts(Configuration configuration) { private static List<ResolvedArtifact> collectArtifacts(Configuration configuration) {

View file

@ -29,9 +29,9 @@ class JLinkGradleBuild {
''' '''
appBuildFile << ''' appBuildFile << '''
plugins { plugins {
id("org.xbib.gradle.plugin.jlink") id "org.xbib.gradle.plugin.jlink"
} }
group = "org.example" group = "org.example"
''' '''
tapFile("app/src/main/java/org/example/app/Main.java") << ''' tapFile("app/src/main/java/org/example/app/Main.java") << '''
package org.example.app; package org.example.app;

View file

@ -16,11 +16,12 @@ class JLinkTest extends Specification {
def taskToRun = ":app:jlink" def taskToRun = ":app:jlink"
appBuildFile << """ appBuildFile << """
dependencies { dependencies {
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.2" api "org.junit.jupiter:junit-jupiter-engine:5.10.2"
} }
jlink { jlink {
addModules.set(List.of("org.example.app")) addModules.set(List.of("org.example.app"))
launcher.set("app=org.example.app/org.example.app.Main") launcher.set("app=org.example.app/org.example.app.Main")
stripDebug.set(true)
} }
""" """
appModuleInfoFile << """ appModuleInfoFile << """

View file

@ -1,11 +1,13 @@
package org.xbib.gradle.plugin.jpackage; package org.xbib.gradle.plugin.jpackage;
import org.gradle.api.GradleException;
import org.gradle.api.NonNullApi; import org.gradle.api.NonNullApi;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.internal.os.OperatingSystem;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@NonNullApi @NonNullApi
@ -13,6 +15,9 @@ public abstract class JPackagePlugin implements Plugin<Project> {
@Override @Override
public void apply(Project project) { public void apply(Project project) {
if (!OperatingSystem.current().isLinux()) {
throw new GradleException("this plugin is only available for Linux");
}
project.getPlugins().apply(JavaPlugin.class); project.getPlugins().apply(JavaPlugin.class);
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
SourceDirectorySet mainResources = sourceSets.getByName("main").getResources(); SourceDirectorySet mainResources = sourceSets.getByName("main").getResources();

View file

@ -1,4 +1,4 @@
package org.xbib.gradle.plugin.jpackage.fixture package org.xbib.gradle.plugin.jpackage
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.GradleRunner
@ -6,7 +6,7 @@ import org.gradle.testkit.runner.GradleRunner
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.nio.file.Files import java.nio.file.Files
class GradleBuild { class JPackageGradleBuild {
final File projectDir final File projectDir
final File settingsFile final File settingsFile
@ -17,7 +17,7 @@ class GradleBuild {
final String gradleVersionUnderTest = System.getProperty('gradleVersionUnderTest') final String gradleVersionUnderTest = System.getProperty('gradleVersionUnderTest')
GradleBuild(File projectDir = Files.createTempDirectory('gradle-build').toFile()) { JPackageGradleBuild(File projectDir = Files.createTempDirectory('gradle-build').toFile()) {
this.projectDir = projectDir this.projectDir = projectDir
this.settingsFile = file('settings.gradle.kts') this.settingsFile = file('settings.gradle.kts')
this.appBuildFile = file('app/build.gradle.kts') this.appBuildFile = file('app/build.gradle.kts')

View file

@ -1,19 +1,19 @@
package org.xbib.gradle.plugin.jpackage package org.xbib.gradle.plugin.jpackage
import org.xbib.gradle.plugin.jpackage.fixture.GradleBuild
import spock.lang.Specification import spock.lang.Specification
import static org.gradle.testkit.runner.TaskOutcome.FAILED import static org.gradle.testkit.runner.TaskOutcome.FAILED
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.hostOs import static JPackageGradleBuild.hostOs
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnLinux import static JPackageGradleBuild.runsOnLinux
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnMacos import static JPackageGradleBuild.runsOnMacos
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnWindows import static JPackageGradleBuild.runsOnWindows
class JPackageTest extends Specification { class JPackageTest extends Specification {
@Delegate @Delegate
GradleBuild build = new GradleBuild() JPackageGradleBuild build = new JPackageGradleBuild()
def "can use plugin on #os with success=#success"() { def "can use plugin on #os with success=#success"() {
given: given: