build: use composite builds instead of buildSrc

This commit is contained in:
Linnea Gräf
2025-07-06 12:03:13 +02:00
parent 08c81862af
commit 6c3f833362
22 changed files with 429 additions and 64 deletions

View File

@@ -0,0 +1,13 @@
import java.io.File
fun parseEnvFile(file: File): Map<String, String> {
if (!file.exists()) return mapOf()
val map = mutableMapOf<String, String>()
for (line in file.readText().lines()) {
if (line.isEmpty() || line.startsWith("#")) continue
val parts = line.split("=", limit = 2)
map[parts[0]] = parts.getOrNull(1) ?: ""
}
return map
}

View File

@@ -0,0 +1,80 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import java.io.Serializable
import net.fabricmc.accesswidener.AccessWidenerReader
import net.fabricmc.accesswidener.AccessWidenerWriter
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream
import org.gradle.api.file.FileTreeElement
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
open class FabricModTransform : ResourceTransformer {
enum class AccessWidenerInclusion : Serializable {
ALL,
NONE,
}
@get:Input
var mergeAccessWideners: AccessWidenerInclusion = AccessWidenerInclusion.ALL
@get:Internal
internal var mergedFmj: JsonObject? = null
@get:Internal
internal val foundAccessWideners = AccessWidenerWriter()
@get:Internal
internal var foundAnyAccessWidener = false
override fun canTransformResource(element: FileTreeElement): Boolean {
if (mergeAccessWideners == AccessWidenerInclusion.ALL && element.name.endsWith(".accesswidener"))
return true
return element.path == "fabric.mod.json"
}
override fun transform(context: TransformerContext) {
if (context.path.endsWith(".accesswidener")) {
foundAnyAccessWidener = true
// TODO: allow filtering for only those mentioned in a fabric.mod.json, potentially
context.inputStream.use { stream ->
AccessWidenerReader(foundAccessWideners).read(stream.bufferedReader())
}
return
}
// TODO: mixins.json relocations
val fmj = context.inputStream.use { stream ->
Gson().fromJson(stream.bufferedReader(), JsonObject::class.java)
}
val mergedFmj = this.mergedFmj
println("${fmj["id"]} is first? ${mergedFmj == null}")
if (mergedFmj == null) {
this.mergedFmj = fmj
} else {
// TODO: merge stuff
}
}
override fun hasTransformedResource(): Boolean {
return mergedFmj != null
}
override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
val mergedFmj = mergedFmj!!
if (foundAnyAccessWidener) {
val awFile = mergedFmj["accessWidener"]
require(awFile is JsonPrimitive && awFile.isString)
os.putNextEntry(ZipEntry(awFile.asString))
os.write(foundAccessWideners.write())
os.closeEntry()
}
os.putNextEntry(ZipEntry("fabric.mod.json"))
os.write(mergedFmj.toString().toByteArray())
os.closeEntry()
}
}

View File

@@ -0,0 +1,70 @@
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import java.io.File
import java.util.zip.ZipInputStream
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import kotlin.io.path.createDirectories
import kotlin.io.path.outputStream
abstract class InnerJarsUnpacker : DefaultTask() {
@get:InputFiles
abstract val inputJars: ConfigurableFileCollection
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
private fun getFabricModJson(inputFile: File): JsonObject {
inputFile.inputStream().use {
val zis = ZipInputStream(it)
while (true) {
val entry = zis.nextEntry ?: error("Failed to find fabric.mod.json")
if (entry.name == "fabric.mod.json") {
return Gson().fromJson(zis.reader(), JsonObject::class.java)
}
}
}
}
@TaskAction
fun unpack() {
inputJars.forEach { inputFile ->
val fabricModObject = getFabricModJson(inputFile)
val jars = fabricModObject["jars"] as? JsonArray ?: error("No jars to unpack in $inputFile")
val jarPaths = jars.map {
((it as? JsonObject)?.get("file") as? JsonPrimitive)?.asString
?: error("Invalid Jar $it in $inputFile")
}
extractJars(inputFile, jarPaths)
}
}
private fun extractJars(inputFile: File, jarPaths: List<String>) {
val outputFile = outputDir.get().asFile.toPath()
val jarPathSet = jarPaths.toMutableSet()
inputFile.inputStream().use {
val zis = ZipInputStream(it)
while (true) {
val entry = zis.nextEntry ?: break
if (jarPathSet.remove(entry.name)) {
val resolvedPath = outputFile.resolve(entry.name)
resolvedPath.parent.createDirectories()
resolvedPath.outputStream().use { os ->
zis.copyTo(os)
}
}
}
}
if (jarPathSet.isNotEmpty()) {
error("Could not extract all jars: $jarPathSet")
}
}
}

View File

@@ -0,0 +1,41 @@
import java.net.URI
import java.util.zip.ZipInputStream
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class RepoDownload : DefaultTask() {
@get:Input
abstract val hash: Property<String>
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
init {
outputDirectory.convention(project.layout.buildDirectory.dir("extracted-test-repo"))
}
@TaskAction
fun performDownload() {
val outputDir = outputDirectory.asFile.get().absoluteFile
outputDir.mkdirs()
URI("https://github.com/notEnoughUpdates/notEnoughUpdates-rEPO/archive/${hash.get()}.zip").toURL().openStream()
.let(::ZipInputStream)
.use { zipInput ->
while (true) {
val entry = zipInput.nextEntry ?: break
val destination = outputDir.resolve(
entry.name.substringAfter('/')).absoluteFile
require(outputDir in generateSequence(destination) { it.parentFile })
if (entry.isDirectory) continue
destination.parentFile.mkdirs()
destination.outputStream().use { output ->
zipInput.copyTo(output)
}
}
}
}
}

View File

@@ -0,0 +1 @@
group = "moe.nea.firmament"

View File

@@ -0,0 +1,2 @@
apply(plugin = "firmament.base")
apply(plugin = "firmament.repositories")

View File

@@ -0,0 +1,5 @@
apply(plugin = "moe.nea.licenseextractificator")
configure<moe.nea.licenseextractificator.LicenseExtension> {
addExtraLicenseMatchers()
}

View File

@@ -0,0 +1,46 @@
repositories {
mavenCentral()
maven("https://maven.terraformersmc.com/releases/")
maven("https://maven.shedaniel.me")
maven("https://maven.fabricmc.net")
maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1")
maven("https://api.modrinth.com/maven") {
content {
includeGroup("maven.modrinth")
}
}
maven("https://repo.sleeping.town") {
content {
includeGroup("com.unascribed")
}
}
ivy("https://github.com/HotswapProjects/HotswapAgent/releases/download") {
patternLayout {
artifact("[revision]/[artifact]-[revision].[ext]")
}
content {
includeGroup("virtual.github.hotswapagent")
}
metadataSources {
artifact()
}
}
maven("https://server.bbkr.space/artifactory/libs-release")
maven("https://repo.nea.moe/releases")
maven("https://maven.notenoughupdates.org/releases")
maven("https://repo.nea.moe/mirror")
maven("https://jitpack.io/") {
content {
includeGroupByRegex("(com|io)\\.github\\..+")
excludeModule("io.github.cottonmc", "LibGui")
}
}
maven("https://repo.hypixel.net/repository/Hypixel/")
maven("https://maven.azureaaron.net/snapshots")
maven("https://maven.azureaaron.net/releases")
maven("https://www.cursemaven.com")
maven("https://maven.isxander.dev/releases") {
name = "Xander Maven"
}
mavenLocal()
}

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
//
// SPDX-License-Identifier: CC0-1.0
import moe.nea.licenseextractificator.LicenseExtension
fun LicenseExtension.addExtraLicenseMatchers() {
solo {
name = "Firmament"
description = "A Hypixel SkyBlock mod"
developer("Linnea Gräf") {
webPresence = "https://nea.moe/"
}
spdxLicense.`GPL-3-0-or-later`()
webPresence = "https://git.nea.moe/nea/Firmament/"
}
match {
if (group == "net.minecraft") useLicense {
name = "Minecraft"
description = "Minecraft - The critically acclaimed video game"
license("All Rights Reserved", "https://www.minecraft.net/en-us/eula")
developer("Mojang") {
webPresence = "https://mojang.com"
}
webPresence = "https://www.minecraft.net/en-us"
}
if (module == "architectury") useLicense {
name = "Architectury API"
description = "An intermediary api aimed at easing development of multiplatform mods."
spdxLicense.`LGPL-3-0-or-later`()
developer("Architectury") {
webPresence = "https://docs.architectury.dev/"
}
webPresence = "https://github.com/architectury/architectury-api"
}
if (module.startsWith("RoughlyEnoughItems")) useLicense {
name = module
description = "Your recipe viewer mod for 1.13+."
spdxLicense.MIT()
developer("Shedaniel") {
webPresence = "https://shedaniel.me/"
}
webPresence = "https://github.com/shedaniel/RoughlyEnoughItems"
}
if (module == "cloth-config") useLicense {
name = "Cloth Config"
description = "Client sided configuration API"
spdxLicense.`LGPL-3-0-or-later`()
developer("Shedaniel") {
webPresence = "https://shedaniel.me/"
}
webPresence = "https://github.com/shedaniel/cloth-config"
}
if (module == "basic-math") useLicense {
name = "Cloth BasicMath"
description = "Basic Math Operations"
spdxLicense.Unlicense()
developer("Shedaniel") {
webPresence = "https://shedaniel.me/"
}
webPresence = "https://github.com/shedaniel/cloth-basic-math"
}
if (module == "fabric-language-kotlin") useLicense {
name = "Fabric Language Kotlin"
description = "Kotlin Language Support for Fabric mods"
webPresence = "https://github.com/FabricMC/fabric-language-kotlin"
spdxLicense.`Apache-2-0`()
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
}
if (group == "com.mojang") useLicense {
name = module
description = "Mojang library packaged by Minecraft"
}
}
module("net.fabricmc", "yarn") {
name = "Yarn"
description = "Libre Minecraft mappings, free to use for everyone. No exceptions."
spdxLicense.`CC0-1-0`()
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
webPresence = "https://github.com/FabricMC/yarn/"
}
module("com.mojang", "datafixerupper") {
name = "DataFixerUpper"
description =
"A set of utilities designed for incremental building, merging and optimization of data transformations."
spdxLicense.MIT()
developer("Mojang") {
webPresence = "https://mojang.com"
}
webPresence = "https://github.com/Mojang/DataFixerUpper"
}
module("com.mojang", "brigadier") {
name = "Brigadier"
description = "Brigadier is a command parser & dispatcher, designed and developed for Minecraft: Java Edition."
spdxLicense.MIT()
developer("Mojang") {
webPresence = "https://mojang.com"
}
webPresence = "https://github.com/Mojang/brigadier"
}
module("net.fabricmc", "tiny-remapper") {
name = "Tiny Remapper"
description = "Tiny JAR remapping tool"
spdxLicense.`LGPL-3-0-or-later`()
webPresence = "https://github.com/FabricMC/tiny-remapper"
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
}
module("net.fabricmc", "sponge-mixin") {
name = "Mixin"
description = "Mixin is a trait/mixin framework for Java using ASM"
spdxLicense.MIT()
webPresence = "https://github.com/FabricMC/mixin"
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
developer("SpongePowered") {
webPresence = "https://spongepowered.org/"
}
}
module("net.fabricmc", "tiny-mappings-parser") {
name = "Tiny Mappings Parser"
webPresence = "https://github.com/fabricMC/tiny-mappings-parser"
description = "Library for parsing .tiny mapping files"
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
spdxLicense.`Apache-2-0`()
}
module("net.fabricmc", "fabric-loader") {
name = "Fabric Loader"
description = " Fabric's mostly-version-independent mod loader."
spdxLicense.`Apache-2-0`()
developer("FabricMC") {
webPresence = "https://fabricmc.net/"
}
webPresence = "https://github.com/FabricMC/fabric-loader/"
}
}

View File

@@ -0,0 +1,25 @@
fun execString(vararg args: String): String {
val pb = ProcessBuilder(*args)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.start()
pb.waitFor()
return pb.inputStream.readAllBytes().decodeToString().trim()
}
private val tag = "([0-9.]+)(?:\\+[^-]*)?".toRegex()
private val tagOffset = "([0-9.]+)(?:\\+.*)?-([0-9]+)-(.+)".toRegex()
inline fun <T> Regex.useMatcher(string: String, block: (MatchResult) -> T): T? {
return matchEntire(string)?.let(block)
}
fun getGitTagInfo(mcVersion: String): String {
val str = execString("git", "describe", "--tags", "HEAD")
tag.useMatcher(str) {
return it.groupValues[1] + "+mc$mcVersion"
}
tagOffset.useMatcher(str) {
return it.groupValues[1] + "-dev+mc$mcVersion+" + it.groupValues[3]
}
return "nogitversion+mc$mcVersion"
}