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>) + .mapNotNull { + if (it["type"] == "User") it["login"] as String else null + } + } + } catch (e: Exception) { + logger.error("Failed to parse GitHub API response for $repoOwner:$repoName", e) + emptyList() + } + } else { + logger.error("Failed to get GitHub API response for $repoOwner:$repoName (HTTP ${response.statusCode()}): ${response.body().bufferedReader().readText()}") + emptyList() + } + } + }.flatMapTo(ArrayList(perPage * maxPage)) { + it.get() + }.also { + logger.info("Successfully collected ${it.size} contributors") + } +} catch (e: Exception) { + logger.error("Failed to fetch contributors of $repoOwner:$repoName", e) + emptyList() +} diff --git a/buildSrc/src/main/kotlin/tasks.kt b/buildSrc/src/main/kotlin/tasks.kt new file mode 100644 index 0000000000..dba92b895a --- /dev/null +++ b/buildSrc/src/main/kotlin/tasks.kt @@ -0,0 +1,92 @@ +/* + * 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.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.TaskAction +import java.io.File + +abstract class CompareJsonKeysTask : DefaultTask() { + + /** + * Baseline file + */ + @get:InputFile + abstract val baselineFile: RegularFileProperty + + /** + * Files to check + */ + @get:InputFiles + abstract val files: ConfigurableFileCollection + + /** + * Logger output limitation of missing keys + */ + @get:Input + abstract val consoleOutputCount: Property + + init { + consoleOutputCount.convention(Int.MAX_VALUE) + } + + @TaskAction + fun run() { + val baselineFile = baselineFile.orNull?.asFile + + if (baselineFile == null || !baselineFile.exists()) { + throw GradleException("Baseline file $baselineFile not found") + } + + @Suppress("UNCHECKED_CAST") + fun File.readJsonObject() = inputStream().use(JsonSlurper()::parse) as Map + + val baseline = baselineFile.readJsonObject() + + val outputCount = consoleOutputCount.get().coerceAtLeast(1) + + for (file in files.files) { + if (file == baselineFile) { + continue + } + + val currentFile = file.readJsonObject() + + val missingKeys = baseline.keys - currentFile.keys + + if (missingKeys.isEmpty()) { + logger.info("${file.name} is complete. No missing keys.") + } else { + val output = missingKeys.joinToString( + separator = ", ", + limit = outputCount, + truncated = "..." + ) + logger.warn("${file.name} is missing the following keys (${missingKeys.size}): $output") + } + } + } + +} diff --git a/gradle.properties b/gradle.properties index c09a4dacf0..bb8932fc99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,18 @@ # along with LiquidBounce. If not, see . # kotlin.code.style=official -org.gradle.jvmargs=-Xms1024m -Xmx4096m +kotlin.incremental=true +kotlin.caching.enabled=true +kotlin.parallel.tasks.in.project=true + +# Gradle settings +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +#org.gradle.configuration-cache=true +# ZGC from JDK 21 +org.gradle.jvmargs=-Xms1024m -Xmx4096m -XX:+UseZGC + # Fabric Properties # Check these on https://fabricmc.net/versions.html minecraft_version=1.21.4 diff --git a/settings.gradle b/settings.gradle.kts similarity index 73% rename from settings.gradle rename to settings.gradle.kts index 4285e5c2fe..1b3208bea3 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -20,15 +20,19 @@ pluginManagement { repositories { maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' + name = "Fabric" + url = uri("https://maven.fabricmc.net/") } gradlePluginPortal() + mavenCentral() } plugins { - id 'fabric-loom' version loom_version - id 'org.jetbrains.kotlin.jvm' version kotlin_version + val loom_version: String by settings + val kotlin_version: String by settings + id("fabric-loom") version loom_version + kotlin("jvm") version kotlin_version } - } + +rootProject.name = "LiquidBounce"