From 5a94b7f63b051d5ac670a26749ec3f27cf6cb693 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?=
<93977077+MukjepScarlet@users.noreply.github.com>
Date: Wed, 4 Jun 2025 22:08:30 +0800
Subject: [PATCH] refactor(gradle): rewrite with kts (#6175)
---
build.gradle | 440 -------------------------
build.gradle.kts | 404 +++++++++++++++++++++++
buildSrc/.gitignore | 3 +
buildSrc/build.gradle.kts | 21 ++
buildSrc/src/main/kotlin/extensions.kt | 112 +++++++
buildSrc/src/main/kotlin/tasks.kt | 92 ++++++
gradle.properties | 13 +-
settings.gradle => settings.gradle.kts | 14 +-
8 files changed, 653 insertions(+), 446 deletions(-)
delete mode 100644 build.gradle
create mode 100644 build.gradle.kts
create mode 100644 buildSrc/.gitignore
create mode 100644 buildSrc/build.gradle.kts
create mode 100644 buildSrc/src/main/kotlin/extensions.kt
create mode 100644 buildSrc/src/main/kotlin/tasks.kt
rename settings.gradle => settings.gradle.kts (73%)
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 169e641a45..0000000000
--- a/build.gradle
+++ /dev/null
@@ -1,440 +0,0 @@
-/*
- * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
- *
- * Copyright (c) 2015 - 2025 CCBlueX
- *
- * LiquidBounce is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * LiquidBounce is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with LiquidBounce. If not, see .
- */
-
-
-import groovy.json.JsonOutput
-import groovy.json.JsonSlurper
-import org.jetbrains.kotlin.gradle.dsl.JvmTarget
-
-def getContributors(repoOwner, repoName) {
- HttpURLConnection connection = null
-
- try {
- String githubToken = System.getenv("GITHUB_TOKEN")
-
- connection = "https://api.github.com/repos/${repoOwner}/${repoName}/contributors?per_page=100".toURL().openConnection().with {
- it as HttpURLConnection
- }.tap {
- requestMethod = "GET"
- connectTimeout = 5000
- readTimeout = 5000
- setRequestProperty("User-Agent", "LiquidBounce-App")
- setRequestProperty("X-GitHub-Api-Version", "2022-11-28")
- setRequestProperty("Accept", "application/vnd.github+json")
-
- if (githubToken?.trim()) {
- setRequestProperty("Authorization", "Bearer ${githubToken}")
- }
- }
-
- def responseCode = connection.responseCode
-
- if (responseCode in 200..299) {
- String responseText = connection.inputStream.withReader { it.text }
- try {
- def contributors = new JsonSlurper().parseText(responseText)
- .collect { it["login"] }
- .findAll { it && !(it as String).contains("[bot]") }
-
- return contributors
- } catch (Exception parseError) {
- logger.log(LogLevel.ERROR, "Failed to parse GitHub API response", parseError)
- return []
- }
- } else {
- String errorDetails = connection.errorStream?.withReader { it.text } ?: "No error details"
- logger.log(LogLevel.ERROR, "GitHub API request failed (HTTP ${responseCode}): ${errorDetails}")
- return []
- }
- } catch (Exception globalError) {
- logger.log(LogLevel.ERROR, "Failed to fetch contributors: ${globalError.message}", globalError)
- return []
- } finally {
- connection?.disconnect()
- }
-}
-
-
-plugins {
- id "fabric-loom"
- id "org.jetbrains.kotlin.jvm"
- id "com.gorylenko.gradle-git-properties" version "2.5.0"
- id "io.gitlab.arturbosch.detekt" version "1.23.6"
- id "com.github.node-gradle.node" version "7.1.0"
- id "org.jetbrains.dokka" version "1.9.10"
-}
-
-base {
- archivesBaseName = project.archives_base_name
- version = project.mod_version
- group = project.maven_group
-}
-
-// These libs are included by the game or other required mods
-final excludeProvidedLibs = {
- exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib"
-
- exclude group: "com.google.code.gson", module: "gson"
- exclude group: "net.java.dev.jna", module: "jna"
- exclude group: "commons-codec", module: "commons-codec"
- exclude group: "commons-io", module: "commons-io"
- exclude group: "org.apache.commons", module: "commons-compress"
- exclude group: "org.apache.commons", module: "commons-lang3"
- exclude group: "org.apache.logging.log4j", module: "log4j-core"
- exclude group: "org.apache.logging.log4j", module: "log4j-api"
- exclude group: "org.apache.logging.log4j", module: "log4j-slf4j-impl"
- exclude group: "org.slf4j", module: "slf4j-api"
- exclude group: "com.mojang", module: "authlib"
-
- exclude group: "io.netty", module: "netty-all"
- exclude group: "io.netty", module: "netty-buffer"
- exclude group: "io.netty", module: "netty-codec"
- exclude group: "io.netty", module: "netty-common"
- exclude group: "io.netty", module: "netty-handler"
- exclude group: "io.netty", module: "netty-resolver"
- exclude group: "io.netty", module: "netty-transport"
- exclude group: "io.netty", module: "netty-transport-native-unix-common"
-}
-
-configurations {
- includeDependency excludeProvidedLibs
- includeModDependency excludeProvidedLibs
-
- include.extendsFrom includeModDependency
- modImplementation.extendsFrom includeModDependency
- modCompileOnlyApi.extendsFrom includeModDependency
-}
-
-repositories {
- mavenCentral()
- mavenLocal()
- maven { url "https://maven.ccbluex.net/releases" }
- maven { url = "https://maven.fabricmc.net/" }
- maven {
- name = "Jitpack"
- url = "https://jitpack.io"
- }
- maven {
- name = "TerraformersMC"
- url = "https://maven.terraformersmc.com/"
- }
- maven {
- name = "ViaVersion"
- url = "https://repo.viaversion.com/"
- }
- maven {
- name = "modrinth"
- url = "https://api.modrinth.com/maven"
- }
- maven {
- name = "OpenCollab Snapshots"
- url = "https://repo.opencollab.dev/maven-snapshots/"
- }
- maven {
- name = "Lenni0451"
- url = "https://maven.lenni0451.net/everything"
- }
-}
-
-loom {
- accessWidenerPath = file("src/main/resources/liquidbounce.accesswidener")
-}
-
-dependencies {
- // Minecraft
- minecraft "com.mojang:minecraft:${project.minecraft_version}"
- mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
-
- // Fabric
- modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
- modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
- modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}"
-
- // Mod menu
- modImplementation "com.terraformersmc:modmenu:${project.mod_menu_version}"
-
- // Recommended mods (on IDE)
- modImplementation "maven.modrinth:sodium:${project.sodium_version}"
- modImplementation "maven.modrinth:lithium:${project.lithium_version}"
-
- // ViaFabricPlus
- modImplementation "com.viaversion:viafabricplus-api:${project.viafabricplus_version}"
- modRuntimeOnly "com.viaversion:viafabricplus:${project.viafabricplus_version}"
-
- // Minecraft Authlib
- includeDependency "com.github.CCBlueX:mc-authlib:${project.mc_authlib_version}"
-
- // JCEF Support
- includeModDependency "com.github.CCBlueX:mcef:${project.mcef_version}"
- includeDependency "net.ccbluex:netty-httpserver:2.2.1"
-
- // Discord RPC Support
- includeDependency "com.github.CCBlueX:DiscordIPC:4.0.0"
-
- // ScriptAPI
- includeDependency "net.fabricmc:tiny-mappings-parser:0.3.0+build.17"
- includeDependency "org.graalvm.polyglot:polyglot:$polyglot_version"
- includeDependency "org.graalvm.polyglot:js-community:$polyglot_version"
- includeDependency "org.graalvm.polyglot:tools-community:$polyglot_version"
-// includeDependency "org.graalvm.polyglot:python-community:$polyglot_version"
-// includeDependency "org.graalvm.polyglot:wasm-community:$polyglot_version"
-// includeDependency "org.graalvm.polyglot:java-community:$polyglot_version"
-// includeDependency "org.graalvm.polyglot:ruby-community:$polyglot_version"
-// includeDependency "org.graalvm.polyglot:llvm-native-community:$polyglot_version"
-
- // Machine Learning
- includeDependency "ai.djl:api:${djl_version}"
- includeDependency "ai.djl.pytorch:pytorch-engine:${djl_version}"
-// runtimeOnly "ai.djl.mxnet:mxnet-engine:${djl_version}"
-// runtimeOnly "ai.djl.tensorflow:tensorflow-engine:${djl_version}"
-
- // HTTP library
- includeDependency "com.squareup.okhttp3:okhttp:5.0.0-alpha.16"
-
- // SOCKS5 Proxy Support
- includeDependency "io.netty:netty-handler-proxy:4.1.97.Final"
-
- // Update Checker
- includeDependency "com.vdurmont:semver4j:3.1.0"
-
- // Name Protect
- includeDependency "org.ahocorasick:ahocorasick:0.6.3"
-
- // Test libraries
- testImplementation "org.junit.jupiter:junit-jupiter:5.13.0"
- testRuntimeOnly "org.junit.platform:junit-platform-launcher"
-
- // Fix nullable annotations
- compileOnly "com.google.code.findbugs:jsr305:3.0.2"
-
- afterEvaluate {
- configurations.includeDependency.incoming.resolutionResult.allDependencies.each {
- dependencies.include(dependencies.implementation(dependencies.compileOnlyApi(it.requested.toString()) {
- transitive = false
- }))
- }
- }
-}
-
-processResources {
- String contributors = JsonOutput.prettyPrint(JsonOutput.toJson(getContributors("CCBlueX", "LiquidBounce")))
-
- inputs.property "version", project.version
-
- inputs.property "minecraft_version", minecraft_version
- inputs.property "fabric_version", fabric_version
- inputs.property "loader_version", loader_version
- inputs.property "min_loader_version", min_loader_version
- inputs.property "fabric_kotlin_version", fabric_kotlin_version
- inputs.property "viafabricplus_version", viafabricplus_version
- inputs.property "contributors", contributors
-
- filesMatching("fabric.mod.json") {
- expand([
- version : project.version,
- minecraft_version : minecraft_version,
- fabric_version : fabric_version,
- loader_version : loader_version,
- min_loader_version : min_loader_version,
- contributors : contributors,
- fabric_kotlin_version: fabric_kotlin_version,
- viafabricplus_version: viafabricplus_version
- ])
- }
-}
-
-// The following code will include the theme into the build
-
-tasks.register("npmInstallTheme", NpmTask) {
- workingDir = file("src-theme")
- args = ["i"]
- doLast {
- println "Successfully installed dependencies for theme"
- }
-
- inputs.files("src-theme/package.json", "src-theme/package-lock.json")
- outputs.dir("src-theme/node_modules")
-}
-
-tasks.register("buildTheme", NpmTask) {
- dependsOn "npmInstallTheme"
- workingDir = file("src-theme")
- args = ["run", "build"]
- doLast {
- println "Successfully build theme"
- }
-
- inputs.files(
- "src-theme/package.json",
- "src-theme/package-lock.json",
- "src-theme/bundle.cjs",
- "src-theme/rollup.config.js"
- )
- inputs.dir("src-theme/src")
- outputs.dir("src-theme/dist")
-}
-
-tasks.register("bundleTheme", NodeTask) {
- dependsOn "buildTheme"
- workingDir = file("src-theme")
- script = file("src-theme/bundle.cjs")
- doLast {
- println "Successfully attached theme to build"
- }
-
- // Incremental stuff
- inputs.files(
- "src-theme/package.json",
- "src-theme/package-lock.json",
- "src-theme/bundle.cjs",
- "src-theme/rollup.config.js"
- )
- inputs.dir("src-theme/src")
- inputs.dir("src-theme/public")
- inputs.dir("src-theme/dist")
- outputs.files("src-theme/resources/assets/liquidbounce/default_theme.zip")
-}
-
-sourceSets {
- main {
- resources {
- srcDirs "src-theme/resources"
- }
- }
-}
-
-processResources.dependsOn bundleTheme
-
-// ensure that the encoding is set to UTF-8, no matter what the system default is
-// this fixes some edge cases with special characters not displaying correctly
-// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
-tasks.withType(JavaCompile).configureEach {
- // ensure that the encoding is set to UTF-8, no matter what the system default is
- // this fixes some edge cases with special characters not displaying correctly
- // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
- // If Javadoc is generated, this must be specified in that task too.
- it.options.encoding = "UTF-8"
-
- // Minecraft 1.20.5 upwards uses Java 17.
- it.options.release = 21
-}
-
-tasks.withType(Test).configureEach {
- useJUnitPlatform()
- dependsOn(tasks.named("genSources"))
-}
-
-detekt {
- config.setFrom(file("${rootProject.projectDir}/config/detekt/detekt.yml"))
- buildUponDefaultConfig = true
- baseline = file("${rootProject.projectDir}/config/detekt/baseline.xml")
-}
-
-task detektProjectBaseline(type: io.gitlab.arturbosch.detekt.DetektCreateBaselineTask) {
- description = "Overrides current baseline."
- ignoreFailures.set(true)
- parallel.set(true)
- buildUponDefaultConfig.set(true)
- setSource(files(rootDir))
- config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
- baseline.set(file("$rootDir/config/detekt/baseline.xml"))
- include("**/*.kt")
- include("**/*.kts")
- exclude("**/resources/**")
- exclude("**/build/**")
-}
-
-tasks.register("verifyI18nJsonKeys") {
- def baselineFileName = "en_us.json"
-
- group = "verification"
- description = "Compare i18n JSON files with ${baselineFileName} as the baseline and report missing keys."
-
- def i18nDir = file("src/main/resources/resources/liquidbounce/lang")
-
- doLast {
- if (!i18nDir.exists() || !i18nDir.isDirectory()) {
- throw new GradleException("The specified directory ${i18nDir} does not exist or is not a directory.")
- }
-
- def baselineFile = new File(i18nDir, baselineFileName)
- if (!baselineFile.exists()) {
- throw new GradleException("Baseline file ${baselineFileName} not found in ${i18nDir}.")
- }
-
- def baseline = new JsonSlurper().parse(baselineFile)
-
- i18nDir.eachFile { file ->
- if (file.name.endsWith(".json") && file.name != baselineFileName) {
- def currentFile = new JsonSlurper().parse(file)
-
- def missingKeys = baseline.keySet() - currentFile.keySet()
-
- if (missingKeys.isEmpty()) {
- println "${file.name} is complete. No missing keys."
- } else {
- def limitedMissingKeys = missingKeys.take(5)
- def output = limitedMissingKeys.join(', ')
- if (missingKeys.size() > 5) {
- output += ", ..."
- }
- println "${file.name} is missing the following keys (${missingKeys.size()}): ${output}"
- }
- }
- }
- }
-}
-
-java {
- // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
- // if it is present.
- // If you remove this line, sources will not be generated.
- withSourcesJar()
-
- sourceCompatibility = JavaVersion.VERSION_21
- targetCompatibility = JavaVersion.VERSION_21
-}
-
-compileKotlin {
- compilerOptions {
- suppressWarnings = true
- jvmTarget = JvmTarget.JVM_21
- }
-}
-
-jar {
- // Rename the project"s license file to LICENSE_ to avoid conflicts
- from("LICENSE") {
- rename {
- "${it}_${project.archives_base_name}"
- }
- }
-
- from(configurations.mappings.collect { zipTree(it) }) {
- include "mappings/mappings.tiny"
- }
-}
-
-tasks.register("copyZipInclude", Copy) {
- from "zip_include/"
- into "build/libs/zip"
-}
-
-sourcesJar.dependsOn bundleTheme
-build.dependsOn copyZipInclude
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000000..dc16a20d9c
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,404 @@
+/*
+ * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
+ *
+ * Copyright (c) 2015 - 2025 CCBlueX
+ *
+ * LiquidBounce is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiquidBounce is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LiquidBounce. If not, see .
+ */
+
+import com.github.gradle.node.npm.task.NpmTask
+import com.github.gradle.node.task.NodeTask
+import groovy.json.JsonOutput
+import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
+
+plugins {
+ id("fabric-loom")
+ kotlin("jvm")
+ id("com.gorylenko.gradle-git-properties") version "2.5.0"
+ id("io.gitlab.arturbosch.detekt") version "1.23.6"
+ id("com.github.node-gradle.node") version "7.1.0"
+ id("org.jetbrains.dokka") version "1.9.10"
+}
+
+val archives_base_name: String by project
+val mod_version: String by project
+val maven_group: String by project
+
+val minecraft_version: String by project
+val fabric_version: String by project
+val loader_version: String by project
+val min_loader_version: String by project
+val fabric_kotlin_version: String by project
+val viafabricplus_version: String by project
+
+base {
+ archivesName = archives_base_name
+ version = mod_version
+ group = maven_group
+}
+
+/** Includes non-mod dependency recursively in the JAR file */
+val includeDependency: Configuration by configurations.creating
+
+/** Includes mod in the JAR file */
+val includeModDependency: Configuration by configurations.creating
+
+/**
+ * Provided by:
+ * - Minecraft
+ * - Mod dependencies
+ */
+fun Configuration.excludeProvidedLibs() = apply {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+
+ exclude(group = "com.google.code.gson", module = "gson")
+ exclude(group = "net.java.dev.jna", module = "jna")
+ exclude(group = "commons-codec", module = "commons-codec")
+ exclude(group = "commons-io", module = "commons-io")
+ exclude(group = "org.apache.commons", module = "commons-compress")
+ exclude(group = "org.apache.commons", module = "commons-lang3")
+ exclude(group = "org.apache.logging.log4j", module = "log4j-core")
+ exclude(group = "org.apache.logging.log4j", module = "log4j-api")
+ exclude(group = "org.apache.logging.log4j", module = "log4j-slf4j-impl")
+ exclude(group = "org.slf4j", module = "slf4j-api")
+ exclude(group = "com.mojang", module = "authlib")
+
+ // Note: from Netty HTTP Server, not all components are used
+ exclude(group = "io.netty", module = "netty-all")
+
+ exclude(group = "io.netty", module = "netty-buffer")
+ exclude(group = "io.netty", module = "netty-codec")
+ exclude(group = "io.netty", module = "netty-common")
+ exclude(group = "io.netty", module = "netty-handler")
+ exclude(group = "io.netty", module = "netty-resolver")
+ exclude(group = "io.netty", module = "netty-transport")
+ exclude(group = "io.netty", module = "netty-transport-native-unix-common")
+}
+
+includeDependency.excludeProvidedLibs()
+includeModDependency.excludeProvidedLibs()
+
+configurations.include.get().extendsFrom(includeModDependency)
+configurations.modApi.get().extendsFrom(includeModDependency)
+configurations.modCompileOnlyApi.get().extendsFrom(includeModDependency)
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+ maven {
+ name = "CCBlueX"
+ url = uri("https://maven.ccbluex.net/releases")
+ }
+ maven {
+ name = "Fabric"
+ url = uri("https://maven.fabricmc.net/")
+ }
+ maven {
+ name = "Jitpack"
+ url = uri("https://jitpack.io")
+ }
+ maven {
+ name = "TerraformersMC"
+ url = uri("https://maven.terraformersmc.com/")
+ }
+ maven {
+ name = "ViaVersion"
+ url = uri("https://repo.viaversion.com/")
+ }
+ maven {
+ name = "modrinth"
+ url = uri("https://api.modrinth.com/maven")
+ }
+ maven {
+ name = "OpenCollab Snapshots"
+ url = uri("https://repo.opencollab.dev/maven-snapshots/")
+ }
+ maven {
+ name = "Lenni0451"
+ url = uri("https://maven.lenni0451.net/everything")
+ }
+}
+
+loom {
+ accessWidenerPath = file("src/main/resources/liquidbounce.accesswidener")
+}
+
+dependencies {
+ // Minecraft
+ minecraft("com.mojang:minecraft:${minecraft_version}")
+ mappings("net.fabricmc:yarn:${project.property("yarn_mappings")}:v2")
+
+ // Fabric
+ modApi("net.fabricmc:fabric-loader:${loader_version}")
+ modApi("net.fabricmc.fabric-api:fabric-api:${fabric_version}")
+ modApi("net.fabricmc:fabric-language-kotlin:${fabric_kotlin_version}")
+
+ // Mod menu
+ modApi("com.terraformersmc:modmenu:${project.property("mod_menu_version")}")
+
+ // Recommended mods (on IDE)
+ modApi("maven.modrinth:sodium:${project.property("sodium_version")}")
+ modApi("maven.modrinth:lithium:${project.property("lithium_version")}")
+
+ // ViaFabricPlus
+ modApi("com.viaversion:viafabricplus-api:${viafabricplus_version}")
+ modRuntimeOnly("com.viaversion:viafabricplus:${viafabricplus_version}")
+
+ // Minecraft Authlib
+ includeDependency("com.github.CCBlueX:mc-authlib:${project.property("mc_authlib_version")}")
+
+ // JCEF Support
+ includeModDependency("com.github.CCBlueX:mcef:${project.property("mcef_version")}")
+ includeDependency("net.ccbluex:netty-httpserver:2.2.1")
+
+ // Discord RPC Support
+ includeDependency("com.github.CCBlueX:DiscordIPC:4.0.0")
+
+ // ScriptAPI
+ includeDependency("net.fabricmc:tiny-mappings-parser:0.3.0+build.17")
+ includeDependency("org.graalvm.polyglot:polyglot:${project.property("polyglot_version")}")
+ includeDependency("org.graalvm.polyglot:js-community:${project.property("polyglot_version")}")
+ includeDependency("org.graalvm.polyglot:tools-community:${project.property("polyglot_version")}")
+// includeDependency("org.graalvm.polyglot:python-community:${project.property("polyglot_version")}")
+// includeDependency("org.graalvm.polyglot:wasm-community:${project.property("polyglot_version")}")
+// includeDependency("org.graalvm.polyglot:java-community:${project.property("polyglot_version")}")
+// includeDependency("org.graalvm.polyglot:ruby-community:${project.property("polyglot_version")}")
+// includeDependency("org.graalvm.polyglot:llvm-native-community:${project.property("polyglot_version")}")
+
+ // Machine Learning
+ includeDependency("ai.djl:api:${project.property("djl_version")}")
+ includeDependency("ai.djl.pytorch:pytorch-engine:${project.property("djl_version")}")
+// runtimeOnly("ai.djl.mxnet:mxnet-engine:${project.property("djl_version")}")
+// runtimeOnly("ai.djl.tensorflow:tensorflow-engine:${project.property("djl_version")}")
+
+ // HTTP library
+ includeDependency("com.squareup.okhttp3:okhttp:5.0.0-alpha.16")
+
+ // SOCKS5 & HTTP Proxy Support
+ includeDependency("io.netty:netty-handler-proxy:4.1.97.Final")
+
+ // Update Checker
+ includeDependency("com.vdurmont:semver4j:3.1.0")
+
+ // Name Protect
+ includeDependency("org.ahocorasick:ahocorasick:0.6.3")
+
+ // Test libraries
+ testImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+
+ // Fix nullable annotations
+ compileOnly("com.google.code.findbugs:jsr305:3.0.2")
+
+ afterEvaluate {
+ includeDependency.incoming.resolutionResult.allDependencies.forEach {
+ val compileOnlyApiDependency = dependencies.compileOnlyApi(it.requested.toString()) {
+ isTransitive = false
+ }
+ val apiDependency = dependencies.api(compileOnlyApiDependency)!!
+
+ dependencies.include(apiDependency)
+ }
+ }
+}
+
+tasks.processResources {
+ dependsOn("bundleTheme")
+
+ val contributors = JsonOutput.prettyPrint(
+ JsonOutput.toJson(getContributors("CCBlueX", "LiquidBounce"))
+ )
+
+ inputs.property("version", mod_version)
+
+ inputs.property("minecraft_version", minecraft_version)
+ inputs.property("fabric_version", fabric_version)
+ inputs.property("loader_version", loader_version)
+ inputs.property("min_loader_version", min_loader_version)
+ inputs.property("fabric_kotlin_version", fabric_kotlin_version)
+ inputs.property("viafabricplus_version", viafabricplus_version)
+ inputs.property("contributors", contributors)
+
+ filesMatching("fabric.mod.json") {
+ expand(
+ mapOf(
+ "version" to mod_version,
+ "minecraft_version" to minecraft_version,
+ "fabric_version" to fabric_version,
+ "loader_version" to loader_version,
+ "min_loader_version" to min_loader_version,
+ "contributors" to contributors,
+ "fabric_kotlin_version" to fabric_kotlin_version,
+ "viafabricplus_version" to viafabricplus_version,
+ )
+ )
+ }
+}
+
+// The following code will include the theme into the build
+
+tasks.register("npmInstallTheme") {
+ workingDir = file("src-theme")
+ args.set(listOf("i"))
+ doLast {
+ logger.info("Successfully installed dependencies for theme")
+ }
+ inputs.files("src-theme/package.json", "src-theme/package-lock.json")
+ outputs.dir("src-theme/node_modules")
+}
+
+tasks.register("buildTheme") {
+ dependsOn("npmInstallTheme")
+ workingDir = file("src-theme")
+ args.set(listOf("run", "build"))
+ doLast {
+ logger.info("Successfully build theme")
+ }
+
+ inputs.files(
+ "src-theme/package.json",
+ "src-theme/package-lock.json",
+ "src-theme/bundle.cjs",
+ "src-theme/rollup.config.js"
+ )
+ inputs.dir("src-theme/src")
+ outputs.dir("src-theme/dist")
+}
+
+tasks.register("bundleTheme") {
+ dependsOn("buildTheme")
+ workingDir = file("src-theme")
+ script = file("src-theme/bundle.cjs")
+ doLast {
+ logger.info("Successfully attached theme to build")
+ }
+
+ // Incremental stuff
+ inputs.files(
+ "src-theme/package.json",
+ "src-theme/package-lock.json",
+ "src-theme/bundle.cjs",
+ "src-theme/rollup.config.js"
+ )
+ inputs.dir("src-theme/src")
+ inputs.dir("src-theme/public")
+ inputs.dir("src-theme/dist")
+ outputs.files("src-theme/resources/assets/liquidbounce/default_theme.zip")
+}
+
+sourceSets {
+ main {
+ resources {
+ srcDirs("src-theme/resources")
+ }
+ }
+}
+
+// ensure that the encoding is set to UTF-8, no matter what the system default is
+// this fixes some edge cases with special characters not displaying correctly
+// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
+tasks.withType().configureEach {
+ // ensure that the encoding is set to UTF-8, no matter what the system default is
+ // this fixes some edge cases with special characters not displaying correctly
+ // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
+ // If Javadoc is generated, this must be specified in that task too.
+ options.encoding = "UTF-8"
+
+ // Minecraft 1.21.1 upwards uses Java 21.
+ options.release = 21
+}
+
+tasks.withType().configureEach {
+ useJUnitPlatform()
+ dependsOn("genSources")
+}
+
+// Detekt check
+
+detekt {
+ config.setFrom(file("${rootProject.projectDir}/config/detekt/detekt.yml"))
+ buildUponDefaultConfig = true
+ baseline = file("${rootProject.projectDir}/config/detekt/baseline.xml")
+}
+
+tasks.register("detektProjectBaseline") {
+ description = "Overrides current baseline."
+ ignoreFailures.set(true)
+ parallel.set(true)
+ buildUponDefaultConfig.set(true)
+ setSource(files(rootDir))
+ config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
+ baseline.set(file("$rootDir/config/detekt/baseline.xml"))
+ include("**/*.kt")
+ include("**/*.kts")
+ exclude("**/resources/**")
+ exclude("**/build/**")
+}
+
+// i18n check
+
+tasks.register("verifyI18nJsonKeys") {
+ val baselineFileName = "en_us.json"
+
+ group = "verification"
+ description = "Compare i18n JSON files with $baselineFileName as the baseline and report missing keys."
+
+ val languageFolder = file("src/main/resources/resources/liquidbounce/lang")
+ baselineFile.set(languageFolder.resolve(baselineFileName))
+ files.from(languageFolder.listFiles().filter { it.extension.equals("json", ignoreCase = true) })
+ consoleOutputCount.set(5)
+}
+
+java {
+ // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+ // if it is present.
+ // If you remove this line, sources will not be generated.
+ withSourcesJar()
+
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+}
+
+kotlin {
+ compilerOptions {
+ suppressWarnings = true
+ jvmToolchain(21)
+ }
+}
+
+tasks.jar {
+ // Rename the project's license file to LICENSE_ to avoid conflicts
+ from("LICENSE") {
+ rename {
+ "${it}_${archives_base_name}"
+ }
+ }
+
+ from(files(project.configurations.mappings.get().map(::zipTree))) {
+ include("mappings/mappings.tiny")
+ }
+}
+
+tasks.register("copyZipInclude") {
+ from("zip_include/")
+ into("build/libs/zip")
+}
+
+tasks.named("sourcesJar") {
+ dependsOn("bundleTheme")
+}
+
+tasks.named("build") {
+ dependsOn("copyZipInclude")
+}
diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore
new file mode 100644
index 0000000000..5d8581f71f
--- /dev/null
+++ b/buildSrc/.gitignore
@@ -0,0 +1,3 @@
+/build/
+/.gradle/
+/.kotlin/
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 0000000000..370fb8211d
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ `kotlin-dsl`
+}
+
+group = "net.ccbluex"
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation(kotlin("test"))
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
+
+kotlin {
+ jvmToolchain(21)
+}
diff --git a/buildSrc/src/main/kotlin/extensions.kt b/buildSrc/src/main/kotlin/extensions.kt
new file mode 100644
index 0000000000..b43c0b2ec2
--- /dev/null
+++ b/buildSrc/src/main/kotlin/extensions.kt
@@ -0,0 +1,112 @@
+/*
+ * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
+ *
+ * Copyright (c) 2015 - 2025 CCBlueX
+ *
+ * LiquidBounce is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiquidBounce is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LiquidBounce. If not, see .
+ */
+import groovy.json.JsonSlurper
+import org.gradle.api.Task
+import java.net.URI
+import java.net.http.HttpClient
+import java.net.http.HttpRequest
+import java.net.http.HttpResponse
+import java.time.Duration
+import java.util.concurrent.Executors
+import java.util.regex.Pattern
+
+private val httpClient = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(5))
+ .executor(Executors.newVirtualThreadPerTaskExecutor())
+ .build()
+
+private inline val HttpResponse<*>.isSuccessful get() = statusCode() in 200..299
+
+/**
+ * [API Docs](https://docs.github.com/zh/rest/collaborators/collaborators?apiVersion=2022-11-28)
+ */
+fun Task.getContributors(repoOwner: String, repoName: String): List = try {
+ val githubToken: String? = System.getenv("GITHUB_TOKEN")
+
+ fun HttpRequest.Builder.generalSettings() = this
+ .timeout(Duration.ofSeconds(10))
+ .header("User-Agent", "LiquidBounce-App")
+ .header("X-GitHub-Api-Version", "2022-11-28")
+ .header("Accept", "application/vnd.github+json")
+ .apply {
+ if (!githubToken.isNullOrBlank())
+ header("Authorization", "Bearer $githubToken")
+ }
+
+ fun HttpClient.fetchLastPage(baseUrl: String, perPage: Int): Int {
+ val request = HttpRequest.newBuilder()
+ .uri(URI("$baseUrl?per_page=$perPage"))
+ .HEAD()
+ .generalSettings()
+ .build()
+
+ val response = send(request, HttpResponse.BodyHandlers.discarding())
+
+ return if (response.isSuccessful) {
+ val linkHeader = response.headers().firstValue("link").orElse("")
+ val pattern = Pattern.compile("&page=(\\d+)>; rel=\"last\"")
+ val matcher = pattern.matcher(linkHeader)
+ return if (matcher.find()) matcher.group(1).toInt() else 1
+ } else {
+ logger.error("HEAD request to ${response.uri()} failed with status: ${response.statusCode()}")
+ 1
+ }
+ }
+
+ val baseUrl = "https://api.github.com/repos/${repoOwner}/${repoName}/contributors"
+
+ val perPage = 100 // Maximum is 100
+ val maxPage = httpClient.fetchLastPage(baseUrl, perPage)
+
+ (1..maxPage).map { page ->
+ val request = HttpRequest.newBuilder()
+ .uri(URI("$baseUrl?per_page=$perPage&page=$page"))
+ .GET()
+ .generalSettings()
+ .build()
+
+ httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
+ .thenApply { response ->
+ if (response.isSuccessful) {
+ try {
+ @Suppress("UNCHECKED_CAST")
+ response.body().use { inputStream ->
+ (JsonSlurper().parse(inputStream) as List