Skip to content

Commit

Permalink
Add code to handle old server jar dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
shartte committed Dec 9, 2024
1 parent 999449c commit f851500
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 10 deletions.
66 changes: 61 additions & 5 deletions .github/workflows/build-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,64 @@ on:

jobs:
build:
uses: neoforged/actions/.github/workflows/build-prs.yml@main
with:
java: 21
gradle_tasks: makeModuleMetadata
jar_compatibility: false
strategy:
matrix:
version:
- 1.7.10
- 1.10.2
- 1.12.2
- 1.16.5
- 1.18.2
- 1.19.2
- 1.20.1
- 1.21.1

runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Java ${{ inputs.java }}
uses: neoforged/actions/setup-java@main
with:
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false

- name: Build with Gradle
run: ./gradlew publish -PminecraftVersion=${{ matrix.version }}

- name: Store repository
uses: actions/upload-artifact@v4
with:
name: repo-${{ matrix.version }}
path: repo

merge-repos:
name: Merge Maven Repositories
runs-on: ubuntu-latest
needs: build
if: success() || failure()
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
pattern: repo-*
path: repo
merge-multiple: true
# the maven-metadata will only be from a single repo and thus wrong
- name: Remove incorrect maven-metadata files
run: find -name maven-metadata.xml* -exec rm {} \;
- name: Upload repository for manual consumption
uses: actions/upload-artifact@v4
with:
name: repo
path: repo
- name: Upload repository for PR-Publishing
uses: actions/upload-artifact@v4
with:
name: maven-publish
path: repo
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction

import javax.inject.Inject
import java.util.jar.JarInputStream
import java.util.stream.Collectors
import java.util.zip.ZipEntry
import java.util.zip.ZipFile

@CompileStatic
Expand All @@ -35,6 +43,12 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf
this.moduleVersion.convention(project.providers.gradleProperty('minecraftVersion').orElse('undefined'))
}

private static final Map<String, String> potentialLibraryUpgrades = Map.of(
// 1.7.10 server jar contained guava 16 instead of 15
"com.google.guava:guava:15.0", "com.google.guava:guava:16.0",
"org.apache.commons:commons-lang3:3.1", "org.apache.commons:commons-lang3:3.2.1"
)

@TaskAction
void run() {
Map metadata = [:]
Expand Down Expand Up @@ -163,6 +177,48 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf

private static final List<String> platforms = ["windows", "osx", "linux"]

private Map<String, String> getFileFingerprints(String artifactId) {
var listingFile = new File(temporaryDir, artifactId.replace(':', '-') + ".txt")
if (listingFile.exists()) {
return listingFile.readLines()
.stream()
.map(v -> v.split("\\|", 2))
.collect(Collectors.toMap((String[] v) -> v[0], (String[] v) -> v[1]))
}

var parts = artifactId.split(":")
String relativeUrl
if (parts.length == 3) {
relativeUrl = parts[0].replace('.', '/') + "/" + parts[1] + "/" + parts[2] + "/" + parts[1] + "-" + parts[2] + ".jar"
} else {
relativeUrl = parts[0].replace('.', '/') + "/" + parts[1] + "/" + parts[2] + "/" + parts[1] + "-" + parts[2] + "-" + parts[3] + ".jar"
}
var url = URI.create("https://libraries.minecraft.net/" + relativeUrl).toURL()
println("Getting $url")
var hashes = new HashMap<String, String>()
url.openStream().with { input ->
var jin = new JarInputStream(input)
for (var entry = jin.nextJarEntry; entry != null; entry = jin.nextJarEntry) {
if (entry.name.endsWith("/")) {
continue
}
if (!entry.name.startsWith("META-INF/")) {
hashes[entry.name] = jin.readAllBytes().md5()
}
}
}

println("Hashed ${hashes.size()} files in jar")

try (var writer = listingFile.newWriter()) {
for (var entry in hashes.entrySet()) {
writer.writeLine(entry.getKey() + "|" + entry.getValue())
}
}

return hashes
}

private void getMcDeps(List<String> server, List<String> client, Map<String, List<String>> clientNatives) {
Map metaJson = new JsonSlurper().parse(meta.get().asFile) as Map
(metaJson.libraries as List<Map<String, Object>>).each { Map lib ->
Expand All @@ -188,17 +244,87 @@ abstract class GenerateModuleMetadata extends DefaultTask implements HasMinecraf
}
}
try (def zf = new ZipFile(serverJar.get().getAsFile())) {
def entry = zf.getEntry('META-INF/libraries.list')
if (entry != null) {
try (def is = zf.getInputStream(entry)) {
def librariesListEntry = zf.getEntry('META-INF/libraries.list')
if (librariesListEntry != null) {
try (def is = zf.getInputStream(librariesListEntry)) {
is.readLines().each {
server.add(it.split('\t')[1])
}
}
} else {
throw new RuntimeException("Could not find libraries.list inside of server.jar")
// This will be slow.

// Fingerprint all files found in the server jar
var fileFingerprints = new HashMap<String, String>();
zf.entries().iterator().each { ZipEntry it ->
if (it.name.endsWith("/")) {
return
}
zf.getInputStream(it).withCloseable { stream ->
fileFingerprints[it.name] = stream.readAllBytes().md5()
}
}

var libraries = new LinkedHashSet<String>()

// For each client library, find the list of folders contained within
// We need to do this in order of the declared libraries since mojang chose to fix log4j2 issues
// by introducing a higherpriority library that contains files from log4j2 and netty
for (var artifactId in client) {
var result = matchLibraryAgainstServerJar(artifactId, fileFingerprints)
while (result == null) {
// Result was uncertain
var upgradedLib = potentialLibraryUpgrades[artifactId]
if (upgradedLib) {
println("Trying upgrade from $artifactId to $upgradedLib")
artifactId = upgradedLib
result = matchLibraryAgainstServerJar(upgradedLib, fileFingerprints)
if (result) {
break // Upgrade successful
}
} else {
break
}
}

if (result == null) {
println("*** Including partial match: $artifactId")
libraries.add(artifactId)
} else if (result) {
libraries.add(artifactId)
}
}

server.addAll(libraries)
}
}
}

private Boolean matchLibraryAgainstServerJar(String artifactId, Map<String, String> fileFingerprints) {
var libFileFingerprints = getFileFingerprints(artifactId)
var matches = 0
var mismatches = new ArrayList<String>()
var missing = new ArrayList<String>()
for (var entry in libFileFingerprints.entrySet()) {
if (fileFingerprints.containsKey(entry.key)) {
if (fileFingerprints[entry.key] == entry.value) {
matches++
} else {
mismatches.add(entry.key)
}
} else {
missing.add(entry.key)
}
}
if (matches == 0 && mismatches.size() == 0) {
return false // Assuredly missing
} else if (matches == libFileFingerprints.size()) {
println(" Matched $artifactId")
return true
} else {
println(" Not sure about $artifactId matches=$matches mismatches=${mismatches.size()} missing=${missing.size()}")
return null
}
}

private static List depsOf(List<String> deps) {
Expand Down

0 comments on commit f851500

Please sign in to comment.