fix: Isolate mixins in alternative source sets

This commit is contained in:
Linnea Gräf
2025-03-18 21:40:32 +01:00
parent 40ce4f8567
commit 4c5ddd2fdd
11 changed files with 205 additions and 29 deletions

View File

@@ -0,0 +1,12 @@
package moe.nea.firmament.compat.jade
import net.fabricmc.loader.api.FabricLoader
import moe.nea.firmament.util.compatloader.CompatMeta
import moe.nea.firmament.util.compatloader.ICompatMeta
@CompatMeta
object Compat : ICompatMeta {
override fun shouldLoad(): Boolean {
return FabricLoader.getInstance().isModLoaded("jade")
}
}

View File

@@ -0,0 +1,12 @@
package moe.nea.firmament.compat.rei
import net.fabricmc.loader.api.FabricLoader
import moe.nea.firmament.util.compatloader.CompatMeta
import moe.nea.firmament.util.compatloader.ICompatMeta
@CompatMeta
object Compat : ICompatMeta {
override fun shouldLoad(): Boolean {
return FabricLoader.getInstance().isModLoaded("roughlyenoughitems")
}
}

View File

@@ -0,0 +1,13 @@
package moe.nea.firmament.compat.gender
import net.fabricmc.loader.api.FabricLoader
import moe.nea.firmament.util.compatloader.CompatMeta
import moe.nea.firmament.util.compatloader.ICompatMeta
@CompatMeta
object Compat : ICompatMeta {
override fun shouldLoad(): Boolean {
return FabricLoader.getInstance().isModLoaded("wildfire_gender")
}
}

View File

@@ -1,6 +1,9 @@
package moe.nea.firmament.init; package moe.nea.firmament.init;
import moe.nea.firmament.util.ErrorUtil;
import moe.nea.firmament.util.compatloader.ICompatMeta;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@@ -94,7 +97,7 @@ public class AutoDiscoveryPlugin {
String norm = (className.substring(0, className.length() - ".class".length())) String norm = (className.substring(0, className.length() - ".class".length()))
.replace("\\", "/") .replace("\\", "/")
.replace("/", "."); .replace("/", ".");
if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) { if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".") && ICompatMeta.Companion.shouldLoad(norm)) {
mixins.add(norm.substring(getMixinPackage().length() + 1)); mixins.add(norm.substring(getMixinPackage().length() + 1));
} }
} }
@@ -125,24 +128,25 @@ public class AutoDiscoveryPlugin {
*/ */
public List<String> getMixins() { public List<String> getMixins() {
if (mixins != null) return mixins; if (mixins != null) return mixins;
System.out.println("Trying to discover mixins"); try {
mixins = new ArrayList<>(); System.out.println("Trying to discover mixins");
URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); mixins = new ArrayList<>();
System.out.println("Found classes at " + classUrl); URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
tryDiscoverFromContentFile(classUrl); System.out.println("Found classes at " + classUrl);
var classRoots = System.getProperty("firmament.classroots"); tryDiscoverFromContentFile(classUrl);
if (classRoots != null && !classRoots.isBlank()) { var classRoots = System.getProperty("firmament.classroots");
System.out.println("Found firmament class roots: " + classRoots); if (classRoots != null && !classRoots.isBlank()) {
for (String s : classRoots.split(File.pathSeparator)) { System.out.println("Found firmament class roots: " + classRoots);
if (s.isBlank()) { for (String s : classRoots.split(File.pathSeparator)) {
continue; if (s.isBlank()) {
} continue;
try { }
tryDiscoverFromContentFile(new File(s).toURI().toURL()); tryDiscoverFromContentFile(new File(s).toURI().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} }
} }
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
} }
return mixins; return mixins;
} }

11
src/main/kotlin/Compat.kt Normal file
View File

@@ -0,0 +1,11 @@
package moe.nea.firmament
import moe.nea.firmament.util.compatloader.CompatMeta
import moe.nea.firmament.util.compatloader.ICompatMeta
@CompatMeta
object Compat : ICompatMeta {
override fun shouldLoad(): Boolean {
return true
}
}

View File

@@ -31,6 +31,7 @@ import moe.nea.firmament.features.mining.PickaxeAbility
import moe.nea.firmament.features.mining.PristineProfitTracker import moe.nea.firmament.features.mining.PristineProfitTracker
import moe.nea.firmament.features.world.FairySouls import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.features.world.Waypoints import moe.nea.firmament.features.world.Waypoints
import moe.nea.firmament.util.compatloader.ICompatMeta
import moe.nea.firmament.util.data.DataHolder import moe.nea.firmament.util.data.DataHolder
object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "features", ::Config) { object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "features", ::Config) {
@@ -83,17 +84,18 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
fun subscribeEvents() { fun subscribeEvents() {
SubscriptionList.allLists.forEach { list -> SubscriptionList.allLists.forEach { list ->
runCatching { if (ICompatMeta.shouldLoad(list.javaClass.name))
list.provideSubscriptions { runCatching {
it.owner.javaClass.classes.forEach { list.provideSubscriptions {
runCatching { it.getDeclaredField("INSTANCE").get(null) } it.owner.javaClass.classes.forEach {
runCatching { it.getDeclaredField("INSTANCE").get(null) }
}
subscribeSingleEvent(it)
} }
subscribeSingleEvent(it) }.getOrElse {
// TODO: allow annotating source sets to specifically opt out of loading for mods, maybe automatically
Firmament.logger.info("Ignoring events from $list, likely due to a missing compat mod.", it)
} }
}.getOrElse {
// TODO: allow annotating source sets to specifically opt out of loading for mods, maybe automatically
Firmament.logger.info("Ignoring events from $list, likely due to a missing compat mod.", it)
}
} }
} }

View File

@@ -6,7 +6,7 @@ import kotlin.reflect.KClass
import kotlin.streams.asSequence import kotlin.streams.asSequence
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
abstract class CompatLoader<T : Any>(val kClass: Class<T>) { open class CompatLoader<T : Any>(val kClass: Class<T>) {
constructor(kClass: KClass<T>) : this(kClass.java) constructor(kClass: KClass<T>) : this(kClass.java)
val loader: ServiceLoader<T> = ServiceLoader.load(kClass) val loader: ServiceLoader<T> = ServiceLoader.load(kClass)

View File

@@ -0,0 +1,48 @@
package moe.nea.firmament.util.compatloader
import java.util.ServiceLoader
import moe.nea.firmament.events.subscription.SubscriptionList
import moe.nea.firmament.init.AutoDiscoveryPlugin
import moe.nea.firmament.util.ErrorUtil
/**
* Declares the compat meta interface for the current source set.
* This is used by [CompatLoader], [SubscriptionList], and [AutoDiscoveryPlugin]. Annotate a [ICompatMeta] object with
* this.
*/
annotation class CompatMeta
interface ICompatMetaGen {
fun owns(className: String): Boolean
val meta: ICompatMeta
}
interface ICompatMeta {
fun shouldLoad(): Boolean
companion object {
val allMetas = ServiceLoader
.load(ICompatMetaGen::class.java)
.toList()
fun shouldLoad(className: String): Boolean {
// TODO: replace this with a more performant package lookup
val meta = if (ErrorUtil.aggressiveErrors) {
val fittingMetas = allMetas.filter { it.owns(className) }
require(fittingMetas.size == 1) { "Orphaned or duplicate owned class $className (${fittingMetas.map { it.meta }}). Consider adding a @CompatMeta object." }
fittingMetas.single()
} else {
allMetas.firstOrNull { it.owns(className) }
}
return meta?.meta?.shouldLoad() ?: true
}
}
}
object CompatHelper {
fun isOwnedByPackage(className: String, vararg packages: String): Boolean {
// TODO: create package lookup structure once
val packageName = className.substringBeforeLast('.')
return packageName in packages
}
}

View File

@@ -0,0 +1,11 @@
package moe.nea.firmament.features.texturepack
import moe.nea.firmament.util.compatloader.CompatMeta
import moe.nea.firmament.util.compatloader.ICompatMeta
@CompatMeta
object Compat : ICompatMeta {
override fun shouldLoad(): Boolean {
return true
}
}

View File

@@ -0,0 +1,63 @@
package moe.nea.firmament.annotations.process
import com.google.auto.service.AutoService
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSName
class CompatMetaProcessor(val logger: KSPLogger, val codeGenerator: CodeGenerator, val sourceSetName: String) :
SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val files = resolver.getAllFiles().toList()
val packages = files.mapTo(mutableSetOf()) { it.packageName.asString() }
packages.add("moe.nea.firmament.annotations.generated.$sourceSetName")
val compatMeta = resolver.getSymbolsWithAnnotation("moe.nea.firmament.util.compatloader.CompatMeta")
.singleOrNull() as KSClassDeclaration? ?: return listOf()
val dependencies = Dependencies(aggregating = true, *files.toTypedArray())
val generatedFileName = "GeneratedCompat${sourceSetName.replaceFirstChar { it.uppercaseChar() }}"
val compatFile =
codeGenerator.createNewFile(dependencies, "moe.nea.firmament.annotations.generated.$sourceSetName", generatedFileName)
.bufferedWriter()
compatFile.appendLine("// This file is @generated by SubscribeAnnotationProcessor")
compatFile.appendLine("// Do not edit")
compatFile.appendLine("package moe.nea.firmament.annotations.generated.$sourceSetName")
compatFile.appendLine("class $generatedFileName : moe.nea.firmament.util.compatloader.ICompatMetaGen {")
compatFile.appendLine("""
override fun owns(className: String): Boolean {
return moe.nea.firmament.util.compatloader.CompatHelper.isOwnedByPackage(className, ${
packages.joinToString { "\"" + it + "\"" }
})
}
override val meta: moe.nea.firmament.util.compatloader.ICompatMeta
get() = ${compatMeta.qualifiedName!!.asString()}
""")
compatFile.appendLine("}")
compatFile.close()
val metaInf = codeGenerator.createNewFileByPath(
dependencies,
"META-INF/services/moe.nea.firmament.util.compatloader.ICompatMetaGen", extensionName = "")
.bufferedWriter()
metaInf.append("moe.nea.firmament.annotations.generated.$sourceSetName.")
metaInf.appendLine(generatedFileName)
metaInf.close()
return listOf()
}
@AutoService(SymbolProcessorProvider::class)
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return CompatMetaProcessor(environment.logger,
environment.codeGenerator,
environment.options["firmament.sourceset"] ?: "main")
}
}
}

View File

@@ -32,7 +32,7 @@ class SubscribeAnnotationProcessor(
val generatedFileName = "AllSubscriptions${sourceSetName.replaceFirstChar { it.uppercaseChar() }}" val generatedFileName = "AllSubscriptions${sourceSetName.replaceFirstChar { it.uppercaseChar() }}"
val subscriptionsFile = val subscriptionsFile =
codeGenerator codeGenerator
.createNewFile(dependencies, "moe.nea.firmament.annotations.generated", generatedFileName) .createNewFile(dependencies, "moe.nea.firmament.annotations.generated.$sourceSetName", generatedFileName)
.bufferedWriter() .bufferedWriter()
subscriptionsFile.apply { subscriptionsFile.apply {
appendLine("// This file is @generated by SubscribeAnnotationProcessor") appendLine("// This file is @generated by SubscribeAnnotationProcessor")
@@ -40,7 +40,7 @@ class SubscribeAnnotationProcessor(
for (file in subscriptionSet) { for (file in subscriptionSet) {
appendLine("// Dependency: ${file.filePath}") appendLine("// Dependency: ${file.filePath}")
} }
appendLine("package moe.nea.firmament.annotations.generated") appendLine("package moe.nea.firmament.annotations.generated.$sourceSetName")
appendLine() appendLine()
appendLine("import moe.nea.firmament.events.subscription.*") appendLine("import moe.nea.firmament.events.subscription.*")
appendLine() appendLine()
@@ -65,7 +65,7 @@ class SubscribeAnnotationProcessor(
dependencies, dependencies,
"META-INF/services/moe.nea.firmament.events.subscription.SubscriptionList", extensionName = "") "META-INF/services/moe.nea.firmament.events.subscription.SubscriptionList", extensionName = "")
.bufferedWriter() .bufferedWriter()
metaInf.append("moe.nea.firmament.annotations.generated.") metaInf.append("moe.nea.firmament.annotations.generated.$sourceSetName.")
metaInf.appendLine(generatedFileName) metaInf.appendLine(generatedFileName)
metaInf.close() metaInf.close()
} }