Files
architectury-plugin/src/main/kotlin/dev/architectury/plugin/ArchitecturyPluginExtension.kt
2022-01-17 01:42:36 +08:00

462 lines
18 KiB
Kotlin

@file:Suppress("UnstableApiUsage")
package dev.architectury.plugin
import dev.architectury.plugin.loom.LoomInterface
import dev.architectury.plugin.transformers.AddRefmapName
import dev.architectury.transformer.Transformer
import dev.architectury.transformer.input.OpenedFileAccess
import dev.architectury.transformer.shadowed.impl.com.google.common.hash.Hashing
import dev.architectury.transformer.shadowed.impl.com.google.gson.Gson
import dev.architectury.transformer.shadowed.impl.com.google.gson.JsonObject
import dev.architectury.transformer.transformers.*
import dev.architectury.transformer.transformers.properties.TransformersWriter
import dev.architectury.transformer.util.TransformerPair
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.jvm.tasks.Jar
import java.io.File
import java.io.StringWriter
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.util.*
import java.util.function.BiConsumer
import java.util.function.Function
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
open class ArchitectPluginExtension(val project: Project) {
var transformerVersion = "5.2.61"
var injectablesVersion = "1.0.10"
var minecraft = ""
var injectInjectables = true
var addCommonMarker = true
private val transforms = mutableMapOf<String, Transform>()
private var transformedLoom = false
private val agentFile by lazy {
project.gradle.rootProject.file(".gradle/architectury/architectury-transformer-agent.jar").also {
it.parentFile.mkdirs()
}
}
private val mainClassTransformerFile by lazy {
project.file(".gradle/architectury/.main_class").also {
it.parentFile.mkdirs()
}
}
private val runtimeTransformerFile by lazy {
project.file(".gradle/architectury/.transforms").also {
it.parentFile.mkdirs()
}
}
private val propertiesTransformerFile by lazy {
project.file(".gradle/architectury/.properties").also {
it.parentFile.mkdirs()
}
}
private val loom: LoomInterface by lazy {
useIfFound(
"net.fabricmc.loom.util.ZipUtils",
"dev.architectury.plugin.loom.LoomInterface010" // >0.10.0.188
) ?: useIfFound(
"net.fabricmc.loom.api.MixinExtensionAPI",
"dev.architectury.plugin.loom.LoomInterface010Legacy" // 0.10.0
) ?: useIfFound(
"net.fabricmc.loom.api.LoomGradleExtensionAPI",
"dev.architectury.plugin.loom.LoomInterface09" // 0.9.0
) ?: use("dev.architectury.plugin.loom.LoomInterface06") // 0.6.0
}
fun useIfFound(className: String, interfaceName: String): LoomInterface? {
return try {
Class.forName(className)
use(interfaceName)
} catch (ignored: ClassNotFoundException) {
null
}
}
fun use(interfaceName: String): LoomInterface {
return Class.forName(interfaceName)
.getDeclaredConstructor(Project::class.java)
.newInstance(project) as LoomInterface
}
init {
project.afterEvaluate {
if (transforms.isNotEmpty()) {
StringWriter().also { strWriter ->
TransformersWriter(strWriter).use { writer ->
for (transform in transforms.values) {
project.configurations.getByName(transform.configName).forEach { file ->
transform.transformers.map { it.apply(file.toPath()) }
.forEach { pair ->
writer.write(file.toPath(), pair.clazz, pair.properties)
}
}
}
}
runtimeTransformerFile.writeText(strWriter.toString())
}
val properties = Properties()
properties(transforms.keys.first()).forEach { (key, value) ->
properties.setProperty(key, value)
}
propertiesTransformerFile.writer().use {
properties.store(it, "Architectury Runtime Transformer Properties")
}
}
}
}
fun properties(platform: String): Map<String, String> {
return mutableMapOf(
BuiltinProperties.MIXIN_MAPPINGS to loom.allMixinMappings.joinToString(File.pathSeparator),
BuiltinProperties.INJECT_INJECTABLES to injectInjectables.toString(),
BuiltinProperties.UNIQUE_IDENTIFIER to project.projectUniqueIdentifier(),
BuiltinProperties.COMPILE_CLASSPATH to getCompileClasspath().joinToString(File.pathSeparator),
BuiltinProperties.MAPPINGS_WITH_SRG to loom.tinyMappingsWithSrg.toString(),
"architectury.platform.name" to platform,
BuiltinProperties.REFMAP_NAME to loom.refmapName,
BuiltinProperties.MCMETA_VERSION to "4"
)
}
private fun getCompileClasspath(): Iterable<File> {
return project.configurations.findByName("architecturyTransformerClasspath")
?: project.configurations.getByName("compileClasspath")
}
fun transform(name: String, action: Action<Transform>) {
transforms.getOrPut(name) {
Transform(project, "development" + name.capitalize()).also { transform ->
project.configurations.create(transform.configName)
if (!transformedLoom) {
var plsAddInjectables = false
project.configurations.findByName("architecturyTransformerClasspath")
?: project.configurations.create("architecturyTransformerClasspath") {
it.extendsFrom(project.configurations.getByName("compileClasspath"))
plsAddInjectables = true
}
val architecturyJavaAgents = project.configurations.create("architecturyJavaAgents") {
project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)
.extendsFrom(it)
}
transformedLoom = true
with(project.dependencies) {
add(
JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME,
"dev.architectury:architectury-transformer:$transformerVersion:runtime"
)
add(
"architecturyJavaAgents",
"dev.architectury:architectury-transformer:$transformerVersion:agent"
)
if (plsAddInjectables && injectInjectables) {
add(
"architecturyTransformerClasspath",
"dev.architectury:architectury-injectables:$injectablesVersion"
)
add("architecturyTransformerClasspath", "net.fabricmc:fabric-loader:+")?.also {
it as ModuleDependency
it.isTransitive = false
}
}
}
loom.settingsPostEdit { config ->
val s = config.mainClass
config.mainClass = "dev.architectury.transformer.TransformerRuntime"
mainClassTransformerFile.writeText(s)
config.addVmArg("-Darchitectury.main.class=${mainClassTransformerFile.absolutePath.escapeSpaces()}")
config.addVmArg("-Darchitectury.runtime.transformer=${runtimeTransformerFile.absolutePath.escapeSpaces()}")
config.addVmArg("-Darchitectury.properties=${propertiesTransformerFile.absolutePath.escapeSpaces()}")
config.addVmArg("-Djdk.attach.allowAttachSelf=true")
if (architecturyJavaAgents.toList().size == 1) {
if (!agentFile.exists() || agentFile.delete()) {
architecturyJavaAgents.first().copyTo(agentFile, overwrite = true)
}
config.addVmArg("-javaagent:${agentFile.absolutePath.escapeSpaces()}")
} else {
throw IllegalStateException(
"Illegal Count of Architectury Java Agents! " + architecturyJavaAgents.toList()
.joinToString(", ")
)
}
}
}
}
}.also {
action.execute(it)
}
}
private fun String.escapeSpaces(): String {
if (any(Char::isWhitespace)) {
return "\"$this\""
}
return this
}
@JvmOverloads
fun fabric(action: Action<Transform> = Action {}) {
transform("fabric", Action {
it.setupFabricTransforms()
action.execute(it)
})
}
@JvmOverloads
fun forge(action: Action<Transform> = Action {}) {
transform("forge", Action {
it.setupForgeTransforms()
action.execute(it)
})
}
fun common() {
common {}
}
data class CommonSettings(
var forgeEnabled: Boolean = true
)
fun platformSetupLoomIde() {
loom.setIdeConfigGenerated()
}
fun common(forgeEnabled: Boolean) {
common {
this.forgeEnabled = forgeEnabled
}
}
fun common(action: CommonSettings.() -> Unit) {
common(Action { it.action() })
}
fun common(action: Action<CommonSettings>) {
val settings = CommonSettings().also { action.execute(it) }
if (injectInjectables) {
var plsAddInjectables = false
project.configurations.findByName("architecturyTransformerClasspath")
?: project.configurations.create("architecturyTransformerClasspath") {
it.extendsFrom(project.configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME))
plsAddInjectables = true
}
with(project.dependencies) {
add(
JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME,
"dev.architectury:architectury-injectables:$injectablesVersion"
)
if (plsAddInjectables) {
add(
"architecturyTransformerClasspath",
"dev.architectury:architectury-injectables:$injectablesVersion"
)
add("architecturyTransformerClasspath", "net.fabricmc:fabric-loader:+")?.also {
it as ModuleDependency
it.isTransitive = false
}
}
}
}
addTasks()
if (settings.forgeEnabled) {
project.configurations.create("transformProductionForge")
}
project.configurations.create("transformProductionFabric")
val buildTask = project.tasks.getByName("build")
val jarTask = project.tasks.getByName("jar") {
it as AbstractArchiveTask
it.archiveClassifier.set("dev")
} as AbstractArchiveTask
val transformProductionFabricTask = project.tasks.getByName("transformProductionFabric") {
it as TransformingTask
it.archiveClassifier.set("transformProductionFabric")
it.input.set(jarTask.archiveFile)
project.artifacts.add("transformProductionFabric", it)
it.dependsOn(jarTask)
buildTask.dependsOn(it)
} as TransformingTask
val remapJarTask = project.tasks.getByName("remapJar") {
it as Jar
it.archiveClassifier.set("")
loom.setRemapJarInput(it, jarTask.archiveFile)
it.dependsOn(jarTask)
it.doLast { _ ->
if (addCommonMarker) {
val output = it.archiveFile.get().asFile
try {
OpenedFileAccess.ofJar(output.toPath()).use { inter ->
inter.addFile("architectury.common.marker", "")
}
} catch (t: Throwable) {
project.logger.warn("Failed to add architectury.common.marker to ${output.absolutePath}")
}
}
}
} as Jar
if (settings.forgeEnabled) {
val transformProductionForgeTask = project.tasks.getByName("transformProductionForge") {
it as TransformingTask
it.input.set(jarTask.archiveFile)
it.archiveClassifier.set("transformProductionForge")
project.artifacts.add("transformProductionForge", it)
it.dependsOn(jarTask)
buildTask.dependsOn(it)
} as TransformingTask
transformProductionForgeTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar()
loom.generateSrgTiny = true
}
transformProductionFabricTask.archiveFile.get().asFile.takeUnless { it.exists() }?.createEmptyJar()
}
private fun addTasks() {
project.tasks.register("transformProductionFabric", TransformingTask::class.java) {
it.group = "Architectury"
it.platform = "fabric"
it += RemapMixinVariables()
it.add(TransformExpectPlatform()) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
it.add(RemapInjectables()) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
it += AddRefmapName()
it += TransformPlatformOnly()
}
project.tasks.register("transformProductionForge", TransformingTask::class.java) {
it.group = "Architectury"
it.platform = "forge"
it.add(TransformExpectPlatform()) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
it.add(RemapInjectables()) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
it += AddRefmapName()
it += TransformPlatformOnly()
it += TransformForgeAnnotations()
it += TransformForgeEnvironment()
it += FixForgeMixin()
}
}
}
private fun File.createEmptyJar() {
parentFile.mkdirs()
JarOutputStream(outputStream(), Manifest()).close()
}
data class Transform(
val project: Project,
val configName: String,
val transformers: MutableList<Function<Path, TransformerPair>> = mutableListOf()
) {
fun setupFabricTransforms() {
this += RuntimeMixinRefmapDetector::class.java
this += GenerateFakeFabricMod::class.java
add(TransformExpectPlatform::class.java) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
add(RemapInjectables::class.java) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
this += TransformPlatformOnly::class.java
}
fun setupForgeTransforms() {
this += RuntimeMixinRefmapDetector::class.java
add(TransformExpectPlatform::class.java) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
add(RemapInjectables::class.java) { file ->
this[BuiltinProperties.UNIQUE_IDENTIFIER] =
(project.projectUniqueIdentifier() + "_" + file.toString()
.toByteArray(StandardCharsets.UTF_8).sha256 + file.fileName).legalizePackageName();
}
this += TransformPlatformOnly::class.java
this += TransformForgeAnnotations::class.java
this += TransformForgeEnvironment::class.java
this += GenerateFakeForgeMod::class.java
this += FixForgeMixin::class.java
}
operator fun plusAssign(transformer: TransformerPair) {
transformers.add(Function { transformer })
}
operator fun <T : Transformer> plusAssign(transformer: Class<T>) {
this += TransformerPair(transformer, null)
}
fun <T : Transformer> add(transformer: Class<T>) {
this += TransformerPair(transformer, null)
}
fun <T : Transformer> add(transformer: Class<T>, properties: JsonObject) =
plusAssign(TransformerPair(transformer, properties))
fun <T : Transformer> add(transformer: Class<T>, config: BiConsumer<Path, MutableMap<String, Any>>) {
transformers.add(Function { file ->
val properties = mutableMapOf<String, Any>()
config.accept(file, properties)
TransformerPair(transformer, Gson().toJsonTree(properties).asJsonObject)
})
}
fun <T : Transformer> add(transformer: Class<T>, config: MutableMap<String, Any>.(file: Path) -> Unit) {
add(transformer, BiConsumer { file, map ->
config(map, file)
})
}
}
fun String.legalizePackageName(): String =
filter { Character.isJavaIdentifierPart(it) }
private val ByteArray.sha256: String
get() = Hashing.sha256().hashBytes(this).toString()