Skip to content

Commit e5bd549

Browse files
committed
Qualify cold Forge 1.20.1 bootstrap
1 parent 2bafa2c commit e5bd549

10 files changed

Lines changed: 1352 additions & 11 deletions

‎.gitattributes‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mcmod.info text
1111
*.lang text
1212
*.mcmeta text
1313
*.md text
14+
*.patch text eol=lf -diff
1415
*.properties text
1516
gradlew text eol=lf
1617
*.sh text eol=lf

‎.github/workflows/ci.yml‎

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,73 @@ concurrency:
1717
cancel-in-progress: true
1818

1919
jobs:
20+
cold-forge-bootstrap:
21+
name: Cold Forge bootstrap
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 60
24+
steps:
25+
- name: Check out source
26+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
27+
28+
- name: Install pinned Java 25 Mavenizer runtime
29+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
30+
with:
31+
distribution: temurin
32+
java-version: '25.0.3+9.0.LTS'
33+
34+
- name: Install pinned Java 8 launcher toolchain
35+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
36+
with:
37+
distribution: temurin
38+
java-version: '8.0.502+7'
39+
40+
- name: Install pinned Java 17 runtime and toolchain
41+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
42+
with:
43+
distribution: temurin
44+
java-version: '17.0.1+12'
45+
46+
- name: Bootstrap Forge from an empty cache
47+
shell: bash
48+
env:
49+
GRADLE_USER_HOME: ${{ runner.temp }}/orespawn-cold-gradle
50+
run: |
51+
set -euo pipefail
52+
test ! -e .gradle
53+
test ! -e "$GRADLE_USER_HOME"
54+
mkdir -p "$GRADLE_USER_HOME"
55+
chmod +x ./gradlew
56+
./gradlew verifyMavenizerCompatibilityFixture help \
57+
--no-daemon --no-build-cache --stacktrace --max-workers=2 \
58+
-Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \
59+
-Dorg.gradle.java.installations.auto-detect=false \
60+
-Dorg.gradle.java.installations.auto-download=false \
61+
| tee "$RUNNER_TEMP/cold-forge-bootstrap.log"
62+
grep -F "OreSpawn Mavenizer compatibility runtime: Java 25.0.3" \
63+
"$RUNNER_TEMP/cold-forge-bootstrap.log"
64+
grep -F "OreSpawn Mavenizer compatibility: applied 8 rule(s) for net.minecraftforge:forge:1.20.1-47.4.10" \
65+
"$RUNNER_TEMP/cold-forge-bootstrap.log"
66+
find "$GRADLE_USER_HOME" .gradle/mavenizer \
67+
-name '*.orespawn-compatibility' -type f -print0 \
68+
| sort -z | xargs -0 -r sha256sum > "$RUNNER_TEMP/markers-before.sha256"
69+
test -s "$RUNNER_TEMP/markers-before.sha256"
70+
71+
- name: Verify same-cache preparation is idempotent
72+
shell: bash
73+
env:
74+
GRADLE_USER_HOME: ${{ runner.temp }}/orespawn-cold-gradle
75+
run: |
76+
set -euo pipefail
77+
./gradlew verifyMavenizerCompatibilityFixture help \
78+
--no-daemon --no-build-cache --stacktrace --max-workers=2 \
79+
-Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \
80+
-Dorg.gradle.java.installations.auto-detect=false \
81+
-Dorg.gradle.java.installations.auto-download=false
82+
find "$GRADLE_USER_HOME" .gradle/mavenizer \
83+
-name '*.orespawn-compatibility' -type f -print0 \
84+
| sort -z | xargs -0 -r sha256sum > "$RUNNER_TEMP/markers-after.sha256"
85+
diff -u "$RUNNER_TEMP/markers-before.sha256" "$RUNNER_TEMP/markers-after.sha256"
86+
2087
build:
2188
name: Build, test, and audit
2289
runs-on: ubuntu-latest
@@ -26,17 +93,23 @@ jobs:
2693
- name: Check out source
2794
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
2895

29-
- name: Install Java 17 toolchain
96+
- name: Install pinned Java 25 Mavenizer runtime
3097
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
3198
with:
3299
distribution: temurin
33-
java-version: '17.0.1+12'
100+
java-version: '25.0.3+9.0.LTS'
34101

35-
- name: Install Java 17 for Gradle
102+
- name: Install pinned Java 8 launcher toolchain
36103
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
37104
with:
38-
distribution: microsoft
39-
java-version: '17'
105+
distribution: temurin
106+
java-version: '8.0.502+7'
107+
108+
- name: Install pinned Java 17 runtime and toolchain
109+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
110+
with:
111+
distribution: temurin
112+
java-version: '17.0.1+12'
40113

41114
- name: Set up Gradle
42115
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6
@@ -48,6 +121,9 @@ jobs:
48121
run: >-
49122
./gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums
50123
verifyEclipseProductionClasspath --no-daemon --stacktrace
124+
-Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64"
125+
-Dorg.gradle.java.installations.auto-detect=false
126+
-Dorg.gradle.java.installations.auto-download=false
51127
52128
- name: Upload audited release candidate
53129
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7

‎.github/workflows/codeql-analysis.yml‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,23 @@ jobs:
2626
- name: Check out source
2727
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
2828

29-
- name: Install Java 17 toolchain
29+
- name: Install pinned Java 25 Mavenizer runtime
3030
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
3131
with:
3232
distribution: temurin
33-
java-version: '17.0.1+12'
33+
java-version: '25.0.3+9.0.LTS'
3434

35-
- name: Install Java 17 for Gradle
35+
- name: Install pinned Java 8 launcher toolchain
3636
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
3737
with:
38-
distribution: microsoft
39-
java-version: '17'
38+
distribution: temurin
39+
java-version: '8.0.502+7'
40+
41+
- name: Install pinned Java 17 runtime and toolchain
42+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
43+
with:
44+
distribution: temurin
45+
java-version: '17.0.1+12'
4046

4147
- name: Set up Gradle
4248
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6
@@ -49,7 +55,22 @@ jobs:
4955
- name: Compile production code
5056
run: |
5157
chmod +x ./gradlew
52-
./gradlew clean classes --no-daemon --stacktrace
58+
gradle_args=(
59+
clean classes --no-daemon --stacktrace
60+
"-Dorg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64"
61+
-Dorg.gradle.java.installations.auto-detect=false
62+
-Dorg.gradle.java.installations.auto-download=false
63+
)
64+
for attempt in 1 2 3; do
65+
if ./gradlew "${gradle_args[@]}"; then
66+
exit 0
67+
fi
68+
if [ "$attempt" -eq 3 ]; then
69+
echo "CodeQL compilation failed after $attempt attempts." >&2
70+
exit 1
71+
fi
72+
echo "::warning::CodeQL compilation attempt $attempt failed; retrying with the preserved Forge cache."
73+
done
5374
5475
- name: Analyze
5576
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4

‎build.gradle‎

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,62 @@ plugins {
1616
id 'net.minecraftforge.gradle' version '7.0.34'
1717
}
1818

19+
def sha256Of = { File inputFile ->
20+
MessageDigest digest = MessageDigest.getInstance('SHA-256')
21+
inputFile.withInputStream { input ->
22+
byte[] buffer = new byte[8192]
23+
for (int read = input.read(buffer); read >= 0; read = input.read(buffer)) {
24+
if (read > 0) digest.update(buffer, 0, read)
25+
}
26+
}
27+
digest.digest().encodeHex().toString().toUpperCase()
28+
}
29+
30+
def mavenizerToolsDirectory = layout.projectDirectory.dir('ci-fixtures/tools')
31+
def mavenizerCompatibilityJar = mavenizerToolsDirectory.file(
32+
'minecraft-mavenizer-0.5.21-orespawn-compat.jar')
33+
def mavenizerRuleManifest = mavenizerToolsDirectory.file(
34+
'minecraft-source-compatibility.json')
35+
def mavenizerSourcePatch = mavenizerToolsDirectory.file(
36+
'minecraft-mavenizer-0.5.21-orespawn-compat.patch')
37+
def mavenizerLicense = mavenizerToolsDirectory.file('LICENSE-MAVENIZER.txt')
38+
def mavenizerReadme = mavenizerToolsDirectory.file('README.md')
39+
def mavenizerFixtureChecksums = [
40+
(mavenizerCompatibilityJar.asFile):
41+
'416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177',
42+
(mavenizerRuleManifest.asFile):
43+
'8945511DFF53B8F1A91EE44B14AF9EC7DB34C373617EA37A608C6958BFEEB22E',
44+
(mavenizerSourcePatch.asFile):
45+
'D856190DEF5574716C5E4F4379912C451B1F5952B0B111576651A98EECF58544',
46+
(mavenizerLicense.asFile):
47+
'20C17D8B8C48A600800DFD14F95D5CB9FF47066A9641DDEAB48DC54AEC96E331',
48+
(mavenizerReadme.asFile):
49+
'177C1DDEDEF9704B9FDC8EA24B8DCDF4DDCC826DD0E253E7872A00DB2EA34518'
50+
]
51+
52+
// ForgeGradle invokes Mavenizer during project configuration. Verify the
53+
// executable and its exact target rules before any Mavenizer code can run.
54+
[mavenizerCompatibilityJar.asFile, mavenizerRuleManifest.asFile].each { fixture ->
55+
if (!fixture.isFile()) {
56+
throw new GradleException("Missing Mavenizer compatibility fixture: ${fixture}")
57+
}
58+
String actual = sha256Of(fixture)
59+
String expected = mavenizerFixtureChecksums[fixture]
60+
if (actual != expected) {
61+
throw new GradleException("Mavenizer compatibility fixture checksum mismatch for " +
62+
"${fixture.name}: expected ${expected}, found ${actual}")
63+
}
64+
}
65+
66+
fgtools.configure('mavenizer') {
67+
classpath.from(mavenizerCompatibilityJar)
68+
mainClass.set('net.minecraftforge.mcmaven.cli.Main')
69+
javaLauncher.set(javaToolchains.launcherFor {
70+
languageVersion = JavaLanguageVersion.of(25)
71+
vendor = JvmVendorSpec.ADOPTIUM
72+
})
73+
}
74+
1975
group = project.mod_group
2076
version = project.mod_version
2177
base.archivesName = 'OreSpawn'
@@ -229,6 +285,93 @@ tasks.register('verifyLegacyFixtures') {
229285
}
230286
}
231287

288+
tasks.register('verifyMavenizerCompatibilityFixture') {
289+
group = 'verification'
290+
description = 'Verifies the sealed target-aware ForgeGradle Mavenizer compatibility tool.'
291+
inputs.files mavenizerFixtureChecksums.keySet()
292+
doLast {
293+
mavenizerFixtureChecksums.each { fixture, expected ->
294+
if (!fixture.isFile()) {
295+
throw new GradleException("Missing Mavenizer compatibility fixture: ${fixture}")
296+
}
297+
String actual = sha256Of(fixture)
298+
if (actual != expected) {
299+
throw new GradleException("Mavenizer compatibility fixture checksum mismatch for " +
300+
"${fixture.name}: expected ${expected}, found ${actual}")
301+
}
302+
}
303+
304+
def parsed = new JsonSlurper().parse(mavenizerRuleManifest.asFile)
305+
String target = 'net.minecraftforge:forge:1.20.1-47.4.10'
306+
if (parsed.schema != 1 || parsed.targets.keySet() != [target] as Set) {
307+
throw new GradleException('Mavenizer target-rule manifest has unexpected targets or schema')
308+
}
309+
def forgeRules = parsed.targets[target]
310+
def ruleIds = forgeRules.rules.collect { it.id }
311+
if (forgeRules.expectedApplications != 8 || forgeRules.rules.size() != 8
312+
|| ruleIds.toSet().size() != 8
313+
|| !ruleIds.every { it.startsWith('forge-1.20.1-') }) {
314+
throw new GradleException('Mavenizer target-rule manifest must define eight unique Forge 47 rules')
315+
}
316+
317+
ZipFile fixtureJar = new ZipFile(mavenizerCompatibilityJar.asFile)
318+
try {
319+
def embeddedEntry = fixtureJar.getEntry(
320+
'META-INF/orespawn/minecraft-source-compatibility.json')
321+
if (embeddedEntry == null) {
322+
throw new GradleException('Mavenizer fixture is missing its embedded target-rule manifest')
323+
}
324+
byte[] embedded = fixtureJar.getInputStream(embeddedEntry).withCloseable { it.readAllBytes() }
325+
byte[] external = mavenizerRuleManifest.asFile.bytes
326+
if (!Arrays.equals(embedded, external)) {
327+
throw new GradleException('Embedded and externally audited Mavenizer manifests differ')
328+
}
329+
330+
def patcherEntry = fixtureJar.getEntry(
331+
'net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.class')
332+
if (patcherEntry == null) {
333+
throw new GradleException('Mavenizer fixture is missing SourceCompatibilityPatcher')
334+
}
335+
byte[] classHeader = fixtureJar.getInputStream(patcherEntry).withCloseable {
336+
it.readNBytes(8)
337+
}
338+
if (classHeader.length != 8 || classHeader[0..3] !=
339+
[0xCA, 0xFE, 0xBA, 0xBE].collect { (byte) it }) {
340+
throw new GradleException('Mavenizer compatibility class has an invalid class header')
341+
}
342+
int classMajor = ((classHeader[6] & 0xff) << 8) | (classHeader[7] & 0xff)
343+
if (classMajor != 69) {
344+
throw new GradleException("Mavenizer compatibility tool must use Java 25 bytecode, found ${classMajor}")
345+
}
346+
347+
def jarManifestEntry = fixtureJar.getEntry('META-INF/MANIFEST.MF')
348+
Manifest jarManifest = new Manifest(fixtureJar.getInputStream(jarManifestEntry))
349+
if (jarManifest.mainAttributes.getValue('Main-Class') !=
350+
'net.minecraftforge.mcmaven.cli.Main') {
351+
throw new GradleException('Mavenizer compatibility fixture has an unexpected main class')
352+
}
353+
} finally {
354+
fixtureJar.close()
355+
}
356+
357+
String readme = mavenizerReadme.asFile.getText('UTF-8')
358+
String patchText = mavenizerSourcePatch.asFile.getText('UTF-8')
359+
String licenseText = mavenizerLicense.asFile.getText('UTF-8')
360+
if (!readme.contains('6968241ce7a0a902cdc1c534b976e8373a423091')
361+
|| !readme.contains('Temurin `25.0.3+9`')
362+
|| !readme.contains('1.20.1-47.4.10')) {
363+
throw new GradleException('Mavenizer provenance, target, or Java 25 build instructions are incomplete')
364+
}
365+
if (!patchText.contains('SourceCompatibilityPatcher.java')
366+
|| !patchText.contains('minecraft-source-compatibility.json')) {
367+
throw new GradleException('Mavenizer source patch is incomplete')
368+
}
369+
if (!licenseText.contains('GNU LESSER GENERAL PUBLIC LICENSE')) {
370+
throw new GradleException('Mavenizer LGPL-2.1 licence fixture is incomplete')
371+
}
372+
}
373+
}
374+
232375
dependencies {
233376
implementation minecraft.dependency(
234377
"net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}")
@@ -276,6 +419,7 @@ tasks.register('verifyJava17Toolchain') {
276419
tasks.named('check') {
277420
dependsOn tasks.named('verifyLegacyOracleIsolation')
278421
dependsOn tasks.named('verifyJava17Toolchain')
422+
dependsOn tasks.named('verifyMavenizerCompatibilityFixture')
279423
}
280424

281425
tasks.named('processResources', ProcessResources) {

0 commit comments

Comments
 (0)