@@ -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+
1975group = project. mod_group
2076version = project. mod_version
2177base. 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+
232375dependencies {
233376 implementation minecraft. dependency(
234377 " net.minecraftforge:forge:${ project.minecraft_version} -${ project.forge_version} " )
@@ -276,6 +419,7 @@ tasks.register('verifyJava17Toolchain') {
276419tasks. named(' check' ) {
277420 dependsOn tasks. named(' verifyLegacyOracleIsolation' )
278421 dependsOn tasks. named(' verifyJava17Toolchain' )
422+ dependsOn tasks. named(' verifyMavenizerCompatibilityFixture' )
279423}
280424
281425tasks. named(' processResources' , ProcessResources ) {
0 commit comments