Initial commit

This commit is contained in:
2026-03-23 11:14:33 +01:00
commit 0cceecdd39
24 changed files with 872 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
plugins {
id("de.siphalor.smcmtk.plugin")
}
description = "This is the main plugin providing utilities through a Gradle extension."
gradlePlugin {
val projectPlugin by plugins.creating {
id = "de.siphalor.minecraft-modding-toolkit.project-plugin"
implementationClass = "de.siphalor.minecraft_modding_toolkit.gradle.project_plugin.SmcmtkProjectPlugin"
}
}

View File

@@ -0,0 +1,35 @@
package de.siphalor.minecraft_modding_toolkit.gradle.project_plugin
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.file.RegularFile
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class SmcmtkLoomProxy(val project: Project, loomGradleExtensionApi: Any) {
val logger = lazy { Logging.getLogger(javaClass) }
val loom = loomGradleExtensionApi as LoomGradleExtensionAPI
fun configureOfficialMojangMappings(parchment: Provider<MinimalExternalModuleDependency>?) {
if (parchment == null) {
project.dependencies.add("mappings", loom.officialMojangMappings())
} else {
project.dependencies.add("mappings", loom.layered { layered ->
layered.officialMojangMappings()
layered.parchment(parchment.map { it.artifact { artifact -> artifact.type = "zip" } })
})
}
}
fun createRemapConfiguration(sourceSet: SourceSet) {
loom.createRemapConfigurations(sourceSet)
}
fun setAccessWidenerPath(file: RegularFile) {
loom.accessWidenerPath.set(file)
}
}

View File

@@ -0,0 +1,14 @@
package de.siphalor.minecraft_modding_toolkit.gradle.project_plugin
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.MapProperty
import org.gradle.api.tasks.SourceSet
abstract class SmcmtkProjectExtension(private val smcmtkProjectPlugin: SmcmtkProjectPlugin) {
abstract val mcProps: MapProperty<String, String>
fun useMojangMappings() = smcmtkProjectPlugin.configureMojangMappings()
fun createModConfigurations(sourceSets: List<SourceSet>) = smcmtkProjectPlugin.createModConfigurations(sourceSets)
fun useAccessWidener(file: RegularFile) = smcmtkProjectPlugin.configureAccessWidener(file)
}

View File

@@ -0,0 +1,128 @@
package de.siphalor.minecraft_modding_toolkit.gradle.project_plugin
import de.siphalor.minecraft_modding_toolkit.gradle.project_plugin.util.joinCamelCase
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.SourceSet
import org.gradle.language.jvm.tasks.ProcessResources
import java.util.*
import javax.inject.Inject
abstract class SmcmtkProjectPlugin : Plugin<Project> {
companion object {
private const val LOOM_PLAIN_PLUGIN_ID = "net.fabricmc.fabric-loom"
private const val LOOM_REMAP_PLUGIN_ID = "net.fabricmc.fabric-loom-remap"
private val MOD_BASE_CONFIGURATION_NAMES = arrayOf("api", "implementation", "compileOnly", "runtimeOnly", "localRuntime")
private val ACCESS_WIDENER_DECLARATION_PATTERN = Regex("^accessWidener\\s+v\\d+\\s+")
}
@get:Inject
protected abstract val providers: ProviderFactory
@get:Inject
protected abstract val projectLayout: ProjectLayout
private val logger = lazy { Logging.getLogger(javaClass) }
private lateinit var project: Project
private lateinit var extension: SmcmtkProjectExtension
private var loomProxy = lazy { project.extensions.findByName("loom")?.let { SmcmtkLoomProxy(project, it)} }
override fun apply(project: Project) {
this.project = project
this.extension = project.extensions.create("smcmtk", SmcmtkProjectExtension::class.java, this)
val minecraftVersionDescriptor = providers.gradleProperty("minecraft.version.descriptor")
val minecraftProperties = minecraftVersionDescriptor
.flatMap { providers.fileContents(projectLayout.settingsDirectory.file("gradle/mc-$it/gradle.properties")).asBytes }
.map { Properties().apply { load(it.inputStream())} }
extension.mcProps.convention(minecraftProperties.map { mapOf(*it.entries.map { entry -> entry.key.toString() to entry.value.toString() }.toTypedArray()) })
project.plugins.withId(LOOM_PLAIN_PLUGIN_ID) {
project.configurations.maybeCreate("namedElements")
.extendsFrom(project.configurations.getByName("apiElements"))
}
}
fun configureMojangMappings() {
project.plugins.withId(LOOM_REMAP_PLUGIN_ID) {
val versionCatalogs = project.extensions.getByType(VersionCatalogsExtension::class.java)
val mcLibsCatalog = versionCatalogs.find("mcLibs").orElse(null)
val parchment = mcLibsCatalog?.findLibrary("parchment")?.orElse(null)
loomProxy.value?.configureOfficialMojangMappings(parchment)
}
project.plugins.withId(LOOM_PLAIN_PLUGIN_ID) {
project.tasks.withType(ProcessResources::class.java).findByName("processResources")?.apply {
filesMatching("*.accesswidener") {
it.filter { line ->
if (line.startsWith("accessWidener")) {
line.replace("named", "official")
} else {
line
}
}
}
}
}
}
fun createModConfigurations(sourceSets: Collection<SourceSet>) {
project.plugins.withId(LOOM_REMAP_PLUGIN_ID) {
sourceSets.filter { it.name != "main" }
.forEach { sourceSet -> loomProxy.value?.createRemapConfiguration(sourceSet) }
}
project.plugins.withId(LOOM_PLAIN_PLUGIN_ID) {
sourceSets.forEach { sourceSet ->
val prefix = if (sourceSet.name == "main") "" else sourceSet.name
MOD_BASE_CONFIGURATION_NAMES.forEach { baseConfigurationName ->
val configurationName = joinCamelCase(prefix, baseConfigurationName)
project.configurations.findByName(configurationName)?.let { baseConfiguration ->
val configuration = project.configurations
.maybeCreate(joinCamelCase("mod", configurationName))
baseConfiguration.extendsFrom(configuration)
}
}
}
}
}
fun configureAccessWidener(file: RegularFile) {
logger.value.debug("Configuring access widener {}", file)
if (!file.asFile.isFile) {
logger.value.debug("Skipping access widener configuration for non-existing file {}", file)
return
}
val accessWidenerNamesType = if (project.plugins.hasPlugin(LOOM_PLAIN_PLUGIN_ID)) {
"official"
} else if (project.plugins.hasPlugin(LOOM_REMAP_PLUGIN_ID)) {
"named"
} else {
null
}
if (accessWidenerNamesType != null) {
logger.value.info("Changing access widener names type of $file to $accessWidenerNamesType")
file.asFile.writeText(file.asFile.readText(Charsets.UTF_8).lines().joinToString("\n") { line ->
ACCESS_WIDENER_DECLARATION_PATTERN.matchAt(line, 0)
?.let { match -> line.substring(match.range) + accessWidenerNamesType }
?: line
})
} else {
logger.value.warn("Could not determine access widener names type for $file")
}
loomProxy.value?.setAccessWidenerPath(file)
}
}

View File

@@ -0,0 +1,73 @@
package de.siphalor.minecraft_modding_toolkit.gradle.project_plugin.filter
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.io.FilterReader
import java.io.Reader
import java.io.StringReader
import java.nio.CharBuffer
class JsonMergeFilterReader(input: Reader) : FilterReader(input) {
companion object {
val GSON: Gson = GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create()
}
var merge: MutableMap<String, Any?> = LinkedHashMap()
val result = lazy {
val inputJson = GSON.fromJson(input, Any::class.java)
StringReader(GSON.toJson(update(inputJson, merge)))
}
private fun update(input: Any?, update: Any?): Any? {
return if (input is Collection<*> && update is Collection<*>) {
ArrayList(input).apply { addAll(update) }
} else if (input is Map<*, *> && update is Map<*, *>) {
LinkedHashMap(input).apply {
update.forEach { (key, value) -> put(key, update(input[key], value)) }
}
} else {
update
}
}
override fun read(): Int {
return result.value.read()
}
override fun read(cbuf: CharArray?): Int {
return result.value.read(cbuf)
}
override fun read(cbuf: CharArray?, off: Int, len: Int): Int {
return result.value.read(cbuf, off, len)
}
override fun read(target: CharBuffer): Int {
return result.value.read(target)
}
override fun skip(n: Long): Long {
return result.value.skip(n)
}
override fun ready(): Boolean {
return result.value.ready()
}
override fun markSupported(): Boolean {
return result.value.markSupported()
}
override fun mark(readAheadLimit: Int) {
result.value.mark(readAheadLimit)
}
override fun reset() {
result.value.reset()
}
override fun close() {
result.value.close()
}
}

View File

@@ -0,0 +1,6 @@
package de.siphalor.minecraft_modding_toolkit.gradle.project_plugin.util
fun joinCamelCase(vararg words: String) = words
.joinToString("") { word -> word.replaceFirstChar { if (it.isLowerCase()) it.uppercaseChar() else it } }
.replaceFirstChar { it.lowercaseChar() }