Compare commits

...
40 Commits
Author SHA1 Message Date
woggioni c1e3e960b1 tmp 2026-06-05 09:27:32 +08:00
woggioni 85ca793893 removed "setup Gradle" build step
CI / build (push) Successful in 3m56s
2026-02-23 18:55:40 +08:00
woggioni 0ccffb0642 fixed default finalguard exclusion prefix
CI / build (push) Has been cancelled
2026-02-23 18:53:21 +08:00
woggioni 1fe7ce65f6 Excluded generated sources from finalguard plugin
CI / build (push) Successful in 5m58s
Also added optional list of exclusion paths
2026-02-19 22:32:14 +08:00
woggioni c5dcee554e updated finalguard to use plugin arguments instead of system properties 2026-02-18 08:31:09 +08:00
woggioni 91cf489630 updated build to Gradle 9 and JDK 25 2026-02-17 14:54:01 +08:00
woggioni b6c4cca2e7 added special handling for abstract method parameters in finalguard plugin
CI / build (push) Successful in 3m31s
2025-11-28 21:07:21 +08:00
woggioni a6e3d692a0 fixed finalguard when Gradle uses java compiler API directly without forking
CI / build (push) Successful in 3m50s
2025-11-28 17:05:42 +08:00
woggioni 8f5c21a03e temporarily downgraded Junit version to keep suporting Java 8
CI / build (push) Successful in 3m4s
2025-11-19 01:12:38 +08:00
woggioni 9dcadec69f added record support to finalguard plugin
CI / build (push) Failing after 1m18s
2025-11-19 00:50:23 +08:00
woggioni d78b8d443e Finalguard promoted to beta
CI / build (push) Successful in 3m39s
2025-11-18 20:33:59 +08:00
woggioni c7d96f3d83 added finalguard plugin in aplha state
CI / build (push) Successful in 3m17s
2025-11-18 12:47:57 +08:00
woggioni 3f6627465c added jdeps plugin
CI / build (push) Successful in 2m26s
2025-04-15 22:04:50 +08:00
woggioni 0d32fb5ba9 added tar distribution to jlink plugin
CI / build (push) Successful in 2m45s
2025-04-15 14:12:20 +08:00
woggioni 8d9861ae75 added toolchain support to NativeImageconfigurationTask
CI / build (push) Successful in 1m32s
2025-03-08 14:36:47 +08:00
woggioni 97df729677 added Gradle toolchain support to NativeImage task
CI / build (push) Successful in 1m39s
2025-03-03 17:57:57 +08:00
woggioni d580161d01 added nativeCompilerPath property to GraalVM NativeImage task
CI / build (push) Successful in 1m49s
2025-03-03 13:29:16 +08:00
woggioni 2b978ddf1c made NativeImageTask cacheable in native image plugin
CI / build (push) Successful in 1m27s
2025-02-28 09:26:50 +08:00
woggioni 0d5865e638 improved graalvm plugin
CI / build (push) Successful in 1m26s
2025-02-25 16:39:14 +08:00
woggioni 04d50e5a52 modernized JPMS check and dependency export plugins
CI / build (push) Successful in 1m57s
2025-02-08 00:48:20 +08:00
woggioni e0d8628188 added compressionLevel and bindServices properties to the jlink task
CI / build (push) Successful in 2m22s
2025-02-05 21:37:05 +08:00
woggioni 4565a626d6 fixed gitea build
CI / build (push) Successful in 1m48s
2025-01-24 16:47:26 +08:00
woggioni 99f1fd16ab added JPMS support to native image plugin
CI / build (push) Has been cancelled
2025-01-24 16:45:52 +08:00
woggioni 5c16f1bc13 updated Gradle to 8.12
CI / build (push) Successful in 1m32s
2025-01-09 16:25:59 +08:00
woggioni 0c3f08bfd5 made Sambal plugin compatible with Gradle configuration cache
CI / build (push) Successful in 2m12s
2024-12-29 22:09:39 +08:00
woggioni b6fa9f2948 fixed configuration cache issue in Sambal plugin
CI / build (push) Successful in 1m53s
2024-12-28 15:04:59 +08:00
woggioni d1af8ef942 fixed gitea build
CI / build (push) Successful in 2m32s
2024-12-08 21:58:22 +08:00
woggioni 6146868f37 update gradle version
CI / build (push) Waiting to run
2024-12-08 21:57:09 +08:00
woggioni 9c361d0419 fully migrated to gitea
CI / build (push) Successful in 38s
2024-04-04 07:04:27 +08:00
woggioni bffa4f1f56 migrated to gitea
CI / build (push) Successful in 38s
2024-04-04 06:12:23 +08:00
woggioni bc92b75136 fixed Java release version 2024-03-11 18:56:50 +08:00
woggioni 72bfcf4842 fixed bug with deploymentName property 2024-02-24 17:30:31 +08:00
woggioni 5304576900 added deployment name to Wildfly plugin 2024-02-22 06:05:48 +08:00
woggioni b3be3ca226 jlink and nativeImage not anymore included in assemble 2023-11-07 16:37:22 +08:00
woggioni f494dc6815 added documentation of NativeImage plugin 2023-10-23 16:53:40 +08:00
woggioni e1028249ae added Gradle toolchain support to NativeImageConfigurationTask 2023-10-20 20:58:57 +08:00
woggioni b1757a85be added jlink plugin 2023-10-06 15:08:42 +08:00
woggioni dcdeb2871a added sambal plugin and native image plugin 2023-10-05 21:55:54 +08:00
woggioni c8222ca9aa improved lombok plugin to support JPMS 2023-07-16 22:39:46 +08:00
woggioni 34810e9263 rewritten multi-release-jar plugin in Java 2023-07-09 20:16:00 +08:00
68 changed files with 3420 additions and 650 deletions
+14
View File
@@ -0,0 +1,14 @@
name: CI
on:
push:
branches: [ master ]
jobs:
build:
runs-on: woryzen
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Execute Gradle build
env:
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
run: ./gradlew build publish
+27 -17
View File
@@ -1,24 +1,36 @@
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(25)
} }
} }
tasks.named(JavaPlugin.JAR_TASK_NAME, Jar.class, {
manifest {
attributes.put(java.util.jar.Attributes.Name.SPECIFICATION_TITLE.toString(), subproject.getName());
attributes.put(java.util.jar.Attributes.Name.SPECIFICATION_VERSION.toString(), subproject.getVersion());
}
})
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
} }
if(subproject.path != ':finalguard:finalguard-javac-plugin') {
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
options.release = javaVersion options.release = javaVersion
options.compilerArgs << '-parameters' options.compilerArgs << '-parameters'
} }
}
pluginManager.withPlugin('groovy') { pluginManager.withPlugin('groovy') {
tasks.named("compileGroovy", GroovyCompile) { tasks.named("compileGroovy", GroovyCompile) {
@@ -27,12 +39,6 @@ subprojects { subproject ->
} }
repositories { repositories {
maven {
url = woggioniMavenRepositoryUrl
content {
includeGroup 'net.woggioni'
}
}
mavenCentral() mavenCentral()
} }
@@ -41,25 +47,29 @@ subprojects { subproject ->
add(conf, [group: "org.projectlombok", name: "lombok", version: catalog.versions.lombok.get()]) add(conf, [group: "org.projectlombok", name: "lombok", version: catalog.versions.lombok.get()])
} }
add("testImplementation", catalog.junit.jupiter.api) add("testImplementation", catalog.junit.jupiter.api)
add("testImplementation", catalog.junit.jupiter.params)
add("testRuntimeOnly", catalog.junit.jupiter.engine) add("testRuntimeOnly", catalog.junit.jupiter.engine)
add("testRuntimeOnly", catalog.junit.platform.launcher)
add("testImplementation", gradleTestKit()) add("testImplementation", gradleTestKit())
} }
tasks.named("test", Test) { tasks.withType(Test) {
useJUnitPlatform() useJUnitPlatform()
} }
}
childProjects.forEach { name, child ->
child.with {
apply plugin: 'maven-publish'
group = "net.woggioni.gradle"
publishing { publishing {
repositories { repositories {
maven { maven {
url = woggioniMavenRepositoryUrl name = "Gitea"
url = uri("https://gitea.woggioni.net/api/packages/woggioni/maven")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token ${System.getenv()["PUBLISHER_TOKEN"]}"
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
} }
@@ -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>() {
@@ -20,10 +20,13 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory; import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.ReportingBasePlugin;
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 +52,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 })
@@ -71,9 +76,8 @@ public class ExportDependencies extends DefaultTask {
@Getter(onMethod_ = { @Input }) @Getter(onMethod_ = { @Input })
private final Property<Boolean> showArtifacts; private final Property<Boolean> showArtifacts;
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,11 +100,11 @@ public class ExportDependencies extends DefaultTask {
@Inject @Inject
public ExportDependencies(ObjectFactory objects) { public ExportDependencies(ObjectFactory objects) {
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); setGroup(DEPENDENCY_EXPORT_GROUP);
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 = final JavaPluginExtension javaPluginExtension = getProject().getExtensions().findByType(JavaPluginExtension.class);
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "dependencies.dot")); final Provider<RegularFile> defaultOutputFileProvider = javaPluginExtension.getDocsDir().file("dependencies.dot");
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider)); outputFile = objects.fileProperty().convention(defaultOutputFileProvider);
showArtifacts = objects.property(Boolean.class).convention(false); showArtifacts = objects.property(Boolean.class).convention(false);
} }
@@ -7,32 +7,40 @@ import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory; import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.JavaPluginExtension;
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,13 +53,12 @@ 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);
} }
private final JavaPluginConvention javaPluginConvention;
@Option(option = "output", description = "Set the output file name") @Option(option = "output", description = "Set the output file name")
public void setOutputCli(String outputFile) { public void setOutputCli(String outputFile) {
Provider<File> fileProvider = getProject().provider(() -> new File(outputFile)); Provider<File> fileProvider = getProject().provider(() -> new File(outputFile));
@@ -64,33 +71,45 @@ 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);
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 = final JavaPluginExtension javaPluginExtension = getProject().getExtensions().findByType(JavaPluginExtension.class);
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "renderedDependencies")); final Provider<RegularFile> defaultOutputFileProvider = javaPluginExtension.getDocsDir().file("renderedDependencies");
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider)); outputFile = objects.fileProperty().convention(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(
outputFile
.map(RegularFile::getAsFile) .map(RegularFile::getAsFile)
.map(File::toPath) .map(File::toPath)
.get(); )
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");
@@ -4,7 +4,7 @@ plugins {
} }
repositories { repositories {
jcenter() mavenCentral()
mavenLocal() mavenLocal()
} }
+12
View File
@@ -0,0 +1,12 @@
plugins {
id 'java-gradle-plugin'
}
gradlePlugin {
plugins {
create("FinalGuardPlugin") {
id = 'net.woggioni.gradle.finalguard'
implementationClass = "net.woggioni.gradle.finalguard.FinalGuardPlugin"
}
}
}
@@ -0,0 +1,51 @@
plugins {
id 'java-library'
}
group = "net.woggioni.finalguard"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
java {
sourceCompatibility(JavaVersion.VERSION_1_8.toString())
targetCompatibility(JavaVersion.VERSION_1_8.toString())
modularity.inferModulePath = false
}
tasks.named(org.gradle.api.plugins.JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile.class) {
options.compilerArgs << '-parameters'
}
configurations {
testCompileClasspath {
attributes {
attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
testRuntimeClasspath {
attributes {
attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
}
test {
def testCompilationClassPath = sourceSets["main"].output.classesDirs.files +
sourceSets["main"].runtimeClasspath.files +
sourceSets["test"].resources.srcDirs
systemProperty("test.compilation.classpath",
String.join(File.pathSeparator, testCompilationClassPath.collect { it.toString() }))
}
publishing {
publications {
maven(MavenPublication) {
from(components["java"])
}
}
}
@@ -0,0 +1,311 @@
package net.woggioni.finalguard;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.tools.Diagnostic;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class FinalGuardPlugin implements Plugin {
public static final String DEFAULT_LEVEL_KEY = "default.level";
public static final String EXCLUDE_KEY = "exclude";
enum VariableType {
LOCAL_VAR("local.variable.level"),
METHOD_PARAM("method.param.level"),
LOOP_PARAM("for.param.level"),
TRY_WITH_PARAM("try.param.level"),
CATCH_PARAM("catch.param.level"),
LAMBDA_PARAM("lambda.param.level"),
ABSTRACT_METHOD_PARAM("abstract.method.param.level");
private final String argKey;
VariableType(final String argKey) {
this.argKey = argKey;
}
public String getArgKey() {
return argKey;
}
public String getMessage(final String variableName) {
switch (this) {
case LOCAL_VAR:
return "Local variable '" + variableName + "' is never reassigned, so it should be declared final";
case METHOD_PARAM:
return "Method parameter '" + variableName + "' is never reassigned, so it should be declared final";
case LOOP_PARAM:
return "Loop parameter '" + variableName + "' is never reassigned, so it should be declared final";
case TRY_WITH_PARAM:
return "Try-with-resources parameter '" + variableName + "' is never reassigned, so it should be declared final";
case CATCH_PARAM:
return "Catch parameter '" + variableName + "' is never reassigned, so it should be declared final";
case LAMBDA_PARAM:
return "Lambda parameter '" + variableName + "' is never reassigned, so it should be declared final";
case ABSTRACT_METHOD_PARAM:
return "Abstract method parameter '" + variableName + "' is never reassigned, so it should be declared final";
default:
throw new UnsupportedOperationException();
}
}
}
private static class VariableInfo {
final VariableTree variableTree;
final VariableType variableType;
VariableInfo(VariableTree variableTree, VariableType variableType) {
this.variableTree = variableTree;
this.variableType = variableType;
}
}
private static final class Configuration {
private final Map<VariableType, Diagnostic.Kind> levels;
private final List<String> excludedPaths;
public Configuration(final String... args) {
final Map<String, String> props = new HashMap<>();
final List<String> excluded = new ArrayList<>();
for (final String arg : args) {
final String[] parts = arg.split("=", 2);
if (parts.length == 2) {
if (EXCLUDE_KEY.equals(parts[0])) {
final Path path = Paths.get(parts[1]);
excluded.add(path.toAbsolutePath().toString());
} else {
props.put(parts[0], parts[1]);
}
}
}
this.excludedPaths = Collections.unmodifiableList(excluded);
final Diagnostic.Kind defaultLevel =
Optional.ofNullable(props.get(DEFAULT_LEVEL_KEY)).map(Diagnostic.Kind::valueOf).orElse(null);
this.levels = Arrays.stream(VariableType.values()).map(vt -> {
final Diagnostic.Kind level = Optional.ofNullable(props.get(vt.getArgKey())).map(Diagnostic.Kind::valueOf).orElse(defaultLevel);
if (level != null) {
return new AbstractMap.SimpleEntry<>(vt, level);
} else {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public boolean isExcluded(final String sourcePath) {
for (final String excludedPath : excludedPaths) {
if (sourcePath.startsWith(excludedPath)) {
return true;
}
}
return false;
}
}
private static boolean isJava17OrHigher() {
return System.getProperty("java.version").compareTo("17") >= 0;
}
private static final boolean isJava17OrHigher = isJava17OrHigher();
@Override
public String getName() {
return getClass().getName();
}
@Override
public void init(JavacTask task, String... args) {
final Configuration configuration = new Configuration(args);
task.addTaskListener(new TaskListener() {
@Override
public void started(TaskEvent e) {
}
@Override
public void finished(TaskEvent e) {
if (e.getKind() == TaskEvent.Kind.ANALYZE) {
analyzeFinalVariables(e.getCompilationUnit(), task, e.getTypeElement(), configuration);
}
}
});
}
private void analyzeFinalVariables(CompilationUnitTree compilationUnit, JavacTask task, Element typeElement, Configuration configuration) {
final String sourcePath = compilationUnit.getSourceFile().toUri().getPath();
if (sourcePath != null && configuration.isExcluded(sourcePath)) {
return;
}
FinalVariableAnalyzer analyzer = new FinalVariableAnalyzer(compilationUnit, task, configuration);
TreePath path = Trees.instance(task).getPath(typeElement);
if (path != null) {
analyzer.scan(path, null);
}
}
private static class FinalVariableAnalyzer extends TreePathScanner<Void, Void> {
private final Configuration configuration;
private final CompilationUnitTree compilationUnit;
private final Trees trees;
private final Map<String, VariableInfo> variableInfoMap = new LinkedHashMap<>();
private final Set<String> reassignedVariables = new HashSet<>();
public FinalVariableAnalyzer(CompilationUnitTree compilationUnit, JavacTask task, Configuration configuration) {
this.configuration = configuration;
this.compilationUnit = compilationUnit;
this.trees = Trees.instance(task);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
variableInfoMap.clear();
reassignedVariables.clear();
super.visitMethod(node, p);
// Check for variables that could be final
checkForFinalCandidates();
return null;
}
@Override
public Void visitVariable(VariableTree node, Void p) {
final String varName = node.getName().toString();
final TreePath currentPath = getCurrentPath();
final TreePath parentPath = currentPath.getParentPath();
final Tree parent = parentPath.getLeaf();
final VariableType type;
if (parent instanceof LambdaExpressionTree) {
type = VariableType.LAMBDA_PARAM;
} else if (parent instanceof ForLoopTree || parent instanceof EnhancedForLoopTree) {
type = VariableType.LOOP_PARAM;
} else if (parent instanceof CatchTree) {
type = VariableType.CATCH_PARAM;
} else if (parent instanceof TryTree) {
type = VariableType.TRY_WITH_PARAM;
} else if (parent instanceof MethodTree) {
if (isAbstractMethodParameter(node, (MethodTree) parent)) {
type = VariableType.ABSTRACT_METHOD_PARAM;
} else {
type = VariableType.METHOD_PARAM;
if (isJava17OrHigher && ((MethodTree) parent).getName().contentEquals("<init>")) {
final TreePath grandParentPath = parentPath.getParentPath();
if (grandParentPath.getLeaf().getKind() == Tree.Kind.RECORD) {
return super.visitVariable(node, p);
}
}
}
} else if (parent instanceof BlockTree) {
type = VariableType.LOCAL_VAR;
} else {
type = VariableType.LOCAL_VAR;
}
variableInfoMap.put(varName, new VariableInfo(node, type));
return super.visitVariable(node, p);
}
private boolean isAbstractMethodParameter(VariableTree variableTree, MethodTree methodTree) {
// Get the element for the method
Element methodElement = trees.getElement(getCurrentPath().getParentPath());
if (methodElement instanceof ExecutableElement) {
ExecutableElement executableElement = (ExecutableElement) methodElement;
return executableElement.getModifiers().contains(Modifier.ABSTRACT);
}
return false;
}
@Override
public Void visitAssignment(AssignmentTree node, Void p) {
if (node.getVariable() instanceof IdentifierTree) {
final IdentifierTree ident = (IdentifierTree) node.getVariable();
reassignedVariables.add(ident.getName().toString());
}
return super.visitAssignment(node, p);
}
@Override
public Void visitUnary(UnaryTree node, Void p) {
if ((node.getKind() == Tree.Kind.PREFIX_INCREMENT ||
node.getKind() == Tree.Kind.PREFIX_DECREMENT ||
node.getKind() == Tree.Kind.POSTFIX_INCREMENT ||
node.getKind() == Tree.Kind.POSTFIX_DECREMENT) &&
node.getExpression() instanceof IdentifierTree) {
IdentifierTree ident = (IdentifierTree) node.getExpression();
reassignedVariables.add(ident.getName().toString());
}
return super.visitUnary(node, p);
}
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
if (node.getVariable() instanceof IdentifierTree) {
final IdentifierTree ident = (IdentifierTree) node.getVariable();
reassignedVariables.add(ident.getName().toString());
}
return super.visitCompoundAssignment(node, p);
}
private void checkForFinalCandidates() {
for (final Map.Entry<String, VariableInfo> entry : variableInfoMap.entrySet()) {
final String varName = entry.getKey();
final VariableInfo info = entry.getValue();
Diagnostic.Kind level = configuration.levels.get(info.variableType);
// Skip if level is not configured
if (level == null) {
continue;
}
// Skip if already final
if (isFinal(info.variableTree)) {
continue;
}
// Skip if reassigned
if (reassignedVariables.contains(varName)) {
continue;
}
trees.printMessage(level,
info.variableType.getMessage(varName),
info.variableTree,
compilationUnit);
}
}
private static boolean isFinal(VariableTree variableTree) {
final Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
return modifiers.contains(Modifier.FINAL);
}
}
}
@@ -0,0 +1 @@
net.woggioni.finalguard.FinalGuardPlugin
@@ -0,0 +1,221 @@
package net.woggioni.finalguard;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import javax.tools.*;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.*;
public class PluginTest {
private static class ClassFile extends SimpleJavaFileObject {
private ByteArrayOutputStream out;
public ClassFile(URI uri) {
super(uri, Kind.CLASS);
}
@Override
public OutputStream openOutputStream() {
return out = new ByteArrayOutputStream();
}
public byte[] getCompiledBinaries() {
return out.toByteArray();
}
}
private static class SourceFile extends SimpleJavaFileObject {
public SourceFile(URI uri) {
super(uri, Kind.SOURCE);
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
Reader r = new InputStreamReader(uri.toURL().openStream());
StringBuilder sb = new StringBuilder();
char[] buffer = new char[0x1000];
while (true) {
int read = r.read(buffer);
if (read < 0) break;
sb.append(buffer, 0, read);
}
return sb.toString();
}
}
private static class FileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final List<ClassFile> compiled = new ArrayList<>();
protected FileManager(StandardJavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className,
JavaFileObject.Kind kind,
FileObject sibling) {
ClassFile result = new ClassFile(URI.create("string://" + className));
compiled.add(result);
return result;
}
public List<ClassFile> getCompiled() {
return compiled;
}
}
private Optional<Iterable<Diagnostic<? extends JavaFileObject>>> compile(Iterable<URI> sources) {
return compile(sources, "");
}
private Optional<Iterable<Diagnostic<? extends JavaFileObject>>> compile(Iterable<URI> sources, String extraPluginArgs) {
StringWriter output = new StringWriter();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileManager fileManager =
new FileManager(compiler.getStandardFileManager(null, null, null));
List<JavaFileObject> compilationUnits = StreamSupport.stream(sources.spliterator(), false)
.map(SourceFile::new).collect(Collectors.toList());
String pluginArg = "-Xplugin:" + FinalGuardPlugin.class.getName() + " " + FinalGuardPlugin.DEFAULT_LEVEL_KEY + "=ERROR";
if (!extraPluginArgs.isEmpty()) {
pluginArg += " " + extraPluginArgs;
}
List<String> arguments = Arrays.asList(
"-classpath", System.getProperty("test.compilation.classpath"),
pluginArg
);
final ArrayList<Diagnostic<? extends JavaFileObject>> compilerMessages = new ArrayList<>();
JavaCompiler.CompilationTask task = compiler.getTask(
output,
fileManager,
compilerMessages::add,
arguments,
null,
compilationUnits
);
if (task.call()) return Optional.empty();
else return Optional.of(compilerMessages);
}
private enum CompilationResult {
SUCCESS, FAILURE
}
private static class TestCaseProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
String prefix = "net/woggioni/finalguard/test/";
return Stream.of(
Arguments.of(prefix + "TestCase1.java", Arrays.asList(
LOOP_PARAM.getMessage("item"),
LAMBDA_PARAM.getMessage("s"),
LOCAL_VAR.getMessage("f"),
LOCAL_VAR.getMessage("localVar"),
LOCAL_VAR.getMessage("loopVar"),
LOOP_PARAM.getMessage("i"),
TRY_WITH_PARAM.getMessage("is"),
METHOD_PARAM.getMessage("param1"),
CATCH_PARAM.getMessage("ioe"),
LOCAL_VAR.getMessage("anotherVar"),
METHOD_PARAM.getMessage("param2")
)),
Arguments.of(prefix + "TestCase2.java", Collections.emptyList()),
Arguments.of(prefix + "TestCase3.java",
Arrays.asList(LOCAL_VAR.getMessage("n"))),
Arguments.of(prefix + "TestCase4.java",
Arrays.asList(LOOP_PARAM.getMessage("i"))),
Arguments.of(prefix + "TestCase5.java", Arrays.asList(LOCAL_VAR.getMessage("loopVar"))),
Arguments.of(prefix + "TestCase6.java", Arrays.asList(LOOP_PARAM.getMessage("item"))),
Arguments.of(prefix + "TestCase7.java", Arrays.asList(CATCH_PARAM.getMessage("re"))),
Arguments.of(prefix + "TestCase8.java", Arrays.asList(TRY_WITH_PARAM.getMessage("is"))),
Arguments.of(prefix + "TestCase9.java", Arrays.asList(LAMBDA_PARAM.getMessage("s"))),
Arguments.of(prefix + "TestCase10.java", Arrays.asList(METHOD_PARAM.getMessage("n"))),
Arguments.of(prefix + "TestCase11.java", Arrays.asList(
ABSTRACT_METHOD_PARAM.getMessage("n"),
LOCAL_VAR.getMessage("result"),
LOCAL_VAR.getMessage("size"),
METHOD_PARAM.getMessage("t1s")
)),
Arguments.of(prefix + "TestCase12.java", Collections.emptyList()),
Arguments.of(prefix + "TestCase13.java", Arrays.asList(ABSTRACT_METHOD_PARAM.getMessage("x"), ABSTRACT_METHOD_PARAM.getMessage("y"))),
Arguments.of(prefix + "TestCase14.java", Arrays.asList(ABSTRACT_METHOD_PARAM.getMessage("x"), ABSTRACT_METHOD_PARAM.getMessage("y"))),
Arguments.of(prefix + "TestCase15.java",
Arrays.asList(
LOCAL_VAR.getMessage("a"),
METHOD_PARAM.getMessage("source")))
);
}
}
@ParameterizedTest(name = "{0}")
@ArgumentsSource(TestCaseProvider.class)
public void test(String sourceFilePath, List<String> expectedErrorMessages) {
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> result;
try {
ClassLoader cl = getClass().getClassLoader();
result = compile(Collections.singletonList(cl.getResource(sourceFilePath).toURI()));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
result.ifPresent(diagnostics -> {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
System.err.printf("%s:%s %s\n",
diagnostic.getSource().getName(),
diagnostic.getLineNumber(),
diagnostic.getMessage(Locale.getDefault()));
}
});
if (expectedErrorMessages.isEmpty()) {
Assertions.assertFalse(result.isPresent(), "Compilation was expected to succeed but it has failed");
} else {
final List<String> compilationErrors = result
.map(it -> StreamSupport.stream(it.spliterator(), false))
.orElse(Stream.empty())
.map(it -> it.getMessage(Locale.ENGLISH))
.collect(Collectors.toList());
for (String expectedErrorMessage : expectedErrorMessages) {
int index = compilationErrors.indexOf(expectedErrorMessage);
Assertions.assertTrue(index >= 0, String.format("Expected compilation error `%s` not found in output", expectedErrorMessage));
compilationErrors.remove(index);
}
Assertions.assertTrue(compilationErrors.isEmpty(), "Unexpected compilation errors found in the output");
}
}
@Test
public void testExcludedSourceIsSkipped() throws Exception {
// TestCase3 normally produces an error (local var 'n' not final).
// When we exclude the directory containing TestCase3, it should compile without errors.
ClassLoader cl = getClass().getClassLoader();
URI sourceUri = cl.getResource("net/woggioni/finalguard/test/TestCase3.java").toURI();
String sourcePath = new File(sourceUri).getParent();
// Compile WITH exclude — should succeed (no errors)
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> resultWithExclude =
compile(Collections.singletonList(sourceUri), FinalGuardPlugin.EXCLUDE_KEY + "=" + sourcePath);
Assertions.assertFalse(resultWithExclude.isPresent(),
"Compilation should succeed when source directory is excluded");
// Compile WITHOUT exclude — should fail (TestCase3 has errors)
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> resultWithoutExclude =
compile(Collections.singletonList(sourceUri));
Assertions.assertTrue(resultWithoutExclude.isPresent(),
"Compilation should fail when source directory is NOT excluded");
}
}
@@ -0,0 +1,40 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.function.Function;
public class TestCase1 {
public void testMethod(String param1, String param2) { // Warning: param1 could be final
String localVar = "hello"; // Warning: localVar could be final
String reassignedVar = "initial"; // No warning (reassigned below)
reassignedVar = "changed";
param2 = "modified"; // No warning for param2 (reassigned)
for (int i = 0; i < 10; ) { // Warning: i could be final
String loopVar = "constant"; // Warning: loopVar could be final
}
// Enhanced for loop - no warning for loop variable
for (String item : Arrays.asList("a", "b")) {
// item is effectively final in each iteration
}
try (InputStream is = Files.newInputStream(Path.of("/tmp/file.txt"))) {
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
Function<String, String> f = s -> s.toLowerCase();
}
public void finalMethod(final String param1, String param2) { // Warning only for param2
final String localVar = "hello"; // No warning (already final)
String anotherVar = "world"; // Warning: anotherVar could be final
}
}
@@ -0,0 +1,5 @@
public class TestCase10 {
public void testMethod(int n) {
}
}
@@ -0,0 +1,15 @@
import java.util.Arrays;
public abstract class TestCase11<T1> {
abstract T1 get(int n);
public T1[] toArray(T1[] t1s) {
int size = 42;
T1[] result = Arrays.copyOf(t1s, size);
for (int i = 0; i < size; i++) {
result[i] = get(i);
}
return result;
}
}
@@ -0,0 +1,2 @@
public record TestCase12<T, U>(double x, double y) {
}
@@ -0,0 +1,3 @@
public interface TestCase13 {
void foo(double x, double y);
}
@@ -0,0 +1,3 @@
public abstract class TestCase14 {
abstract public void foo(double x, double y);
}
@@ -0,0 +1,24 @@
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class TestCase15 {
public static void foo() {
final InputStream source = null;
final InputStream is = new FilterInputStream(source) {
@Override
public int read() throws IOException {
int a = 5;
return a;
}
};
}
static class Bar extends FilterInputStream {
public Bar(InputStream source) {
super(source);
}
};
}
@@ -0,0 +1,18 @@
public class TestCase2 {
public void testMethod(int a, int b, int c, int d, int e, int f, int g, int h, int j, int k, int l, int m, int n) {
a++;
b--;
++c;
--d;
e |= 2;
f &= 1;
g <<= 1;
h >>= 1;
j ^= 1;
k += 1;
l -= 1;
m *= 1;
n /= 1;
}
}
@@ -0,0 +1,6 @@
public class TestCase3 {
public void testMethod() {
int n = 5; // Error: n could be final
}
}
@@ -0,0 +1,7 @@
public class TestCase4 {
public void testMethod() {
for (int i = 0; i < 10; ) { // Error: i could be final
}
}
}
@@ -0,0 +1,8 @@
public class TestCase5 {
public void testMethod() {
for (int i = 0; i < 10; i++) {
String loopVar = "constant"; // Error: loopVar should be final
}
}
}
@@ -0,0 +1,9 @@
import java.util.Arrays;
public class TestCase6 {
public void testMethod() {
for (String item : Arrays.asList("a", "b")) { // Error: item should be final
}
}
}
@@ -0,0 +1,10 @@
public class TestCase7 {
public void testMethod() {
try {
} catch (RuntimeException re) { // Error: re should be final
}
}
}
@@ -0,0 +1,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class TestCase8 {
public void testMethod() throws IOException {
try (InputStream is = Files.newInputStream(Path.of("some-path"))) { // Error: is could be final
}
}
}
@@ -0,0 +1,8 @@
import java.util.function.Function;
public class TestCase9 {
public void testMethod() {
final Function<String, String> d = s -> s.toLowerCase();
}
}
@@ -0,0 +1,29 @@
package net.woggioni.gradle.finalguard;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import javax.tools.Diagnostic;
public interface FinalGuardExtension {
Property<Diagnostic.Kind> getDefaultLevel();
Property<Diagnostic.Kind> getLocalVariableLevel();
Property<Diagnostic.Kind> getLambdaParameterLevel();
Property<Diagnostic.Kind> getForLoopParameterLevel();
Property<Diagnostic.Kind> getTryWithResourceLevel();
Property<Diagnostic.Kind> getMethodParameterLevel();
Property<Diagnostic.Kind> getAbstractMethodParameterLevel();
Property<Diagnostic.Kind> getCatchParameterLevel();
Property<Boolean> getSkipGeneratedSources();
ListProperty<String> getExcludedPrefixes();
}
@@ -0,0 +1,92 @@
package net.woggioni.gradle.finalguard;
import lombok.SneakyThrows;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.compile.CompileOptions;
import org.gradle.api.tasks.compile.JavaCompile;
import javax.tools.Diagnostic;
import java.io.File;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public class FinalGuardPlugin implements Plugin<Project> {
private static final String FINALGUARD_PLUGIN_CONFIGURATION = "finalguard_plugin";
private static final String JAVAC_PLUGIN_NAME = "net.woggioni.finalguard.FinalGuardPlugin";
private static final String EXCLUDE_KEY = "exclude";
@Override
public void apply(final Project project) {
final ExtensionContainer extensionContainer = project.getExtensions();
final TaskContainer tasks = project.getTasks();
final ObjectFactory objects = project.getObjects();
Configuration javacPluginConfiguration = project.getConfigurations().create(FINALGUARD_PLUGIN_CONFIGURATION);
javacPluginConfiguration.withDependencies(new Action<DependencySet>() {
@Override
@SneakyThrows
public void execute(DependencySet dependencies) {
final Class<?> cls = getClass();
final String resourceName = cls.getName().replace('.', '/') + ".class";
final URL classUrl = cls.getClassLoader().getResource(resourceName);
if (classUrl.getProtocol().startsWith("jar")) {
final String path = classUrl.toString();
String manifestPath = path.substring(0, path.lastIndexOf("!") + 1) +
"/META-INF/MANIFEST.MF";
final Manifest manifest = new Manifest(new URL(manifestPath).openStream());
final Attributes attr = manifest.getMainAttributes();
final String version = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
dependencies.add(project.getDependencies().create("net.woggioni.finalguard:finalguard-javac-plugin:" + version));
}
}
});
final FinalGuardExtension finalGuardExtension = objects.newInstance(FinalGuardExtension.class);
finalGuardExtension.getSkipGeneratedSources().convention(true);
extensionContainer.add("finalguard", finalGuardExtension);
tasks.withType(JavaCompile.class, javaCompileTask -> {
javaCompileTask.doFirst(t -> {
final CompileOptions options = javaCompileTask.getOptions();
options.setAnnotationProcessorPath(options.getAnnotationProcessorPath().plus(javacPluginConfiguration));
final StringBuilder xpluginArg = new StringBuilder("-Xplugin:").append(JAVAC_PLUGIN_NAME);
appendOption(xpluginArg, "default.level", finalGuardExtension.getDefaultLevel());
appendOption(xpluginArg, "local.variable.level", finalGuardExtension.getLocalVariableLevel());
appendOption(xpluginArg, "method.param.level", finalGuardExtension.getMethodParameterLevel());
appendOption(xpluginArg, "abstract.method.param.level", finalGuardExtension.getAbstractMethodParameterLevel());
appendOption(xpluginArg, "for.param.level", finalGuardExtension.getForLoopParameterLevel());
appendOption(xpluginArg, "try.param.level", finalGuardExtension.getTryWithResourceLevel());
appendOption(xpluginArg, "catch.param.level", finalGuardExtension.getCatchParameterLevel());
appendOption(xpluginArg, "lambda.param.level", finalGuardExtension.getLambdaParameterLevel());
if (finalGuardExtension.getSkipGeneratedSources().getOrElse(true)) {
final File generatedSourceDir = project.getLayout().getBuildDirectory().getAsFile().get();
if (generatedSourceDir != null) {
appendOption(xpluginArg, EXCLUDE_KEY, generatedSourceDir.getAbsolutePath());
}
}
for(final String excludedPrefix : finalGuardExtension.getExcludedPrefixes().get()) {
appendOption(xpluginArg, EXCLUDE_KEY, excludedPrefix);
}
options.getCompilerArgs().add(xpluginArg.toString());
});
});
}
private static void appendOption(StringBuilder sb, String key, String value) {
sb.append(' ').append(key).append('=').append(value);
}
private static void appendOption(StringBuilder sb, String key, Property<Diagnostic.Kind> property) {
if (property.isPresent()) {
appendOption(sb, key, property.get().toString());
}
}
}
+132
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
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"
}
}
}
@@ -0,0 +1,5 @@
package net.woggioni.gradle.graalvm;
public class Constants {
public static final String GRAALVM_TASK_GROUP = "graalvm";
}
@@ -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;
}
}
@@ -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);
});
}
}
@@ -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();
}
}
@@ -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;
}
});
}
}
@@ -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();
}
@@ -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);
}
});
}
}
@@ -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);
}
}
@@ -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);
}
});
}
}
+4 -14
View File
@@ -1,15 +1,5 @@
woggioniMavenRepositoryUrl=https://mvn.woggioni.net/ lys.catalog.version=2025.12.27
version.myGradlePlugins=2026.02.23
version.gradle=9.3.1
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.
+3 -1
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-9.3.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+32 -18
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -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
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -133,22 +132,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
@@ -165,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -193,18 +198,27 @@ 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, 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" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
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.
Vendored
+22 -18
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,32 +59,33 @@ 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
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%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
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"
}
}
}
@@ -0,0 +1,5 @@
package net.woggioni.gradle.jdeps;
public class Constants {
public static final String JDEPS_TASK_GROUP = "jdeps";
}
@@ -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)));
});
}
}
@@ -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();
}
}
+6
View File
@@ -2,6 +2,12 @@ plugins {
id 'groovy-gradle-plugin' id 'groovy-gradle-plugin'
} }
java {
sourceCompatibility(JavaVersion.VERSION_1_8.toString())
targetCompatibility(JavaVersion.VERSION_1_8.toString())
modularity.inferModulePath = false
}
gradlePlugin { gradlePlugin {
plugins { plugins {
create("JPMSCheckPlugin") { create("JPMSCheckPlugin") {
@@ -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")
}
}
task.doLast {
Stream<Project> projects = Stream.of(project)
if(recursive) {
projects = Stream.concat(projects, project.subprojects.stream())
}
Set<CheckResult> results = projects.flatMap {
Configuration requestedConfiguration = (project.configurations.<Configuration>find { Configuration cfg ->
cfg.canBeResolved && cfg.name == cfgName
} ?: {
def resolvableConfigurations = "[" + project.configurations
.grep { Configuration cfg -> cfg.canBeResolved }
.collect { "'${it.name}'" }
.join(",") + "]"
throw new GradleException("Configuration '$cfgName' doesn't exist or cannot be resolved, " +
"resolvable configurations in this project are " + resolvableConfigurations)
}) as Configuration
computeResults(requestedConfiguration.incoming.artifacts.artifacts.stream())
}.collect(Collectors.toSet())
Files.createDirectories(outputFile.parent)
Files.newBufferedWriter(outputFile).withWriter {
Stream<CheckResult> resultStream = results.stream().sorted(Comparator.<CheckResult, String>comparing { CheckResult res ->
res.dep.id.componentIdentifier.displayName
})
switch(outputFormat) {
case "html":
createHtmlReport(project, resultStream, it)
break
case "json":
createJsonReport(resultStream, it)
break
default:
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
}
}
} }
result
}))
} }
} }
} }
@@ -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")
}
}
}
}
@@ -0,0 +1,5 @@
package net.woggioni.gradle.jpms.check
enum OutputFormat {
html, json
}
@@ -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());
SourceSet sourceSet = getSourceSet().get();
Boolean inferModulePath = getInferModulePath().get();
FileCollection classpath = javaModuleDetector.inferClasspath(inferModulePath, sourceSet.getCompileClasspath());
if(!classpath.isEmpty()) {
args.add("-c"); args.add("-c");
args.add(classpath); args.add(classpath.getAsPath());
for(File sourceDir : sourceDirs) args.add(sourceDir.getPath()); }
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();
} }
} }
@@ -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;
} }
@@ -6,7 +6,7 @@ import org.gradle.api.Project;
import org.gradle.api.Task; 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.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,20 +27,20 @@ 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 -> { }
SourceSetContainer sourceSetContainer = project.getExtensions().findByType(JavaPluginExtension.class).getSourceSets(); JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
Provider<Map<String, String>> dependencyNotationProvider = project.provider(() -> { SourceSetContainer sourceSetContainer = javaPluginExtension.getSourceSets();
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");
@@ -69,21 +69,18 @@ public class LombokPlugin implements Plugin<Project> {
String delombokTaskName = "delombok" + ss.getName().substring(0, 1).toUpperCase() + ss.getName().substring(1); String delombokTaskName = "delombok" + ss.getName().substring(0, 1).toUpperCase() + ss.getName().substring(1);
File outputDir = new File(new File(project.getBuildDir(), "delombok"), ss.getName()); File outputDir = new File(new File(project.getBuildDir(), "delombok"), ss.getName());
Javadoc javadoc = (Javadoc) javadocTask; Javadoc javadoc = (Javadoc) javadocTask;
TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName, TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName, Delombok.class, (delombok -> {
Delombok.class, delombok.getSourceSet().set(ss);
lombokConfiguration.getSingleFile(), delombok.getOutputDir().set(outputDir);
outputDir, delombok.getLombokJar().set(lombokConfiguration);
ss.getJava().getSrcDirs(), // Disabled for now due to https://github.com/projectlombok/lombok/issues/2829
ss.getCompileClasspath().getAsPath() //delombok.getInferModulePath().set(javaPluginExtension.getModularity().getInferModulePath());
); delombok.getInferModulePath().set(false);
delombokTaskProvider.configure(delombokTask -> { }));
delombokTask.getInputs().files(ss.getAllSource().getSourceDirectories());
});
javadoc.setSource(outputDir); javadoc.setSource(outputDir);
javadoc.getInputs().files(delombokTaskProvider); javadoc.getInputs().files(delombokTaskProvider);
} }
} }
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
JavaToolchainSpec toolchain = javaPluginExtension.getToolchain(); JavaToolchainSpec toolchain = javaPluginExtension.getToolchain();
if(toolchain.getLanguageVersion().isPresent()) { if(toolchain.getLanguageVersion().isPresent()) {
project.afterEvaluate((Project pro) -> { project.afterEvaluate((Project pro) -> {
@@ -98,6 +95,5 @@ public class LombokPlugin implements Plugin<Project> {
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit"); t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
}); });
} }
});
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id 'maven-publish' id 'maven-publish'
id 'groovy-gradle-plugin' id 'java-gradle-plugin'
} }
gradlePlugin { gradlePlugin {
@@ -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())
}
}
}
}
}
@@ -0,0 +1,217 @@
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.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());
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());
});
});
}
}
}
@@ -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;
} }
} }
-19
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 {
@@ -18,7 +18,9 @@ dependencies {
} }
jar { jar {
bundle {
bnd '''\ bnd '''\
Import-Package: !lombok, * Import-Package: !lombok, *
''' '''
}
} }
+16
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"
}
}
}
@@ -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);
});
});
});
});
}
}
@@ -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);
}
}
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
+6 -2
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,8 @@ 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'
include 'finalguard'
include 'finalguard:finalguard-javac-plugin'
@@ -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")
); );
} }
} }