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 -> subprojects { subproject ->
apply plugin: 'java-library' apply plugin: 'java-library'
apply plugin: 'maven-publish'
group = "net.woggioni.gradle"
version = getProperty('version.myGradlePlugins') version = getProperty('version.myGradlePlugins')
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(11) languageVersion = JavaLanguageVersion.of(21)
} }
} }
int javaVersion int javaVersion
if(subproject.path == ':osgi-app') { if(subproject.path == ':osgi-app' || subproject.path == ':multi-release-jar') {
javaVersion = 11 javaVersion = 11
} else { } else {
javaVersion = 8 javaVersion = 8
@@ -27,12 +29,6 @@ subprojects { subproject ->
} }
repositories { repositories {
maven {
url = woggioniMavenRepositoryUrl
content {
includeGroup 'net.woggioni'
}
}
mavenCentral() mavenCentral()
} }
@@ -45,27 +41,30 @@ subprojects { subproject ->
add("testImplementation", gradleTestKit()) add("testImplementation", gradleTestKit())
} }
tasks.named("test", Test) { tasks.withType(Test) {
useJUnitPlatform() useJUnitPlatform()
} }
}
childProjects.forEach { name, child -> publishing {
child.with { repositories {
apply plugin: 'maven-publish' 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 { authentication {
repositories { header(HttpHeaderAuthentication)
maven {
url = woggioniMavenRepositoryUrl
} }
} }
} }
} }
} }
wrapper { wrapper {
gradleVersion = getProperty("version.gradle") gradleVersion = getProperty("version.gradle")
distributionType = Wrapper.DistributionType.ALL distributionType = Wrapper.DistributionType.ALL

View File

@@ -8,6 +8,9 @@ import org.gradle.api.plugins.ObjectConfigurationAction;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
public class DependencyExportPlugin implements Plugin<Project> { public class DependencyExportPlugin implements Plugin<Project> {
public static final String DEPENDENCY_EXPORT_GROUP = "dependency-export";
@Override @Override
public void apply(Project project) { public void apply(Project project) {
project.apply(new Action<ObjectConfigurationAction>() { 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.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Internal;
@@ -49,6 +50,8 @@ import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static net.woggioni.gradle.dependency.export.DependencyExportPlugin.DEPENDENCY_EXPORT_GROUP;
public class ExportDependencies extends DefaultTask { public class ExportDependencies extends DefaultTask {
@Getter(onMethod_ = { @Input }) @Getter(onMethod_ = { @Input })
@@ -74,6 +77,7 @@ public class ExportDependencies extends DefaultTask {
private final JavaPluginConvention javaPluginConvention; private final JavaPluginConvention javaPluginConvention;
@InputFiles @InputFiles
@Classpath
public Provider<FileCollection> getConfigurationFiles() { public Provider<FileCollection> getConfigurationFiles() {
return configurationName.map(this::fetchConfiguration); return configurationName.map(this::fetchConfiguration);
} }
@@ -96,6 +100,7 @@ public class ExportDependencies extends DefaultTask {
@Inject @Inject
public ExportDependencies(ObjectFactory objects) { public ExportDependencies(ObjectFactory objects) {
setGroup(DEPENDENCY_EXPORT_GROUP);
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class);
configurationName = objects.property(String.class).convention(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); configurationName = objects.property(String.class).convention(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
Provider<File> defaultOutputFileProvider = 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.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile; 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.TaskAction;
import org.gradle.api.tasks.options.Option; import org.gradle.api.tasks.options.Option;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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 { public class RenderDependencies extends DefaultTask {
@Getter(onMethod_ = { @InputFile }) @Getter(onMethod_ = {@InputFile, @PathSensitive(PathSensitivity.NONE)})
private Provider<File> sourceFile; private Provider<File> sourceFile;
@Getter(onMethod_ = { @Input}) @Getter(onMethod_ = {@Input})
private final Property<String> format; private final Property<String> format;
@Getter(onMethod_ = { @Input }) @Getter(onMethod_ = {@Input})
private final Property<String> graphvizExecutable; private final Property<String> graphvizExecutable;
@Getter @Getter
@@ -45,6 +53,7 @@ public class RenderDependencies extends DefaultTask {
return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull(); return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull();
} }
@Optional
@OutputFile @OutputFile
public Provider<File> getResult() { public Provider<File> getResult() {
return outputFile.map(RegularFile::getAsFile); return outputFile.map(RegularFile::getAsFile);
@@ -64,33 +73,46 @@ public class RenderDependencies extends DefaultTask {
} }
public void setExportTask(Provider<ExportDependencies> taskProvider) { public void setExportTask(Provider<ExportDependencies> taskProvider) {
dependsOn(taskProvider);
sourceFile = taskProvider.flatMap(ExportDependencies::getResult); sourceFile = taskProvider.flatMap(ExportDependencies::getResult);
} }
@Inject @Inject
public RenderDependencies(ObjectFactory objects) { public RenderDependencies(ObjectFactory objects) {
setGroup(DEPENDENCY_EXPORT_GROUP);
sourceFile = objects.property(File.class); sourceFile = objects.property(File.class);
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class);
format = objects.property(String.class).convention("xlib"); format = objects.property(String.class).convention("xlib");
graphvizExecutable = objects.property(String.class).convention("dot"); graphvizExecutable = objects.property(String.class).convention("dot");
Provider<File> defaultOutputFileProvider = Provider<File> defaultOutputFileProvider =
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "renderedDependencies")); 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 @TaskAction
@SneakyThrows @SneakyThrows
void run() { void run() {
Path destination = outputFile java.util.Optional<Path> destination = java.util.Optional.of(
.map(RegularFile::getAsFile) outputFile
.map(File::toPath) .map(RegularFile::getAsFile)
.get(); .map(File::toPath)
List<String> cmd = Arrays.asList( )
.filter(Provider::isPresent)
.map(Provider::get);
List<String> cmd = new ArrayList<>(Arrays.asList(
graphvizExecutable.get(), graphvizExecutable.get(),
"-T" + format.get(), "-T" + format.get()
"-o" + destination, ));
sourceFile.get().toString()
); if (destination.isPresent()) {
cmd.add("-o");
cmd.add(destination.get().toString());
}
cmd.add(sourceFile.get().toString());
int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor(); int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor();
if (returnCode != 0) { if (returnCode != 0) {
throw new GradleException("Error invoking graphviz"); 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 gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
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

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

43
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -133,22 +133,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
# shell script including quotes and variable substitutions, so put them in DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded. # 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +216,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ 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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # 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 See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal 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 package net.woggioni.gradle.jpms.check
import groovy.json.JsonBuilder
import groovy.transform.Canonical
import groovy.transform.CompileStatic import groovy.transform.CompileStatic
import groovy.xml.MarkupBuilder
import org.gradle.api.GradleException
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration import org.gradle.api.file.RegularFile
import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.ReportingBasePlugin
import java.nio.file.Files import org.gradle.api.reporting.ReportingExtension
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
class JPMSCheckPlugin implements Plugin<Project> { 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 @Override
@CompileStatic @CompileStatic
void apply(Project project) { void apply(Project project) {
project.tasks.register("jpms-check") {task -> project.pluginManager.apply(ReportingBasePlugin.class)
boolean recursive = project.properties["jpms-check.recursive"]?.with(Boolean.&parseBoolean) ?: false project.tasks.register("jpms-check", JPMSCheckTask) {task ->
String cfgName = project.properties["jpms-check.configurationName"] ?: "default" ReportingExtension reporting = project.extensions.getByType(ReportingExtension.class)
String outputFormat = project.properties["jpms-check.outputFormat"] ?: "html" boolean recursive = project.properties["jpms-check.recursive"]?.with(Object.&toString)?.with(Boolean.&parseBoolean) ?: false
Path outputFile = project.properties["jpms-check.outputFile"]?.with { String cfgName = project.properties["jpms-check.configurationName"] ?: JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME
Paths.get(it as String) OutputFormat defaultOutputFormat = (project.properties["jpms-check.outputFormat"]
} ?: with { ?.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) { switch(outputFormat) {
case "html": case OutputFormat.html:
Paths.get(project.buildDir.path, "jpms-report.html") result = dir.file( "jpms-report.html")
break break
case "json": case OutputFormat.json:
Paths.get(project.buildDir.path, "jpms-report.json") result = dir.file( "jpms-report.json")
break break
default:
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
} }
} result
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")
}
}
}
} }
} }
} }

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; 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.JavaExec;
import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.internal.jvm.JavaModuleDetector;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
@@ -10,12 +20,10 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
public class Delombok extends JavaExec { @RequiredArgsConstructor(onConstructor_ = {@Inject})
public abstract class Delombok extends JavaExec {
@Getter
@OutputDirectory
private final File outputDir;
private final JavaModuleDetector javaModuleDetector;
private static String buildClasspathString(Iterable<File> classpath) { private static String buildClasspathString(Iterable<File> classpath) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Iterator<File> it = classpath.iterator(); Iterator<File> it = classpath.iterator();
@@ -33,19 +41,56 @@ public class Delombok extends JavaExec {
return sb.toString(); return sb.toString();
} }
@Inject @InputFiles
public Delombok(File lombokJar, File outputDir, Iterable<File> sourceDirs, String classpath) { public Provider<FileCollection> getSourceClasspath() {
this.outputDir = outputDir; return getSourceSet()
classpath(lombokJar); .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<>(); List<String> args = new ArrayList<>();
args.add("delombok"); args.add("delombok");
args.add("-d"); args.add("-d");
args.add(outputDir.getPath()); args.add(getOutputDir().getAsFile().get().getPath());
args.add("-c"); SourceSet sourceSet = getSourceSet().get();
args.add(classpath); Boolean inferModulePath = getInferModulePath().get();
for(File sourceDir : sourceDirs) args.add(sourceDir.getPath()); 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()]; Object[] argsArray = new String[args.size()];
args.toArray(argsArray); args.toArray(argsArray);
args(argsArray); args(argsArray);
super.exec();
} }
} }

View File

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

View File

@@ -7,6 +7,7 @@ import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.model.ObjectFactory; import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
@@ -27,77 +28,73 @@ public class LombokPlugin implements Plugin<Project> {
@Override @Override
public void apply(Project project) { public void apply(Project project) {
project.getPluginManager().apply(JavaPlugin.class); project.getPluginManager().apply(JavaPlugin.class);
ObjectFactory objectFactory = project.getObjects(); LombokExtension ext = project.getExtensions().create("lombok", LombokExtension.class);
LombokExtension ext = project.getExtensions() ExtraPropertiesExtension epe = project.getExtensions().getExtraProperties();
.create("lombok", LombokExtension.class, if(epe.has("version.lombok")) {
objectFactory.property(String.class) ext.getVersion().convention(
.convention(project.provider( project.provider(() -> (String) epe.get("version.lombok"))
() -> (String) project.getExtensions().getExtraProperties().get("version.lombok"))) );
); }
project.afterEvaluate(p -> { JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
SourceSetContainer sourceSetContainer = project.getExtensions().findByType(JavaPluginExtension.class).getSourceSets(); SourceSetContainer sourceSetContainer = javaPluginExtension.getSourceSets();
Provider<Map<String, String>> dependencyNotationProvider = project.provider(() -> { Provider<Map<String, String>> dependencyNotationProvider = ext.getVersion().map((String version) -> {
Map<String, String> m = new HashMap<>(); Map<String, String> m = new HashMap<>();
m.put("group", "org.projectlombok"); m.put("group", "org.projectlombok");
m.put("name", "lombok"); m.put("name", "lombok");
m.put("version", ext.getVersion().get()); m.put("version", version);
return Collections.unmodifiableMap(m); return Collections.unmodifiableMap(m);
}); });
Configuration lombokConfiguration = project.getConfigurations().create("lombok"); Configuration lombokConfiguration = project.getConfigurations().create("lombok");
project.getDependencies().addProvider( project.getDependencies().addProvider(
lombokConfiguration.getName(), lombokConfiguration.getName(),
dependencyNotationProvider,
externalModuleDependency -> {
}
);
for (SourceSet ss : sourceSetContainer) {
DependencyHandler dependencyHandler = project.getDependencies();
dependencyHandler.addProvider(
ss.getCompileOnlyConfigurationName(),
dependencyNotationProvider, dependencyNotationProvider,
externalModuleDependency -> { 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); dependencyHandler.addProvider(
javadoc.getInputs().files(delombokTaskProvider); 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); } else if(JavaVersion.current().compareTo(JavaVersion.VERSION_16) >= 0) {
JavaToolchainSpec toolchain = javaPluginExtension.getToolchain(); project.getTasks().withType(JavaCompile.class, t -> {
if(toolchain.getLanguageVersion().isPresent()) { t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
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");
});
}
});
} }
} }

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id 'maven-publish' id 'maven-publish'
id 'groovy-gradle-plugin' id 'java-gradle-plugin'
} }
gradlePlugin { 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; package net.woggioni.gradle.multi.release.jar;
import org.gradle.api.Project;
import org.gradle.api.model.ObjectFactory; 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 org.gradle.api.provider.Provider;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.List;
public class MultiReleaseJarPluginExtension { public class MultiReleaseJarPluginExtension {
private final Provider<List<String>> listCtor;
private final ObjectFactory objects; private final MapProperty<String, List<String>> patchModules;
private final Map<String, ListProperty<String>> patchModules;
@Inject @Inject
public MultiReleaseJarPluginExtension(ObjectFactory objects) { public MultiReleaseJarPluginExtension(Project project, ObjectFactory objects) {
this.objects = objects; patchModules = objects.mapProperty(String.class,
patchModules = new HashMap<>(); (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) { public void patchModule(String moduleName, Provider<String> path) {
this.patchModules Provider<List<String>> listProvider = this.patchModules.getting(moduleName);
.computeIfAbsent(moduleName, key -> objects.listProperty(String.class) if(listProvider.isPresent()) {
.convention(new ArrayList<>())) patchModules.put(moduleName, listProvider.zip(path, MultiReleaseJarPluginExtension::listAdd));
.add(path); } else {
patchModules.put(moduleName, listCtor.zip(path, MultiReleaseJarPluginExtension::listAdd));
}
} }
public Map<String, ListProperty<String>> getPatchModules() { public MapProperty<String, List<String>> getPatchModules() {
return patchModules; return patchModules;
} }
} }

View File

@@ -2,25 +2,6 @@ plugins {
id "java-gradle-plugin" 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() evaluationDependsOnChildren()
configurations { configurations {

View File

@@ -18,7 +18,9 @@ dependencies {
} }
jar { jar {
bnd '''\ bundle {
Import-Package: !lombok, * 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 { dependencyResolutionManagement {
repositories { repositories {
maven { maven {
url = 'https://woggioni.net/mvn/' url = getProperty('gitea.maven.url')
content { content {
includeGroup 'com.lys' includeGroup 'com.lys'
} }
@@ -10,7 +10,6 @@ dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
catalog { catalog {
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.catalog.version') 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-api'
include 'osgi-app:osgi-simple-bootstrapper-application' include 'osgi-app:osgi-simple-bootstrapper-application'
include 'wildfly' include 'wildfly'
include 'sambal'
include 'graalvm'
include 'jdeps'

View File

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