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 36c02fe..d1290b1 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 @@ -8,7 +8,11 @@ import java.util.List; public class JLinkExtension { - private final ListProperty modules; + private final ListProperty addModules; + + private final Property bindServices; + + private final Property launcher; private final Property 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 modules) { - this.modules.set(modules); + public void setAddModules(List addModules) { + this.addModules.set(addModules); } - public ListProperty getModules() { - return modules; + public ListProperty getAddModules() { + return addModules; + } + + public void setBindServices(Boolean bindServices) { + this.bindServices.set(bindServices); + } + + public Property getBindServices() { + return bindServices; + } + + public void setLauncher(String launcher) { + this.launcher.set(launcher); + } + + public Property getLauncher() { + return launcher; } public void setCompress(Integer compress) { 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 30a6dd3..30af010 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 @@ -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 { @@ -17,7 +21,9 @@ public abstract class JLinkPlugin implements Plugin { 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 { TaskProvider 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()); diff --git a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkTask.java b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkTask.java index d703a96..c36aaf3 100644 --- a/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkTask.java +++ b/gradle-plugin-jlink/src/main/java/org/xbib/gradle/plugin/jlink/JLinkTask.java @@ -35,9 +35,26 @@ public class JLinkTask extends AbstractExecTask { @Nested Property javaCompiler; + @Input + ListProperty addModules; + @Input @Optional - ListProperty modules; + ListProperty limitModules; + + @Input + @Optional + Property bindServices; + + @InputDirectory + DirectoryProperty modulePath; + + @OutputDirectory + DirectoryProperty outputDirectory; + + @Input + @Optional + Property launcher; @Input @Optional @@ -59,31 +76,36 @@ public class JLinkTask extends AbstractExecTask { @Optional Property endian; - @InputDirectory - DirectoryProperty modulePath; - - @OutputDirectory - DirectoryProperty outputDirectory; + @Input + @Optional + ListProperty 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 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 { }); 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 = 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 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 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 { 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 { return javaCompiler; } - public void setModules(List modules) { - this.modules.set(modules); + public void setModulePath(Directory modulePath) { + this.modulePath.set(modulePath); } - public ListProperty 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 addModules) { + this.addModules.set(addModules); + } + + public ListProperty getAddModules() { + return addModules; + } + + public void setLimitModules(List limitModules) { + this.limitModules.set(limitModules); + } + + public ListProperty getLimitModules() { + return limitModules; + } + + public void setBindServices(Boolean bindServices) { + this.bindServices.set(bindServices); + } + + public Property getBindServices() { + return bindServices; + } + + public void setLauncher(String launcher) { + this.launcher.set(launcher); + } + + public Property getLauncher() { + return launcher; } public void setCompress(Integer compress) { @@ -190,20 +269,12 @@ public class JLinkTask extends AbstractExecTask { return endian; } - public void setModulePath(Directory modulePath) { - this.modulePath.set(modulePath); + public void setIncludeLocales(List 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 getIncludeLocales() { + return includeLocales; } private static void createDirectory(Path path) throws IOException { @@ -221,9 +292,9 @@ public class JLinkTask extends AbstractExecTask { 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()); } } 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 49bf8cb..9ac6821 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 @@ -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 { @@ -33,8 +41,8 @@ public class JModTask extends AbstractExecTask { @InputFile RegularFileProperty jarFile; - @OutputFile - RegularFileProperty jmodFile; + @OutputDirectory + DirectoryProperty jmodDirectory; @Inject public JModTask() { @@ -47,28 +55,42 @@ public class JModTask extends AbstractExecTask { TaskProvider 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 args = new ArrayList<>(List.of( + List 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 { 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 { return FileVisitResult.CONTINUE; } }); - Files.delete(path); + Files.deleteIfExists(path); } - // create only parent - Files.createDirectories(path.getParent()); + Files.createDirectory(path); + } + + private static List collectArtifacts(Configuration configuration) { + return configuration.getResolvedConfiguration() + .getFirstLevelModuleDependencies() + .stream() + .flatMap(JModTask::of) + .flatMap(dependency -> dependency.getModuleArtifacts().stream()) + .distinct() + .toList(); + } + + private static Stream of(ResolvedDependency node) { + return of(node, ResolvedDependency::getChildren); + } + + private static Stream of(T node, Function> childrenFn) { + return Stream.concat(Stream.of(node), childrenFn.apply(node).stream().flatMap(n -> of(n, childrenFn))); } } diff --git a/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkGradleBuild.groovy b/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkGradleBuild.groovy index 999d0b7..456bcf4 100644 --- a/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkGradleBuild.groovy +++ b/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkGradleBuild.groovy @@ -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) diff --git a/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkTest.groovy b/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkTest.groovy index b3a67e2..badb447 100644 --- a/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkTest.groovy +++ b/gradle-plugin-jlink/src/test/groovy/org/xbib/gradle/plugin/jlink/JLinkTest.groovy @@ -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 << """