Compare commits

..
3 Commits
Author SHA1 Message Date
woggioni 8605fcecaf Fixed npe in finalguard
CI / Compile, test and publish (push) Successful in 4m32s
2026-07-13 17:15:48 +08:00
woggioni 185456c10e improved GraalVM plugin
CI / build (push) Failing after 41s
2026-06-10 09:02:08 +08:00
woggioni 6dc946d71b downgrade to gradle 8.14.5
CI / build (push) Successful in 2m35s
2026-06-07 19:15:05 +08:00
16 changed files with 238 additions and 34 deletions
+29 -4
View File
@@ -2,13 +2,38 @@ name: CI
on: on:
push: push:
branches: [ master ] branches: [ master ]
env:
JAVA_VERSION: '21'
GRADLE_VERSION: '8.14.5'
jobs: jobs:
build: build:
runs-on: woryzen name: Compile, test and publish
runs-on: hostinger
timeout-minutes: 30
container:
image: gitea.woggioni.net/woggioni/gitea-runner/debian:latest
steps: steps:
- name: Checkout sources - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Execute Gradle build with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'oracle'
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
gradle-version: ${{ env.GRADLE_VERSION }}
# Only the main branch writes to the cache; PRs/feature branches read-only
cache-read-only: false
- name: Run Gradle build
run: ./gradlew build --no-daemon
- name: Publish to Gitea
env: env:
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
run: ./gradlew build publish run: ./gradlew build publish
+1 -1
View File
@@ -7,7 +7,7 @@ subprojects { subproject ->
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(25) languageVersion = JavaLanguageVersion.of(21)
} }
} }
@@ -4,12 +4,6 @@ plugins {
group = "net.woggioni.finalguard" group = "net.woggioni.finalguard"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
java { java {
sourceCompatibility(JavaVersion.VERSION_1_8.toString()) sourceCompatibility(JavaVersion.VERSION_1_8.toString())
targetCompatibility(JavaVersion.VERSION_1_8.toString()) targetCompatibility(JavaVersion.VERSION_1_8.toString())
@@ -21,12 +21,12 @@ import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath; import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner; import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees; import com.sun.source.util.Trees;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -44,8 +44,6 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.sun.tools.javac.code.Flags.RECORD;
public class FinalGuardPlugin implements Plugin { public class FinalGuardPlugin implements Plugin {
public static final String DEFAULT_LEVEL_KEY = "default.level"; public static final String DEFAULT_LEVEL_KEY = "default.level";
public static final String EXCLUDE_KEY = "exclude"; public static final String EXCLUDE_KEY = "exclude";
@@ -185,6 +183,7 @@ public class FinalGuardPlugin implements Plugin {
private final Configuration configuration; private final Configuration configuration;
private final CompilationUnitTree compilationUnit; private final CompilationUnitTree compilationUnit;
private final Trees trees; private final Trees trees;
private final Elements elements;
private final Map<String, VariableInfo> variableInfoMap = new LinkedHashMap<>(); private final Map<String, VariableInfo> variableInfoMap = new LinkedHashMap<>();
private final Set<String> reassignedVariables = new HashSet<>(); private final Set<String> reassignedVariables = new HashSet<>();
@@ -192,6 +191,7 @@ public class FinalGuardPlugin implements Plugin {
this.configuration = configuration; this.configuration = configuration;
this.compilationUnit = compilationUnit; this.compilationUnit = compilationUnit;
this.trees = Trees.instance(task); this.trees = Trees.instance(task);
this.elements = task.getElements();
} }
@Override @Override
@@ -203,6 +203,8 @@ public class FinalGuardPlugin implements Plugin {
return null; // skip implicit constructor of anonymous class return null; // skip implicit constructor of anonymous class
} }
} }
variableInfoMap.clear();
reassignedVariables.clear();
super.visitMethod(node, p); super.visitMethod(node, p);
// Check for variables that could be final // Check for variables that could be final
checkForFinalCandidates(); checkForFinalCandidates();
@@ -232,9 +234,13 @@ public class FinalGuardPlugin implements Plugin {
type = VariableType.ABSTRACT_METHOD_PARAM; type = VariableType.ABSTRACT_METHOD_PARAM;
} else { } else {
type = VariableType.METHOD_PARAM; type = VariableType.METHOD_PARAM;
if (isJava17OrHigher && ((MethodTree) parent).getName().contentEquals("<init>")) { final MethodTree methodTree = (MethodTree) parent;
if(TreeInfo.isCanonicalConstructor((JCTree) parent)) { if (isJava17OrHigher && (methodTree.getName().contentEquals("<init>"))) {
return super.visitVariable(node, p); final TreePath grandParentPath = parentPath.getParentPath();
if(grandParentPath.getLeaf().getKind() == Tree.Kind.RECORD) {
if(RecordUtils.isCanonicalConstructor(trees, elements, (TypeElement) trees.getElement(grandParentPath), parentPath)) {
return super.visitVariable(node, p);
}
} }
} }
} }
@@ -317,5 +323,7 @@ public class FinalGuardPlugin implements Plugin {
final Set<Modifier> modifiers = variableTree.getModifiers().getFlags(); final Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
return modifiers.contains(Modifier.FINAL); return modifiers.contains(Modifier.FINAL);
} }
} }
} }
@@ -0,0 +1,95 @@
package net.woggioni.finalguard;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import java.util.List;
public class RecordUtils {
private static class RecordConstructorDetector {
private final Trees trees;
public RecordConstructorDetector(Trees trees) {
this.trees = trees;
}
public enum ConstructorKind {
CANONICAL_FULL, // Record(int x, String y) { ... }
CANONICAL_COMPACT, // Record { ... }
SECONDARY // Record(String s) { this(0, s); }
}
public ConstructorKind detect(TreePath parent, MethodTree method, TypeElement recordType) {
// 1. Verify it's a constructor
if (method.getReturnType() != null || !method.getName().contentEquals("<init>")) {
throw new IllegalArgumentException("Not a constructor: " + method.getName());
}
List<? extends VariableTree> params = method.getParameters();
List<? extends javax.lang.model.element.RecordComponentElement> components =
recordType.getRecordComponents();
// 2. Compact constructor: no explicit parameters
if (params.isEmpty()) {
return ConstructorKind.CANONICAL_COMPACT;
}
// 3. Check if parameters match record components exactly
if (params.size() == components.size()) {
boolean allMatch = true;
for (int i = 0; i < components.size(); i++) {
TypeMirror paramType = trees.getTypeMirror(new TreePath(parent, params.get(i)));
TypeMirror componentType = components.get(i).asType();
if (paramType == null || !paramType.toString().equals(componentType.toString())) {
allMatch = false;
break;
}
}
if (allMatch) {
return ConstructorKind.CANONICAL_FULL;
}
}
// 4. Otherwise it's a secondary constructor
return ConstructorKind.SECONDARY;
}
}
public static boolean isCanonicalConstructor(Trees trees, Elements elements, TypeElement recordType, TreePath method) {
final RecordConstructorDetector.ConstructorKind ctorKind = new RecordConstructorDetector(trees).detect(method, (MethodTree) method.getLeaf(), recordType);
return ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_COMPACT || ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_FULL;
}
public static boolean isCompactConstructor(Trees trees, Elements elements, TypeElement recordType, TreePath method) {
final RecordConstructorDetector.ConstructorKind ctorKind = new RecordConstructorDetector(trees).detect(method, (MethodTree) method.getLeaf(), recordType);
return ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_COMPACT;
}
public static boolean isCanonicalConstructor(Trees trees, Elements elements, TreePath method) {
Element element = trees.getElement(method);
if(element instanceof ExecutableElement) {
return elements.isCanonicalConstructor((ExecutableElement) element);
} else {
return false;
}
}
public static boolean isCompactConstructor(Trees trees, Elements elements, TreePath method) {
Element element = trees.getElement(method);
if(element instanceof ExecutableElement) {
return elements.isCompactConstructor((ExecutableElement) element);
} else {
return false;
}
}
}
@@ -171,6 +171,12 @@ public class PluginTest {
Arrays.asList( Arrays.asList(
METHOD_PARAM.getMessage("a") METHOD_PARAM.getMessage("a")
) )
),
Arguments.of(prefix + "TestCase18.java",
Collections.emptyList()
),
Arguments.of(prefix + "TestCase19.java",
Collections.emptyList()
) )
); );
} }
@@ -1,7 +1,3 @@
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public record TestCase17(String s) { public record TestCase17(String s) {
TestCase17(double a) { TestCase17(double a) {
this(""); this("");
@@ -0,0 +1,7 @@
import java.util.Set;
public class TestCase18 {
private String name;
private String password;
private Set<Integer> roles;
}
@@ -0,0 +1,49 @@
public class TestCase19 {
private long id;
@java.lang.SuppressWarnings("all")
public TestCase19() {
}
@java.lang.SuppressWarnings("all")
public long getId() {
return this.id;
}
@java.lang.SuppressWarnings("all")
public void setId(final long id) {
this.id = id;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (!(o instanceof TestCase19)) return false;
final TestCase19 other = (TestCase19) o;
if (!other.canEqual((java.lang.Object) this)) return false;
if (this.getId() != other.getId()) return false;
return true;
}
@java.lang.SuppressWarnings("all")
protected boolean canEqual(final java.lang.Object other) {
return other instanceof TestCase19;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long $id = this.getId();
result = result * PRIME + (int) ($id >>> 32 ^ $id);
return result;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString() {
return "TestCase19(id=" + this.getId() + ")";
}
}
@@ -16,6 +16,7 @@ import org.gradle.api.tasks.compile.JavaCompile;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
import java.util.Optional;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.Manifest; import java.util.jar.Manifest;
@@ -56,7 +57,10 @@ public class FinalGuardPlugin implements Plugin<Project> {
tasks.withType(JavaCompile.class, javaCompileTask -> { tasks.withType(JavaCompile.class, javaCompileTask -> {
javaCompileTask.doFirst(t -> { javaCompileTask.doFirst(t -> {
final CompileOptions options = javaCompileTask.getOptions(); final CompileOptions options = javaCompileTask.getOptions();
options.setAnnotationProcessorPath(options.getAnnotationProcessorPath().plus(javacPluginConfiguration)); options.setAnnotationProcessorPath(Optional.ofNullable(options.getAnnotationProcessorPath())
.orElseGet(project::files)
.plus(javacPluginConfiguration)
);
final StringBuilder xpluginArg = new StringBuilder("-Xplugin:").append(JAVAC_PLUGIN_NAME); final StringBuilder xpluginArg = new StringBuilder("-Xplugin:").append(JAVAC_PLUGIN_NAME);
appendOption(xpluginArg, "default.level", finalGuardExtension.getDefaultLevel()); appendOption(xpluginArg, "default.level", finalGuardExtension.getDefaultLevel());
appendOption(xpluginArg, "local.variable.level", finalGuardExtension.getLocalVariableLevel()); appendOption(xpluginArg, "local.variable.level", finalGuardExtension.getLocalVariableLevel());
@@ -76,7 +80,6 @@ public class FinalGuardPlugin implements Plugin<Project> {
appendOption(xpluginArg, EXCLUDE_KEY, excludedPrefix); appendOption(xpluginArg, EXCLUDE_KEY, excludedPrefix);
} }
options.getCompilerArgs().add(xpluginArg.toString()); options.getCompilerArgs().add(xpluginArg.toString());
options.getAllCompilerArgs().add("-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED");
}); });
}); });
} }
@@ -58,9 +58,10 @@ public abstract class NativeImageConfigurationTask extends JavaExec {
JavaPluginExtension javaExtension = getProject().getExtensions().getByType(JavaPluginExtension.class); JavaPluginExtension javaExtension = getProject().getExtensions().getByType(JavaPluginExtension.class);
ExtensionContainer ext = getProject().getExtensions(); ExtensionContainer ext = getProject().getExtensions();
Property<JavaLauncher> javaLauncherProperty = getJavaLauncher(); Property<JavaLauncher> javaLauncherProperty = getJavaLauncher();
Optional.ofNullable(ext.findByType(JavaToolchainService.class)) javaLauncherProperty.set(getProject().provider(() -> Optional.of(toolchain).orElseGet(javaExtension::getToolchain)).flatMap(toolchainSpec -> {
.flatMap(ts -> Optional.of(toolchain).map(ts::launcherFor)) final JavaToolchainService jts = ext.findByType(JavaToolchainService.class);
.ifPresent(javaLauncherProperty::convention); return jts.launcherFor(toolchainSpec);
}));
if(!Objects.isNull(javaApplication)) { if(!Objects.isNull(javaApplication)) {
getMainClass().convention(javaApplication.getMainClass()); getMainClass().convention(javaApplication.getMainClass());
getMainModule().convention(javaApplication.getMainModule()); getMainModule().convention(javaApplication.getMainModule());
@@ -2,7 +2,9 @@ package net.woggioni.gradle.graalvm;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Nested;
import org.gradle.jvm.toolchain.JavaToolchainSpec; import org.gradle.jvm.toolchain.JavaToolchainSpec;
@@ -15,6 +17,7 @@ public interface NativeImageExtension {
JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action); JavaToolchainSpec toolchain(Action<? super JavaToolchainSpec> action);
ListProperty<String> getJvmArgs();
Property<Boolean> getUseMusl(); Property<Boolean> getUseMusl();
Property<Boolean> getBuildStaticImage(); Property<Boolean> getBuildStaticImage();
Property<Boolean> getEnableFallback(); Property<Boolean> getEnableFallback();
@@ -28,5 +31,7 @@ public interface NativeImageExtension {
Property<Boolean> getUseLZMA(); Property<Boolean> getUseLZMA();
Property<Boolean> getVerbose();
Property<Integer> getCompressionLevel(); Property<Integer> getCompressionLevel();
} }
@@ -15,8 +15,6 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskContainer;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec;
@CacheableTask @CacheableTask
public class NativeImagePlugin implements Plugin<Project> { public class NativeImagePlugin implements Plugin<Project> {
@@ -67,6 +65,7 @@ public class NativeImagePlugin implements Plugin<Project> {
nativeImageExtension.getBuildStaticImage().convention(false); nativeImageExtension.getBuildStaticImage().convention(false);
nativeImageExtension.getCompressExecutable().convention(false); nativeImageExtension.getCompressExecutable().convention(false);
nativeImageExtension.getUseLZMA().convention(false); nativeImageExtension.getUseLZMA().convention(false);
nativeImageExtension.getVerbose().convention(false);
nativeImageExtension.getCompressionLevel().convention(6); nativeImageExtension.getCompressionLevel().convention(6);
ConfigurationContainer configurations = project.getConfigurations(); ConfigurationContainer configurations = project.getConfigurations();
@@ -93,9 +92,11 @@ public class NativeImagePlugin implements Plugin<Project> {
jts.getLanguageVersion().convention(nativeImageExtension.getToolchain().getLanguageVersion()); jts.getLanguageVersion().convention(nativeImageExtension.getToolchain().getLanguageVersion());
}); });
nativeImageTask.getJvmArgs().set(nativeImageExtension.getJvmArgs());
nativeImageTask.getBuildStaticImage().set(nativeImageExtension.getBuildStaticImage()); nativeImageTask.getBuildStaticImage().set(nativeImageExtension.getBuildStaticImage());
nativeImageTask.getUseMusl().set(nativeImageExtension.getUseMusl()); nativeImageTask.getUseMusl().set(nativeImageExtension.getUseMusl());
nativeImageTask.getLinkAtBuildTime().set(nativeImageExtension.getLinkAtBuildTime()); nativeImageTask.getLinkAtBuildTime().set(nativeImageExtension.getLinkAtBuildTime());
nativeImageTask.getLinkAtBuildTime().set(nativeImageExtension.getVerbose());
nativeImageTask.getMainClass().set(nativeImageExtension.getMainClass()); nativeImageTask.getMainClass().set(nativeImageExtension.getMainClass());
nativeImageTask.getMainModule().set(nativeImageExtension.getMainModule()); nativeImageTask.getMainModule().set(nativeImageExtension.getMainModule());
nativeImageTask.getEnableFallback().set(nativeImageExtension.getEnableFallback()); nativeImageTask.getEnableFallback().set(nativeImageExtension.getEnableFallback());
@@ -13,6 +13,7 @@ import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.BasePluginExtension; import org.gradle.api.plugins.BasePluginExtension;
import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.CacheableTask;
@@ -65,6 +66,8 @@ public abstract class NativeImageTask extends Exec {
@PathSensitive(PathSensitivity.ABSOLUTE) @PathSensitive(PathSensitivity.ABSOLUTE)
public abstract RegularFileProperty getNativeCompilerPath(); public abstract RegularFileProperty getNativeCompilerPath();
@Input
public abstract ListProperty<String> getJvmArgs();
@Input @Input
public abstract Property<Boolean> getUseMusl(); public abstract Property<Boolean> getUseMusl();
@Input @Input
@@ -73,7 +76,8 @@ public abstract class NativeImageTask extends Exec {
public abstract Property<Boolean> getEnableFallback(); public abstract Property<Boolean> getEnableFallback();
@Input @Input
public abstract Property<Boolean> getLinkAtBuildTime(); public abstract Property<Boolean> getLinkAtBuildTime();
@Input
public abstract Property<Boolean> getVerbose();
@Input @Input
public abstract Property<String> getMainClass(); public abstract Property<String> getMainClass();
@@ -99,6 +103,7 @@ public abstract class NativeImageTask extends Exec {
getBuildStaticImage().convention(false); getBuildStaticImage().convention(false);
getEnableFallback().convention(false); getEnableFallback().convention(false);
getLinkAtBuildTime().convention(false); getLinkAtBuildTime().convention(false);
getVerbose().convention(false);
Provider<File> nativeComnpilerProvider = project.provider(() -> { Provider<File> nativeComnpilerProvider = project.provider(() -> {
String envVar; String envVar;
File compilerPath = null; File compilerPath = null;
@@ -143,6 +148,12 @@ public abstract class NativeImageTask extends Exec {
@Override @Override
public Iterable<String> asArguments() { public Iterable<String> asArguments() {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
if(getJvmArgs().isPresent()) {
final List<String> jvmArgs = getJvmArgs().get();
for(final String arg : jvmArgs) {
result.add("-J" + arg);
}
}
if(!getEnableFallback().get()) { if(!getEnableFallback().get()) {
result.add("--no-fallback"); result.add("--no-fallback");
} }
@@ -155,6 +166,9 @@ public abstract class NativeImageTask extends Exec {
if(getLinkAtBuildTime().get()) { if(getLinkAtBuildTime().get()) {
result.add("--link-at-build-time"); result.add("--link-at-build-time");
} }
if(getVerbose().get()) {
result.add("--verbose");
}
if(getNativeCompilerPath().isPresent()) { if(getNativeCompilerPath().isPresent()) {
result.add("--native-compiler-path=" + getNativeCompilerPath().getAsFile().get()); result.add("--native-compiler-path=" + getNativeCompilerPath().getAsFile().get());
} }
+3 -3
View File
@@ -1,5 +1,5 @@
lys.catalog.version=2026.05.27 lys.catalog.version=2026.06.08
version.myGradlePlugins=2026.06.05 version.myGradlePlugins=2026.07.13
version.gradle=9.3.1 version.gradle=8.14.5
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-all.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME