Compare commits

..

28 Commits

Author SHA1 Message Date
3f6627465c added jdeps plugin
All checks were successful
CI / build (push) Successful in 2m26s
2025-04-15 22:04:50 +08:00
0d32fb5ba9 added tar distribution to jlink plugin
All checks were successful
CI / build (push) Successful in 2m45s
2025-04-15 14:12:20 +08:00
8d9861ae75 added toolchain support to NativeImageconfigurationTask
All checks were successful
CI / build (push) Successful in 1m32s
2025-03-08 14:36:47 +08:00
97df729677 added Gradle toolchain support to NativeImage task
All checks were successful
CI / build (push) Successful in 1m39s
2025-03-03 17:57:57 +08:00
d580161d01 added nativeCompilerPath property to GraalVM NativeImage task
All checks were successful
CI / build (push) Successful in 1m49s
2025-03-03 13:29:16 +08:00
2b978ddf1c made NativeImageTask cacheable in native image plugin
All checks were successful
CI / build (push) Successful in 1m27s
2025-02-28 09:26:50 +08:00
0d5865e638 improved graalvm plugin
All checks were successful
CI / build (push) Successful in 1m26s
2025-02-25 16:39:14 +08:00
04d50e5a52 modernized JPMS check and dependency export plugins
All checks were successful
CI / build (push) Successful in 1m57s
2025-02-08 00:48:20 +08:00
e0d8628188 added compressionLevel and bindServices properties to the jlink task
All checks were successful
CI / build (push) Successful in 2m22s
2025-02-05 21:37:05 +08:00
4565a626d6 fixed gitea build
All checks were successful
CI / build (push) Successful in 1m48s
2025-01-24 16:47:26 +08:00
99f1fd16ab added JPMS support to native image plugin
Some checks failed
CI / build (push) Has been cancelled
2025-01-24 16:45:52 +08:00
5c16f1bc13 updated Gradle to 8.12
All checks were successful
CI / build (push) Successful in 1m32s
2025-01-09 16:25:59 +08:00
0c3f08bfd5 made Sambal plugin compatible with Gradle configuration cache
All checks were successful
CI / build (push) Successful in 2m12s
2024-12-29 22:09:39 +08:00
b6fa9f2948 fixed configuration cache issue in Sambal plugin
All checks were successful
CI / build (push) Successful in 1m53s
2024-12-28 15:04:59 +08:00
d1af8ef942 fixed gitea build
All checks were successful
CI / build (push) Successful in 2m32s
2024-12-08 21:58:22 +08:00
6146868f37 update gradle version
Some checks are pending
CI / build (push) Waiting to run
2024-12-08 21:57:09 +08:00
9c361d0419 fully migrated to gitea
All checks were successful
CI / build (push) Successful in 38s
2024-04-04 07:04:27 +08:00
bffa4f1f56 migrated to gitea
All checks were successful
CI / build (push) Successful in 38s
2024-04-04 06:12:23 +08:00
bc92b75136 fixed Java release version 2024-03-11 18:56:50 +08:00
72bfcf4842 fixed bug with deploymentName property 2024-02-24 17:30:31 +08:00
5304576900 added deployment name to Wildfly plugin 2024-02-22 06:05:48 +08:00
b3be3ca226 jlink and nativeImage not anymore included in assemble 2023-11-07 16:37:22 +08:00
f494dc6815 added documentation of NativeImage plugin 2023-10-23 16:53:40 +08:00
e1028249ae added Gradle toolchain support to NativeImageConfigurationTask 2023-10-20 20:58:57 +08:00
b1757a85be added jlink plugin 2023-10-06 15:08:42 +08:00
dcdeb2871a added sambal plugin and native image plugin 2023-10-05 21:55:54 +08:00
c8222ca9aa improved lombok plugin to support JPMS 2023-07-16 22:39:46 +08:00
34810e9263 rewritten multi-release-jar plugin in Java 2023-07-09 20:16:00 +08:00
44 changed files with 2501 additions and 625 deletions

View File

@@ -0,0 +1,16 @@
name: CI
on:
push:
branches: [ master ]
jobs:
build:
runs-on: hostinger
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Execute Gradle build
env:
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
run: ./gradlew build publish

View File

@@ -1,16 +1,18 @@
subprojects { subproject ->
apply plugin: 'java-library'
apply plugin: 'maven-publish'
group = "net.woggioni.gradle"
version = getProperty('version.myGradlePlugins')
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
languageVersion = JavaLanguageVersion.of(21)
}
}
int javaVersion
if(subproject.path == ':osgi-app') {
if(subproject.path == ':osgi-app' || subproject.path == ':multi-release-jar') {
javaVersion = 11
} else {
javaVersion = 8
@@ -27,12 +29,6 @@ subprojects { subproject ->
}
repositories {
maven {
url = woggioniMavenRepositoryUrl
content {
includeGroup 'net.woggioni'
}
}
mavenCentral()
}
@@ -45,27 +41,30 @@ subprojects { subproject ->
add("testImplementation", gradleTestKit())
}
tasks.named("test", Test) {
tasks.withType(Test) {
useJUnitPlatform()
}
}
childProjects.forEach { name, child ->
child.with {
apply plugin: 'maven-publish'
publishing {
repositories {
maven {
name = "Gitea"
url = uri("https://gitea.woggioni.net/api/packages/woggioni/maven")
group = "net.woggioni.gradle"
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token ${System.getenv()["PUBLISHER_TOKEN"]}"
}
publishing {
repositories {
maven {
url = woggioniMavenRepositoryUrl
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
}
wrapper {
gradleVersion = getProperty("version.gradle")
distributionType = Wrapper.DistributionType.ALL

View File

@@ -8,6 +8,9 @@ import org.gradle.api.plugins.ObjectConfigurationAction;
import org.gradle.api.provider.Provider;
public class DependencyExportPlugin implements Plugin<Project> {
public static final String DEPENDENCY_EXPORT_GROUP = "dependency-export";
@Override
public void apply(Project project) {
project.apply(new Action<ObjectConfigurationAction>() {

View File

@@ -24,6 +24,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
@@ -49,6 +50,8 @@ import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.woggioni.gradle.dependency.export.DependencyExportPlugin.DEPENDENCY_EXPORT_GROUP;
public class ExportDependencies extends DefaultTask {
@Getter(onMethod_ = { @Input })
@@ -74,6 +77,7 @@ public class ExportDependencies extends DefaultTask {
private final JavaPluginConvention javaPluginConvention;
@InputFiles
@Classpath
public Provider<FileCollection> getConfigurationFiles() {
return configurationName.map(this::fetchConfiguration);
}
@@ -96,6 +100,7 @@ public class ExportDependencies extends DefaultTask {
@Inject
public ExportDependencies(ObjectFactory objects) {
setGroup(DEPENDENCY_EXPORT_GROUP);
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class);
configurationName = objects.property(String.class).convention(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
Provider<File> defaultOutputFileProvider =

View File

@@ -10,29 +10,37 @@ import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.options.Option;
import javax.inject.Inject;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import static net.woggioni.gradle.dependency.export.DependencyExportPlugin.DEPENDENCY_EXPORT_GROUP;
@CacheableTask
public class RenderDependencies extends DefaultTask {
@Getter(onMethod_ = { @InputFile })
@Getter(onMethod_ = {@InputFile, @PathSensitive(PathSensitivity.NONE)})
private Provider<File> sourceFile;
@Getter(onMethod_ = { @Input})
@Getter(onMethod_ = {@Input})
private final Property<String> format;
@Getter(onMethod_ = { @Input })
@Getter(onMethod_ = {@Input})
private final Property<String> graphvizExecutable;
@Getter
@@ -45,6 +53,7 @@ public class RenderDependencies extends DefaultTask {
return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull();
}
@Optional
@OutputFile
public Provider<File> getResult() {
return outputFile.map(RegularFile::getAsFile);
@@ -64,33 +73,46 @@ public class RenderDependencies extends DefaultTask {
}
public void setExportTask(Provider<ExportDependencies> taskProvider) {
dependsOn(taskProvider);
sourceFile = taskProvider.flatMap(ExportDependencies::getResult);
}
@Inject
public RenderDependencies(ObjectFactory objects) {
setGroup(DEPENDENCY_EXPORT_GROUP);
sourceFile = objects.property(File.class);
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class);
format = objects.property(String.class).convention("xlib");
graphvizExecutable = objects.property(String.class).convention("dot");
Provider<File> defaultOutputFileProvider =
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "renderedDependencies"));
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider));
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider)
.zip(format, (file, type) -> Objects.equals("xlib", type) ? null : file));
getOutputs().upToDateWhen(t -> outputFile.isPresent());
}
@TaskAction
@SneakyThrows
void run() {
Path destination = outputFile
.map(RegularFile::getAsFile)
.map(File::toPath)
.get();
List<String> cmd = Arrays.asList(
java.util.Optional<Path> destination = java.util.Optional.of(
outputFile
.map(RegularFile::getAsFile)
.map(File::toPath)
)
.filter(Provider::isPresent)
.map(Provider::get);
List<String> cmd = new ArrayList<>(Arrays.asList(
graphvizExecutable.get(),
"-T" + format.get(),
"-o" + destination,
sourceFile.get().toString()
);
"-T" + format.get()
));
if (destination.isPresent()) {
cmd.add("-o");
cmd.add(destination.get().toString());
}
cmd.add(sourceFile.get().toString());
int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor();
if (returnCode != 0) {
throw new GradleException("Error invoking graphviz");

132
graalvm/README.md Normal file
View File

@@ -0,0 +1,132 @@
## Overview
This project contains 2 Gradle plugin:
- NativeImage allows you to create a native executable file from a Gradle project using GraalVM's `native-image` tool
- Jlink allows you to create a native distribution of a Gradle project using GraalVM `jlink` tool
### Native Image plugin
Declare the plugin in your build's `settings.gradle` like this
```groovy
pluginManagement {
repositories {
maven {
url = 'https://woggioni.net/mvn/'
}
}
plugins {
id "net.woggioni.gradle.graalvm.native-image" version "2023.10.23"
}
}
```
Then add it to a project's `build.gradle`
```groovy
plugins {
id 'net.woggioni.gradle.graalvm.native-image'
}
configureNativeImage {
args = [ 'your', 'command', 'line', 'arguments']
}
application {
mainClass = 'your.main.Class'
}
```
Mind that if your project also uses the built-in Gradle `application` plugin, that must be applied before the `net.woggioni.gradle.graalvm.native-image`.
The plugin adds 2 tasks to your project:
- `configureNativeImage` of type `net.woggioni.gradle.graalvm.NativeImageConfigurationTask` which launches
your application with the [native-image-agent](https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/)
to generate the native image configuration files in `${project.projectDir}/native-image`
- `nativeImage` of type `net.woggioni.gradle.graalvm.NativeImageTask` that creates the native executable the project's
libraries folder (`${project.buildDir}/libs`)
#### Configuration for `NativeImageConfigurationTask`
###### mergeConfiguration
This boolean property decides whether to create a new set of configuration files or simply append to the existing ones
#### Configuration for `NativeImageTask`
###### mainClass
The name of the main class the native executable will launch, defaults to the value set in
```groovy
application {
mainClass = 'my.main.class'
}
```
###### mainModule
The name of the main JPMS module the native executable will launch, defaults to the value set in
```groovy
application {
mainModule = 'my.main.module'
}
```
Only applicable when `useJpms` is true
###### useJpms
Whether or not enable JPMS in the generated executable
(dependencies that support JPMS will be forwarded to `native-image` using the `-p` instead of the `-cp` option)
###### useMusl
This boolean property allows to link the generated executable to musl libc,
note that in order to do that a valid (linked to musl-libc) compiler toolchain must be available
on the `PATH`, for example if building x86_64 executables on Linux, GraalVM will look for
`x86_64-linux-musl-gcc`
###### buildStaticImage
This boolean property allows to create a statically linked executable for maximum portability
(a static executable only depends on the kernel and can be moved freely to
another different machine with the same operating system and CPU architecture).
Beware that this requires the libc to support static linking
(most notably, the glibc does not support static linking, the only way to build static executables
on linux is to link to a different libc implementation like musl libc).
###### enableFallback
Whether or not to allow the creation of the [fallback image](https://www.graalvm.org/22.0/reference-manual/native-image/Limitations/)
when native-image configuration is missing.
###### graalVmHome
This points to the installation folder of GraalVM, defaults to the installation directory
of the selected Gradle toolchain (if any) or the installation directory
of the JVM Gradle is running on otherwise.
#### Customize native-image command line
A [native-image.properties](https://www.graalvm.org/22.0/reference-manual/native-image/BuildConfiguration/)
file can be added to `${project.projectDir}/native-image` to add custom parameters:
```bash
-H:Optimize=3 --initialize-at-run-time=your.main.class --gc=G1
```
#### Limitations
GraalVM with the [native-image](https://www.graalvm.org/22.0/reference-manual/native-image/) plugin is required in order
for these plugins to work, this can be achieved either running Gradle under GraalVM directly or using Gradle toolchains
support to request GraalVM at the project level
```groovy
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
vendor = JvmVendorSpec.GRAAL_VM
}
}
```

20
graalvm/build.gradle Normal file
View File

@@ -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"
}
}
}

View File

@@ -0,0 +1,5 @@
package net.woggioni.gradle.graalvm;
public class Constants {
public static final String GRAALVM_TASK_GROUP = "graalvm";
}

View File

@@ -0,0 +1,22 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import javax.inject.Inject;
abstract class DefaultNativeImageExtension implements NativeImageExtension {
@Inject
public DefaultNativeImageExtension(ObjectFactory objects) {
}
@Override
public JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action) {
JavaToolchainSpec jts = getToolchain();
action.execute(getToolchain());
return jts;
}
}

View File

@@ -0,0 +1,63 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
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.Tar;
import org.gradle.api.tasks.bundling.Zip;
import java.util.Optional;
public class JlinkPlugin implements Plugin<Project> {
public static final String JLINK_TASK_NAME = "jlink";
public static final String JLINK_DIST_ZIP_TASK_NAME = "jlinkDistZip";
public static final String JLINK_DIST_TAR_TASK_NAME = "jlinkDistTar";
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaLibraryPlugin.class);
ExtensionContainer extensionContainer = project.getExtensions();
BasePluginExtension basePluginExtension = extensionContainer.getByType(BasePluginExtension.class);
TaskContainer tasks = project.getTasks();
Provider<JlinkTask> 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<Zip> jlinkZipTaskProvider = tasks.register(JLINK_DIST_ZIP_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);
});
Provider<Tar> jlinkTarTaskProvider = tasks.register(JLINK_DIST_TAR_TASK_NAME, Tar.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(JLINK_TASK_NAME, JlinkTask.class, jlinkTask -> {
jlinkTask.finalizedBy(jlinkZipTaskProvider);
jlinkTask.finalizedBy(jlinkTarTaskProvider);
});
}
}

View File

@@ -0,0 +1,234 @@
package net.woggioni.gradle.graalvm;
import lombok.SneakyThrows;
import org.gradle.api.Action;
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.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.model.ObjectFactory;
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.internal.jvm.JavaModuleDetector;
import org.gradle.jvm.toolchain.JavaInstallationMetadata;
import org.gradle.jvm.toolchain.JavaLauncher;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
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 {
private final JavaToolchainSpec toolchain;
public JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action) {
action.execute(toolchain);
return toolchain;
}
@Classpath
public abstract Property<FileCollection> getClasspath();
@InputDirectory
public abstract DirectoryProperty getGraalVmHome();
@Input
@Optional
public abstract Property<String> getMainClass();
@Input
@Optional
public abstract Property<String> getMainModule();
@Input
public abstract ListProperty<String> getAdditionalModules();
@Input
public abstract ListProperty<String> getLimitModules();
@Input
public abstract Property<Boolean> getBindServices();
@Input
public abstract Property<Boolean> getIncludeHeaderFiles();
@Input
public abstract Property<Boolean> getIncludeManPages();
@Input
public abstract Property<Boolean> getStripDebug();
@Input
public abstract Property<Boolean> getGenerateCdsArchive();
@Input
@Optional
public abstract Property<Integer> getCompressionLevel();
@Inject
protected abstract JavaModuleDetector getJavaModuleDetector();
@OutputDirectory
public abstract DirectoryProperty getOutputDir();
private static final Logger log = Logging.getLogger(JlinkTask.class);
@Inject
public JlinkTask(ObjectFactory objects) {
Project project = getProject();
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());
}
getIncludeManPages().convention(false);
getIncludeHeaderFiles().convention(false);
getGenerateCdsArchive().convention(true);
getStripDebug().convention(true);
getClasspath().convention(project.files());
ProjectLayout layout = project.getLayout();
toolchain = getObjectFactory().newInstance(DefaultToolchainSpec.class);
JavaToolchainService javaToolchainService = ext.findByType(JavaToolchainService.class);
Provider<Directory> graalHomeDirectoryProvider = javaToolchainService.launcherFor(it -> {
it.getLanguageVersion().set(toolchain.getLanguageVersion());
it.getVendor().set(toolchain.getVendor());
it.getImplementation().set(toolchain.getImplementation());
}).map(javaLauncher ->
javaLauncher.getMetadata().getInstallationPath()
).orElse(layout.dir(project.provider(() -> project.file(System.getProperty("java.home")))));
getGraalVmHome().convention(graalHomeDirectoryProvider);
getGraalVmHome().convention(graalHomeDirectoryProvider);
getAdditionalModules().convention(new ArrayList<>());
getLimitModules().convention(new ArrayList<>());
getBindServices().convention(false);
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<String> asArguments() {
List<String> result = new ArrayList<>();
final Property<Integer> compressionLevelProperty = getCompressionLevel();
if(compressionLevelProperty.isPresent()) {
result.add(String.format("--compress=zip-%d", compressionLevelProperty.get()));
}
if(getBindServices().get()) {
result.add("--bind-services");
}
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<String> additionalModules = getAdditionalModules().get();
if(getMainModule().isPresent() || !additionalModules.isEmpty()) {
result.add("--add-modules");
final List<String> modules2BeAdded = new ArrayList<>();
ofNullable(getMainModule().getOrElse(null)).ifPresent(modules2BeAdded::add);
modules2BeAdded.addAll(additionalModules);
if(!modules2BeAdded.isEmpty()) {
result.add(String.join(",", modules2BeAdded));
}
}
List<String> limitModules = getLimitModules().get();
if(!limitModules.isEmpty()) {
result.add("--limit-modules");
final List<String> modules2BeAdded = new ArrayList<>();
modules2BeAdded.addAll(limitModules);
if(!modules2BeAdded.isEmpty()) {
result.add(String.join(",", modules2BeAdded));
}
}
if(getStripDebug().getOrElse(false)) {
result.add("--strip-debug");
}
if(getGenerateCdsArchive().getOrElse(false)) {
result.add("--generate-cds-archive");
}
if(!getIncludeHeaderFiles().getOrElse(true)) {
result.add("--no-header-files");
}
if(!getIncludeManPages().getOrElse(true)) {
result.add("--no-man-pages");
}
return Collections.unmodifiableList(result);
}
};
getArgumentProviders().add(argumentProvider);
}
@Override
@SneakyThrows
protected void exec() {
Files.walk(getOutputDir().get().getAsFile().toPath())
.sorted(Comparator.reverseOrder())
.forEach(new Consumer<Path>() {
@Override
@SneakyThrows
public void accept(Path path) {
Files.delete(path);
}
});
super.exec();
}
}

View File

@@ -0,0 +1,97 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Action;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
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;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.jvm.toolchain.JavaLauncher;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
import org.gradle.process.CommandLineArgumentProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
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 {
@Input
public abstract Property<Boolean> getMergeConfiguration();
@OutputDirectory
public abstract DirectoryProperty getConfigurationDir();
private final JavaToolchainSpec toolchain;
public JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action) {
action.execute(toolchain);
return toolchain;
}
public NativeImageConfigurationTask() {
setGroup(GRAALVM_TASK_GROUP);
setDescription("Run the application with the native-image-agent " +
"to create a configuration for native image creation");
toolchain = getProject().getObjects().newInstance(DefaultToolchainSpec.class);
ProjectLayout layout = getProject().getLayout();
TaskContainer taskContainer = getProject().getTasks();
JavaApplication javaApplication = getProject().getExtensions().findByType(JavaApplication.class);
JavaPluginExtension javaExtension = getProject().getExtensions().getByType(JavaPluginExtension.class);
ExtensionContainer ext = getProject().getExtensions();
Property<JavaLauncher> javaLauncherProperty = getJavaLauncher();
Optional.ofNullable(ext.findByType(JavaToolchainService.class))
.flatMap(ts -> Optional.of(toolchain).map(ts::launcherFor))
.ifPresent(javaLauncherProperty::convention);
if(!Objects.isNull(javaApplication)) {
getMainClass().convention(javaApplication.getMainClass());
getMainModule().convention(javaApplication.getMainModule());
}
getConfigurationDir().convention(layout.getProjectDirectory()
.dir(NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME));
getMergeConfiguration().convention(true);
ConfigurationContainer cc = getProject().getConfigurations();
Configuration runtimeClasspath = cc.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
setClasspath(runtimeClasspath);
Provider<Jar> jarTaskProvider = taskContainer.named(JavaPlugin.JAR_TASK_NAME, Jar.class);
setClasspath(
getProject().files(
jarTaskProvider,
runtimeClasspath
)
);
getJvmArgumentProviders().add(new CommandLineArgumentProvider() {
@Override
public Iterable<String> asArguments() {
List<String> jvmArgs = new ArrayList<>();
if(getMergeConfiguration().get()) {
jvmArgs.add("-agentlib:native-image-agent=config-merge-dir=" + getConfigurationDir().get());
} else {
jvmArgs.add("-agentlib:native-image-agent=config-output-dir=" + getConfigurationDir().get());
}
for(String jvmArg : Optional.ofNullable(javaApplication)
.map(JavaApplication::getApplicationDefaultJvmArgs)
.orElse(Collections.emptyList())) {
jvmArgs.add(jvmArg);
}
return jvmArgs;
}
});
}
}

View File

@@ -0,0 +1,32 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Action;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Nested;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
public interface NativeImageExtension {
Property<FileCollection> getClasspath();
@Nested
JavaToolchainSpec getToolchain();
JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action);
Property<Boolean> getUseMusl();
Property<Boolean> getBuildStaticImage();
Property<Boolean> getEnableFallback();
Property<Boolean> getLinkAtBuildTime();
Property<String> getMainClass();
Property<String> getMainModule();
Property<Boolean> getCompressExecutable();
Property<Boolean> getUseLZMA();
Property<Integer> getCompressionLevel();
}

View File

@@ -0,0 +1,118 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
@CacheableTask
public class NativeImagePlugin implements Plugin<Project> {
public static final String NATIVE_IMAGE_TASK_NAME = "nativeImage";
public static final String UPX_TASK_NAME = "upx";
public static final String CONFIGURE_NATIVE_IMAGE_TASK_NAME = "configureNativeImage";
public static final String NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME = "native-image";
private static <T> void setIfPresent(Property<T> p1, Provider<T> provider) {
if (provider.isPresent()) {
p1.set(provider);
}
}
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaLibraryPlugin.class);
ProjectLayout layout = project.getLayout();
ExtensionContainer extensionContainer = project.getExtensions();
TaskContainer tasks = project.getTasks();
Provider<Jar> jarTaskProvider = tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, jar -> {
jar.from(layout.getProjectDirectory().dir(NATIVE_IMAGE_CONFIGURATION_FOLDER_NAME), copySpec -> {
copySpec.into(
String.format("META-INF/native-image/%s/%s/",
project.getName(),
project.getGroup()
)
);
});
});
ExtensionContainer ext = project.getExtensions();
JavaPluginExtension javaPluginExtension = ext.findByType(JavaPluginExtension.class);
ObjectFactory objects = project.getObjects();
NativeImageExtension nativeImageExtension = objects.newInstance(DefaultNativeImageExtension.class);
extensionContainer.add("nativeImage", nativeImageExtension);
nativeImageExtension.toolchain(jts -> {
jts.getImplementation().convention(javaPluginExtension.getToolchain().getImplementation());
jts.getVendor().convention(javaPluginExtension.getToolchain().getVendor());
jts.getLanguageVersion().convention(javaPluginExtension.getToolchain().getLanguageVersion());
});
nativeImageExtension.getUseMusl().convention(false);
nativeImageExtension.getEnableFallback().convention(false);
nativeImageExtension.getLinkAtBuildTime().convention(false);
nativeImageExtension.getBuildStaticImage().convention(false);
nativeImageExtension.getCompressExecutable().convention(false);
nativeImageExtension.getUseLZMA().convention(false);
nativeImageExtension.getCompressionLevel().convention(6);
ConfigurationContainer configurations = project.getConfigurations();
FileCollection classpath = project.files(jarTaskProvider,
configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
nativeImageExtension.getClasspath().convention(classpath);
Provider<NativeImageConfigurationTask> nativeImageConfigurationTaskProvider = tasks.register(
CONFIGURE_NATIVE_IMAGE_TASK_NAME,
NativeImageConfigurationTask.class,
nativeImageConfigurationTask -> {
nativeImageConfigurationTask.toolchain(jts -> {
jts.getImplementation().convention(nativeImageExtension.getToolchain().getImplementation());
jts.getVendor().convention(nativeImageExtension.getToolchain().getVendor());
jts.getLanguageVersion().convention(nativeImageExtension.getToolchain().getLanguageVersion());
});
});
Provider<NativeImageTask> nativeImageTaskProvider = tasks.register(NATIVE_IMAGE_TASK_NAME, NativeImageTask.class, nativeImageTask -> {
nativeImageTask.getClasspath().set(nativeImageExtension.getClasspath());
nativeImageTask.toolchain(jts -> {
jts.getImplementation().convention(nativeImageExtension.getToolchain().getImplementation());
jts.getVendor().convention(nativeImageExtension.getToolchain().getVendor());
jts.getLanguageVersion().convention(nativeImageExtension.getToolchain().getLanguageVersion());
});
nativeImageTask.getBuildStaticImage().set(nativeImageExtension.getBuildStaticImage());
nativeImageTask.getUseMusl().set(nativeImageExtension.getUseMusl());
nativeImageTask.getLinkAtBuildTime().set(nativeImageExtension.getLinkAtBuildTime());
nativeImageTask.getMainClass().set(nativeImageExtension.getMainClass());
nativeImageTask.getMainModule().set(nativeImageExtension.getMainModule());
nativeImageTask.getEnableFallback().set(nativeImageExtension.getEnableFallback());
});
Provider<UpxTask> upxTaskProvider = tasks.register(UPX_TASK_NAME, UpxTask.class, t -> {
t.getInputFile().set(nativeImageTaskProvider.flatMap(NativeImageTask::getOutputFile));
setIfPresent(t.getUseLZMA(), nativeImageExtension.getUseLZMA());
setIfPresent(t.getCompressionLevel(), nativeImageExtension.getCompressionLevel());
setIfPresent(t.getCompressionLevel(), nativeImageExtension.getCompressionLevel());
});
tasks.named(NATIVE_IMAGE_TASK_NAME, NativeImageTask.class, t -> {
if (nativeImageExtension.getCompressExecutable().getOrElse(false)) {
t.finalizedBy(upxTaskProvider);
}
});
}
}

View File

@@ -0,0 +1,190 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Action;
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.logging.Logging;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
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.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.internal.jvm.JavaModuleDetector;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
import org.gradle.process.CommandLineArgumentProvider;
import javax.inject.Inject;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static net.woggioni.gradle.graalvm.Constants.GRAALVM_TASK_GROUP;
@CacheableTask
public abstract class NativeImageTask extends Exec {
public static final String NATIVE_COMPILER_PATH_ENV_VARIABLE = "GRAAL_NATIVE_COMPILER_PATH";
public static final String NATIVE_COMPILER_PATH_PROPERTY_KEY = "graal.native.compiler.path";
@Classpath
public abstract Property<FileCollection> getClasspath();
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
public abstract DirectoryProperty getGraalVmHome();
private final JavaToolchainSpec toolchain;
public JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action) {
action.execute(toolchain);
return toolchain;
}
@Optional
@InputFile
@PathSensitive(PathSensitivity.ABSOLUTE)
public abstract RegularFileProperty getNativeCompilerPath();
@Input
public abstract Property<Boolean> getUseMusl();
@Input
public abstract Property<Boolean> getBuildStaticImage();
@Input
public abstract Property<Boolean> getEnableFallback();
@Input
public abstract Property<Boolean> getLinkAtBuildTime();
@Input
public abstract Property<String> getMainClass();
@Input
@Optional
public abstract Property<String> getMainModule();
@Inject
protected abstract JavaModuleDetector getJavaModuleDetector();
@OutputFile
protected abstract RegularFileProperty getOutputFile();
private static final Logger log = Logging.getLogger(NativeImageTask.class);
@Inject
public NativeImageTask(ObjectFactory objects) {
Project project = getProject();
setGroup(GRAALVM_TASK_GROUP);
setDescription("Create a native image of the application using GraalVM");
toolchain = objects.newInstance(DefaultToolchainSpec.class);
getUseMusl().convention(false);
getBuildStaticImage().convention(false);
getEnableFallback().convention(false);
getLinkAtBuildTime().convention(false);
Provider<File> nativeComnpilerProvider = project.provider(() -> {
String envVar;
File compilerPath = null;
if(project.hasProperty(NATIVE_COMPILER_PATH_PROPERTY_KEY)) {
compilerPath = new File(project.property(NATIVE_COMPILER_PATH_PROPERTY_KEY).toString());
} else if((envVar = System.getenv(NATIVE_COMPILER_PATH_ENV_VARIABLE)) != null) {
compilerPath = new File(envVar);
}
return compilerPath;
});
getNativeCompilerPath().convention(project.getLayout().file(nativeComnpilerProvider));
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);
Provider<Directory> graalHomeDirectoryProvider = javaToolchainService.launcherFor(it -> {
it.getLanguageVersion().set(toolchain.getLanguageVersion());
it.getVendor().set(toolchain.getVendor());
it.getImplementation().set(toolchain.getImplementation());
}).map(javaLauncher ->
javaLauncher.getMetadata().getInstallationPath()
).orElse(layout.dir(project.provider(() -> project.file(System.getProperty("java.home")))));
getGraalVmHome().convention(graalHomeDirectoryProvider);
BasePluginExtension basePluginExtension =
ext.getByType(BasePluginExtension.class);
getOutputFile().convention(basePluginExtension.getLibsDirectory().file(project.getName()));
Object executableProvider = new Object() {
@Override
public String toString() {
return getGraalVmHome().get() + "/bin/native-image";
}
};
executable(executableProvider);
CommandLineArgumentProvider argumentProvider = new CommandLineArgumentProvider() {
@Override
public Iterable<String> asArguments() {
List<String> result = new ArrayList<>();
if(!getEnableFallback().get()) {
result.add("--no-fallback");
}
if(getBuildStaticImage().get()) {
result.add("--static");
}
if(getUseMusl().get()) {
result.add("--libc=musl");
}
if(getLinkAtBuildTime().get()) {
result.add("--link-at-build-time");
}
if(getNativeCompilerPath().isPresent()) {
result.add("--native-compiler-path=" + getNativeCompilerPath().getAsFile().get());
}
JavaModuleDetector javaModuleDetector = getJavaModuleDetector();
boolean useJpms = getMainModule().isPresent();
FileCollection classpath = getClasspath().get();
FileCollection cp = javaModuleDetector.inferClasspath(useJpms, classpath);
FileCollection mp = javaModuleDetector.inferModulePath(useJpms, classpath);
if(!cp.isEmpty()) {
result.add("-cp");
result.add(cp.getAsPath());
}
if(!mp.isEmpty()) {
result.add("-p");
result.add(mp.getAsPath());
}
result.add("-o");
result.add(getOutputFile().get().getAsFile().toString());
if(getMainModule().isPresent()) {
result.add("--module");
String mainModule = getMainModule().get();
result.add(getMainClass()
.map(mainClass -> String.format("%s/%s", mainModule, mainClass))
.getOrElse(mainModule));
} else {
result.add(getMainClass().get());
}
return Collections.unmodifiableList(result);
}
};
getArgumentProviders().add(argumentProvider);
}
}

View File

@@ -0,0 +1,108 @@
package net.woggioni.gradle.graalvm;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Exec;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.process.CommandLineArgumentProvider;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@CacheableTask
public abstract class UpxTask extends Exec {
@InputFile
@PathSensitive(PathSensitivity.NONE)
public abstract RegularFileProperty getInputFile();
@OutputFile
public abstract RegularFileProperty getOutputFile();
@Input
public abstract Property<Boolean> getUseLZMA();
@Input
public abstract Property<Integer> getCompressionLevel();
@Inject
public UpxTask(Project project) {
BasePluginExtension be = project.getExtensions().findByType(BasePluginExtension.class);
getOutputFile().convention(
be.getDistsDirectory().file(String.format("%s.upx", project.getName()))
);
getUseLZMA().convention(false);
getCompressionLevel().convention(10);
executable("upx");
getArgumentProviders().add(new CommandLineArgumentProvider() {
@Override
public Iterable<String> asArguments() {
List<String> result = new ArrayList<String>();
if(getUseLZMA().get()) {
result.add("--lzma");
} else {
result.add("--no-lzma");
}
String compressionLevel;
int cl = getCompressionLevel().get();
switch (cl) {
case 1:
compressionLevel = "-1";
break;
case 2:
compressionLevel = "-2";
break;
case 3:
compressionLevel = "-3";
break;
case 4:
compressionLevel = "-4";
break;
case 5:
compressionLevel = "-5";
break;
case 6:
compressionLevel = "-6";
break;
case 7:
compressionLevel = "-7";
break;
case 8:
compressionLevel = "-8";
break;
case 9:
compressionLevel = "-9";
break;
case 10:
compressionLevel = "--best";
break;
case 11:
compressionLevel = "--brute";
break;
case 12:
compressionLevel = "--ultra-brute";
break;
default:
throw new IllegalArgumentException(String.format("Unsupported compression level %d", cl));
}
result.add(compressionLevel);
result.add(getInputFile().getAsFile().get().toString());
result.add("-f");
result.add("-o");
result.add(getOutputFile().getAsFile().get().toString());
return Collections.unmodifiableList(result);
}
});
}
}

View File

@@ -1,15 +1,5 @@
woggioniMavenRepositoryUrl=https://mvn.woggioni.net/
lys.catalog.version=2025.02.05
version.myGradlePlugins=2025.04.16
version.gradle=8.12
lys.catalog.version=2023.06.11
version.myGradlePlugins=2023.06.13
version.gradle=7.6
version.felix.config.admin=1.9.26
version.felix=7.0.5
version.felix.scr=2.2.4
version.felix.security=2.8.2
version.osgi=8.0.0
version.osgi.cm=1.6.0
version.osgi.service.component=1.5.1
version.osgi.function=1.2.0
version.osgi.promise=1.3.0
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

43
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +216,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

37
gradlew.bat vendored
View File

@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

13
jdeps/build.gradle Normal file
View File

@@ -0,0 +1,13 @@
plugins {
id 'java-gradle-plugin'
}
gradlePlugin {
plugins {
create("JdepsPlugin") {
id = 'net.woggioni.gradle.jdeps'
implementationClass = "net.woggioni.gradle.jdeps.JdepsPlugin"
}
}
}

View File

@@ -0,0 +1,5 @@
package net.woggioni.gradle.jdeps;
public class Constants {
public static final String JDEPS_TASK_GROUP = "jdeps";
}

View File

@@ -0,0 +1,26 @@
package net.woggioni.gradle.jdeps;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskContainer;
public class JdepsPlugin implements Plugin<Project> {
public static final String JDEPS_TASK_NAME = "jdeps";
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaLibraryPlugin.class);
TaskContainer tasks = project.getTasks();
Provider<JdepsTask> jdepsTaskProvider = tasks.register(JDEPS_TASK_NAME, JdepsTask.class, jdepsTask -> {
ConfigurationContainer configurations = project.getConfigurations();
FileCollection classpath = project.files(configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
jdepsTask.getClasspath().set(classpath);
jdepsTask.getArchives().set(project.files(tasks.named(JavaPlugin.JAR_TASK_NAME)));
});
}
}

View File

@@ -0,0 +1,209 @@
package net.woggioni.gradle.jdeps;
import lombok.SneakyThrows;
import org.gradle.api.Action;
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.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.reporting.ReportingExtension;
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.internal.jvm.JavaModuleDetector;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
import org.gradle.process.CommandLineArgumentProvider;
import javax.inject.Inject;
import java.io.File;
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.jdeps.Constants.JDEPS_TASK_GROUP;
public abstract class JdepsTask extends Exec {
private final JavaToolchainSpec toolchain;
public JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action) {
action.execute(toolchain);
return toolchain;
}
@Classpath
public abstract Property<FileCollection> getClasspath();
@Classpath
public abstract Property<FileCollection> getArchives();
@InputDirectory
public abstract DirectoryProperty getJavaHome();
@Input
@Optional
public abstract Property<String> getMainClass();
@Input
@Optional
public abstract Property<String> getMainModule();
@Input
public abstract ListProperty<String> getAdditionalModules();
@Inject
protected abstract JavaModuleDetector getJavaModuleDetector();
@OutputDirectory
public abstract DirectoryProperty getOutputDir();
@Optional
@OutputDirectory
public abstract DirectoryProperty getDotOutput();
@Input
@Optional
public abstract Property<Integer> getJavaRelease();
@Input
public abstract Property<Boolean> getRecursive();
private static final Logger log = Logging.getLogger(JdepsTask.class);
public JdepsTask() {
Project project = getProject();
setGroup(JDEPS_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());
getRecursive().convention(true);
ProjectLayout layout = project.getLayout();
toolchain = getObjectFactory().newInstance(DefaultToolchainSpec.class);
JavaToolchainService javaToolchainService = ext.findByType(JavaToolchainService.class);
Provider<Directory> graalHomeDirectoryProvider = javaToolchainService.launcherFor(it -> {
it.getLanguageVersion().set(toolchain.getLanguageVersion());
it.getVendor().set(toolchain.getVendor());
it.getImplementation().set(toolchain.getImplementation());
}).map(javaLauncher ->
javaLauncher.getMetadata().getInstallationPath()
).orElse(layout.dir(project.provider(() -> project.file(System.getProperty("java.home")))));
getJavaHome().convention(graalHomeDirectoryProvider);
getJavaHome().convention(graalHomeDirectoryProvider);
getAdditionalModules().convention(new ArrayList<>());
ReportingExtension reporting = ext.getByType(ReportingExtension.class);
getOutputDir().convention(
reporting.getBaseDirectory()
.dir(project.getName() +
ofNullable(project.getVersion()).map(it -> "-" + it).orElse(""))
);
getDotOutput().convention(getOutputDir().dir("graphviz"));
Object executableProvider = new Object() {
@Override
public String toString() {
return getJavaHome().get() + "/bin/jdeps";
}
};
executable(executableProvider);
CommandLineArgumentProvider argumentProvider = new CommandLineArgumentProvider() {
@Override
@SneakyThrows
public Iterable<String> asArguments() {
List<String> result = new ArrayList<>();
JavaModuleDetector javaModuleDetector = getJavaModuleDetector();
FileCollection classpath = getClasspath().get();
FileCollection mp = javaModuleDetector.inferModulePath(true, classpath);
if (!mp.isEmpty()) {
result.add("--module-path");
result.add(mp.getAsPath());
}
FileCollection cp = classpath.minus(mp);
if(!cp.isEmpty()) {
result.add("-cp");
result.add(cp.getAsPath());
}
List<String> additionalModules = getAdditionalModules().get();
if (!additionalModules.isEmpty()) {
result.add("--add-modules");
final List<String> modules2BeAdded = new ArrayList<>();
modules2BeAdded.addAll(additionalModules);
if (!modules2BeAdded.isEmpty()) {
result.add(String.join(",", modules2BeAdded));
}
}
if (getDotOutput().isPresent()) {
result.add("-dotoutput");
result.add(getDotOutput().get().getAsFile().toString());
}
if(getRecursive().get()) {
result.add("--recursive");
} else {
result.add("--no-recursive");
}
if (getMainModule().isPresent()) {
result.add("-m");
result.add(getMainModule().get());
}
if (getJavaRelease().isPresent()) {
result.add("--multi-release");
result.add(getJavaRelease().get().toString());
}
for (File archive : getArchives().get()) {
result.add(archive.toString());
}
return Collections.unmodifiableList(result);
}
};
getArgumentProviders().add(argumentProvider);
}
@Override
@SneakyThrows
protected void exec() {
Files.walk(getOutputDir().get().getAsFile().toPath())
.sorted(Comparator.reverseOrder())
.forEach(new Consumer<Path>() {
@Override
@SneakyThrows
public void accept(Path path) {
Files.delete(path);
}
});
super.exec();
}
}

View File

@@ -1,216 +1,43 @@
package net.woggioni.gradle.jpms.check
import groovy.json.JsonBuilder
import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.xml.MarkupBuilder
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.jar.JarFile
import java.util.stream.Collectors
import java.util.stream.Stream
import java.util.zip.ZipFile
import org.gradle.api.file.RegularFile
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.ReportingBasePlugin
import org.gradle.api.reporting.ReportingExtension
class JPMSCheckPlugin implements Plugin<Project> {
@Canonical
@CompileStatic
private class CheckResult {
ResolvedArtifactResult dep
String automaticModuleName
boolean multiReleaseJar
boolean moduleInfo
boolean getJpmsFriendly() {
return automaticModuleName != null || moduleInfo
}
@Override
boolean equals(Object other) {
if(other == null) {
return false
} else if(other.class == CheckResult.class) {
return dep?.id?.componentIdentifier == ((CheckResult) other).dep?.id?.componentIdentifier
} else {
return false
}
}
@Override
int hashCode() {
return dep.id.componentIdentifier.hashCode()
}
}
@CompileStatic
private Stream<CheckResult> computeResults(Stream<ResolvedArtifactResult> artifacts) {
return artifacts.filter { ResolvedArtifactResult res ->
res.file.exists() && res.file.name.endsWith(".jar")
}.<CheckResult>map { resolvedArtifact ->
JarFile jarFile = new JarFile(resolvedArtifact.file).with {
if (it.isMultiRelease()) {
new JarFile(
resolvedArtifact.file,
false,
ZipFile.OPEN_READ,
Runtime.version()
)
} else {
it
}
}
String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") }
def moduleInfoEntry = jarFile.getJarEntry("module-info.class")
new CheckResult(
resolvedArtifact,
automaticModuleName,
jarFile.isMultiRelease(),
moduleInfoEntry != null
)
}
}
private void createHtmlReport(Project project, Stream<CheckResult> checkResults, Writer writer) {
def builder = new MarkupBuilder(writer)
int friendly = 0
int total = 0
def results = checkResults.peek { CheckResult res ->
total += 1
if(res.jpmsFriendly) friendly += 1
}.collect(Collectors.toList())
builder.html {
head {
meta name: "viewport", content: "width=device-width, initial-scale=1"
InputStream resourceStream = getClass().classLoader.getResourceAsStream('net/woggioni/plugins/jpms/check/github-markdown.css')
resourceStream.withReader { Reader reader ->
style reader.text
}
body {
article(class: 'markdown-body') {
h1 "Project ${project.group}:${project.name}:${project.version}", style: "text-align: center;"
div {
table {
thead {
tr {
th "JPMS friendly"
th "Not JPMS friendly", colspan: 2
th "Total", colspan: 2
}
}
tbody {
tr {
td friendly, style: "text-align: center;"
td total - friendly, style: "text-align: center;", colspan: 2
td total, style: "text-align: center;", colspan: 2
}
}
thead {
th "Name"
th "Multi-release jar"
th "Automatic-Module-Name"
th "Module descriptor"
th "JPMS friendly"
}
tbody {
results.forEach {res ->
String color = res.jpmsFriendly ? "#dfd" : "fdd"
tr(style: "background-color:$color;") {
td res.dep.id.displayName
td style: "text-align: center;", res.multiReleaseJar ? "✓" : "✕"
td style: "text-align: center;", res.automaticModuleName ?: "n/a"
td style: "text-align: center;", res.moduleInfo ? "✓" : "✕"
td style: "text-align: center;", res.jpmsFriendly ? "✓" : "✕"
}
total += 1
if(res.jpmsFriendly) friendly += 1
}
}
}
}
}
}
}
}
}
@CompileStatic
private createJsonReport(Stream<CheckResult> checkResults, Writer writer) {
def builder = new JsonBuilder()
builder (checkResults.map {
[
name: it.dep.id.componentIdentifier.displayName,
automaticModuleName: it.automaticModuleName,
isMultiReleaseJar: it.multiReleaseJar,
hasModuleInfo: it.moduleInfo,
jpmsFriendly: it.jpmsFriendly
]
}.collect(Collectors.toList()))
builder.writeTo(writer)
}
@Override
@CompileStatic
void apply(Project project) {
project.tasks.register("jpms-check") {task ->
boolean recursive = project.properties["jpms-check.recursive"]?.with(Boolean.&parseBoolean) ?: false
String cfgName = project.properties["jpms-check.configurationName"] ?: "default"
String outputFormat = project.properties["jpms-check.outputFormat"] ?: "html"
Path outputFile = project.properties["jpms-check.outputFile"]?.with {
Paths.get(it as String)
} ?: with {
project.pluginManager.apply(ReportingBasePlugin.class)
project.tasks.register("jpms-check", JPMSCheckTask) {task ->
ReportingExtension reporting = project.extensions.getByType(ReportingExtension.class)
boolean recursive = project.properties["jpms-check.recursive"]?.with(Object.&toString)?.with(Boolean.&parseBoolean) ?: false
String cfgName = project.properties["jpms-check.configurationName"] ?: JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME
OutputFormat defaultOutputFormat = (project.properties["jpms-check.outputFormat"]
?.with(Object.&toString)
?.with(OutputFormat.&valueOf)
?: OutputFormat.html)
task.getConfigurationName().convention(cfgName)
task.getRecursive().convention(recursive)
task.outputFormat.convention(defaultOutputFormat)
task.getOutputFile().convention(reporting.baseDirectory.zip(task.getOutputFormat(), { dir, outputFormat ->
RegularFile result = null
switch(outputFormat) {
case "html":
Paths.get(project.buildDir.path, "jpms-report.html")
case OutputFormat.html:
result = dir.file( "jpms-report.html")
break
case "json":
Paths.get(project.buildDir.path, "jpms-report.json")
case OutputFormat.json:
result = dir.file( "jpms-report.json")
break
default:
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
}
}
task.doLast {
Stream<Project> projects = Stream.of(project)
if(recursive) {
projects = Stream.concat(projects, project.subprojects.stream())
}
Set<CheckResult> results = projects.flatMap {
Configuration requestedConfiguration = (project.configurations.<Configuration>find { Configuration cfg ->
cfg.canBeResolved && cfg.name == cfgName
} ?: {
def resolvableConfigurations = "[" + project.configurations
.grep { Configuration cfg -> cfg.canBeResolved }
.collect { "'${it.name}'" }
.join(",") + "]"
throw new GradleException("Configuration '$cfgName' doesn't exist or cannot be resolved, " +
"resolvable configurations in this project are " + resolvableConfigurations)
}) as Configuration
computeResults(requestedConfiguration.incoming.artifacts.artifacts.stream())
}.collect(Collectors.toSet())
Files.createDirectories(outputFile.parent)
Files.newBufferedWriter(outputFile).withWriter {
Stream<CheckResult> resultStream = results.stream().sorted(Comparator.<CheckResult, String>comparing { CheckResult res ->
res.dep.id.componentIdentifier.displayName
})
switch(outputFormat) {
case "html":
createHtmlReport(project, resultStream, it)
break
case "json":
createJsonReport(resultStream, it)
break
default:
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
}
}
}
result
}))
}
}
}

View File

@@ -0,0 +1,213 @@
package net.woggioni.gradle.jpms.check
import groovy.json.JsonBuilder
import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.xml.MarkupBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.JarFile
import java.util.stream.Collectors
import java.util.stream.Stream
import java.util.zip.ZipFile
abstract class JPMSCheckTask extends DefaultTask {
@Input
abstract Property<String> getConfigurationName()
@Input
abstract Property<Boolean> getRecursive()
@Input
abstract Property<OutputFormat> getOutputFormat()
@OutputFile
abstract RegularFileProperty getOutputFile()
@Canonical
@CompileStatic
private class CheckResult {
ResolvedArtifactResult dep
String automaticModuleName
boolean multiReleaseJar
boolean moduleInfo
boolean getJpmsFriendly() {
return automaticModuleName != null || moduleInfo
}
@Override
boolean equals(Object other) {
if(other == null) {
return false
} else if(other.class == CheckResult.class) {
return dep?.id?.componentIdentifier == ((CheckResult) other).dep?.id?.componentIdentifier
} else {
return false
}
}
@Override
int hashCode() {
return dep.id.componentIdentifier.hashCode()
}
}
@CompileStatic
private Stream<CheckResult> computeResults(Stream<ResolvedArtifactResult> artifacts) {
return artifacts.filter { ResolvedArtifactResult res ->
res.file.exists() && res.file.name.endsWith(".jar")
}.<CheckResult>map { resolvedArtifact ->
JarFile jarFile = new JarFile(resolvedArtifact.file).with {
if (it.isMultiRelease()) {
new JarFile(
resolvedArtifact.file,
false,
ZipFile.OPEN_READ,
Runtime.version()
)
} else {
it
}
}
String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") }
def moduleInfoEntry = jarFile.getJarEntry("module-info.class")
new CheckResult(
resolvedArtifact,
automaticModuleName,
jarFile.isMultiRelease(),
moduleInfoEntry != null
)
}
}
private void createHtmlReport(Project project, Stream<CheckResult> checkResults, Writer writer) {
def builder = new MarkupBuilder(writer)
int friendly = 0
int total = 0
def results = checkResults.peek { CheckResult res ->
total += 1
if(res.jpmsFriendly) friendly += 1
}.collect(Collectors.toList())
builder.html {
head {
meta name: "viewport", content: "width=device-width, initial-scale=1"
InputStream resourceStream = getClass().classLoader.getResourceAsStream('net/woggioni/plugins/jpms/check/github-markdown.css')
resourceStream.withReader { Reader reader ->
style reader.text
}
body {
article(class: 'markdown-body') {
h1 "Project ${project.group}:${project.name}:${project.version}", style: "text-align: center;"
div {
table {
thead {
tr {
th "JPMS friendly"
th "Not JPMS friendly", colspan: 2
th "Total", colspan: 2
}
}
tbody {
tr {
td friendly, style: "text-align: center;"
td total - friendly, style: "text-align: center;", colspan: 2
td total, style: "text-align: center;", colspan: 2
}
}
thead {
th "Name"
th "Multi-release jar"
th "Automatic-Module-Name"
th "Module descriptor"
th "JPMS friendly"
}
tbody {
results.forEach {res ->
String color = res.jpmsFriendly ? "#dfd" : "fdd"
tr(style: "background-color:$color;") {
td res.dep.id.displayName
td style: "text-align: center;", res.multiReleaseJar ? "✓" : "✕"
td style: "text-align: center;", res.automaticModuleName ?: "n/a"
td style: "text-align: center;", res.moduleInfo ? "✓" : "✕"
td style: "text-align: center;", res.jpmsFriendly ? "✓" : "✕"
}
total += 1
if(res.jpmsFriendly) friendly += 1
}
}
}
}
}
}
}
}
}
@CompileStatic
private createJsonReport(Stream<CheckResult> checkResults, Writer writer) {
def builder = new JsonBuilder()
builder (checkResults.map {
[
name: it.dep.id.componentIdentifier.displayName,
automaticModuleName: it.automaticModuleName,
isMultiReleaseJar: it.multiReleaseJar,
hasModuleInfo: it.moduleInfo,
jpmsFriendly: it.jpmsFriendly
]
}.collect(Collectors.toList()))
builder.writeTo(writer)
}
@TaskAction
@CompileStatic
def createReport() {
String cfgName = configurationName.get()
Path outputFile = outputFile.get().asFile.toPath()
Stream<Project> projects = Stream.of(project)
if(recursive.get()) {
projects = Stream.concat(projects, project.subprojects.stream())
}
Set<CheckResult> results = projects.flatMap {
Configuration requestedConfiguration = (project.configurations.<Configuration>find { Configuration cfg ->
cfg.canBeResolved && cfg.name == cfgName
} ?: {
def resolvableConfigurations = "[" + project.configurations
.grep { Configuration cfg -> cfg.canBeResolved }
.collect { "'${it.name}'" }
.join(",") + "]"
throw new GradleException("Configuration '$cfgName' doesn't exist or cannot be resolved, " +
"resolvable configurations in this project are " + resolvableConfigurations)
}) as Configuration
computeResults(requestedConfiguration.incoming.artifacts.artifacts.stream())
}.collect(Collectors.toSet())
Files.createDirectories(outputFile.parent)
Files.newBufferedWriter(outputFile).withWriter {
Stream<CheckResult> resultStream = results.stream().sorted(Comparator.<CheckResult, String>comparing { CheckResult res ->
res.dep.id.componentIdentifier.displayName
})
switch(outputFormat.get()) {
case OutputFormat.html:
createHtmlReport(project, resultStream, it)
break
case OutputFormat.json:
createJsonReport(resultStream, it)
break
default:
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
}
}
}
}

View File

@@ -0,0 +1,5 @@
package net.woggioni.gradle.jpms.check
enum OutputFormat {
html, json
}

View File

@@ -1,8 +1,18 @@
package net.woggioni.gradle.lombok;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.internal.jvm.JavaModuleDetector;
import javax.inject.Inject;
import java.io.File;
@@ -10,12 +20,10 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Delombok extends JavaExec {
@Getter
@OutputDirectory
private final File outputDir;
@RequiredArgsConstructor(onConstructor_ = {@Inject})
public abstract class Delombok extends JavaExec {
private final JavaModuleDetector javaModuleDetector;
private static String buildClasspathString(Iterable<File> classpath) {
StringBuilder sb = new StringBuilder();
Iterator<File> it = classpath.iterator();
@@ -33,19 +41,56 @@ public class Delombok extends JavaExec {
return sb.toString();
}
@Inject
public Delombok(File lombokJar, File outputDir, Iterable<File> sourceDirs, String classpath) {
this.outputDir = outputDir;
classpath(lombokJar);
@InputFiles
public Provider<FileCollection> getSourceClasspath() {
return getSourceSet()
.map(SourceSet::getCompileClasspath);
}
@Internal
abstract public Property<SourceSet> getSourceSet();
@Internal
abstract public Property<Configuration> getLombokJar();
@Internal
abstract public Property<Boolean> getInferModulePath();
@OutputDirectory
abstract public RegularFileProperty getOutputDir();
@InputFiles
public Provider<FileCollection> getInputFiles() {
return getSourceSet()
.map(SourceSet::getAllSource)
.map(SourceDirectorySet::getSourceDirectories)
.zip(getLombokJar(), FileCollection::plus);
}
@Override
public void exec() {
classpath(getLombokJar());
List<String> args = new ArrayList<>();
args.add("delombok");
args.add("-d");
args.add(outputDir.getPath());
args.add("-c");
args.add(classpath);
for(File sourceDir : sourceDirs) args.add(sourceDir.getPath());
args.add(getOutputDir().getAsFile().get().getPath());
SourceSet sourceSet = getSourceSet().get();
Boolean inferModulePath = getInferModulePath().get();
FileCollection classpath = javaModuleDetector.inferClasspath(inferModulePath, sourceSet.getCompileClasspath());
if(!classpath.isEmpty()) {
args.add("-c");
args.add(classpath.getAsPath());
}
if(inferModulePath) {
FileCollection modulepath = javaModuleDetector.inferModulePath(true, sourceSet.getCompileClasspath());
if(!modulepath.isEmpty()) {
args.add("--module-path");
args.add(modulepath.getAsPath());
}
}
for(File sourceDir : sourceSet.getJava().getSrcDirs()) {
args.add(sourceDir.getPath());
}
Object[] argsArray = new String[args.size()];
args.toArray(argsArray);
args(argsArray);
super.exec();
}
}

View File

@@ -1,13 +1,7 @@
package net.woggioni.gradle.lombok;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.gradle.api.provider.Property;
import javax.inject.Inject;
@RequiredArgsConstructor(onConstructor_ = { @Inject })
public class LombokExtension {
@Getter
private final Property<String> version;
abstract public class LombokExtension {
abstract public Property<String> getVersion();
}

View File

@@ -7,6 +7,7 @@ import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
@@ -27,77 +28,73 @@ public class LombokPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class);
ObjectFactory objectFactory = project.getObjects();
LombokExtension ext = project.getExtensions()
.create("lombok", LombokExtension.class,
objectFactory.property(String.class)
.convention(project.provider(
() -> (String) project.getExtensions().getExtraProperties().get("version.lombok")))
);
project.afterEvaluate(p -> {
SourceSetContainer sourceSetContainer = project.getExtensions().findByType(JavaPluginExtension.class).getSourceSets();
Provider<Map<String, String>> dependencyNotationProvider = project.provider(() -> {
Map<String, String> m = new HashMap<>();
m.put("group", "org.projectlombok");
m.put("name", "lombok");
m.put("version", ext.getVersion().get());
return Collections.unmodifiableMap(m);
});
Configuration lombokConfiguration = project.getConfigurations().create("lombok");
project.getDependencies().addProvider(
lombokConfiguration.getName(),
LombokExtension ext = project.getExtensions().create("lombok", LombokExtension.class);
ExtraPropertiesExtension epe = project.getExtensions().getExtraProperties();
if(epe.has("version.lombok")) {
ext.getVersion().convention(
project.provider(() -> (String) epe.get("version.lombok"))
);
}
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
SourceSetContainer sourceSetContainer = javaPluginExtension.getSourceSets();
Provider<Map<String, String>> dependencyNotationProvider = ext.getVersion().map((String version) -> {
Map<String, String> m = new HashMap<>();
m.put("group", "org.projectlombok");
m.put("name", "lombok");
m.put("version", version);
return Collections.unmodifiableMap(m);
});
Configuration lombokConfiguration = project.getConfigurations().create("lombok");
project.getDependencies().addProvider(
lombokConfiguration.getName(),
dependencyNotationProvider,
externalModuleDependency -> {
}
);
for (SourceSet ss : sourceSetContainer) {
DependencyHandler dependencyHandler = project.getDependencies();
dependencyHandler.addProvider(
ss.getCompileOnlyConfigurationName(),
dependencyNotationProvider,
externalModuleDependency -> {
}
);
for (SourceSet ss : sourceSetContainer) {
DependencyHandler dependencyHandler = project.getDependencies();
dependencyHandler.addProvider(
ss.getCompileOnlyConfigurationName(),
dependencyNotationProvider,
externalModuleDependency -> {
});
dependencyHandler.addProvider(
ss.getAnnotationProcessorConfigurationName(),
dependencyNotationProvider,
externalModuleDependency -> {
});
TaskContainer tasks = project.getTasks();
String javadocTaskName = ss.getJavadocTaskName();
Task javadocTask = tasks.findByName(javadocTaskName);
if(javadocTask != null) {
String delombokTaskName = "delombok" + ss.getName().substring(0, 1).toUpperCase() + ss.getName().substring(1);
File outputDir = new File(new File(project.getBuildDir(), "delombok"), ss.getName());
Javadoc javadoc = (Javadoc) javadocTask;
TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName,
Delombok.class,
lombokConfiguration.getSingleFile(),
outputDir,
ss.getJava().getSrcDirs(),
ss.getCompileClasspath().getAsPath()
);
delombokTaskProvider.configure(delombokTask -> {
delombokTask.getInputs().files(ss.getAllSource().getSourceDirectories());
});
javadoc.setSource(outputDir);
javadoc.getInputs().files(delombokTaskProvider);
dependencyHandler.addProvider(
ss.getAnnotationProcessorConfigurationName(),
dependencyNotationProvider,
externalModuleDependency -> {
});
TaskContainer tasks = project.getTasks();
String javadocTaskName = ss.getJavadocTaskName();
Task javadocTask = tasks.findByName(javadocTaskName);
if(javadocTask != null) {
String delombokTaskName = "delombok" + ss.getName().substring(0, 1).toUpperCase() + ss.getName().substring(1);
File outputDir = new File(new File(project.getBuildDir(), "delombok"), ss.getName());
Javadoc javadoc = (Javadoc) javadocTask;
TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName, Delombok.class, (delombok -> {
delombok.getSourceSet().set(ss);
delombok.getOutputDir().set(outputDir);
delombok.getLombokJar().set(lombokConfiguration);
// Disabled for now due to https://github.com/projectlombok/lombok/issues/2829
//delombok.getInferModulePath().set(javaPluginExtension.getModularity().getInferModulePath());
delombok.getInferModulePath().set(false);
}));
javadoc.setSource(outputDir);
javadoc.getInputs().files(delombokTaskProvider);
}
}
JavaToolchainSpec toolchain = javaPluginExtension.getToolchain();
if(toolchain.getLanguageVersion().isPresent()) {
project.afterEvaluate((Project pro) -> {
if(toolchain.getLanguageVersion().get().asInt() >= 16) {
pro.getTasks().withType(JavaCompile.class, t -> {
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
});
}
}
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
JavaToolchainSpec toolchain = javaPluginExtension.getToolchain();
if(toolchain.getLanguageVersion().isPresent()) {
project.afterEvaluate((Project pro) -> {
if(toolchain.getLanguageVersion().get().asInt() >= 16) {
pro.getTasks().withType(JavaCompile.class, t -> {
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
});
}
});
} else if(JavaVersion.current().compareTo(JavaVersion.VERSION_16) >= 0) {
project.getTasks().withType(JavaCompile.class, t -> {
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
});
}
});
});
} else if(JavaVersion.current().compareTo(JavaVersion.VERSION_16) >= 0) {
project.getTasks().withType(JavaCompile.class, t -> {
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
});
}
}
}

View File

@@ -1,6 +1,6 @@
plugins {
id 'maven-publish'
id 'groovy-gradle-plugin'
id 'java-gradle-plugin'
}
gradlePlugin {

View File

@@ -1,198 +0,0 @@
package net.woggioni.gradle.multi.release.jar
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.plugins.DslObject
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.process.CommandLineArgumentProvider
import java.lang.module.ModuleDescriptor
import java.util.jar.JarFile
import java.util.stream.Collectors
import java.util.zip.ZipFile
import static org.gradle.api.attributes.LibraryElements.JAR
import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE
class MultiReleaseJarPlugin implements Plugin<Project> {
private static void jpmsModuleName(File file) {
JarFile jarFile = new JarFile(file).with {
if (it.isMultiRelease()) {
new JarFile(
file,
false,
ZipFile.OPEN_READ,
Runtime.version()
)
} else {
it
}
}
String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") }
def moduleInfoEntry = jarFile.getJarEntry("module-info.class")
moduleInfoEntry
?.with(jarFile.&getInputStream)
?.withCloseable(ModuleDescriptor.&read) ?: automaticModuleName
ModuleDescriptor.read()
}
// @Canonical
static class CompilerArgumentProvider implements CommandLineArgumentProvider {
private final Project project
private final ObjectFactory objects
@Input
final Provider<String> jpmsModuleName
private final Map<String, ListProperty<String>> patchModules
@InputFiles
@CompileClasspath
final FileCollection sourceSetOutput
// @InputFiles
// FileCollection getPatchModules() {
// return project.files(patchModules.entrySet().stream().flatMap {
// it.getValue().get().stream()
// }.toArray(String::new))
// }
CompilerArgumentProvider(
Project project,
ObjectFactory objects,
Provider<String> jpmsModuleName,
Map<String, ListProperty<String>> patchModules,
FileCollection sourceSetOutput) {
this.project = project
this.objects = objects
this.jpmsModuleName = jpmsModuleName
this.patchModules = patchModules
this.sourceSetOutput = sourceSetOutput
}
@Override
Iterable<String> asArguments() {
Map<String, ListProperty<String>> patchModules = new HashMap<>(patchModules)
String name = jpmsModuleName.get()
if(name) {
patchModules.computeIfAbsent(name) {
objects.listProperty(String.class).convention(new ArrayList<String>())
}.addAll(sourceSetOutput.collect { it.toString() })
} else {
throw new GradleException("Missing property 'jpms.module.name'")
}
String sep = System.getProperty('path.separator')
List<String> result = new ArrayList<>()
for(Map.Entry<String, ListProperty<String>> entry : patchModules.entrySet()) {
String arg = entry.getValue().get().stream().collect(Collectors.joining(sep))
result += '--patch-module'
result += "${entry.getKey()}=${arg}"
}
result
}
}
@Override
void apply(Project project) {
project.pluginManager.apply(JavaPlugin)
MultiReleaseJarPluginExtension mrjpe = new MultiReleaseJarPluginExtension(project.objects)
project.extensions.add('multiReleaseJar', mrjpe)
JavaPluginExtension javaPluginExtension = project.extensions.findByType(JavaPluginExtension.class)
JavaVersion binaryVersion = javaPluginExtension.targetCompatibility ?: javaPluginExtension.toolchain?.with {
it.languageVersion.get()
} ?: JavaVersion.current()
if(binaryVersion > JavaVersion.VERSION_1_8) {
Configuration compileClasspathConfiguration = project.configurations.compileClasspath
project.configurations.named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) {
attributes {
attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.class, JAR))
}
}
SourceSet mainSourceSet = (project.sourceSets.main as SourceSet)
JavaCompile compileJavaTask = project.tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile).get()
compileJavaTask.configure {
options.release.set(JavaVersion.VERSION_1_8.majorVersion.toInteger())
}
Jar jarTask = project.tasks.named(JavaPlugin.JAR_TASK_NAME, Jar).get()
jarTask.configure {
manifest.attributes('Multi-Release': 'true')
}
ArrayList<FileCollection> compileOutputs = new ArrayList<>()
compileOutputs << compileJavaTask.outputs.files
ArrayList<FileCollection> sourcePaths = new ArrayList<>()
sourcePaths << mainSourceSet.java.sourceDirectories
Arrays.stream(JavaVersion.values()).filter {
it > JavaVersion.VERSION_1_8 && it <= binaryVersion
}.forEach {javaVersion ->
SourceDirectorySet sourceDirectorySet =
project.objects.sourceDirectorySet("java${javaVersion.majorVersion}", javaVersion.toString())
sourceDirectorySet.with {
srcDir(new File(project.projectDir, "src/${mainSourceSet.name}/${sourceDirectorySet.name}"))
destinationDirectory.set(new File(project.buildDir, "classes/${mainSourceSet.name}/${sourceDirectorySet.name}"))
sourcePaths << sourceDirectories
}
new DslObject(mainSourceSet).getConvention().getPlugins().put(sourceDirectorySet.name, sourceDirectorySet)
mainSourceSet.getExtensions().add(SourceDirectorySet.class, sourceDirectorySet.name, sourceDirectorySet)
TaskProvider<JavaCompile> compileTask = project.tasks.register(JavaPlugin.COMPILE_JAVA_TASK_NAME + javaVersion.majorVersion, JavaCompile, { javaCompileTask ->
javaCompileTask.options.release.set(javaVersion.majorVersion.toInteger())
javaCompileTask.classpath = compileClasspathConfiguration + compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
javaCompileTask.options.compilerArgumentProviders.add(
new CompilerArgumentProvider(
project,
project.objects,
project.provider {
project.hasProperty("jpms.module.name") ?
project.property("jpms.module.name") : null
},
mrjpe.patchModules,
mainSourceSet.output
)
)
javaCompileTask.source = sourceDirectorySet
javaCompileTask.destinationDirectory.set(sourceDirectorySet.destinationDirectory)
javaCompileTask.options.annotationProcessorPath = mainSourceSet.annotationProcessorPath
javaCompileTask.modularity.inferModulePath = javaPluginExtension.modularity.inferModulePath
javaCompileTask.options.sourcepath = sourcePaths.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
javaCompileTask.javaCompiler = compileJavaTask.javaCompiler
})
compileOutputs << compileTask.get().outputs.files
sourceDirectorySet.compiledBy(compileTask, { it.getDestinationDirectory()})
jarTask.configure {
from(compileTask.get().destinationDirectory) {
into("META-INF/versions/${javaVersion.majorVersion}")
}
}
}
SourceSet testSourceSet = (project.sourceSets.test as SourceSet)
testSourceSet.compileClasspath += compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
testSourceSet.runtimeClasspath += compileOutputs.stream().reduce { fc1, fc2 -> fc1 + fc2 }.get()
["apiElements", "runtimeElements"].forEach { String name ->
Configuration conf = project.configurations.getByName(name)
conf.attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, compileJavaTask.options.release.get())
}
}
}
}
}

View File

@@ -0,0 +1,219 @@
package net.woggioni.gradle.multi.release.jar;
import lombok.Getter;
import org.gradle.api.GradleException;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.internal.plugins.DslObject;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CompileClasspath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.AbstractCompile;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.process.CommandLineArgumentProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static org.gradle.api.attributes.LibraryElements.JAR;
import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE;
public class MultiReleaseJarPlugin implements Plugin<Project> {
// @SneakyThrows
// private static void jpmsModuleName(File file) {
// JarFile jarFile = new JarFile(file);
// if (jarFile.isMultiRelease()) {
// jarFile = new JarFile(
// file,
// false,
// ZipFile.OPEN_READ,
// Runtime.version()
// );
// }
// String automaticModuleName = Optional.ofNullable(jarFile.getManifest())
// .map(Manifest::getMainAttributes)
// .map(mainAttr -> mainAttr.getValue("Automatic-Module-Name"))
// .orElse(null);
// Optional<JarEntry> moduleInfoEntry = Optional.ofNullable(jarFile.getJarEntry("module-info.class"));
// moduleInfoEntry
// .map(jarFile::getInputStream)
// .map(ModuleDescriptor::read)
// .orElse(automaticModuleName);
// }
static class CompilerArgumentProvider implements CommandLineArgumentProvider {
@Getter(onMethod_ = {@Input})
private final Provider<String> jpmsModuleName;
@Getter(onMethod_ = {@Input})
private final MapProperty<String, List<String>> patchModules;
@Getter(onMethod_ = {@InputFiles, @CompileClasspath})
private final FileCollection sourceSetOutput;
public CompilerArgumentProvider(
Provider<String> jpmsModuleName,
MapProperty<String, List<String>> patchModules,
FileCollection sourceSetOutput) {
this.jpmsModuleName = jpmsModuleName;
this.patchModules = patchModules;
this.sourceSetOutput = sourceSetOutput;
}
@Override
public Iterable<String> asArguments() {
Map<String, List<String>> patchModules = new TreeMap<>(this.patchModules.get());
if (jpmsModuleName.isPresent()) {
String name = jpmsModuleName.get();
patchModules.computeIfAbsent(name, k -> new ArrayList<>())
.addAll(
StreamSupport.stream(sourceSetOutput.spliterator(), false)
.map(File::toString)
.collect(Collectors.toList())
);
} else {
throw new GradleException("Missing property 'jpms.module.name'");
}
String sep = System.getProperty("path.separator");
List<String> result = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : patchModules.entrySet()) {
String arg = String.join(sep, entry.getValue());
result.add("--patch-module");
result.add(entry.getKey() + "=" + arg);
}
return result;
}
}
@Override
public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class);
MultiReleaseJarPluginExtension mrjpe = project.getObjects().newInstance(MultiReleaseJarPluginExtension.class);
project.getExtensions().add("multiReleaseJar", mrjpe);
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
SourceSetContainer ssc = javaPluginExtension.getSourceSets();
JavaVersion binaryVersion = Optional.ofNullable(javaPluginExtension.getTargetCompatibility())
.or(() -> Optional.ofNullable(javaPluginExtension.getToolchain())
.map(JavaToolchainSpec::getLanguageVersion)
.filter(Property::isPresent)
.map(Property::get)
.map(JavaLanguageVersion::asInt)
.map(JavaVersion::toVersion)
).orElseGet(JavaVersion::current);
if (Comparator.<JavaVersion>naturalOrder().compare(binaryVersion, JavaVersion.VERSION_1_8) > 0) {
Configuration compileClasspathConfiguration = project.getConfigurations()
.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
project.getConfigurations().named(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, cfg -> {
cfg.attributes(attr -> {
attr.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, JAR));
});
});
SourceSet mainSourceSet = ssc.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
JavaCompile compileJavaTask = project.getTasks()
.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile.class, (JavaCompile t) -> {
t.getOptions().getRelease().set(Integer.parseInt(JavaVersion.VERSION_1_8.getMajorVersion()));
}).get();
Jar jarTask = project.getTasks().named(JavaPlugin.JAR_TASK_NAME, Jar.class, (Jar t) -> {
Map<String, String> attrs = new HashMap<>();
attrs.put("Multi-Release", "true");
t.getManifest().attributes(attrs);
}).get();
SourceDirectorySet java = mainSourceSet.getJava();
ArrayList<FileCollection> compileOutputs = new ArrayList<>();
compileOutputs.add(compileJavaTask.getOutputs().getFiles());
ArrayList<FileCollection> sourcePaths = new ArrayList<>();
sourcePaths.add(java.getSourceDirectories());
Comparator<JavaVersion> cmp = Comparator.naturalOrder();
Arrays.stream(JavaVersion.values())
.filter((JavaVersion jv) -> cmp.compare(jv, JavaVersion.VERSION_1_8) > 0 && cmp.compare(jv, binaryVersion) <= 0)
.forEach(javaVersion -> {
SourceDirectorySet sourceDirectorySet = project.getObjects()
.sourceDirectorySet("java" + javaVersion.getMajorVersion(), javaVersion.toString());
sourceDirectorySet.srcDir(new File(project.getProjectDir(),
"src/" + mainSourceSet.getName() + "/" + sourceDirectorySet.getName()));
sourceDirectorySet.getDestinationDirectory().set(
new File(project.getBuildDir(),
"classes/" + mainSourceSet.getName() + "/" + sourceDirectorySet.getName())
);
sourcePaths.add(sourceDirectorySet.getSourceDirectories());
new DslObject(mainSourceSet).getConvention().getPlugins().put(sourceDirectorySet.getName(), sourceDirectorySet);
mainSourceSet.getExtensions().add(SourceDirectorySet.class, sourceDirectorySet.getName(), sourceDirectorySet);
TaskProvider<JavaCompile> compileTask =
project.getTasks().register(JavaPlugin.COMPILE_JAVA_TASK_NAME + javaVersion.getMajorVersion(), JavaCompile.class,
(JavaCompile javaCompileTask) -> {
javaCompileTask.getOptions().getRelease().set(Integer.parseInt(javaVersion.getMajorVersion()));
javaCompileTask.setClasspath(compileClasspathConfiguration.plus(
compileOutputs.stream().reduce(project.getObjects().fileCollection(), FileCollection::plus)));
javaCompileTask.getOptions().getCompilerArgumentProviders().add(
new CompilerArgumentProvider(
project.provider(() -> Optional.of("jpms.module.name")
.filter(project::hasProperty)
.map(project::property)
.map(Object::toString)
.orElse(null)),
mrjpe.getPatchModules(),
mainSourceSet.getOutput()
)
);
javaCompileTask.source(sourceDirectorySet);
javaCompileTask.getDestinationDirectory().set(sourceDirectorySet.getDestinationDirectory());
javaCompileTask.getOptions().setAnnotationProcessorPath(mainSourceSet.getAnnotationProcessorPath());
javaCompileTask.getModularity().getInferModulePath().set(javaPluginExtension.getModularity().getInferModulePath());
javaCompileTask.getOptions().setSourcepath(sourcePaths.stream().reduce(project.files(), FileCollection::plus));
javaCompileTask.getJavaCompiler().set(compileJavaTask.getJavaCompiler());
});
compileOutputs.add(compileTask.get().getOutputs().getFiles());
sourceDirectorySet.compiledBy(compileTask, AbstractCompile::getDestinationDirectory);
jarTask.from(compileTask.get().getDestinationDirectory(), copySpec -> {
copySpec.into("META-INF/versions/" + javaVersion.getMajorVersion());
});
});
SourceSet testSourceSet = ssc.getByName(SourceSet.TEST_SOURCE_SET_NAME);
testSourceSet.setCompileClasspath(
testSourceSet.getCompileClasspath().plus(compileOutputs.stream().reduce(project.files(), FileCollection::plus))
);
testSourceSet.setRuntimeClasspath(
testSourceSet.getRuntimeClasspath().plus(compileOutputs.stream().reduce(project.files(), FileCollection::plus))
);
Arrays.asList("apiElements", "runtimeElements").forEach((String name) -> {
Configuration conf = project.getConfigurations().getByName(name);
conf.attributes(attrs -> {
attrs.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
compileJavaTask.getOptions().getRelease().get());
});
});
}
}
}

View File

@@ -1,33 +1,41 @@
package net.woggioni.gradle.multi.release.jar;
import org.gradle.api.Project;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Provider;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
public class MultiReleaseJarPluginExtension {
private final ObjectFactory objects;
private final Map<String, ListProperty<String>> patchModules;
private final Provider<List<String>> listCtor;
private final MapProperty<String, List<String>> patchModules;
@Inject
public MultiReleaseJarPluginExtension(ObjectFactory objects) {
this.objects = objects;
patchModules = new HashMap<>();
public MultiReleaseJarPluginExtension(Project project, ObjectFactory objects) {
patchModules = objects.mapProperty(String.class,
(Class<List<String>>) ((List<String>) new ArrayList<String>()).getClass());
listCtor = project.provider(ArrayList::new);
}
private static <T> List<T> listAdd(List<T> l, T el) {
l.add(el);
return l;
}
public void patchModule(String moduleName, Provider<String> path) {
this.patchModules
.computeIfAbsent(moduleName, key -> objects.listProperty(String.class)
.convention(new ArrayList<>()))
.add(path);
Provider<List<String>> listProvider = this.patchModules.getting(moduleName);
if(listProvider.isPresent()) {
patchModules.put(moduleName, listProvider.zip(path, MultiReleaseJarPluginExtension::listAdd));
} else {
patchModules.put(moduleName, listCtor.zip(path, MultiReleaseJarPluginExtension::listAdd));
}
}
public Map<String, ListProperty<String>> getPatchModules() {
public MapProperty<String, List<String>> getPatchModules() {
return patchModules;
}
}

View File

@@ -2,25 +2,6 @@ plugins {
id "java-gradle-plugin"
}
childProjects.forEach {name, child ->
child.with {
apply plugin: 'maven-publish'
publishing {
repositories {
maven {
url = woggioniMavenRepositoryUrl
}
}
publications {
maven(MavenPublication) {
from(components["java"])
}
}
}
}
}
evaluationDependsOnChildren()
configurations {

View File

@@ -18,7 +18,9 @@ dependencies {
}
jar {
bnd '''\
Import-Package: !lombok, *
'''
bundle {
bnd '''\
Import-Package: !lombok, *
'''
}
}

16
sambal/build.gradle Normal file
View File

@@ -0,0 +1,16 @@
plugins {
id 'java-gradle-plugin'
}
dependencies {
implementation catalog.jgit
}
gradlePlugin {
plugins {
create("SambalPlugin") {
id = "net.woggioni.gradle.sambal"
implementationClass = "net.woggioni.gradle.sambal.SambalPlugin"
}
}
}

View File

@@ -0,0 +1,244 @@
package net.woggioni.gradle.sambal;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.codehaus.groovy.runtime.MethodClosure;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.lib.Ref;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeContainer;
import org.gradle.api.java.archives.Attributes;
import org.gradle.api.plugins.AppliedPlugin;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.PluginManager;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ValueSource;
import org.gradle.api.provider.ValueSourceParameters;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.bundling.Jar;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SambalPlugin implements Plugin<Project> {
private static Pattern tagPattern = Pattern.compile("^refs/tags/v?(\\d+\\.\\d+.*)");
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
private static void copyConfigurationAttributes(Configuration source, Configuration destination) {
destination.attributes(new Action<AttributeContainer>() {
@Override
public void execute(@Nonnull AttributeContainer destinationAttributes) {
AttributeContainer sourceAttributes = source.getAttributes();
for (Attribute attr : sourceAttributes.keySet()) {
destinationAttributes.attribute(attr, Objects.requireNonNull(sourceAttributes.getAttribute(attr)));
}
}
});
}
@SneakyThrows
private static String sha256(File f) {
byte[] buffer = new byte[0x10000];
MessageDigest md = MessageDigest.getInstance("SHA-256");
try (InputStream inputStream = new DigestInputStream(new FileInputStream(f), md)) {
while (true) {
int read = inputStream.read(buffer);
if (read < 0) break;
md.update(buffer, 0, read);
}
}
return bytesToHex(md.digest());
}
private static Optional<Path> which(String command) {
return Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator)))
.map(path -> Paths.get(path, command))
.filter(Files::exists)
.filter(Files::isExecutable)
.findFirst();
}
private static int getVersionInt(Project project) {
int result = 0;
int scale = 100 * 100;
String versionString = project.getVersion().toString();
int dash = versionString.indexOf('-');
if (dash < 0) {
dash = versionString.length();
}
versionString = versionString.substring(0, dash);
for (String part : versionString.split("\\.")) {
if (scale >= 1) {
result += scale * Integer.parseInt(part);
scale /= 100;
}
}
return result;
}
@SneakyThrows
private static String runCommand(String... args) {
ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
Process p = pb.start();
p.waitFor();
if (p.exitValue() != 0) throw new GradleException("Error invoking subprocess");
StringWriter sw = new StringWriter();
char[] buffer = new char[0x1024];
try (Reader reader = new InputStreamReader(p.getInputStream())) {
while (true) {
int read = reader.read(buffer);
if (read <= 0) break;
sw.write(buffer, 0, read);
}
}
return sw.toString();
}
@SneakyThrows
private static List<String> getCurrentTag(Git git) {
List<Ref> tags = git.tagList().call();
Ref currentRef = git.getRepository().findRef("HEAD");
List<String> currentTag = tags.stream()
.filter(it -> Objects.equals(it.getObjectId(), currentRef.getObjectId()))
.map(it -> tagPattern.matcher(it.getName())).filter(Matcher::matches)
.map(it -> it.group(1))
.collect(Collectors.toList());
if (currentTag.isEmpty()) return null;
else {
return currentTag;
}
}
private static String resolveProperty(Project project, String key, String defaultValue) {
if (project.hasProperty(key)) return project.property(key).toString();
else {
String envVarKey = key.replace('.', '_').toUpperCase();
String envVarValue = System.getenv().get(envVarKey);
if (envVarValue != null) return envVarValue;
else return defaultValue;
}
}
private static String resolveProperty(Project project, String key) {
if (project.hasProperty(key)) return project.property(key).toString();
else {
String envVarKey = key.replace('.', '_').toUpperCase();
String envVarValue = System.getenv().get(envVarKey);
if (envVarValue != null) return envVarValue;
else {
String msg = String.format("Impossible to resolve property '%s'," +
" either add it to Gradle's project properties from the command line using:\n" +
"./gradlew -P%s=someValue\n" +
"or set the environmental variable %s", key, key, envVarKey);
throw new GradleException(msg);
}
}
}
@Getter
@Setter
public static class ProjectParameters implements ValueSourceParameters, Serializable {
private File rootDirectory;
}
public abstract static class GitTagValueSource implements ValueSource<List<String>, ProjectParameters> {
@Override
@SneakyThrows
public List<String> obtain() {
File rootDirectory = getParameters().getRootDirectory();
try(Git git = Git.open(rootDirectory)) {
Status status = git.status().call();
if (status.isClean()) {
return getCurrentTag(git);
} else {
return null;
}
}
}
}
public abstract static class GitRevisionValueSource implements ValueSource<String, ProjectParameters> {
@Override
@SneakyThrows
public String obtain() {
File rootDirectory = getParameters().getRootDirectory();
try (Git git = Git.open(rootDirectory)) {
return git.getRepository().findRef("HEAD").getObjectId().name();
}
}
}
@Override
public void apply(Project project) {
ExtraPropertiesExtension ext = project.getRootProject().getExtensions().getExtraProperties();
ext.set("getIntegerVersion", new MethodClosure(this, "getVersionInt").curry(project));
final Provider<List<String>> gitTagProvider = project.getProviders().of(GitTagValueSource.class, it -> {
it.parameters( params -> params.setRootDirectory(project.getRootDir()));
});
ext.set("currentTag", gitTagProvider);
ext.set("resolveProperty", new MethodClosure(this, "resolveProperty").curry(project));
ext.set("copyConfigurationAttributes", new MethodClosure(this, "copyConfigurationAttributes"));
final Provider<String> gitRevisionProvider = project.getProviders().of(GitRevisionValueSource.class, it -> {
it.parameters( params -> params.setRootDirectory(project.getRootDir()));
});
ext.set("gitRevision", gitRevisionProvider);
ext.set("which", new MethodClosure(this, "which").curry(project));
PluginManager pluginManager = project.getPluginManager();
pluginManager.withPlugin("java-library", (AppliedPlugin plugin) -> {
TaskContainer tasks = project.getTasks();
project.afterEvaluate(p -> {
tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, jarTask -> {
jarTask.manifest(mf -> {
Attributes attrs = mf.getAttributes();
attrs.put(java.util.jar.Attributes.Name.SPECIFICATION_TITLE.toString(), project.getName());
attrs.put(java.util.jar.Attributes.Name.SPECIFICATION_VERSION.toString(), project.getVersion());
attrs.put(java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION.toString(), gitRevisionProvider);
});
});
});
});
}
}

View File

@@ -0,0 +1,86 @@
package net.woggioni.gradle.sambal;
import org.codehaus.groovy.ant.Groovy;
import org.codehaus.groovy.util.StringUtil;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.bundling.Jar;
import javax.inject.Inject;
import java.io.File;
import java.util.Map;
import java.util.TreeMap;
abstract class SignJarTask extends DefaultTask {
@InputFile
public abstract RegularFileProperty getInputJarFile();
@Input
public abstract MapProperty<String,String> getOptions();
@Input
public abstract DirectoryProperty getArchiveDestination();
@Input
public abstract Property<String> getArchiveBaseName();
@Input
public abstract Property<String> getArchiveVersion();
@Input
public abstract Property<String> getArchiveExtension();
@Input
@Optional
public abstract Property<String> getArchiveClassifier();
@OutputFile
public Provider<RegularFile> getArchiveFile() {
StringBuilder sb = new StringBuilder(getArchiveBaseName().get());
sb.append('-').append(getArchiveVersion().get());
if(getArchiveClassifier().isPresent() && !getArchiveClassifier().get().isEmpty()) {
sb.append('-').append(getArchiveClassifier().get());
}
sb.append('.').append(getArchiveExtension().get());
return getProject().getLayout().file(getArchiveDestination().map(ad ->
new File(ad.getAsFile(), sb.toString())
));
}
@Inject
public SignJarTask() {
BasePluginExtension bpe = getProject()
.getExtensions()
.getByType(BasePluginExtension.class);
getArchiveDestination().convention(bpe.getLibsDirectory());
getArchiveBaseName().convention(getProject().getName());
getArchiveVersion().convention(getProject().getVersion().toString());
getArchiveExtension().convention("jar");
getArchiveClassifier().convention("signed");
getArchiveClassifier().set("signed");
getInputJarFile().convention(
getProject().getTasks()
.named(JavaPlugin.JAR_TASK_NAME, Jar.class)
.flatMap(Jar::getArchiveFile));
}
@TaskAction
public void run() {
Map<String, String> signingOptions = new TreeMap<>(getOptions().get());
signingOptions.put("jar", getInputJarFile().map(RegularFile::getAsFile).get().toString());
signingOptions.put("signedJar", getArchiveFile().get().getAsFile().toString());
getAnt().invokeMethod("signjar", signingOptions);
}
}

View File

@@ -0,0 +1,53 @@
package net.woggioni.gradle.sambal.attribute;
import org.gradle.api.Named;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeCompatibilityRule;
import org.gradle.api.attributes.AttributeDisambiguationRule;
import org.gradle.api.attributes.CompatibilityCheckDetails;
import org.gradle.api.attributes.MultipleCandidatesDetails;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
public interface Sealing extends Named {
Attribute<Sealing> SEALING_ATTRIBUTE = Attribute.of("net.woggioni.gradle.lys.artifact.type", Sealing.class);
String sealed = "sealed";
String open = "open";
class CompatibilityRules implements AttributeCompatibilityRule<Sealing> {
public void execute(CompatibilityCheckDetails<Sealing> details) {
Sealing consumerValue = details.getConsumerValue();
Sealing producerValue = details.getProducerValue();
if (consumerValue == null) {
details.compatible();
} else if(producerValue == null) {
details.incompatible();
} else if(Objects.equals(consumerValue.getName(), producerValue.getName())) {
details.compatible();
} else {
details.incompatible();
}
}
}
class DisambiguationRules implements AttributeDisambiguationRule<Sealing> {
private static final List<String> ORDER = Arrays.asList(open, sealed);
private static final Comparator<Sealing> comparator =
Comparator.comparingInt(sealing -> ORDER.indexOf(sealing.getName()));
@Override
public void execute(MultipleCandidatesDetails<Sealing> details) {
if(details.getConsumerValue() == null) {
details.closestMatch(null);
} else {
details.getCandidateValues().stream()
.min(comparator)
.ifPresent(details::closestMatch);
}
}
}
}

View File

@@ -0,0 +1,53 @@
package net.woggioni.gradle.sambal.attribute;
import org.gradle.api.Named;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeCompatibilityRule;
import org.gradle.api.attributes.AttributeDisambiguationRule;
import org.gradle.api.attributes.CompatibilityCheckDetails;
import org.gradle.api.attributes.MultipleCandidatesDetails;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
public interface Signing extends Named {
Attribute<Signing> SIGNING_ATTRIBUTE = Attribute.of("net.woggioni.gradle.lys.artifact.signing", Signing.class);
String signed = "signed";
String unsigned = "unsigned";
class CompatibilityRules implements AttributeCompatibilityRule<Signing> {
public void execute(CompatibilityCheckDetails<Signing> details) {
Signing consumerValue = details.getConsumerValue();
Signing producerValue = details.getProducerValue();
if (consumerValue == null) {
details.compatible();
} else if(producerValue == null) {
details.incompatible();
} else if(Objects.equals(consumerValue.getName(), producerValue.getName())) {
details.compatible();
} else {
details.incompatible();
}
}
}
class DisambiguationRules implements AttributeDisambiguationRule<Signing> {
private static final List<String> ORDER = Arrays.asList(unsigned, signed);
private static final Comparator<Signing> comparator =
Comparator.comparingInt(signing -> ORDER.indexOf(signing.getName()));
@Override
public void execute(MultipleCandidatesDetails<Signing> details) {
if(details.getConsumerValue() == null) {
details.closestMatch(null);
} else {
details.getCandidateValues().stream()
.min(comparator)
.ifPresent(details::closestMatch);
}
}
}
}

View File

@@ -1,7 +1,7 @@
dependencyResolutionManagement {
repositories {
maven {
url = 'https://woggioni.net/mvn/'
url = getProperty('gitea.maven.url')
content {
includeGroup 'com.lys'
}
@@ -10,7 +10,6 @@ dependencyResolutionManagement {
versionCatalogs {
catalog {
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.catalog.version')
version("slf4j", "1.7.36")
}
}
}
@@ -26,3 +25,6 @@ include 'osgi-app:osgi-simple-bootstrapper'
include 'osgi-app:osgi-simple-bootstrapper-api'
include 'osgi-app:osgi-simple-bootstrapper-application'
include 'wildfly'
include 'sambal'
include 'graalvm'
include 'jdeps'

View File

@@ -1,19 +1,16 @@
package net.woggioni.gradle.wildfly;
import lombok.Getter;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Exec;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Arrays;
public class Deploy2WildflyTask extends Exec {
public abstract class Deploy2WildflyTask extends Exec {
private static final String PROPERTY_PREFIX = "net.woggioni.gradle.wildfly.";
private static final String HOST_PROPERTY_KEY = PROPERTY_PREFIX + "rpcHost";
private static final String PORT_PROPERTY_KEY = PROPERTY_PREFIX + "rpcPort";
@@ -21,24 +18,21 @@ public class Deploy2WildflyTask extends Exec {
private static final String PASSWORD_PROPERTY_KEY = PROPERTY_PREFIX + "rpcPassword";
@Input
@Getter
private final Property<String> rpcHost;
public abstract Property<String> getRpcHost();
@Input
@Getter
private final Property<Integer> rpcPort;
public abstract Property<Integer> getRpcPort();
@Input
@Getter
private final Property<String> rpcUsername;
public abstract Property<String> getRpcUsername();
@Input
@Getter
private final Property<String> rpcPassword;
public abstract Property<String> getRpcPassword();
@Input
public abstract Property<String> getDeploymentName();
@Getter
@InputFile
private final RegularFileProperty artifact;
public abstract RegularFileProperty getArtifact();
private String projectProperty(String key, String defaultValue) {
String result = (String) getProject().findProperty(key);
@@ -46,7 +40,7 @@ public class Deploy2WildflyTask extends Exec {
}
@Inject
public Deploy2WildflyTask(@Nonnull ObjectFactory objectFactory) {
public Deploy2WildflyTask() {
setGroup("deploy");
setDescription("Deploy this project artifact to Wildfly application server");
Provider<String> defaultHostProvider = getProject()
@@ -59,19 +53,21 @@ public class Deploy2WildflyTask extends Exec {
.provider(() -> projectProperty(PASSWORD_PROPERTY_KEY, "password"));
executable("/opt/wildfly/bin/jboss-cli.sh");
rpcHost = objectFactory.property(String.class).convention(defaultHostProvider);
rpcPort = objectFactory.property(Integer.class).convention(defaultPortProvider);
rpcUsername = objectFactory.property(String.class).convention(defaultUsernameProvider);
rpcPassword = objectFactory.property(String.class).convention(defaultPasswordProvider);
artifact = objectFactory.fileProperty();
getRpcHost().convention(defaultHostProvider);
getRpcPort().convention(defaultPortProvider);
getRpcUsername().convention(defaultUsernameProvider);
getRpcPassword().convention(defaultPasswordProvider);
getDeploymentName().convention(getArtifact().map(it -> it.getAsFile().getName()));
getArgumentProviders().add(() ->
Arrays.asList(
"--controller=" + rpcHost.get() + ":" + rpcPort.get(),
"--controller=" + getRpcHost().get() + ":" + getRpcPort().get(),
"--connect",
"--user=" + rpcUsername.get(),
"--password=" + rpcPassword.get(),
"--command=deploy " + artifact.getAsFile().get().getPath() + " --force")
"--user=" + getRpcUsername().get(),
"--password=" + getRpcPassword().get(),
"--command=deploy "
+ getArtifact().getAsFile().get().getPath()
+ " --name=" + getDeploymentName().get()
+ " --force")
);
}
}