create jmods of runtime dependencies and add more jlink options

This commit is contained in:
Jörg Prante 2024-10-31 18:09:19 +01:00
parent e91f776db2
commit 942e765c78
6 changed files with 224 additions and 82 deletions

View file

@ -8,7 +8,11 @@ import java.util.List;
public class JLinkExtension {
private final ListProperty<String> modules;
private final ListProperty<String> addModules;
private final Property<Boolean> bindServices;
private final Property<String> launcher;
private final Property<Integer> compress;
@ -27,7 +31,9 @@ public class JLinkExtension {
}
public JLinkExtension(Project project) {
this.modules = project.getObjects().listProperty(String.class);
this.addModules = project.getObjects().listProperty(String.class);
this.bindServices = project.getObjects().property(Boolean.class);
this.launcher = project.getObjects().property(String.class);
this.compress = project.getObjects().property(Integer.class);
this.stripDebug = project.getObjects().property(Boolean.class);
this.noHeaderFiles = project.getObjects().property(Boolean.class);
@ -35,12 +41,28 @@ public class JLinkExtension {
this.endian = project.getObjects().property(Endian.class);
}
public void setModules(List<String> modules) {
this.modules.set(modules);
public void setAddModules(List<String> addModules) {
this.addModules.set(addModules);
}
public ListProperty<String> getModules() {
return modules;
public ListProperty<String> getAddModules() {
return addModules;
}
public void setBindServices(Boolean bindServices) {
this.bindServices.set(bindServices);
}
public Property<Boolean> getBindServices() {
return bindServices;
}
public void setLauncher(String launcher) {
this.launcher.set(launcher);
}
public Property<String> getLauncher() {
return launcher;
}
public void setCompress(Integer compress) {

View file

@ -3,13 +3,17 @@ package org.xbib.gradle.plugin.jlink;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedDependency;
import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class JLinkPlugin implements Plugin<Project> {
@ -17,7 +21,9 @@ public abstract class JLinkPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class);
JLinkExtension extension = project.getExtensions().create("jlink", JLinkExtension.class);
extension.getModules().convention(List.of("java.base"));
extension.getAddModules().convention(List.of("java.base"));
extension.getBindServices().convention(false);
extension.getLauncher().unsetConvention();
extension.getCompress().convention(6); // zip-6, default
extension.getStripDebug().convention(true);
extension.getNoHeaderFiles().convention(true);
@ -33,7 +39,9 @@ public abstract class JLinkPlugin implements Plugin<Project> {
TaskProvider<JLinkTask> jLinkTask = project.getTasks().register("jlink", JLinkTask.class);
project.getTasks().withType(JLinkTask.class).forEach(it -> {
it.dependsOn(jModTask);
it.modules.set(extension.getModules());
it.addModules.set(extension.getAddModules());
it.bindServices.set(extension.getBindServices());
it.launcher.set(extension.getLauncher());
it.compress.set(extension.getCompress());
it.stripDebug.set(extension.getStripDebug());
it.noHeaderFiles.set(extension.getNoHeaderFiles());

View file

@ -35,9 +35,26 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
@Nested
Property<JavaCompiler> javaCompiler;
@Input
ListProperty<String> addModules;
@Input
@Optional
ListProperty<String> modules;
ListProperty<String> limitModules;
@Input
@Optional
Property<Boolean> bindServices;
@InputDirectory
DirectoryProperty modulePath;
@OutputDirectory
DirectoryProperty outputDirectory;
@Input
@Optional
Property<String> launcher;
@Input
@Optional
@ -59,31 +76,36 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
@Optional
Property<JLinkExtension.Endian> endian;
@InputDirectory
DirectoryProperty modulePath;
@OutputDirectory
DirectoryProperty outputDirectory;
@Input
@Optional
ListProperty<String> includeLocales;
@Inject
public JLinkTask() {
super(JLinkTask.class);
this.javaCompiler = getProject().getObjects().property(JavaCompiler.class);
this.modules = getProject().getObjects().listProperty(String.class);
this.compress = getProject().getObjects().property(Integer.class);
this.stripDebug = getProject().getObjects().property(Boolean.class);
this.noHeaderFiles = getProject().getObjects().property(Boolean.class);
this.noManPages = getProject().getObjects().property(Boolean.class);
this.endian = getProject().getObjects().property(JLinkExtension.Endian.class);
this.modulePath = getProject().getObjects().directoryProperty();
this.outputDirectory = getProject().getObjects().directoryProperty();
javaCompiler = getProject().getObjects().property(JavaCompiler.class);
addModules = getProject().getObjects().listProperty(String.class);
limitModules = getProject().getObjects().listProperty(String.class);
bindServices = getProject().getObjects().property(Boolean.class);
modulePath = getProject().getObjects().directoryProperty();
outputDirectory = getProject().getObjects().directoryProperty();
launcher = getProject().getObjects().property(String.class);
compress = getProject().getObjects().property(Integer.class);
stripDebug = getProject().getObjects().property(Boolean.class);
noHeaderFiles = getProject().getObjects().property(Boolean.class);
noManPages = getProject().getObjects().property(Boolean.class);
endian = getProject().getObjects().property(JLinkExtension.Endian.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));
modules.convention(List.of("java.base"));
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?");
@ -92,30 +114,42 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
});
modulePath.convention(modulePathProvider);
outputDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jlink"));
launcher.unsetConvention();
includeLocales.unsetConvention();
}
@Override
public void exec() {
setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jlink"));
TaskProvider<JModTask> jmodTask = getProject().getTasks().named("jmod", JModTask.class);
File jlinkOutput = outputDirectory.dir("jlink-output").get().getAsFile();
File jlinkOutput = outputDirectory.get().getAsFile();
try {
createDirectory(jlinkOutput.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
List<String> args = new ArrayList<>(List.of(
"--module-path",
modulePath.get().getAsFile().getAbsolutePath(),
"--module-path",
jmodTask.get().jmodFile.getAsFile().get().getAbsolutePath(),
"--add-modules",
String.join(",", modules.get()),
"--compress",
"zip-" + compress.get(),
"--output",
jlinkOutput.getAbsolutePath()
));
List<String> args = new ArrayList<>();
args.add("--module-path");
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(String.join(",", limitModules.get()));
}
args.add("--compress");
args.add("zip-" + compress.get());
args.add("--output");
args.add(jlinkOutput.getAbsolutePath());
if (bindServices.get()) {
args.add("--bind-services");
}
if (launcher.isPresent()) {
args.add("--launcher");
args.add(launcher.get());
}
if (stripDebug.get()) {
args.add("--strip-debug");
}
@ -129,8 +163,13 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
args.add("--endian");
args.add(endian.get().toString().toLowerCase(Locale.ROOT));
}
if (includeLocales.isPresent()) {
args.add("--include-locales");
args.add(String.join(",", includeLocales.get()));
}
setArgs(args);
System.err.println( "executing " + getExecutable() + " with " + args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec();
}
@ -142,12 +181,52 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
return javaCompiler;
}
public void setModules(List<String> modules) {
this.modules.set(modules);
public void setModulePath(Directory modulePath) {
this.modulePath.set(modulePath);
}
public ListProperty<String> getModules() {
return modules;
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) {
this.addModules.set(addModules);
}
public ListProperty<String> getAddModules() {
return addModules;
}
public void setLimitModules(List<String> limitModules) {
this.limitModules.set(limitModules);
}
public ListProperty<String> getLimitModules() {
return limitModules;
}
public void setBindServices(Boolean bindServices) {
this.bindServices.set(bindServices);
}
public Property<Boolean> getBindServices() {
return bindServices;
}
public void setLauncher(String launcher) {
this.launcher.set(launcher);
}
public Property<String> getLauncher() {
return launcher;
}
public void setCompress(Integer compress) {
@ -190,20 +269,12 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
return endian;
}
public void setModulePath(Directory modulePath) {
this.modulePath.set(modulePath);
public void setIncludeLocales(List<String> includeLocales) {
this.includeLocales.set(includeLocales);
}
public DirectoryProperty getModulePath() {
return modulePath;
}
public void setOutputDirectory(Directory outputDirectory) {
this.outputDirectory.set(outputDirectory);
}
public DirectoryProperty getOutputDirectory() {
return outputDirectory;
public ListProperty<String> getIncludeLocales() {
return includeLocales;
}
private static void createDirectory(Path path) throws IOException {
@ -221,9 +292,9 @@ public class JLinkTask extends AbstractExecTask<JLinkTask> {
return FileVisitResult.CONTINUE;
}
});
Files.delete(path);
Files.deleteIfExists(path);
}
// create only parent, jlink aborts if output directory exists
// jlink bails out if directory is created, so we create the parent
Files.createDirectories(path.getParent());
}
}

View file

@ -1,13 +1,19 @@
package org.xbib.gradle.plugin.jlink;
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.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.Property;
import org.gradle.api.tasks.AbstractExecTask;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaCompiler;
@ -21,9 +27,11 @@ 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;
import java.util.function.Function;
import java.util.stream.Stream;
public class JModTask extends AbstractExecTask<JModTask> {
@ -33,8 +41,8 @@ public class JModTask extends AbstractExecTask<JModTask> {
@InputFile
RegularFileProperty jarFile;
@OutputFile
RegularFileProperty jmodFile;
@OutputDirectory
DirectoryProperty jmodDirectory;
@Inject
public JModTask() {
@ -47,28 +55,42 @@ public class JModTask extends AbstractExecTask<JModTask> {
TaskProvider<Jar> jarTask = getProject().getTasks().named("jar", Jar.class);
Objects.requireNonNull(jarTask);
jarFile.convention(jarTask.get().getArchiveFile());
jmodFile = getProject().getObjects().fileProperty();
jmodFile.convention(getProject().getLayout().getBuildDirectory()
.dir("jmod").get().file(getProject().getName() + ".jmod"));
jmodDirectory = getProject().getObjects().directoryProperty();
jmodDirectory.convention(getProject().getLayout().getBuildDirectory().dir("jmods"));
}
@Override
public void exec() {
setExecutable(javaCompiler.get().getMetadata().getInstallationPath().file("bin/jmod"));
try {
createDirectory(jmodFile.getAsFile().get().toPath());
System.err.println("creating directory " + jmodDirectory.getAsFile().get().toPath());
createDirectory(jmodDirectory.getAsFile().get().toPath());
} catch (IOException e) {
getLogger().log(LogLevel.ERROR, e.getMessage(), e);
throw new RuntimeException(e);
}
List<String> args = new ArrayList<>(List.of(
List<String> args = List.of(
"create",
"--class-path",
jarFile.get().getAsFile().getAbsolutePath(),
jmodFile.get().getAsFile().getAbsolutePath()
));
jmodDirectory.get().file(getProject().getName() + ".jmod").getAsFile().getAbsolutePath()
);
setArgs(args);
System.err.println( "executing " + getExecutable() + " with " + args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec();
for (ResolvedArtifact artifact : collectArtifacts(getProject().getConfigurations().getByName("runtimeClasspath"))) {
args = List.of(
"create",
"--class-path",
artifact.getFile().getAbsolutePath(),
jmodDirectory.get().file(artifact.getName() + ".jmod").getAsFile().getAbsolutePath()
);
setArgs(args);
System.err.println("executing " + getExecutable() + " with " + args);
getLogger().log(LogLevel.INFO, "executing " + getExecutable() + " with " + args);
super.exec();
}
}
public void setJavaCompiler(JavaCompiler javaCompiler) {
@ -87,12 +109,12 @@ public class JModTask extends AbstractExecTask<JModTask> {
return jarFile;
}
public void setJmodFile(RegularFile jmodFile) {
this.jmodFile.set(jmodFile);
public void setJmodDirectory(Directory jmodDirectory) {
this.jmodDirectory.set(jmodDirectory);
}
public RegularFileProperty getJmodFile() {
return jmodFile;
public DirectoryProperty getJmodDirectory() {
return jmodDirectory;
}
private static void createDirectory(Path path) throws IOException {
@ -110,9 +132,26 @@ public class JModTask extends AbstractExecTask<JModTask> {
return FileVisitResult.CONTINUE;
}
});
Files.delete(path);
Files.deleteIfExists(path);
}
// create only parent
Files.createDirectories(path.getParent());
Files.createDirectory(path);
}
private static List<ResolvedArtifact> collectArtifacts(Configuration configuration) {
return configuration.getResolvedConfiguration()
.getFirstLevelModuleDependencies()
.stream()
.flatMap(JModTask::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

@ -32,11 +32,6 @@ class JLinkGradleBuild {
id("org.xbib.gradle.plugin.jlink")
}
group = "org.example"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
'''
tapFile("app/src/main/java/org/example/app/Main.java") << '''
package org.example.app;
@ -98,7 +93,10 @@ class JLinkGradleBuild {
}
GradleRunner runner(String... args) {
def err = new PrintWriter(System.err)
GradleRunner.create()
.forwardStdError(err)
.forwardStdOutput(err)
.forwardOutput()
.withPluginClasspath()
.withProjectDir(projectDir)

View file

@ -11,12 +11,16 @@ class JLinkTest extends Specification {
@Delegate
JLinkGradleBuild build = new JLinkGradleBuild()
def "can use jlink plugin with success=#success"() {
def "can use jlink plugin"() {
given:
def taskToRun = ":app:jlink"
appBuildFile << """
dependencies {
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.2"
}
jlink {
modules.set(List.of("org.example.app"))
addModules.set(List.of("org.example.app"))
launcher.set("app=org.example.app/org.example.app.Main")
}
"""
appModuleInfoFile << """