diff --git a/graalvm/build.gradle b/graalvm/build.gradle new file mode 100644 index 0000000..c333dfd --- /dev/null +++ b/graalvm/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'java-gradle-plugin' +} + + +gradlePlugin { + plugins { + create("NativeImagePlugin") { + id = 'net.woggioni.gradle.graalvm.native-image' + implementationClass = "net.woggioni.gradle.graalvm.NativeImagePlugin" + } + } + + plugins { + create("JlinkPlugin") { + id = 'net.woggioni.gradle.graalvm.jlink' + implementationClass = "net.woggioni.gradle.graalvm.JlinkPlugin" + } + } +} diff --git a/graalvm/src/main/java/net/woggioni/gradle/graalvm/Constants.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/Constants.java new file mode 100644 index 0000000..f7c1206 --- /dev/null +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/Constants.java @@ -0,0 +1,5 @@ +package net.woggioni.gradle.graalvm; + +public class Constants { + public static final String GRAALVM_TASK_GROUP = "graalvm"; +} diff --git a/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkPlugin.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkPlugin.java new file mode 100644 index 0000000..162fa8d --- /dev/null +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkPlugin.java @@ -0,0 +1,57 @@ +package net.woggioni.gradle.graalvm; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaLibraryPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.bundling.Zip; +import org.gradle.jvm.tasks.Jar; + +import java.util.Optional; + +public class JlinkPlugin implements Plugin { + + public static final String JLINK_TASK_NAME = "jlink"; + public static final String JLINK_DIST_TASK_NAME = "jlinkDist"; + @Override + public void apply(Project project) { + project.getPluginManager().apply(JavaLibraryPlugin.class); + ExtensionContainer extensionContainer = project.getExtensions(); + BasePluginExtension basePluginExtension = extensionContainer.getByType(BasePluginExtension.class); + JavaApplication javaApplicationExtension = + Optional.ofNullable(extensionContainer.findByType(JavaApplication.class)) + .orElseGet(() -> extensionContainer.create("application", JavaApplication.class)); + + TaskContainer tasks = project.getTasks(); + Provider jlinTaskProvider = tasks.register(JLINK_TASK_NAME, JlinkTask.class, jlinkTask -> { + ConfigurationContainer configurations = project.getConfigurations(); + FileCollection classpath = project.files(tasks.named(JavaPlugin.JAR_TASK_NAME), + configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + jlinkTask.getClasspath().set(classpath); + }); + + Provider jlinkZipTaskProvider = tasks.register(JLINK_DIST_TASK_NAME, Zip.class, zip -> { + zip.getArchiveBaseName().set(project.getName()); + if(project.getVersion() != null) { + zip.getArchiveVersion().set(project.getVersion().toString()); + } + zip.getDestinationDirectory().set(basePluginExtension.getDistsDirectory()); + zip.from(jlinTaskProvider); + }); + + tasks.named(BasePlugin.ASSEMBLE_TASK_NAME, Task.class, t -> { + t.getInputs().files(jlinkZipTaskProvider); + }); + + } +} diff --git a/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkTask.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkTask.java new file mode 100644 index 0000000..498dc80 --- /dev/null +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/JlinkTask.java @@ -0,0 +1,158 @@ +package net.woggioni.gradle.graalvm; + +import lombok.SneakyThrows; +import org.gradle.api.Project; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Exec; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.OutputFile; +import org.gradle.internal.jvm.JavaModuleDetector; +import org.gradle.jvm.toolchain.JavaInstallationMetadata; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.process.CommandLineArgumentProvider; + +import javax.inject.Inject; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +import static java.util.Optional.ofNullable; +import static net.woggioni.gradle.graalvm.Constants.GRAALVM_TASK_GROUP; + +public abstract class JlinkTask extends Exec { + + @Classpath + public abstract Property getClasspath(); + + @InputDirectory + public abstract DirectoryProperty getGraalVmHome(); + + @Input + @Optional + public abstract Property getMainClass(); + + @Input + @Optional + public abstract Property getMainModule(); + + @Input + public abstract ListProperty getAdditionalModules(); + + @Inject + protected abstract JavaModuleDetector getJavaModuleDetector(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDir(); + private final Logger logger; + public JlinkTask() { + Project project = getProject(); + logger = project.getLogger(); + setGroup(GRAALVM_TASK_GROUP); + setDescription( + "Generates a custom Java runtime image that contains only the platform modules" + + " that are required for a given application"); + ExtensionContainer ext = project.getExtensions(); + JavaApplication javaApplication = ext.findByType(JavaApplication.class); + if(!Objects.isNull(javaApplication)) { + getMainClass().convention(javaApplication.getMainClass()); + getMainModule().convention(javaApplication.getMainModule()); + } + getClasspath().convention(project.files()); + ProjectLayout layout = project.getLayout(); + JavaToolchainService javaToolchainService = ext.findByType(JavaToolchainService.class); + JavaPluginExtension javaPluginExtension = ext.findByType(JavaPluginExtension.class); + Provider graalHomeDirectoryProvider = ofNullable(javaPluginExtension.getToolchain()).map(javaToolchainSpec -> + javaToolchainService.launcherFor(javaToolchainSpec) + ).map(javaLauncher -> + javaLauncher.map(JavaLauncher::getMetadata).map(JavaInstallationMetadata::getInstallationPath) + ).orElseGet(() -> layout.dir(project.provider(() ->project.file(System.getProperty("java.home"))))); + getGraalVmHome().convention(graalHomeDirectoryProvider); + getAdditionalModules().convention(new ArrayList<>()); + + BasePluginExtension basePluginExtension = + ext.getByType(BasePluginExtension.class); + getOutputDir().convention( + basePluginExtension.getLibsDirectory() + .dir(project.getName() + + ofNullable(project.getVersion()).map(it -> "-" + it).orElse("")) + ); + Object executableProvider = new Object() { + @Override + public String toString() { + return getGraalVmHome().get() + "/bin/jlink"; + } + }; + executable(executableProvider); + CommandLineArgumentProvider argumentProvider = new CommandLineArgumentProvider() { + @Override + @SneakyThrows + public Iterable asArguments() { + List result = new ArrayList<>(); + result.add("--compress=2"); + JavaModuleDetector javaModuleDetector = getJavaModuleDetector(); + FileCollection classpath = getClasspath().get(); + FileCollection mp = javaModuleDetector.inferModulePath(true, classpath); + if(!mp.isEmpty()) { + result.add("-p"); + result.add(mp.getAsPath()); + } + + if(getMainModule().isPresent()) { + result.add("--launcher"); + String launcherArg = project.getName() + '=' + + getMainModule().get() + + ofNullable(getMainClass().getOrElse(null)).map(it -> '/' + it).orElse(""); + result.add(launcherArg); + } + result.add("--output"); + result.add(getOutputDir().get().getAsFile().toString()); + List additionalModules = getAdditionalModules().get(); + if(getMainModule().isPresent() || !additionalModules.isEmpty()) { + result.add("--add-modules"); + ofNullable(getMainModule().getOrElse(null)).ifPresent(result::add); + additionalModules.forEach(result::add); + } + return Collections.unmodifiableList(result); + } + }; + getArgumentProviders().add(argumentProvider); + + } + + @Override + @SneakyThrows + protected void exec() { + Files.walk(getOutputDir().get().getAsFile().toPath()) + .sorted(Comparator.reverseOrder()) + .forEach(new Consumer() { + @Override + @SneakyThrows + public void accept(Path path) { + Files.delete(path); + } + }); + super.exec(); + } +} diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageConfigurationTask.java similarity index 85% rename from native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java rename to graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageConfigurationTask.java index c68572b..4f05d02 100644 --- a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageConfigurationTask.java +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageConfigurationTask.java @@ -1,14 +1,12 @@ -package net.woggioni.gradle.nativeimage; +package net.woggioni.gradle.graalvm; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; -import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.ProjectLayout; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Input; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskContainer; @@ -19,10 +17,9 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.StreamSupport; -import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME; -import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_TASK_GROUP; +import static net.woggioni.gradle.graalvm.Constants.GRAALVM_TASK_GROUP; +import static net.woggioni.gradle.graalvm.NativeImagePlugin.NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME; public abstract class NativeImageConfigurationTask extends JavaExec { @@ -30,7 +27,7 @@ public abstract class NativeImageConfigurationTask extends JavaExec { public abstract DirectoryProperty getConfigurationDir(); public NativeImageConfigurationTask() { - setGroup(NATIVE_IMAGE_TASK_GROUP); + setGroup(GRAALVM_TASK_GROUP); setDescription("Run the application with the native-image-agent " + "to create a configuration for native image creation"); ProjectLayout layout = getProject().getLayout(); diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImagePlugin.java similarity index 76% rename from native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java rename to graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImagePlugin.java index 15cfee4..392272f 100644 --- a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImagePlugin.java +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImagePlugin.java @@ -1,10 +1,12 @@ -package net.woggioni.gradle.nativeimage; +package net.woggioni.gradle.graalvm; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.file.ProjectLayout; +import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaLibraryPlugin; @@ -21,8 +23,6 @@ public class NativeImagePlugin implements Plugin { public static final String CONFIGURE_NATIVE_IMAGE_TASK_NAME = "configureNativeImage"; public static final String NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME = "native-image"; - public static final String NATIVE_IMAGE_TASK_GROUP = "native image"; - @Override public void apply(Project project) { project.getPluginManager().apply(JavaLibraryPlugin.class); @@ -33,6 +33,7 @@ public class NativeImagePlugin implements Plugin { .orElseGet(() -> extensionContainer.create("application", JavaApplication.class)); TaskContainer tasks = project.getTasks(); + Provider jarTaskProvider = tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, jar -> { jar.from(layout.getProjectDirectory().dir(NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME), copySpec -> { copySpec.into( @@ -43,12 +44,20 @@ public class NativeImagePlugin implements Plugin { ); }); }); - tasks.register(CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask.class); - tasks.register(NATIVE_IMAGE_TASK_NAME, NativeImageTask.class, nativeImageTask -> { + + Provider nativeImageConfigurationTaskProvider = + tasks.register(CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask.class); + + Provider nativeImageTaskProvider = tasks.register(NATIVE_IMAGE_TASK_NAME, NativeImageTask.class, nativeImageTask -> { + nativeImageTask.getInputs().files(nativeImageConfigurationTaskProvider); ConfigurationContainer configurations = project.getConfigurations(); FileCollection classpath = project.files(jarTaskProvider, configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); nativeImageTask.getClasspath().set(classpath); }); + + tasks.named(BasePlugin.ASSEMBLE_TASK_NAME, Task.class, t -> { + t.getInputs().files(nativeImageTaskProvider); + }); } } diff --git a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageTask.java similarity index 89% rename from native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java rename to graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageTask.java index abe3a0c..cdbc51b 100644 --- a/native-image/src/main/java/net/woggioni/gradle/nativeimage/NativeImageTask.java +++ b/graalvm/src/main/java/net/woggioni/gradle/graalvm/NativeImageTask.java @@ -1,4 +1,4 @@ -package net.woggioni.gradle.nativeimage; +package net.woggioni.gradle.graalvm; import org.gradle.api.Project; import org.gradle.api.file.Directory; @@ -32,7 +32,7 @@ import java.util.List; import java.util.Objects; import static java.util.Optional.ofNullable; -import static net.woggioni.gradle.nativeimage.NativeImagePlugin.NATIVE_IMAGE_TASK_GROUP; +import static net.woggioni.gradle.graalvm.Constants.GRAALVM_TASK_GROUP; public abstract class NativeImageTask extends Exec { @@ -45,6 +45,10 @@ public abstract class NativeImageTask extends Exec { @Input public abstract Property getUseJpms(); @Input + public abstract Property getUseMusl(); + @Input + public abstract Property getBuildStaticImage(); + @Input public abstract Property getEnableFallback(); @Input @@ -63,9 +67,11 @@ public abstract class NativeImageTask extends Exec { public NativeImageTask() { Project project = getProject(); logger = project.getLogger(); - setGroup(NATIVE_IMAGE_TASK_GROUP); + setGroup(GRAALVM_TASK_GROUP); setDescription("Create a native image of the application using GraalVM"); getUseJpms().convention(false); + getUseMusl().convention(false); + getBuildStaticImage().convention(false); getEnableFallback().convention(false); ExtensionContainer ext = project.getExtensions(); JavaApplication javaApplication = ext.findByType(JavaApplication.class); @@ -102,6 +108,12 @@ public abstract class NativeImageTask extends Exec { if(!getEnableFallback().get()) { result.add("--no-fallback"); } + if(getBuildStaticImage().get()) { + result.add("--static"); + } + if(getUseMusl().get()) { + result.add("--libc=musl"); + } JavaModuleDetector javaModuleDetector = getJavaModuleDetector(); boolean useJpms = getUseJpms().get(); FileCollection classpath = getClasspath().get(); @@ -118,7 +130,6 @@ public abstract class NativeImageTask extends Exec { result.add("-o"); result.add(getOutputFile().get().getAsFile().toString()); result.add(getMainClass().get()); - logger.info("Native image arguments: " + String.join(" ", result)); return Collections.unmodifiableList(result); } }; diff --git a/gradle.properties b/gradle.properties index 9ce2e79..590de1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ woggioniMavenRepositoryUrl=https://mvn.woggioni.net/ lys.catalog.version=2023.10.01 -version.myGradlePlugins=2023.10.05 +version.myGradlePlugins=2023.10.06 version.gradle=7.6 version.felix.config.admin=1.9.26 version.felix=7.0.5 diff --git a/native-image/build.gradle b/native-image/build.gradle deleted file mode 100644 index 53f4771..0000000 --- a/native-image/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'java-gradle-plugin' -} - - -gradlePlugin { - plugins { - create("NativeImagePlugin") { - id = "net.woggioni.gradle.native-image" - implementationClass = "net.woggioni.gradle.nativeimage.NativeImagePlugin" - } - } -} diff --git a/settings.gradle b/settings.gradle index ef5191d..671d1e0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,4 +26,4 @@ include 'osgi-app:osgi-simple-bootstrapper-api' include 'osgi-app:osgi-simple-bootstrapper-application' include 'wildfly' include 'sambal' -include 'native-image' +include 'graalvm'