From ff6f9758250fa362f372829d4d1a2e522ddf447f Mon Sep 17 00:00:00 2001 From: Enrico Martelli Date: Sat, 8 Aug 2026 15:54:17 +0200 Subject: [PATCH 1/4] Gradle 9.7 best practices --- build.gradle | 10 ++++------ .../github/stickerifier/stickerify/JlinkTask.java | 12 +++++------- gradle.properties | 1 + 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 05e2b53c..33bf2c6c 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ dependencies { implementation(libs.tika) constraints { - implementation(libs.jackson.core) + add('implementation', libs.jackson.core) } testCompileOnly(libs.jspecify) @@ -53,12 +53,10 @@ def jlink = tasks.register('jlink', JlinkTask) { 'jdk.unsupported' // for gson ] includeModulePath = false - - group = 'build' - description = 'Generates a minimal JRE for the project.' + javaCompiler = javaToolchains.compilerFor(java.toolchain) } -tasks.named("compileTestJava") { +tasks.named(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaCompile) { options.nullability.checking = "tests" } @@ -99,7 +97,7 @@ distributions { } } -tasks.named('startScripts', CreateStartScripts) { +tasks.named(ApplicationPlugin.TASK_START_SCRIPTS_NAME, CreateStartScripts) { unixStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customUnixStartScript.txt') windowsStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customWindowsStartScript.txt') } diff --git a/buildSrc/src/main/java/com/github/stickerifier/stickerify/JlinkTask.java b/buildSrc/src/main/java/com/github/stickerifier/stickerify/JlinkTask.java index d1f3c921..318c7f9d 100644 --- a/buildSrc/src/main/java/com/github/stickerifier/stickerify/JlinkTask.java +++ b/buildSrc/src/main/java/com/github/stickerifier/stickerify/JlinkTask.java @@ -5,7 +5,6 @@ import org.gradle.api.file.FileSystemOperations; import org.gradle.api.file.ProjectLayout; import org.gradle.api.logging.LogLevel; -import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -13,7 +12,6 @@ import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.toolchain.JavaCompiler; -import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.process.ExecOperations; import org.jetbrains.annotations.NotNull; @@ -39,7 +37,7 @@ public abstract class JlinkTask extends DefaultTask { public abstract DirectoryProperty getOutputDirectory(); @Nested - protected abstract Property<@NotNull JavaCompiler> getJavaCompiler(); + public abstract Property getJavaCompiler(); @Inject protected abstract FileSystemOperations getFs(); @@ -48,14 +46,14 @@ public abstract class JlinkTask extends DefaultTask { protected abstract ExecOperations getExec(); @Inject - public JlinkTask(ProjectLayout layout, JavaToolchainService javaToolchain) { + public JlinkTask(ProjectLayout layout) { + setGroup("build"); + setDescription("Generates a custom Java runtime image using jlink."); + getOptions().convention(List.of()); getModules().convention(List.of("ALL-MODULE-PATH")); getIncludeModulePath().convention(true); getOutputDirectory().convention(layout.getBuildDirectory().dir(getName())); - - var toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain(); - getJavaCompiler().convention(javaToolchain.compilerFor(toolchain)); } @TaskAction diff --git a/gradle.properties b/gradle.properties index bd672934..2ac30350 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.cache=true org.gradle.configuration-cache=true +org.gradle.isolated-projects=true org.gradle.jvmargs=-Dfile.encoding=UTF-8 From 85397328d12f921c8945b44c1f258eed1d9845b6 Mon Sep 17 00:00:00 2001 From: Enrico Martelli Date: Sat, 8 Aug 2026 18:24:04 +0200 Subject: [PATCH 2/4] Convert to Kotlin script --- build.gradle | 103 ------------------ build.gradle.kts | 110 ++++++++++++++++++++ buildSrc/{build.gradle => build.gradle.kts} | 2 +- settings.gradle | 5 - settings.gradle.kts | 5 + 5 files changed, 116 insertions(+), 109 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts rename buildSrc/{build.gradle => build.gradle.kts} (87%) delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 33bf2c6c..00000000 --- a/build.gradle +++ /dev/null @@ -1,103 +0,0 @@ -import com.github.stickerifier.stickerify.JlinkJavaLauncher -import com.github.stickerifier.stickerify.JlinkTask - -plugins { - id('java') - id('application') - alias(libs.plugins.spring.nullability) -} - -repositories { - mavenCentral() -} - -dependencies { - compileOnly(libs.jspecify) - implementation(libs.gson) - implementation(libs.logback.classic) - implementation(libs.logstash.logback.encoder) - implementation(libs.telegram.bot.api) - implementation(libs.tika) - - constraints { - add('implementation', libs.jackson.core) - } - - testCompileOnly(libs.jspecify) - testImplementation(libs.hamcrest) - testImplementation(libs.junit.jupiter) - testImplementation(libs.mockwebserver) - testRuntimeOnly(libs.junit.platform) -} - -group = 'com.github.stickerifier' -version = '2.0' -description = 'Telegram bot to convert medias into the format required to be used as Telegram stickers' - -java.toolchain { - languageVersion = JavaLanguageVersion.of(26) - vendor = JvmVendorSpec.ADOPTIUM -} - -updateDaemonJvm { - languageVersion = JavaLanguageVersion.of(26) - vendor = JvmVendorSpec.ADOPTIUM -} - -def jlink = tasks.register('jlink', JlinkTask) { - options = ['--strip-debug', '--no-header-files', '--no-man-pages', '--ignore-modified-runtime'] - modules = [ - 'java.instrument', // for junit - 'java.naming', // for logback - 'java.sql', // for tika - 'jdk.unsupported' // for gson - ] - includeModulePath = false - javaCompiler = javaToolchains.compilerFor(java.toolchain) -} - -tasks.named(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaCompile) { - options.nullability.checking = "tests" -} - -test { - inputs.dir(jlink.map { it.outputDirectory.get().asFile }) - javaLauncher = providers.provider { new JlinkJavaLauncher(jlink.get()) } - - useJUnitPlatform() - jvmArgs('--enable-final-field-mutation=ALL-UNNAMED') - - testLogging { - events('started', 'passed', 'failed', 'skipped') - } -} - -def generateCohArchive = tasks.register('generateCohArchive', Exec) { - inputs.dir(jlink.map { it.outputDirectory.get().asFile }) - - def java = jlink.map { it.outputDirectory.file('jre/bin/java').get().asFile.absolutePath } - doFirst { commandLine(java.get(), '-XX:+UseCompactObjectHeaders', '-Xshare:dump') } -} - -jlink.configure { finalizedBy(generateCohArchive) } -test.mustRunAfter(generateCohArchive) -installDist.mustRunAfter(generateCohArchive) - -application { - mainClass = 'com.github.stickerifier.stickerify.runner.Main' - applicationDefaultJvmArgs = ['-XX:+UseCompactObjectHeaders', '-XX:+UseShenandoahGC', '-XX:ShenandoahGCMode=generational', '--enable-final-field-mutation=ALL-UNNAMED'] -} - -distributions { - main { - contents { - //noinspection GroovyAssignabilityCheck - from(jlink) - } - } -} - -tasks.named(ApplicationPlugin.TASK_START_SCRIPTS_NAME, CreateStartScripts) { - unixStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customUnixStartScript.txt') - windowsStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customWindowsStartScript.txt') -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..9eeda365 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,110 @@ +import com.github.stickerifier.stickerify.JlinkJavaLauncher +import com.github.stickerifier.stickerify.JlinkTask +import io.spring.gradle.nullability.NullabilityOptions +import org.gradle.internal.buildconfiguration.DaemonJvmPropertiesConfigurator + +plugins { + java + application + alias(libs.plugins.spring.nullability) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.gson) + implementation(libs.jspecify) + implementation(libs.logback.classic) + implementation(libs.logstash.logback.encoder) + implementation(libs.telegram.bot.api) + implementation(libs.tika) + + constraints { + add("implementation", libs.jackson.core) + } + + testImplementation(libs.hamcrest) + testImplementation(libs.junit.jupiter) + testImplementation(libs.mockwebserver) + testRuntimeOnly(libs.junit.platform) +} + +group = "com.github.stickerifier" +version = "2.0" +description = "Telegram bot to convert medias into the format required to be used as Telegram stickers" + +java.toolchain { + languageVersion = JavaLanguageVersion.of(26) + vendor = JvmVendorSpec.ADOPTIUM +} + +tasks.named(DaemonJvmPropertiesConfigurator.TASK_NAME) { + languageVersion = JavaLanguageVersion.of(26) + vendor = JvmVendorSpec.ADOPTIUM +} + +val jlink = tasks.register("jlink") { + description = "Generates a minimal JRE for the project." + + options = listOf("--strip-debug", "--no-header-files", "--no-man-pages", "--ignore-modified-runtime") + modules = listOf( + "java.instrument", // for junit + "java.naming", // for logback + "java.sql", // for tika + "jdk.unsupported" // for gson + ) + includeModulePath = false + javaCompiler = javaToolchains.compilerFor(java.toolchain) +} + +val CompileOptions.nullability: NullabilityOptions + get() = (this as ExtensionAware).extensions["nullability"] as NullabilityOptions + +tasks.named(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME) { + options.nullability.checking = "tests" +} + +tasks.test { + inputs.dir(jlink.map { it.outputDirectory.get().asFile }) + javaLauncher = providers.provider { JlinkJavaLauncher(jlink.get()) } + + useJUnitPlatform() + jvmArgs("--enable-final-field-mutation=ALL-UNNAMED") + + testLogging { + events("started", "passed", "failed", "skipped") + } +} + +val generateCohArchive = tasks.register("generateCohArchive") { + description = "Generates compact object headers archive for the jre." + + inputs.dir(jlink.map { it.outputDirectory.get().asFile }) + + val java = jlink.map { it.outputDirectory.file("jre/bin/java").get().asFile.absolutePath } + doFirst { commandLine(java.get(), "-XX:+UseCompactObjectHeaders", "-Xshare:dump") } +} + +jlink.configure { finalizedBy(generateCohArchive) } +tasks.test.configure { mustRunAfter(generateCohArchive) } +tasks.named(DistributionPlugin.TASK_INSTALL_NAME).configure { mustRunAfter(generateCohArchive) } + +application { + mainClass = "com.github.stickerifier.stickerify.runner.Main" + applicationDefaultJvmArgs = listOf("-XX:+UseCompactObjectHeaders", "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", "--enable-final-field-mutation=ALL-UNNAMED") +} + +distributions { + main { + contents { + from(jlink) + } + } +} + +tasks.named(ApplicationPlugin.TASK_START_SCRIPTS_NAME) { + (unixStartScriptGenerator as TemplateBasedScriptGenerator).template = resources.text.fromFile("src/main/resources/customUnixStartScript.txt") + (windowsStartScriptGenerator as TemplateBasedScriptGenerator).template = resources.text.fromFile("src/main/resources/customWindowsStartScript.txt") +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle.kts similarity index 87% rename from buildSrc/build.gradle rename to buildSrc/build.gradle.kts index 0d772edd..cec700b6 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id('java-library') + `java-library` } repositories { diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 1851cda2..00000000 --- a/settings.gradle +++ /dev/null @@ -1,5 +0,0 @@ -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' -} - -rootProject.name = 'Stickerify' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..21117e61 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "Stickerify" From 20a819e5d3520ac8e80522a3e308f2f01cb7b893 Mon Sep 17 00:00:00 2001 From: Enrico Martelli Date: Sat, 8 Aug 2026 18:49:46 +0200 Subject: [PATCH 3/4] Put archive generation inside jlink task --- build.gradle.kts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9eeda365..7b9dcc08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import com.github.stickerifier.stickerify.JlinkJavaLauncher import com.github.stickerifier.stickerify.JlinkTask import io.spring.gradle.nullability.NullabilityOptions import org.gradle.internal.buildconfiguration.DaemonJvmPropertiesConfigurator +import org.gradle.kotlin.dsl.support.serviceOf plugins { java @@ -46,7 +47,7 @@ tasks.named(DaemonJvmPropertiesConfigurator.TASK_NAME) { } val jlink = tasks.register("jlink") { - description = "Generates a minimal JRE for the project." + description = "Generates a minimal JRE for the project with compact object headers archive." options = listOf("--strip-debug", "--no-header-files", "--no-man-pages", "--ignore-modified-runtime") modules = listOf( @@ -57,6 +58,14 @@ val jlink = tasks.register("jlink") { ) includeModulePath = false javaCompiler = javaToolchains.compilerFor(java.toolchain) + + val execOps = serviceOf() + doLast { + val javaExe = outputDirectory.file("jre/bin/java").get().asFile.absolutePath + execOps.exec { + commandLine(javaExe, "-XX:+UseCompactObjectHeaders", "-Xshare:dump") + } + } } val CompileOptions.nullability: NullabilityOptions @@ -78,19 +87,6 @@ tasks.test { } } -val generateCohArchive = tasks.register("generateCohArchive") { - description = "Generates compact object headers archive for the jre." - - inputs.dir(jlink.map { it.outputDirectory.get().asFile }) - - val java = jlink.map { it.outputDirectory.file("jre/bin/java").get().asFile.absolutePath } - doFirst { commandLine(java.get(), "-XX:+UseCompactObjectHeaders", "-Xshare:dump") } -} - -jlink.configure { finalizedBy(generateCohArchive) } -tasks.test.configure { mustRunAfter(generateCohArchive) } -tasks.named(DistributionPlugin.TASK_INSTALL_NAME).configure { mustRunAfter(generateCohArchive) } - application { mainClass = "com.github.stickerifier.stickerify.runner.Main" applicationDefaultJvmArgs = listOf("-XX:+UseCompactObjectHeaders", "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", "--enable-final-field-mutation=ALL-UNNAMED") From 1134b0ca03676a2a27507835d56b7aceb4ec9531 Mon Sep 17 00:00:00 2001 From: Enrico Martelli Date: Sat, 8 Aug 2026 19:31:51 +0200 Subject: [PATCH 4/4] Added random test runner --- src/test/resources/junit-platform.properties | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/test/resources/junit-platform.properties diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..acfa9e5a --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$Random +junit.jupiter.testmethod.order.default=org.junit.jupiter.api.MethodOrderer$Random