Skip to content

Commit

Permalink
forge support (#3)
Browse files Browse the repository at this point in the history
* initial forge support

* run loaders from a task
  • Loading branch information
deirn authored Sep 15, 2024
1 parent 8b62cf9 commit 35c6866
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 70 deletions.
55 changes: 49 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,43 @@ version = providers.environmentVariable("VERSION").getOrElse("9999-local")
repositories {
mavenCentral()
maven("https://maven.fabricmc.net/")
maven("https://maven.minecraftforge.net/")
maven("https://libraries.minecraft.net/")
}

configurations {
create("shade")
}

dependencies {
val shade by configurations
fun compileOnlyShade(artifact: String, action: ExternalModuleDependency.() -> Unit = {}) {
compileOnly(artifact, action)
shade(artifact, action)
}

implementation("com.google.guava:guava:32.1.2-jre")
implementation("org.apache.maven:maven-artifact:3.8.1")
implementation("com.electronwill.night-config:core:3.7.3")
implementation("com.electronwill.night-config:toml:3.7.3")
implementation("org.apache.logging.log4j:log4j-api:2.22.1")
implementation("org.apache.logging.log4j:log4j-core:2.22.1")
implementation("org.slf4j:slf4j-api:2.0.16")
implementation("com.google.code.gson:gson:2.11.0")

compileOnlyShade("net.fabricmc:fabric-loader:0.15.10")
shade("net.fabricmc:access-widener:2.1.0")

compileOnly("net.fabricmc:fabric-loader:0.14.22")
"shade"("net.fabricmc:fabric-loader:0.14.22")
"shade"("net.fabricmc:access-widener:2.1.0")
compileOnlyShade("com.mojang:logging:1.2.7") { isTransitive = false }
compileOnlyShade("net.minecraftforge:fmlloader:1.20.4-49.0.49") { isTransitive = false }
compileOnlyShade("net.minecraftforge:modlauncher:10.2.1") { isTransitive = false }
compileOnlyShade("net.minecraftforge:securemodules:2.2.12") { isTransitive = false }
compileOnlyShade("net.minecraftforge:forgespi:7.1.5") { isTransitive = false }
compileOnlyShade("net.minecraftforge:JarJarFileSystems:0.3.26") { isTransitive = false }
compileOnlyShade("net.minecraftforge:JarJarMetadata:0.3.26") { isTransitive = false }
compileOnlyShade("net.minecraftforge:JarJarSelector:0.3.26") { isTransitive = false }
compileOnlyShade("net.minecraftforge:unsafe:0.9.2") { isTransitive = false }
compileOnlyShade("net.minecraftforge:mergetool-api:1.0")

testImplementation(kotlin("test"))
}
Expand All @@ -34,7 +59,7 @@ tasks.test {
}

kotlin {
jvmToolchain(8)
jvmToolchain(17)
}

gradlePlugin {
Expand All @@ -46,14 +71,32 @@ gradlePlugin {
}
}

tasks.processResources {
val meta = "${project.group}:${project.name}:${project.version}"
inputs.property("meta", meta)

filesMatching("__meta.txt") {
expand("meta" to meta)
}
}

tasks.shadowJar {
configurations = listOf(project.configurations["shade"])
archiveClassifier.set("")
relocate("net.fabricmc", "lol.bai.explosion.internal.lib.fabricloader")
minimize()
mergeServiceFiles()

relocate("net.fabricmc", "lol.bai.explosion.internal.lib.net.fabricmc")
relocate("net.minecraftforge", "lol.bai.explosion.internal.lib.net.minecraftforge")
relocate("cpw", "lol.bai.explosion.internal.lib.cpw")
relocate("com.mojang", "lol.bai.explosion.internal.lib.com.mojang")

exclude("ui/**")
exclude("assets/**")
exclude("fabric*.json")
exclude("log4j2*")
exclude("LICENSE_fabric-loader")
exclude("META-INF/jars/**")
exclude("META-INF/org/apache/logging/**")
}

tasks.build {
Expand Down
29 changes: 29 additions & 0 deletions src/main/kotlin/lol/bai/explosion/ExplosionExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ package lol.bai.explosion
import groovy.lang.Closure
import groovy.lang.DelegatesTo
import org.gradle.api.Action
import org.gradle.api.Transformer
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.provider.Provider
import java.io.File
import java.nio.file.Path

interface ExplosionExt {

fun withTransformer(id: String, transformer: Transformer<Path, Path>): ExplosionExt

fun withTransformer(id: String, @DelegatesTo(File::class) closure: Closure<Path>) = withTransformer(id) r@{
closure.delegate = this
return@r closure.call(this)
}

// ---

fun fabric(action: Action<ExplosionDesc>): Provider<String>

fun fabric(notation: String) = fabric {
Expand All @@ -23,4 +35,21 @@ interface ExplosionExt {
closure.call(this)
}

// ---

fun forge(action: Action<ExplosionDesc>): Provider<String>

fun forge(notation: String) = forge {
maven(notation)
}

fun <T : ExternalModuleDependency> forge(notation: Provider<T>) = forge {
maven(notation)
}

fun forge(@DelegatesTo(ExplosionDesc::class) closure: Closure<*>) = forge {
closure.delegate = this
closure.call(this)
}

}
10 changes: 8 additions & 2 deletions src/main/kotlin/lol/bai/explosion/ExplosionPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package lol.bai.explosion

import lol.bai.explosion.internal.ExplosionExtImpl
import lol.bai.explosion.internal.resolver.ResolverTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.maven
import org.gradle.kotlin.dsl.*
import java.io.File
import kotlin.io.path.createDirectories

Expand All @@ -24,6 +24,12 @@ class ExplosionPlugin : Plugin<Project> {
repositories.maven(outputDir.toFile()) {
name = "ExplodedPluginCache"
}

val resolver = configurations.create(ResolverTask.CONFIGURATION)
dependencies {
resolver(embeddedKotlin("stdlib"))
resolver(ExplosionPlugin::class.java.classLoader.getResource("__meta.txt")!!.readText().trim())
}
}

}
146 changes: 84 additions & 62 deletions src/main/kotlin/lol/bai/explosion/internal/ExplosionExtImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,69 @@ package lol.bai.explosion.internal
import com.google.common.hash.Hashing
import lol.bai.explosion.ExplosionDesc
import lol.bai.explosion.ExplosionExt
import lol.bai.explosion.internal.fabric.FakeGameProvider
import net.fabricmc.api.EnvType
import net.fabricmc.loader.impl.FabricLoaderImpl
import net.fabricmc.loader.impl.discovery.ModResolver
import net.fabricmc.loader.impl.discovery.createModDiscoverer
import net.fabricmc.loader.impl.launch.FabricLauncherBase
import net.fabricmc.loader.impl.launch.knot.Knot
import lol.bai.explosion.internal.resolver.ResolverTask
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Transformer
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.invoke
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*

abstract class ExplosionExtImpl(
private class BomDependency(
val group: String,
val name: String,
val version: String
)

open class ExplosionExtImpl(
private val project: Project,
private val outputDir: Path
private val outputDir: Path,
private val transformerId: String?,
private val transformer: Transformer<Path, Path>?
) : ExplosionExt {

@Suppress("unused")
constructor(project: Project, outputDir: Path) : this(project, outputDir, null, null)

private fun Path.resolve(vararg path: String): Path {
return resolve(path.joinToString(File.separator))
}

private fun sanitizeVersion(version: String): String {
return version.replace(Regex("[^A-Za-z0-9.]"), "_")
}
private fun createPom(loader: String, name: String, version: String, jarPlacer: (Path) -> Unit): BomDependency {
val group = "exploded"

var sanitizedVersion = loader + "_" + version.replace(Regex("[^A-Za-z0-9.]"), "_")
if (transformerId != null) sanitizedVersion = "${sanitizedVersion}_transformed_$transformerId"

private fun createPom(name: String, version: String): Path {
val pom = this.javaClass.classLoader.getResource("artifact.xml")!!.readText()
.replace("%GROUP_ID%", "exploded")
.replace("%GROUP_ID%", group)
.replace("%ARTIFACT_ID%", name)
.replace("%VERSION%", version)
.replace("%VERSION%", sanitizedVersion)

val dir = outputDir.resolve("exploded", name, sanitizedVersion)

val dir = outputDir.resolve("exploded", name, version)
dir.createDirectories()
dir.resolve("${name}-${version}.pom").writeText(pom)
return dir.resolve("${name}-${version}.jar")
dir.resolve("${name}-${sanitizedVersion}.pom").writeText(pom)

val jarPath = dir.resolve("${name}-${sanitizedVersion}.jar")
jarPlacer(jarPath)

if (transformer != null) {
val originalJarPath = dir.resolve("__original-${transformerId}.jar")
jarPath.moveTo(originalJarPath, overwrite = true)

val transformed = transformer.transform(originalJarPath)
transformed.moveTo(jarPath, overwrite = true)

originalJarPath.deleteIfExists()
}

return BomDependency(group, name, sanitizedVersion)
}

private fun getOrCreateBom(hash: String, deps: () -> List<Pair<String, String>>): String {
private fun getOrCreateBom(hash: String, deps: () -> List<BomDependency>): String {
val bom = "exploded-bom:${hash}:1"
val dir = outputDir.resolve("exploded-bom", hash, "1")
dir.createDirectories()
Expand All @@ -57,19 +80,16 @@ abstract class ExplosionExtImpl(

val depTemplate = this.javaClass.classLoader.getResource("bom_dependency.xml")!!.readText()

fun createDependency(group: String, name: String, version: String): String {
val lines = depTemplate.replace("%GROUP_ID%", group)
.replace("%ARTIFACT_ID%", name)
.replace("%VERSION%", version)
.lines()

return lines.joinToString(separator = "\n ", prefix = " ")
}

val depsStr = StringBuilder()
deps().forEach { (depName, depVersion) ->
deps().forEach {
depsStr.append('\n')
depsStr.append(createDependency("exploded", depName, depVersion))

val lines = depTemplate
.replace("%GROUP_ID%", it.group)
.replace("%ARTIFACT_ID%", it.name)
.replace("%VERSION%", it.version)
.lines()
depsStr.append(lines.joinToString(separator = "\n ", prefix = " "))
}
depsStr.append('\n')

Expand All @@ -83,62 +103,64 @@ abstract class ExplosionExtImpl(
return bom
}

override fun fabric(action: Action<ExplosionDesc>) = project.provider {
private fun resolve(
action: Action<ExplosionDesc>,
loader: String
) = project.provider {
val desc = ExplosionDescImpl(project)
action(desc)

val tempDir = createTempDirectory()
val inputDir = createTempDirectory()
val outputDir = createTempDirectory()

try {
val hashBuilder = StringBuilder()
val hashBuilder = StringBuilder(loader).append(";")
if (transformerId != null) hashBuilder.append(transformerId).append(";")

desc.resolveJars {
hashBuilder.append(Hashing.murmur3_128().hashBytes(it.readBytes()))
hashBuilder.append(";")

it.copyTo(tempDir.resolve(it.name).toFile())
it.copyTo(inputDir.resolve(it.name).toFile())
}

val hash = Hashing.murmur3_128().hashString(hashBuilder.toString(), Charsets.UTF_8).toString()
return@provider getOrCreateBom(hash) {
if (FabricLauncherBase.getLauncher() == null) Knot(EnvType.CLIENT)

val loader = FabricLoaderImpl.INSTANCE.apply {
gameProvider = FakeGameProvider(tempDir)
}

val candidates = createModDiscoverer(tempDir).discoverMods(loader, mutableMapOf())
candidates.removeIf { it.id == "java" }

val candidateIds = hashSetOf<String>()

candidates.forEach {
candidateIds.add(it.id)
candidateIds.addAll(it.provides)
}

candidates.forEach { candidate ->
candidate.metadata.dependencies = candidate.metadata.dependencies.filter {
candidateIds.contains(it.modId)
}
return@provider getOrCreateBom(hash) {
val task = project.tasks.create<ResolverTask>("__explosion_resolver_" + Any().hashCode()) {
this.loader.set(loader)
this.inputDir.set(inputDir.toFile())
this.outputDir.set(outputDir.toFile())
}

val mods = ModResolver.resolve(candidates, EnvType.CLIENT, mutableMapOf())
val bomDeps = arrayListOf<Pair<String, String>>()
task.exec()
task.enabled = false
val metaLines = outputDir.resolve("__meta.txt").readLines()
val bomDeps = arrayListOf<BomDependency>()

for (mod in mods) {
val version = sanitizeVersion(mod.version.friendlyString)
for (line in metaLines) {
val trimmed = line.trim()
if (trimmed.isEmpty()) continue

bomDeps.add(mod.id to version)
val out = createPom(mod.id, version)
mod.copyToDir(out.parent, false).moveTo(out, overwrite = true)
val (modFile, modId, version) = trimmed.split("\t")
bomDeps.add(createPom(loader, modId, version) { path ->
outputDir.resolve(modFile).copyTo(path)
})
}

return@getOrCreateBom bomDeps
}
} finally {
tempDir.toFile().deleteRecursively()
inputDir.toFile().deleteRecursively()
outputDir.toFile().deleteRecursively()
}
}

override fun withTransformer(id: String, transformer: Transformer<Path, Path>): ExplosionExt {
return ExplosionExtImpl(project, outputDir, id, transformer)
}

override fun fabric(action: Action<ExplosionDesc>) = resolve(action, "fabric")
override fun forge(action: Action<ExplosionDesc>) = resolve(action, "forge")

}
Loading

0 comments on commit 35c6866

Please sign in to comment.