Compare commits
40
Commits
cf91e7a28b
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1e3e960b1
|
||
|
|
85ca793893
|
||
|
|
0ccffb0642
|
||
|
|
1fe7ce65f6
|
||
|
|
c5dcee554e
|
||
|
|
91cf489630
|
||
|
|
b6c4cca2e7
|
||
|
|
a6e3d692a0
|
||
|
|
8f5c21a03e
|
||
|
|
9dcadec69f
|
||
|
|
d78b8d443e
|
||
|
|
c7d96f3d83
|
||
|
|
3f6627465c
|
||
|
|
0d32fb5ba9
|
||
|
|
8d9861ae75
|
||
|
|
97df729677
|
||
|
|
d580161d01
|
||
|
|
2b978ddf1c
|
||
|
|
0d5865e638
|
||
|
|
04d50e5a52
|
||
|
|
e0d8628188
|
||
|
|
4565a626d6
|
||
|
|
99f1fd16ab
|
||
|
|
5c16f1bc13
|
||
|
|
0c3f08bfd5
|
||
|
|
b6fa9f2948
|
||
|
|
d1af8ef942
|
||
|
|
6146868f37
|
||
|
|
9c361d0419
|
||
|
|
bffa4f1f56
|
||
|
|
bc92b75136
|
||
|
|
72bfcf4842
|
||
|
|
5304576900
|
||
|
|
b3be3ca226
|
||
|
|
f494dc6815
|
||
|
|
e1028249ae
|
||
|
|
b1757a85be
|
||
|
|
dcdeb2871a
|
||
|
|
c8222ca9aa
|
||
|
|
34810e9263
|
@@ -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
@@ -1,24 +1,36 @@
|
||||
subprojects { subproject ->
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
group = "net.woggioni.gradle"
|
||||
version = getProperty('version.myGradlePlugins')
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
languageVersion = JavaLanguageVersion.of(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
|
||||
if(subproject.path == ':osgi-app') {
|
||||
if(subproject.path == ':osgi-app' || subproject.path == ':multi-release-jar') {
|
||||
javaVersion = 11
|
||||
} else {
|
||||
javaVersion = 8
|
||||
}
|
||||
|
||||
if(subproject.path != ':finalguard:finalguard-javac-plugin') {
|
||||
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||
options.release = javaVersion
|
||||
options.compilerArgs << '-parameters'
|
||||
}
|
||||
}
|
||||
|
||||
pluginManager.withPlugin('groovy') {
|
||||
tasks.named("compileGroovy", GroovyCompile) {
|
||||
@@ -27,12 +39,6 @@ subprojects { subproject ->
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = woggioniMavenRepositoryUrl
|
||||
content {
|
||||
includeGroup 'net.woggioni'
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -41,25 +47,29 @@ subprojects { subproject ->
|
||||
add(conf, [group: "org.projectlombok", name: "lombok", version: catalog.versions.lombok.get()])
|
||||
}
|
||||
add("testImplementation", catalog.junit.jupiter.api)
|
||||
add("testImplementation", catalog.junit.jupiter.params)
|
||||
add("testRuntimeOnly", catalog.junit.jupiter.engine)
|
||||
add("testRuntimeOnly", catalog.junit.platform.launcher)
|
||||
add("testImplementation", gradleTestKit())
|
||||
}
|
||||
|
||||
tasks.named("test", Test) {
|
||||
tasks.withType(Test) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
childProjects.forEach { name, child ->
|
||||
child.with {
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
group = "net.woggioni.gradle"
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -8,6 +8,9 @@ import org.gradle.api.plugins.ObjectConfigurationAction;
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
public class DependencyExportPlugin implements Plugin<Project> {
|
||||
|
||||
public static final String DEPENDENCY_EXPORT_GROUP = "dependency-export";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.apply(new Action<ObjectConfigurationAction>() {
|
||||
|
||||
+11
-7
@@ -20,10 +20,13 @@ import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.RegularFile;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.BasePluginExtension;
|
||||
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.Provider;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
@@ -49,6 +52,8 @@ import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.woggioni.gradle.dependency.export.DependencyExportPlugin.DEPENDENCY_EXPORT_GROUP;
|
||||
|
||||
public class ExportDependencies extends DefaultTask {
|
||||
|
||||
@Getter(onMethod_ = { @Input })
|
||||
@@ -71,9 +76,8 @@ public class ExportDependencies extends DefaultTask {
|
||||
@Getter(onMethod_ = { @Input })
|
||||
private final Property<Boolean> showArtifacts;
|
||||
|
||||
private final JavaPluginConvention javaPluginConvention;
|
||||
|
||||
@InputFiles
|
||||
@Classpath
|
||||
public Provider<FileCollection> getConfigurationFiles() {
|
||||
return configurationName.map(this::fetchConfiguration);
|
||||
}
|
||||
@@ -96,11 +100,11 @@ public class ExportDependencies extends DefaultTask {
|
||||
|
||||
@Inject
|
||||
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);
|
||||
Provider<File> defaultOutputFileProvider =
|
||||
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "dependencies.dot"));
|
||||
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider));
|
||||
final JavaPluginExtension javaPluginExtension = getProject().getExtensions().findByType(JavaPluginExtension.class);
|
||||
final Provider<RegularFile> defaultOutputFileProvider = javaPluginExtension.getDocsDir().file("dependencies.dot");
|
||||
outputFile = objects.fileProperty().convention(defaultOutputFileProvider);
|
||||
showArtifacts = objects.property(Boolean.class).convention(false);
|
||||
}
|
||||
|
||||
|
||||
+34
-15
@@ -7,26 +7,34 @@ import org.gradle.api.GradleException;
|
||||
import org.gradle.api.file.RegularFile;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
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.Provider;
|
||||
import org.gradle.api.tasks.CacheableTask;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.options.Option;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.woggioni.gradle.dependency.export.DependencyExportPlugin.DEPENDENCY_EXPORT_GROUP;
|
||||
|
||||
@CacheableTask
|
||||
public class RenderDependencies extends DefaultTask {
|
||||
|
||||
@Getter(onMethod_ = { @InputFile })
|
||||
@Getter(onMethod_ = {@InputFile, @PathSensitive(PathSensitivity.NONE)})
|
||||
private Provider<File> sourceFile;
|
||||
|
||||
@Getter(onMethod_ = {@Input})
|
||||
@@ -45,13 +53,12 @@ public class RenderDependencies extends DefaultTask {
|
||||
return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull();
|
||||
}
|
||||
|
||||
@Optional
|
||||
@OutputFile
|
||||
public Provider<File> getResult() {
|
||||
return outputFile.map(RegularFile::getAsFile);
|
||||
}
|
||||
|
||||
private final JavaPluginConvention javaPluginConvention;
|
||||
|
||||
@Option(option = "output", description = "Set the output file name")
|
||||
public void setOutputCli(String outputFile) {
|
||||
Provider<File> fileProvider = getProject().provider(() -> new File(outputFile));
|
||||
@@ -64,33 +71,45 @@ public class RenderDependencies extends DefaultTask {
|
||||
}
|
||||
|
||||
public void setExportTask(Provider<ExportDependencies> taskProvider) {
|
||||
dependsOn(taskProvider);
|
||||
sourceFile = taskProvider.flatMap(ExportDependencies::getResult);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public RenderDependencies(ObjectFactory objects) {
|
||||
setGroup(DEPENDENCY_EXPORT_GROUP);
|
||||
sourceFile = objects.property(File.class);
|
||||
javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class);
|
||||
format = objects.property(String.class).convention("xlib");
|
||||
graphvizExecutable = objects.property(String.class).convention("dot");
|
||||
Provider<File> defaultOutputFileProvider =
|
||||
getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "renderedDependencies"));
|
||||
outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider));
|
||||
final JavaPluginExtension javaPluginExtension = getProject().getExtensions().findByType(JavaPluginExtension.class);
|
||||
final Provider<RegularFile> defaultOutputFileProvider = javaPluginExtension.getDocsDir().file("renderedDependencies");
|
||||
outputFile = objects.fileProperty().convention(defaultOutputFileProvider
|
||||
.zip(format, (file, type) -> Objects.equals("xlib", type) ? null : file));
|
||||
getOutputs().upToDateWhen(t -> outputFile.isPresent());
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
@SneakyThrows
|
||||
void run() {
|
||||
Path destination = outputFile
|
||||
java.util.Optional<Path> destination = java.util.Optional.of(
|
||||
outputFile
|
||||
.map(RegularFile::getAsFile)
|
||||
.map(File::toPath)
|
||||
.get();
|
||||
List<String> cmd = Arrays.asList(
|
||||
)
|
||||
.filter(Provider::isPresent)
|
||||
.map(Provider::get);
|
||||
|
||||
List<String> cmd = new ArrayList<>(Arrays.asList(
|
||||
graphvizExecutable.get(),
|
||||
"-T" + format.get(),
|
||||
"-o" + destination,
|
||||
sourceFile.get().toString()
|
||||
);
|
||||
"-T" + format.get()
|
||||
));
|
||||
|
||||
if (destination.isPresent()) {
|
||||
cmd.add("-o");
|
||||
cmd.add(destination.get().toString());
|
||||
}
|
||||
cmd.add(sourceFile.get().toString());
|
||||
|
||||
int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor();
|
||||
if (returnCode != 0) {
|
||||
throw new GradleException("Error invoking graphviz");
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
+311
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
net.woggioni.finalguard.FinalGuardPlugin
|
||||
+221
@@ -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");
|
||||
}
|
||||
}
|
||||
+40
@@ -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
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
public class TestCase10 {
|
||||
public void testMethod(int n) {
|
||||
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
public record TestCase12<T, U>(double x, double y) {
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public interface TestCase13 {
|
||||
void foo(double x, double y);
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
public abstract class TestCase14 {
|
||||
abstract public void foo(double x, double y);
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
public class TestCase3 {
|
||||
|
||||
public void testMethod() {
|
||||
int n = 5; // Error: n could be final
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
public class TestCase4 {
|
||||
|
||||
public void testMethod() {
|
||||
for (int i = 0; i < 10; ) { // Error: i could be final
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
public class TestCase7 {
|
||||
|
||||
public void testMethod() {
|
||||
try {
|
||||
|
||||
} catch (RuntimeException re) { // Error: re should be final
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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
@@ -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
|
||||
|
||||
version.myGradlePlugins=2023.06.13
|
||||
version.gradle=7.6
|
||||
version.felix.config.admin=1.9.26
|
||||
version.felix=7.0.5
|
||||
version.felix.scr=2.2.4
|
||||
version.felix.security=2.8.2
|
||||
version.osgi=8.0.0
|
||||
version.osgi.cm=1.6.0
|
||||
version.osgi.service.component=1.5.1
|
||||
version.osgi.function=1.2.0
|
||||
version.osgi.promise=1.3.0
|
||||
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (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
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +82,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -133,22 +132,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -165,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+20
-16
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,32 +59,33 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@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
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,12 @@ plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility(JavaVersion.VERSION_1_8.toString())
|
||||
targetCompatibility(JavaVersion.VERSION_1_8.toString())
|
||||
modularity.inferModulePath = false
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("JPMSCheckPlugin") {
|
||||
|
||||
@@ -1,216 +1,43 @@
|
||||
package net.woggioni.gradle.jpms.check
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.transform.Canonical
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.xml.MarkupBuilder
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.jar.JarFile
|
||||
import java.util.stream.Collectors
|
||||
import java.util.stream.Stream
|
||||
import java.util.zip.ZipFile
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
import org.gradle.api.plugins.ReportingBasePlugin
|
||||
import org.gradle.api.reporting.ReportingExtension
|
||||
|
||||
class JPMSCheckPlugin implements Plugin<Project> {
|
||||
|
||||
@Canonical
|
||||
@CompileStatic
|
||||
private class CheckResult {
|
||||
ResolvedArtifactResult dep
|
||||
String automaticModuleName
|
||||
boolean multiReleaseJar
|
||||
boolean moduleInfo
|
||||
|
||||
boolean getJpmsFriendly() {
|
||||
return automaticModuleName != null || moduleInfo
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean equals(Object other) {
|
||||
if(other == null) {
|
||||
return false
|
||||
} else if(other.class == CheckResult.class) {
|
||||
return dep?.id?.componentIdentifier == ((CheckResult) other).dep?.id?.componentIdentifier
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int hashCode() {
|
||||
return dep.id.componentIdentifier.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private Stream<CheckResult> computeResults(Stream<ResolvedArtifactResult> artifacts) {
|
||||
return artifacts.filter { ResolvedArtifactResult res ->
|
||||
res.file.exists() && res.file.name.endsWith(".jar")
|
||||
}.<CheckResult>map { resolvedArtifact ->
|
||||
JarFile jarFile = new JarFile(resolvedArtifact.file).with {
|
||||
if (it.isMultiRelease()) {
|
||||
new JarFile(
|
||||
resolvedArtifact.file,
|
||||
false,
|
||||
ZipFile.OPEN_READ,
|
||||
Runtime.version()
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") }
|
||||
def moduleInfoEntry = jarFile.getJarEntry("module-info.class")
|
||||
new CheckResult(
|
||||
resolvedArtifact,
|
||||
automaticModuleName,
|
||||
jarFile.isMultiRelease(),
|
||||
moduleInfoEntry != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private void createHtmlReport(Project project, Stream<CheckResult> checkResults, Writer writer) {
|
||||
def builder = new MarkupBuilder(writer)
|
||||
int friendly = 0
|
||||
int total = 0
|
||||
def results = checkResults.peek { CheckResult res ->
|
||||
total += 1
|
||||
if(res.jpmsFriendly) friendly += 1
|
||||
}.collect(Collectors.toList())
|
||||
builder.html {
|
||||
head {
|
||||
meta name: "viewport", content: "width=device-width, initial-scale=1"
|
||||
InputStream resourceStream = getClass().classLoader.getResourceAsStream('net/woggioni/plugins/jpms/check/github-markdown.css')
|
||||
resourceStream.withReader { Reader reader ->
|
||||
style reader.text
|
||||
}
|
||||
body {
|
||||
article(class: 'markdown-body') {
|
||||
h1 "Project ${project.group}:${project.name}:${project.version}", style: "text-align: center;"
|
||||
div {
|
||||
table {
|
||||
thead {
|
||||
tr {
|
||||
th "JPMS friendly"
|
||||
th "Not JPMS friendly", colspan: 2
|
||||
th "Total", colspan: 2
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
td friendly, style: "text-align: center;"
|
||||
td total - friendly, style: "text-align: center;", colspan: 2
|
||||
td total, style: "text-align: center;", colspan: 2
|
||||
}
|
||||
}
|
||||
thead {
|
||||
th "Name"
|
||||
th "Multi-release jar"
|
||||
th "Automatic-Module-Name"
|
||||
th "Module descriptor"
|
||||
th "JPMS friendly"
|
||||
}
|
||||
tbody {
|
||||
results.forEach {res ->
|
||||
String color = res.jpmsFriendly ? "#dfd" : "fdd"
|
||||
tr(style: "background-color:$color;") {
|
||||
td res.dep.id.displayName
|
||||
td style: "text-align: center;", res.multiReleaseJar ? "✓" : "✕"
|
||||
td style: "text-align: center;", res.automaticModuleName ?: "n/a"
|
||||
td style: "text-align: center;", res.moduleInfo ? "✓" : "✕"
|
||||
td style: "text-align: center;", res.jpmsFriendly ? "✓" : "✕"
|
||||
}
|
||||
total += 1
|
||||
if(res.jpmsFriendly) friendly += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private createJsonReport(Stream<CheckResult> checkResults, Writer writer) {
|
||||
def builder = new JsonBuilder()
|
||||
builder (checkResults.map {
|
||||
[
|
||||
name: it.dep.id.componentIdentifier.displayName,
|
||||
automaticModuleName: it.automaticModuleName,
|
||||
isMultiReleaseJar: it.multiReleaseJar,
|
||||
hasModuleInfo: it.moduleInfo,
|
||||
jpmsFriendly: it.jpmsFriendly
|
||||
]
|
||||
}.collect(Collectors.toList()))
|
||||
builder.writeTo(writer)
|
||||
}
|
||||
|
||||
@Override
|
||||
@CompileStatic
|
||||
void apply(Project project) {
|
||||
project.tasks.register("jpms-check") {task ->
|
||||
boolean recursive = project.properties["jpms-check.recursive"]?.with(Boolean.&parseBoolean) ?: false
|
||||
String cfgName = project.properties["jpms-check.configurationName"] ?: "default"
|
||||
String outputFormat = project.properties["jpms-check.outputFormat"] ?: "html"
|
||||
Path outputFile = project.properties["jpms-check.outputFile"]?.with {
|
||||
Paths.get(it as String)
|
||||
} ?: with {
|
||||
project.pluginManager.apply(ReportingBasePlugin.class)
|
||||
project.tasks.register("jpms-check", JPMSCheckTask) {task ->
|
||||
ReportingExtension reporting = project.extensions.getByType(ReportingExtension.class)
|
||||
boolean recursive = project.properties["jpms-check.recursive"]?.with(Object.&toString)?.with(Boolean.&parseBoolean) ?: false
|
||||
String cfgName = project.properties["jpms-check.configurationName"] ?: JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME
|
||||
OutputFormat defaultOutputFormat = (project.properties["jpms-check.outputFormat"]
|
||||
?.with(Object.&toString)
|
||||
?.with(OutputFormat.&valueOf)
|
||||
?: OutputFormat.html)
|
||||
task.getConfigurationName().convention(cfgName)
|
||||
task.getRecursive().convention(recursive)
|
||||
task.outputFormat.convention(defaultOutputFormat)
|
||||
task.getOutputFile().convention(reporting.baseDirectory.zip(task.getOutputFormat(), { dir, outputFormat ->
|
||||
RegularFile result = null
|
||||
switch(outputFormat) {
|
||||
case "html":
|
||||
Paths.get(project.buildDir.path, "jpms-report.html")
|
||||
case OutputFormat.html:
|
||||
result = dir.file( "jpms-report.html")
|
||||
break
|
||||
case "json":
|
||||
Paths.get(project.buildDir.path, "jpms-report.json")
|
||||
case OutputFormat.json:
|
||||
result = dir.file( "jpms-report.json")
|
||||
break
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
|
||||
}
|
||||
}
|
||||
task.doLast {
|
||||
Stream<Project> projects = Stream.of(project)
|
||||
if(recursive) {
|
||||
projects = Stream.concat(projects, project.subprojects.stream())
|
||||
}
|
||||
Set<CheckResult> results = projects.flatMap {
|
||||
Configuration requestedConfiguration = (project.configurations.<Configuration>find { Configuration cfg ->
|
||||
cfg.canBeResolved && cfg.name == cfgName
|
||||
} ?: {
|
||||
def resolvableConfigurations = "[" + project.configurations
|
||||
.grep { Configuration cfg -> cfg.canBeResolved }
|
||||
.collect { "'${it.name}'" }
|
||||
.join(",") + "]"
|
||||
throw new GradleException("Configuration '$cfgName' doesn't exist or cannot be resolved, " +
|
||||
"resolvable configurations in this project are " + resolvableConfigurations)
|
||||
}) as Configuration
|
||||
computeResults(requestedConfiguration.incoming.artifacts.artifacts.stream())
|
||||
}.collect(Collectors.toSet())
|
||||
Files.createDirectories(outputFile.parent)
|
||||
Files.newBufferedWriter(outputFile).withWriter {
|
||||
Stream<CheckResult> resultStream = results.stream().sorted(Comparator.<CheckResult, String>comparing { CheckResult res ->
|
||||
res.dep.id.componentIdentifier.displayName
|
||||
})
|
||||
switch(outputFormat) {
|
||||
case "html":
|
||||
createHtmlReport(project, resultStream, it)
|
||||
break
|
||||
case "json":
|
||||
createJsonReport(resultStream, it)
|
||||
break
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported output format: $outputFormat")
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.file.SourceDirectorySet;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.internal.jvm.JavaModuleDetector;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
@@ -10,12 +20,10 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class Delombok extends JavaExec {
|
||||
|
||||
@Getter
|
||||
@OutputDirectory
|
||||
private final File outputDir;
|
||||
@RequiredArgsConstructor(onConstructor_ = {@Inject})
|
||||
public abstract class Delombok extends JavaExec {
|
||||
|
||||
private final JavaModuleDetector javaModuleDetector;
|
||||
private static String buildClasspathString(Iterable<File> classpath) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Iterator<File> it = classpath.iterator();
|
||||
@@ -33,19 +41,56 @@ public class Delombok extends JavaExec {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Inject
|
||||
public Delombok(File lombokJar, File outputDir, Iterable<File> sourceDirs, String classpath) {
|
||||
this.outputDir = outputDir;
|
||||
classpath(lombokJar);
|
||||
@InputFiles
|
||||
public Provider<FileCollection> getSourceClasspath() {
|
||||
return getSourceSet()
|
||||
.map(SourceSet::getCompileClasspath);
|
||||
}
|
||||
|
||||
@Internal
|
||||
abstract public Property<SourceSet> getSourceSet();
|
||||
@Internal
|
||||
abstract public Property<Configuration> getLombokJar();
|
||||
@Internal
|
||||
abstract public Property<Boolean> getInferModulePath();
|
||||
@OutputDirectory
|
||||
abstract public RegularFileProperty getOutputDir();
|
||||
@InputFiles
|
||||
public Provider<FileCollection> getInputFiles() {
|
||||
return getSourceSet()
|
||||
.map(SourceSet::getAllSource)
|
||||
.map(SourceDirectorySet::getSourceDirectories)
|
||||
.zip(getLombokJar(), FileCollection::plus);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exec() {
|
||||
classpath(getLombokJar());
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("delombok");
|
||||
args.add("-d");
|
||||
args.add(outputDir.getPath());
|
||||
args.add(getOutputDir().getAsFile().get().getPath());
|
||||
SourceSet sourceSet = getSourceSet().get();
|
||||
Boolean inferModulePath = getInferModulePath().get();
|
||||
FileCollection classpath = javaModuleDetector.inferClasspath(inferModulePath, sourceSet.getCompileClasspath());
|
||||
if(!classpath.isEmpty()) {
|
||||
args.add("-c");
|
||||
args.add(classpath);
|
||||
for(File sourceDir : sourceDirs) args.add(sourceDir.getPath());
|
||||
args.add(classpath.getAsPath());
|
||||
}
|
||||
if(inferModulePath) {
|
||||
FileCollection modulepath = javaModuleDetector.inferModulePath(true, sourceSet.getCompileClasspath());
|
||||
if(!modulepath.isEmpty()) {
|
||||
args.add("--module-path");
|
||||
args.add(modulepath.getAsPath());
|
||||
}
|
||||
}
|
||||
for(File sourceDir : sourceSet.getJava().getSrcDirs()) {
|
||||
args.add(sourceDir.getPath());
|
||||
}
|
||||
Object[] argsArray = new String[args.size()];
|
||||
args.toArray(argsArray);
|
||||
args(argsArray);
|
||||
super.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
package net.woggioni.gradle.lombok;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@RequiredArgsConstructor(onConstructor_ = { @Inject })
|
||||
public class LombokExtension {
|
||||
@Getter
|
||||
private final Property<String> version;
|
||||
abstract public class LombokExtension {
|
||||
abstract public Property<String> getVersion();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.provider.Provider;
|
||||
@@ -27,20 +27,20 @@ public class LombokPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPluginManager().apply(JavaPlugin.class);
|
||||
ObjectFactory objectFactory = project.getObjects();
|
||||
LombokExtension ext = project.getExtensions()
|
||||
.create("lombok", LombokExtension.class,
|
||||
objectFactory.property(String.class)
|
||||
.convention(project.provider(
|
||||
() -> (String) project.getExtensions().getExtraProperties().get("version.lombok")))
|
||||
LombokExtension ext = project.getExtensions().create("lombok", LombokExtension.class);
|
||||
ExtraPropertiesExtension epe = project.getExtensions().getExtraProperties();
|
||||
if(epe.has("version.lombok")) {
|
||||
ext.getVersion().convention(
|
||||
project.provider(() -> (String) epe.get("version.lombok"))
|
||||
);
|
||||
project.afterEvaluate(p -> {
|
||||
SourceSetContainer sourceSetContainer = project.getExtensions().findByType(JavaPluginExtension.class).getSourceSets();
|
||||
Provider<Map<String, String>> dependencyNotationProvider = project.provider(() -> {
|
||||
}
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
|
||||
SourceSetContainer sourceSetContainer = javaPluginExtension.getSourceSets();
|
||||
Provider<Map<String, String>> dependencyNotationProvider = ext.getVersion().map((String version) -> {
|
||||
Map<String, String> m = new HashMap<>();
|
||||
m.put("group", "org.projectlombok");
|
||||
m.put("name", "lombok");
|
||||
m.put("version", ext.getVersion().get());
|
||||
m.put("version", version);
|
||||
return Collections.unmodifiableMap(m);
|
||||
});
|
||||
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);
|
||||
File outputDir = new File(new File(project.getBuildDir(), "delombok"), ss.getName());
|
||||
Javadoc javadoc = (Javadoc) javadocTask;
|
||||
TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName,
|
||||
Delombok.class,
|
||||
lombokConfiguration.getSingleFile(),
|
||||
outputDir,
|
||||
ss.getJava().getSrcDirs(),
|
||||
ss.getCompileClasspath().getAsPath()
|
||||
);
|
||||
delombokTaskProvider.configure(delombokTask -> {
|
||||
delombokTask.getInputs().files(ss.getAllSource().getSourceDirectories());
|
||||
});
|
||||
TaskProvider<Delombok> delombokTaskProvider = tasks.register(delombokTaskName, Delombok.class, (delombok -> {
|
||||
delombok.getSourceSet().set(ss);
|
||||
delombok.getOutputDir().set(outputDir);
|
||||
delombok.getLombokJar().set(lombokConfiguration);
|
||||
// Disabled for now due to https://github.com/projectlombok/lombok/issues/2829
|
||||
//delombok.getInferModulePath().set(javaPluginExtension.getModularity().getInferModulePath());
|
||||
delombok.getInferModulePath().set(false);
|
||||
}));
|
||||
javadoc.setSource(outputDir);
|
||||
javadoc.getInputs().files(delombokTaskProvider);
|
||||
}
|
||||
}
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class);
|
||||
JavaToolchainSpec toolchain = javaPluginExtension.getToolchain();
|
||||
if(toolchain.getLanguageVersion().isPresent()) {
|
||||
project.afterEvaluate((Project pro) -> {
|
||||
@@ -98,6 +95,5 @@ public class LombokPlugin implements Plugin<Project> {
|
||||
t.getOptions().getForkOptions().getJvmArgs().add("--illegal-access=permit");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'maven-publish'
|
||||
id 'groovy-gradle-plugin'
|
||||
id 'java-gradle-plugin'
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
||||
-198
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+217
@@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-14
@@ -1,33 +1,41 @@
|
||||
package net.woggioni.gradle.multi.release.jar;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.MapProperty;
|
||||
import org.gradle.api.provider.Provider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
public class MultiReleaseJarPluginExtension {
|
||||
|
||||
private final ObjectFactory objects;
|
||||
private final Map<String, ListProperty<String>> patchModules;
|
||||
|
||||
private final Provider<List<String>> listCtor;
|
||||
private final MapProperty<String, List<String>> patchModules;
|
||||
@Inject
|
||||
public MultiReleaseJarPluginExtension(ObjectFactory objects) {
|
||||
this.objects = objects;
|
||||
patchModules = new HashMap<>();
|
||||
public MultiReleaseJarPluginExtension(Project project, ObjectFactory objects) {
|
||||
patchModules = objects.mapProperty(String.class,
|
||||
(Class<List<String>>) ((List<String>) new ArrayList<String>()).getClass());
|
||||
listCtor = project.provider(ArrayList::new);
|
||||
}
|
||||
|
||||
private static <T> List<T> listAdd(List<T> l, T el) {
|
||||
l.add(el);
|
||||
return l;
|
||||
}
|
||||
|
||||
public void patchModule(String moduleName, Provider<String> path) {
|
||||
this.patchModules
|
||||
.computeIfAbsent(moduleName, key -> objects.listProperty(String.class)
|
||||
.convention(new ArrayList<>()))
|
||||
.add(path);
|
||||
Provider<List<String>> listProvider = this.patchModules.getting(moduleName);
|
||||
if(listProvider.isPresent()) {
|
||||
patchModules.put(moduleName, listProvider.zip(path, MultiReleaseJarPluginExtension::listAdd));
|
||||
} else {
|
||||
patchModules.put(moduleName, listCtor.zip(path, MultiReleaseJarPluginExtension::listAdd));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, ListProperty<String>> getPatchModules() {
|
||||
public MapProperty<String, List<String>> getPatchModules() {
|
||||
return patchModules;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,25 +2,6 @@ plugins {
|
||||
id "java-gradle-plugin"
|
||||
}
|
||||
|
||||
childProjects.forEach {name, child ->
|
||||
child.with {
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url = woggioniMavenRepositoryUrl
|
||||
}
|
||||
}
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evaluationDependsOnChildren()
|
||||
|
||||
configurations {
|
||||
|
||||
@@ -18,7 +18,9 @@ dependencies {
|
||||
}
|
||||
|
||||
jar {
|
||||
bundle {
|
||||
bnd '''\
|
||||
Import-Package: !lombok, *
|
||||
'''
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven {
|
||||
url = 'https://woggioni.net/mvn/'
|
||||
url = getProperty('gitea.maven.url')
|
||||
content {
|
||||
includeGroup 'com.lys'
|
||||
}
|
||||
@@ -10,7 +10,6 @@ dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
catalog {
|
||||
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.catalog.version')
|
||||
version("slf4j", "1.7.36")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,3 +25,8 @@ include 'osgi-app:osgi-simple-bootstrapper'
|
||||
include 'osgi-app:osgi-simple-bootstrapper-api'
|
||||
include 'osgi-app:osgi-simple-bootstrapper-application'
|
||||
include 'wildfly'
|
||||
include 'sambal'
|
||||
include 'graalvm'
|
||||
include 'jdeps'
|
||||
include 'finalguard'
|
||||
include 'finalguard:finalguard-javac-plugin'
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package net.woggioni.gradle.wildfly;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.Exec;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Deploy2WildflyTask extends Exec {
|
||||
public abstract class Deploy2WildflyTask extends Exec {
|
||||
private static final String PROPERTY_PREFIX = "net.woggioni.gradle.wildfly.";
|
||||
private static final String HOST_PROPERTY_KEY = PROPERTY_PREFIX + "rpcHost";
|
||||
private static final String PORT_PROPERTY_KEY = PROPERTY_PREFIX + "rpcPort";
|
||||
@@ -21,24 +18,21 @@ public class Deploy2WildflyTask extends Exec {
|
||||
private static final String PASSWORD_PROPERTY_KEY = PROPERTY_PREFIX + "rpcPassword";
|
||||
|
||||
@Input
|
||||
@Getter
|
||||
private final Property<String> rpcHost;
|
||||
public abstract Property<String> getRpcHost();
|
||||
|
||||
@Input
|
||||
@Getter
|
||||
private final Property<Integer> rpcPort;
|
||||
public abstract Property<Integer> getRpcPort();
|
||||
|
||||
@Input
|
||||
@Getter
|
||||
private final Property<String> rpcUsername;
|
||||
public abstract Property<String> getRpcUsername();
|
||||
|
||||
@Input
|
||||
@Getter
|
||||
private final Property<String> rpcPassword;
|
||||
public abstract Property<String> getRpcPassword();
|
||||
@Input
|
||||
public abstract Property<String> getDeploymentName();
|
||||
|
||||
@Getter
|
||||
@InputFile
|
||||
private final RegularFileProperty artifact;
|
||||
public abstract RegularFileProperty getArtifact();
|
||||
|
||||
private String projectProperty(String key, String defaultValue) {
|
||||
String result = (String) getProject().findProperty(key);
|
||||
@@ -46,7 +40,7 @@ public class Deploy2WildflyTask extends Exec {
|
||||
}
|
||||
|
||||
@Inject
|
||||
public Deploy2WildflyTask(@Nonnull ObjectFactory objectFactory) {
|
||||
public Deploy2WildflyTask() {
|
||||
setGroup("deploy");
|
||||
setDescription("Deploy this project artifact to Wildfly application server");
|
||||
Provider<String> defaultHostProvider = getProject()
|
||||
@@ -59,19 +53,21 @@ public class Deploy2WildflyTask extends Exec {
|
||||
.provider(() -> projectProperty(PASSWORD_PROPERTY_KEY, "password"));
|
||||
|
||||
executable("/opt/wildfly/bin/jboss-cli.sh");
|
||||
rpcHost = objectFactory.property(String.class).convention(defaultHostProvider);
|
||||
rpcPort = objectFactory.property(Integer.class).convention(defaultPortProvider);
|
||||
rpcUsername = objectFactory.property(String.class).convention(defaultUsernameProvider);
|
||||
rpcPassword = objectFactory.property(String.class).convention(defaultPasswordProvider);
|
||||
artifact = objectFactory.fileProperty();
|
||||
|
||||
getRpcHost().convention(defaultHostProvider);
|
||||
getRpcPort().convention(defaultPortProvider);
|
||||
getRpcUsername().convention(defaultUsernameProvider);
|
||||
getRpcPassword().convention(defaultPasswordProvider);
|
||||
getDeploymentName().convention(getArtifact().map(it -> it.getAsFile().getName()));
|
||||
getArgumentProviders().add(() ->
|
||||
Arrays.asList(
|
||||
"--controller=" + rpcHost.get() + ":" + rpcPort.get(),
|
||||
"--controller=" + getRpcHost().get() + ":" + getRpcPort().get(),
|
||||
"--connect",
|
||||
"--user=" + rpcUsername.get(),
|
||||
"--password=" + rpcPassword.get(),
|
||||
"--command=deploy " + artifact.getAsFile().get().getPath() + " --force")
|
||||
"--user=" + getRpcUsername().get(),
|
||||
"--password=" + getRpcPassword().get(),
|
||||
"--command=deploy "
|
||||
+ getArtifact().getAsFile().get().getPath()
|
||||
+ " --name=" + getDeploymentName().get()
|
||||
+ " --force")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user