From bf11d4437085ebf48026a2359cb436cfc628432e Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 09:33:26 -0400 Subject: [PATCH 1/3] VolmLib pin removal - The build resolves VolmLib master-SNAPSHOT when no sibling checkout is substituted - The native runtime manifest, roots and keep rules come from the embedded natives - The packed jar natives are verified against the embedded provider shas - Every jar carries META-INF/volmit/volmlib.properties - The build logs the resolved VolmLib once - publishedNativeRuntime, the loader coordinate flags, mavenLocal and publish-volmlib are gone --- README.md | 6 + adapters/bukkit/plugin/build.gradle | 8 +- adapters/fabric/build.gradle | 15 +- adapters/forge/build.gradle | 17 +- adapters/neoforge/build.gradle | 15 +- build.gradle | 151 ++++-------- buildSrc/build.gradle | 1 + .../src/main/java/NativeRuntimeArtifacts.java | 151 ++++++++---- .../test/java/NativeRuntimeArtifactsTest.java | 222 ++++++++++-------- core/build.gradle | 9 +- gradle.properties | 1 - gradle/generation-build-revision.gradle | 3 +- gradle/native-minecraft-sources.gradle | 6 +- gradle/volmlib-resolution.settings.gradle | 53 ++++- gradle/volmlib-stamp.gradle | 26 ++ jitpack.yml | 2 +- probe/build.gradle | 8 +- publish-volmlib.bat | 59 ----- publish-volmlib.sh | 57 ----- spi/build.gradle | 2 +- 20 files changed, 386 insertions(+), 426 deletions(-) create mode 100644 gradle/volmlib-stamp.gradle delete mode 100644 publish-volmlib.bat delete mode 100644 publish-volmlib.sh diff --git a/README.md b/README.md index 96180d806..d0f4e8ab7 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,12 @@ when it exists, otherwise `build/consumers/` inside the repo. `-Plocation=/path/ changes only the consumer directory. Both tasks copy all four verified platform jars into `../PluginOuts/`. +VolmLib is built from source when a `VolmLib` checkout sits beside the repo or in a parent directory +(`VOLMLIB_DIR` or `-PlocalVolmLibDirectory=` pick another one). Without one, or with +`-PuseLocalVolmLib=false`, the build uses VolmLib `master-SNAPSHOT` from JitPack. There is no VolmLib +version to bump. The build logs which VolmLib it used, and every jar records it in +`META-INF/volmit/volmlib.properties`. + If you need help compiling as a developer or contributor, ask in the Discord. ## Adapters / modded development diff --git a/adapters/bukkit/plugin/build.gradle b/adapters/bukkit/plugin/build.gradle index 38793cded..e3f1b98ed 100644 --- a/adapters/bukkit/plugin/build.gradle +++ b/adapters/bukkit/plugin/build.gradle @@ -2,15 +2,11 @@ String apiVersion = providers.gradleProperty('apiVersion').get() def mainClass = 'art.arcane.iris.Iris' def bootstrapperClass = 'art.arcane.iris.IrisBootstrap' -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') - .get() - dependencies { compileOnly(project(':core')) compileOnly(libs.lombok) annotationProcessor(libs.lombok) - compileOnly(volmLibCoordinate) { + compileOnly('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } compileOnly(libs.paper.api) @@ -23,7 +19,7 @@ dependencies { testImplementation(libs.bstats) testImplementation(project(':core')) testImplementation(testFixtures(project(':core'))) - testImplementation(volmLibCoordinate) { + testImplementation('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } compileOnly(libs.placeholderApi) diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index 3936168bd..264e015b1 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -32,11 +32,7 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - Properties libraryProperties = new Properties() - file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } - String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') - .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) - classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") + classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') } } @@ -56,13 +52,13 @@ interface IrisFileOperations { IrisFileOperations irisFileOperations = objects.newInstance(IrisFileOperations) apply from: file('../../gradle/modded-generation-build-revision.gradle') +apply from: file('../../gradle/volmlib-stamp.gradle') Properties rootProperties = new Properties() file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) } String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3')) String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.5')) -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')) Closure irisArtifactName = { String platform, String targetVersion -> return "Iris v${project.version} [${platform}] ${targetVersion}.jar" } @@ -180,10 +176,10 @@ dependencies { compileOnly(libs.spigot) { transitive = false } - add('bundle', volmLibCoordinate) { + add('bundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } - add('devBundle', volmLibCoordinate) { + add('devBundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } add('bundle', libs.zip) { @@ -286,6 +282,9 @@ tasks.named('shadowJar', ShadowJar).configure { spec.delete(staleLoaderJars, legacyLoaderJar) } } + from(tasks.named('generateVolmLibStamp')) { + into('META-INF/volmit') + } archiveFileName.set(irisArtifactName('Fabric', "${minecraftVersion}+${fabricLoaderVersion}")) configurations = [project.configurations.named('bundle').get()] mergeServiceFiles() diff --git a/adapters/forge/build.gradle b/adapters/forge/build.gradle index 8a61c81b3..cae2d8269 100644 --- a/adapters/forge/build.gradle +++ b/adapters/forge/build.gradle @@ -32,11 +32,7 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - Properties libraryProperties = new Properties() - file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } - String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') - .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) - classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") + classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') } } @@ -56,13 +52,13 @@ interface IrisFileOperations { IrisFileOperations irisFileOperations = objects.newInstance(IrisFileOperations) apply from: file('../../gradle/modded-generation-build-revision.gradle') +apply from: file('../../gradle/volmlib-stamp.gradle') Properties rootProperties = new Properties() file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) } String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3')) String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.3-66.0.8')) -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')) Closure loaderDisplayVersion = { String loaderVersion -> String coordinatePrefix = "${minecraftVersion}-" if (loaderVersion.startsWith(coordinatePrefix)) { @@ -173,13 +169,13 @@ dependencies { compileOnly(libs.spigot) { transitive = false } - add('bundle', volmLibCoordinate) { + add('bundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } - add('devBundle', volmLibCoordinate) { + add('devBundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } - add('runBundle', volmLibCoordinate) { + add('runBundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } add('bundle', libs.zip) { @@ -271,6 +267,9 @@ tasks.named('shadowJar', ShadowJar).configure { spec.delete(staleLoaderJars, legacyLoaderJar) } } + from(tasks.named('generateVolmLibStamp')) { + into('META-INF/volmit') + } manifest { // FML 26.3 has no TOML mixin handling at all (ModFileParser reads neither [[mixins]] nor // accessTransformers on Forge). This manifest attribute is the only registration mechanism; diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index 5b8f1eb86..0ffff3760 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -32,11 +32,7 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - Properties libraryProperties = new Properties() - file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } - String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') - .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) - classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") + classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') } } @@ -56,13 +52,13 @@ interface IrisFileOperations { IrisFileOperations irisFileOperations = objects.newInstance(IrisFileOperations) apply from: file('../../gradle/modded-generation-build-revision.gradle') +apply from: file('../../gradle/volmlib-stamp.gradle') Properties rootProperties = new Properties() file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) } String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.1.2-26.2')) String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.3')) String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.3.0.33-beta')) -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')) Closure irisArtifactName = { String platform, String targetVersion -> return "Iris v${project.version} [${platform}] ${targetVersion}.jar" } @@ -152,10 +148,10 @@ dependencies { compileOnly(libs.spigot) { transitive = false } - add('bundle', volmLibCoordinate) { + add('bundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } - add('devBundle', volmLibCoordinate) { + add('devBundle', 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } add('bundle', libs.zip) { @@ -274,6 +270,9 @@ tasks.named('shadowJar', ShadowJar).configure { spec.delete(staleLoaderJars, legacyLoaderJar) } } + from(tasks.named('generateVolmLibStamp')) { + into('META-INF/volmit') + } archiveFileName.set(irisArtifactName('NeoForge', "${minecraftVersion}+${neoForgeVersion}")) configurations = [project.configurations.named('bundle').get()] from(sourceSets.main.output) diff --git a/build.gradle b/build.gradle index b4269ebad..1295ab47b 100644 --- a/build.gradle +++ b/build.gradle @@ -43,8 +43,7 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - String volmLibRevision = providers.gradleProperty('volmLibCoordinate').get().tokenize(':').last() - classpath("com.github.VolmitSoftware.VolmLib:packaging:${volmLibRevision}") + classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') } } @@ -55,6 +54,7 @@ plugins { apply plugin: 'art.arcane.volmit-packaging' apply from: file('gradle/runtime-relocations.gradle') +apply from: file('gradle/volmlib-stamp.gradle') interface IrisFileOperations { @Inject @@ -76,15 +76,9 @@ String bukkitMinecraftRange = providers.gradleProperty('bukkitMinecraftRange').g String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.5') String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.3-66.0.8') String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.3.0.33-beta') -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') - .get() -String useLocalVolmLib = providers.gradleProperty('useLocalVolmLib').getOrElse('true') -String localVolmLibDirectory = providers.gradleProperty('localVolmLibDirectory').getOrNull() -boolean useMavenLocal = providers.gradleProperty('useMavenLocal') - .orElse(useLocalVolmLib.equalsIgnoreCase('true') ? 'false' : 'true') - .map { String value -> value.equalsIgnoreCase('true') } - .get() +String volmLibShared = 'com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT' +File localVolmLibCheckout = (File) gradle.extensions.extraProperties.get('volmLibDirectory') +Map volmLibStamp = (Map) gradle.extensions.extraProperties.get('volmLibStamp') Closure loaderDisplayVersion = { String loaderVersion -> String coordinatePrefix = "${minecraftVersion}-" if (loaderVersion.startsWith(coordinatePrefix)) { @@ -153,11 +147,11 @@ nmsBindings.each { String key -> } dependencies { - implementation("com.github.VolmitSoftware.VolmLib:native-${key}:${volmLibCoordinate.tokenize(':').last()}") { + implementation("com.github.VolmitSoftware.VolmLib:native-${key}:master-SNAPSHOT") { transitive = false } compileOnly(project(':core')) - compileOnly(volmLibCoordinate) { + compileOnly(volmLibShared) { transitive = false } compileOnly(rootProject.libs.annotations) @@ -169,10 +163,10 @@ nmsBindings.each { String key -> } testImplementation(project(':core')) testImplementation(testFixtures(project(':core'))) - testImplementation("com.github.VolmitSoftware.VolmLib:native-common:${volmLibCoordinate.tokenize(':').last()}") { + testImplementation('com.github.VolmitSoftware.VolmLib:native-common:master-SNAPSHOT') { transitive = false } - testCompileOnly(volmLibCoordinate) { + testCompileOnly(volmLibShared) { transitive = false } testRuntimeOnly(project(':core')) @@ -183,49 +177,32 @@ nmsBindings.each { String key -> def included = configurations.create('included') def nativeRuntime = configurations.create('nativeRuntime') -def publishedNativeRuntime = configurations.create('publishedNativeRuntime') { - canBeConsumed = false - transitive = false - resolutionStrategy.useGlobalDependencySubstitutionRules = false -} -repositories.configureEach { repository -> - if (repository.name != 'publishedNativeProviders') { - repository.content { notForConfigurations('publishedNativeRuntime') } - } -} -repositories.maven { - name = 'publishedNativeProviders' - url = uri('https://jitpack.io/') - content { - onlyForConfigurations('publishedNativeRuntime') - includeGroup('com.github.VolmitSoftware.VolmLib') - } -} def nativeRuntimeOutput = layout.buildDirectory.dir('generated/native-runtime') def nativeRuntimeRules = layout.buildDirectory.file('generated/native-runtime-keep.pro') List nativeRuntimeModules = ['native-common'] + nmsBindings.collect { String adapter -> 'native-' + adapter } dependencies { nativeRuntimeModules.each { String module -> - add('nativeRuntime', "com.github.VolmitSoftware.VolmLib:${module}:${volmLibCoordinate.tokenize(':').last()}") { + add('nativeRuntime', "com.github.VolmitSoftware.VolmLib:${module}:master-SNAPSHOT") { transitive = false } - add('publishedNativeRuntime', "com.github.VolmitSoftware.VolmLib:${module}:${volmLibCoordinate.tokenize(':').last()}") } } +// The natives PackedJar embeds are the single source for the runtime manifest, the reachability roots and +// the ProGuard keep rules, so the shrinker can never prune against a different provider build. +Closure> embeddedNatives = { -> + Map resolved = new LinkedHashMap<>() + nativeRuntime.incoming.artifacts.artifacts.each { artifact -> + resolved.put(artifact.id.componentIdentifier, artifact.file) + } + return PackedJar.resolveNativeArtifacts(resolved) +} def generateNativeRuntime = tasks.register('generateNativeRuntime') { - inputs.files(publishedNativeRuntime) - inputs.property('version', volmLibCoordinate.tokenize(':').last()) - inputs.property('adapters', nmsBindings) + inputs.files(nativeRuntime) + inputs.property('modules', nativeRuntimeModules) outputs.dir(nativeRuntimeOutput) outputs.file(nativeRuntimeRules) doLast { - Map resolved = new LinkedHashMap<>() - publishedNativeRuntime.incoming.artifacts.artifacts.each { artifact -> - resolved.put(artifact.id.componentIdentifier, artifact.file) - } - Map artifacts = NativeRuntimeArtifacts.publishedArtifacts(resolved, - nativeRuntimeModules, volmLibCoordinate.tokenize(':').last()) - NativeRuntimeArtifacts.generate(artifacts, volmLibCoordinate.tokenize(':').last(), + NativeRuntimeArtifacts.generate(embeddedNatives(), nativeRuntimeModules, nativeRuntimeOutput.get().asFile, nativeRuntimeRules.get().asFile) } } @@ -240,6 +217,9 @@ tasks.named('jar', Jar).configure { inputs.files(included) dependsOn(generateNativeRuntime) from(nativeRuntimeOutput) + from(tasks.named('generateVolmLibStamp')) { + into('META-INF/volmit') + } duplicatesStrategy = DuplicatesStrategy.EXCLUDE includeEmptyDirs = false from(jarJar) @@ -312,31 +292,6 @@ tasks.register('buildBukkit', Copy) { // task therefore has to carry the full inventory of what those builds read: every shared source tree, // every build script and version file they load, and the local VolmLib checkout when the composite // resolves one. Anything missing from that list publishes a stale jar. -Closure resolveLocalVolmLibDirectory = { -> - String configured = localVolmLibDirectory != null && !localVolmLibDirectory.isBlank() - ? localVolmLibDirectory - : System.getenv('VOLMLIB_DIR') - if (configured != null && !configured.isBlank()) { - File configuredDirectory = new File(configured) - if (!configuredDirectory.isAbsolute()) { - configuredDirectory = new File(rootDir, configured) - } - return configuredDirectory.isDirectory() ? configuredDirectory : null - } - - File current = rootDir - while (current != null) { - File candidate = new File(current, 'VolmLib') - if (new File(candidate, 'settings.gradle').isFile() || new File(candidate, 'settings.gradle.kts').isFile()) { - return candidate - } - - current = current.parentFile - } - - return null -} - List moddedSharedInputs = [] [ 'adapters/modded-common/src', @@ -353,11 +308,11 @@ List moddedSharedInputs = [] 'lombok.config', 'gradle/libs.versions.toml', 'gradle/volmlib-resolution.settings.gradle', + 'gradle/volmlib-stamp.gradle', 'gradle/generation-build-revision.gradle', 'gradle/modded-generation-build-revision.gradle', 'gradle/native-minecraft-sources.gradle' ].each { String path -> moddedSharedInputs.add(layout.projectDirectory.file(path).asFile) } -File localVolmLibCheckout = useLocalVolmLib.equalsIgnoreCase('true') ? resolveLocalVolmLibDirectory() : null if (localVolmLibCheckout != null) { moddedSharedInputs.add(fileTree(localVolmLibCheckout) { include('shared/src/main/**', 'native-api/src/main/**', 'native-terrain-api/src/main/**', @@ -377,8 +332,7 @@ Closure declareModdedJarIo = { Exec task, String adapter, String artifactN layout.projectDirectory.file("adapters/${adapter}/settings.gradle").asFile ).withPropertyName('adapterSources') versions.each { String key, String value -> task.inputs.property(key, value) } - task.inputs.property('useLocalVolmLib', useLocalVolmLib) - task.inputs.property('volmLibCoordinate', volmLibCoordinate) + task.inputs.property('volmLibStamp', volmLibStamp) task.outputs.file(layout.projectDirectory.file("adapters/${adapter}/build/libs/${artifactName}")) return null } @@ -394,8 +348,7 @@ tasks.register('fabricJar', Exec) { "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", "-PfabricLoaderVersion=${fabricLoaderVersion}", - "-PuseLocalVolmLib=${useLocalVolmLib}", - "-PvolmLibCoordinate=${volmLibCoordinate}" + "-PuseLocalVolmLib=${localVolmLibCheckout != null}" ] if (localVolmLibCheckout != null) { environment('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath) @@ -432,8 +385,7 @@ tasks.register('forgeJar', Exec) { "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", "-PforgeVersion=${forgeVersion}", - "-PuseLocalVolmLib=${useLocalVolmLib}", - "-PvolmLibCoordinate=${volmLibCoordinate}" + "-PuseLocalVolmLib=${localVolmLibCheckout != null}" ] if (localVolmLibCheckout != null) { environment('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath) @@ -470,8 +422,7 @@ tasks.register('neoforgeJar', Exec) { "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", "-PneoForgeVersion=${neoForgeVersion}", - "-PuseLocalVolmLib=${useLocalVolmLib}", - "-PvolmLibCoordinate=${volmLibCoordinate}" + "-PuseLocalVolmLib=${localVolmLibCheckout != null}" ] if (localVolmLibCheckout != null) { environment('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath) @@ -528,8 +479,7 @@ tasks.register('moddedTest', Exec) { "-PirisVersion=${project.version}", "-PminecraftVersion=${minecraftVersion}", "-PfabricLoaderVersion=${fabricLoaderVersion}", - "-PuseLocalVolmLib=${useLocalVolmLib}", - "-PvolmLibCoordinate=${volmLibCoordinate}" + "-PuseLocalVolmLib=${localVolmLibCheckout != null}" ] if (localVolmLibCheckout != null) { environment('ORG_GRADLE_PROJECT_localVolmLibDirectory', localVolmLibCheckout.absolutePath) @@ -545,8 +495,7 @@ tasks.register('moddedTest', Exec) { inputs.property('irisVersion', project.version.toString()) inputs.property('minecraftVersion', minecraftVersion) inputs.property('fabricLoaderVersion', fabricLoaderVersion) - inputs.property('useLocalVolmLib', useLocalVolmLib) - inputs.property('volmLibCoordinate', volmLibCoordinate) + inputs.property('volmLibStamp', volmLibStamp) outputs.dir(layout.projectDirectory.dir('adapters/fabric/build/test-results/test')) mustRunAfter(tasks.named('jar')) mustRunAfter(project(':core').tasks.named('check')) @@ -585,6 +534,7 @@ List commonModdedArtifactEntries = [ 'native/windows-x86_64/jlibdeflate.dll', 'META-INF/iris/generation-kernels/catalog.tsv', 'META-INF/iris/generation-kernels/abi-1.revision', + 'META-INF/volmit/volmlib.properties', 'META-INF/services/art.arcane.iris.localization.IrisMessageContributor', 'art/arcane/iris/generation/runtime/IrisEngine.class', 'art/arcane/iris/pack/datapack/DataVersion.class', @@ -700,6 +650,7 @@ List requiredBukkitArtifactEntries = [ 'slimjar.dat', 'slimjar-resolutions.dat', 'META-INF/iris/native-runtime.properties', + 'META-INF/volmit/volmlib.properties', 'iris-language-source.properties', 'agent.jar', 'art/arcane/iris/Iris.class', @@ -725,6 +676,7 @@ tasks.register('verifyBukkitArtifact') { dependsOn('jar') File artifact = bukkitArtifact inputs.file(artifact) + inputs.files(nativeRuntime) inputs.files(commandHandlerSources) inputs.files(mantleComponentSources) doLast { @@ -733,7 +685,7 @@ tasks.register('verifyBukkitArtifact') { requiredBukkitArtifactEntries, 16, runtimeDownloadedPrefixes) - NativeRuntimeArtifacts.verify(artifact, nmsBindings, volmLibCoordinate.tokenize(':').last()) + NativeRuntimeArtifacts.verify(artifact, nativeRuntimeModules, embeddedNatives()) logger.lifecycle("Verified ${artifact.name} packaging and class reference graph") } } @@ -819,11 +771,6 @@ tasks.register('irisDev', Copy) { dependsOn(':core:javadocJar') } -configurations.configureEach { - resolutionStrategy.cacheChangingModulesFor(0, 'seconds') - resolutionStrategy.cacheDynamicVersionsFor(0, 'seconds') -} - allprojects { apply plugin: 'java' @@ -834,9 +781,6 @@ allprojects { } repositories { - if (useMavenLocal) { - mavenLocal() - } mavenCentral() maven { url = uri('https://repo.papermc.io/repository/maven-public/') } maven { url = uri('https://repo.codemc.org/repository/maven-public/') } @@ -990,7 +934,7 @@ project(':adapters:bukkit:nms:v26_3_R1') { canBeResolved = true } dependencies.add(headlessNativeSources.name, - "com.github.VolmitSoftware.VolmLib:native-minecraft26_2-sources:${volmLibCoordinate.tokenize(':').last()}") + 'com.github.VolmitSoftware.VolmLib:native-minecraft26_2-sources:master-SNAPSHOT') List headlessNativeTypes = ['ModdedBlockState', 'ModdedBiome', 'NativeBlockProperties', 'NativeBlockResolver', 'NativeBlockFaces', 'NativeStateRotator', 'NativeStateMerger', 'NativeRegistryAccess', 'ModdedItem', 'ModdedEntityType', 'NativeTileReader', 'NativeTileData'] @@ -1016,10 +960,10 @@ project(':adapters:bukkit:nms:v26_3_R1') { dependencies { offlineImplementation(project(':probe')) offlineImplementation(project(':core')) - offlineImplementation("com.github.VolmitSoftware.VolmLib:native-common:${volmLibCoordinate.tokenize(':').last()}") { + offlineImplementation('com.github.VolmitSoftware.VolmLib:native-common:master-SNAPSHOT') { transitive = false } - offlineImplementation(volmLibCoordinate) { + offlineImplementation(volmLibShared) { transitive = false } } @@ -1132,14 +1076,11 @@ commands: } tasks.withType(PackedJar).configureEach { - nativeManifestPath.set('META-INF/iris/native-runtime.properties') - nativeResourceDirectory.set('META-INF/iris/native/') - dependsOn(tasks.named('verifyBukkitArtifact'), configurations.nativeRuntime) - nativeArtifacts.set(providers.provider { - Map resolved = new LinkedHashMap<>() - configurations.nativeRuntime.incoming.artifacts.artifacts.each { artifact -> - resolved.put(artifact.id.componentIdentifier, artifact.file) - } - PackedJar.resolveNativeArtifacts(resolved) - }) + nativeManifestPath.set(NativeRuntimeArtifacts.MANIFEST) + nativeResourceDirectory.set(NativeRuntimeArtifacts.RESOURCES) + dependsOn(tasks.named('verifyBukkitArtifact'), nativeRuntime) + nativeArtifacts.set(providers.provider { embeddedNatives() }) + doLast { PackedJar task -> + NativeRuntimeArtifacts.verifyPacked(task.outputJar.get().asFile, nativeRuntimeModules, embeddedNatives()) + } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 67f86ee32..6aa6169ff 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation(gradleApi()) implementation('org.ow2.asm:asm:9.10.1') implementation('org.apache.commons:commons-compress:1.28.0') + implementation('org.tukaani:xz:1.10') implementation('io.papermc.paperweight:paperweight-userdev:2.0.0-beta.21') testImplementation('junit:junit:4.13.2') } diff --git a/buildSrc/src/main/java/NativeRuntimeArtifacts.java b/buildSrc/src/main/java/NativeRuntimeArtifacts.java index e49bba3ed..f5aa0aaf5 100644 --- a/buildSrc/src/main/java/NativeRuntimeArtifacts.java +++ b/buildSrc/src/main/java/NativeRuntimeArtifacts.java @@ -1,7 +1,7 @@ import org.gradle.api.GradleException; -import org.gradle.api.artifacts.component.ComponentIdentifier; -import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.tukaani.xz.XZInputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Enumeration; import java.util.HexFormat; import java.util.List; @@ -21,46 +22,32 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; public final class NativeRuntimeArtifacts { public static final String MANIFEST = "META-INF/iris/native-runtime.properties"; + public static final String RESOURCES = "META-INF/iris/native/"; + private static final String PAYLOAD = "META-INF/volmit/runtime.jar.xz"; private static final String GROUP = "com.github.VolmitSoftware.VolmLib"; private static final String NATIVE = "art/arcane/volmlib/nativelib/"; private NativeRuntimeArtifacts() { } - public static Map publishedArtifacts(Map resolved, - List modules, String version) { - Map artifacts = new TreeMap<>(); - for (Map.Entry entry : resolved.entrySet()) { - if (!(entry.getKey() instanceof ModuleComponentIdentifier component) - || !GROUP.equals(component.getGroup()) || !version.equals(component.getVersion()) - || !modules.contains(component.getModule())) { - throw new GradleException("Native runtime providers must resolve to published coordinates at " - + version + ": " + entry.getKey().getDisplayName()); - } - if (artifacts.put(component.getModule(), entry.getValue()) != null) { - throw new GradleException("Duplicate native runtime provider: " + component.getModule()); - } - } - if (!artifacts.keySet().equals(new TreeSet<>(modules))) { - throw new GradleException("Published native runtime providers differ: " + artifacts.keySet() - + "; expected " + modules); - } - return artifacts; - } - - public static void generate(Map artifacts, String version, File output, File rules) throws IOException { + public static void generate(Map natives, List modules, File output, File rules) throws IOException { + requireModules(natives, modules); + Map providers = new TreeMap<>(natives); List manifest = new ArrayList<>(); - manifest.add("modules=" + String.join(",", artifacts.keySet())); - manifest.add("repository=https://jitpack.io/"); + manifest.add("modules=" + String.join(",", providers.keySet())); + manifest.add("storage=embedded"); Set retained = new TreeSet<>(); - for (Map.Entry artifact : artifacts.entrySet()) { - String module = artifact.getKey(); - manifest.add(module + ".coordinate=" + GROUP + ":" + module + ":" + version); - manifest.add(module + ".sha256=" + digest(artifact.getValue())); - try (JarFile jar = new JarFile(artifact.getValue())) { + for (Map.Entry provider : providers.entrySet()) { + String module = provider.getKey(); + String digest = digest(Files.readAllBytes(provider.getValue().toPath())); + manifest.add(module + ".coordinate=" + GROUP + ":" + module + ":embedded-" + digest); + manifest.add(module + ".sha256=" + digest); + try (JarFile jar = new JarFile(provider.getValue())) { Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); @@ -92,31 +79,15 @@ public final class NativeRuntimeArtifacts { Files.write(rules.toPath(), keep, StandardCharsets.UTF_8); } - public static void verify(File artifact, List adapters, String version) throws IOException { + public static void verify(File artifact, List modules, Map natives) throws IOException { + requireModules(natives, modules); try (JarFile jar = new JarFile(artifact)) { JarEntry entry = jar.getJarEntry(MANIFEST); if (entry == null) { throw new GradleException("Missing native runtime dependency manifest"); } - Properties manifest = new Properties(); try (InputStream input = jar.getInputStream(entry)) { - manifest.load(input); - } - Set expected = new TreeSet<>(); - expected.add("native-common"); - for (String adapter : adapters) { - expected.add("native-" + adapter); - } - Set actual = new TreeSet<>(List.of(manifest.getProperty("modules", "").split(","))); - if (!expected.equals(actual)) { - throw new GradleException("Native dependency modules differ: " + actual + "; expected " + expected); - } - for (String module : expected) { - String coordinate = GROUP + ":" + module + ":" + version; - if (!coordinate.equals(manifest.getProperty(module + ".coordinate")) - || !manifest.getProperty(module + ".sha256", "").matches("[0-9a-f]{64}")) { - throw new GradleException("Invalid native runtime dependency: " + module); - } + verifyManifest(input.readAllBytes(), natives); } Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { @@ -128,6 +99,82 @@ public final class NativeRuntimeArtifacts { } } + public static void verifyPacked(File artifact, List modules, Map natives) throws IOException { + requireModules(natives, modules); + Map contents; + try (JarFile jar = new JarFile(artifact)) { + JarEntry payload = jar.getJarEntry(PAYLOAD); + if (payload == null) { + contents = read(Files.newInputStream(artifact.toPath())); + } else { + try (InputStream input = new XZInputStream(jar.getInputStream(payload))) { + contents = read(new ByteArrayInputStream(input.readAllBytes())); + } + JarEntry outer = jar.getJarEntry(MANIFEST); + byte[] embedded = contents.get(MANIFEST); + if (outer != null && embedded != null) { + try (InputStream input = jar.getInputStream(outer)) { + if (!Arrays.equals(input.readAllBytes(), embedded)) { + throw new GradleException("Packed native runtime manifests differ"); + } + } + } + } + } + byte[] manifest = contents.get(MANIFEST); + if (manifest == null) { + throw new GradleException("Missing native runtime dependency manifest"); + } + verifyManifest(manifest, natives); + for (String module : natives.keySet()) { + byte[] provider = contents.get(RESOURCES + module + ".jar"); + if (provider == null) { + throw new GradleException("Missing embedded native provider: " + module); + } + if (!digest(provider).equals(digest(Files.readAllBytes(natives.get(module).toPath())))) { + throw new GradleException("Embedded native provider differs from the resolved build: " + module); + } + } + } + + private static void verifyManifest(byte[] bytes, Map natives) throws IOException { + Properties manifest = new Properties(); + manifest.load(new ByteArrayInputStream(bytes)); + Set actual = new TreeSet<>(List.of(manifest.getProperty("modules", "").split(","))); + if (!natives.keySet().equals(actual)) { + throw new GradleException("Native dependency modules differ: " + actual + "; expected " + natives.keySet()); + } + if (!"embedded".equals(manifest.getProperty("storage"))) { + throw new GradleException("Native runtime providers must be embedded: " + manifest.getProperty("storage")); + } + for (Map.Entry provider : natives.entrySet()) { + String module = provider.getKey(); + String digest = digest(Files.readAllBytes(provider.getValue().toPath())); + if (!digest.equals(manifest.getProperty(module + ".sha256")) + || !(GROUP + ":" + module + ":embedded-" + digest).equals(manifest.getProperty(module + ".coordinate"))) { + throw new GradleException("Native runtime manifest does not describe the embedded provider: " + module); + } + } + } + + private static void requireModules(Map natives, List modules) { + if (!natives.keySet().equals(new TreeSet<>(modules))) { + throw new GradleException("Embedded native providers differ: " + natives.keySet() + "; expected " + modules); + } + } + + private static Map read(InputStream source) throws IOException { + Map contents = new TreeMap<>(); + try (ZipInputStream archive = new ZipInputStream(source)) { + for (ZipEntry entry = archive.getNextEntry(); entry != null; entry = archive.getNextEntry()) { + if (!entry.isDirectory()) { + contents.put(entry.getName(), archive.readAllBytes()); + } + } + } + return contents; + } + private static boolean implementation(String name) { if (!name.startsWith(NATIVE)) { return false; @@ -137,9 +184,9 @@ public final class NativeRuntimeArtifacts { || relative.matches("v[0-9_]+R[0-9]+/.*"); } - private static String digest(File file) throws IOException { + private static String digest(byte[] bytes) { try { - return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file.toPath()))); + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)); } catch (NoSuchAlgorithmException exception) { throw new IllegalStateException(exception); } diff --git a/buildSrc/src/test/java/NativeRuntimeArtifactsTest.java b/buildSrc/src/test/java/NativeRuntimeArtifactsTest.java index 52d9df462..fb4afa76b 100644 --- a/buildSrc/src/test/java/NativeRuntimeArtifactsTest.java +++ b/buildSrc/src/test/java/NativeRuntimeArtifactsTest.java @@ -1,12 +1,15 @@ import org.gradle.api.GradleException; -import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.artifacts.component.ComponentIdentifier; -import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; +import org.tukaani.xz.LZMA2Options; +import org.tukaani.xz.XZOutputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; @@ -15,122 +18,149 @@ import java.util.HexFormat; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class NativeRuntimeArtifactsTest { - @Test - public void acceptsOnlyRequestedPublishedCoordinates() { - File common = new File("common.jar"); - File provider = new File("provider.jar"); - Map resolved = Map.of( - module("native-common", "revision"), common, - module("native-v26_2_R1", "revision"), provider); - assertEquals(Map.of("native-common", common, "native-v26_2_R1", provider), - NativeRuntimeArtifacts.publishedArtifacts(resolved, - List.of("native-common", "native-v26_2_R1"), "revision")); - } + private static final List MODULES = List.of("native-common", "native-v26_2_R1"); + + @Rule + public TemporaryFolder temporary = new TemporaryFolder(); @Test - public void rejectsCompositeProjectArtifacts() { - ComponentIdentifier project = () -> "project :VolmLib:native-common"; - assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.publishedArtifacts( - Map.of(project, new File("native-common-local-SNAPSHOT.jar")), List.of("native-common"), "revision")); - } - - @Test - public void rejectsUnexpectedCoordinates() { - List invalid = List.of( - new PublishedModule("other.group", "native-common", "revision"), - module("native-common", "other-revision"), - module("native-v26_3_R1", "revision")); - for (ComponentIdentifier component : invalid) { - assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.publishedArtifacts( - Map.of(component, new File("provider.jar")), List.of("native-common"), "revision")); - } - } - - @Test - public void rejectsMissingProviders() { - assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.publishedArtifacts( - Map.of(module("native-common", "revision"), new File("common.jar")), - List.of("native-common", "native-v26_2_R1"), "revision")); - } - - @Test - public void recordsExactDependenciesAndRetainsSharedProviderReferences() throws Exception { - Path directory = Files.createTempDirectory("native-artifacts"); - File provider = directory.resolve("provider.jar").toFile(); - ClassWriter writer = new ClassWriter(0); - writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "art/arcane/volmlib/nativelib/v26_2_R1/terrain/Provider", null, - "art/arcane/volmlib/util/collection/KList", null); - writer.visitEnd(); - try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(provider.toPath()))) { - jar.putNextEntry(new JarEntry("art/arcane/volmlib/nativelib/v26_2_R1/terrain/Provider.class")); - jar.write(writer.toByteArray()); - jar.closeEntry(); - } - File rules = directory.resolve("keep.pro").toFile(); - NativeRuntimeArtifacts.generate(Map.of("native-common", provider, "native-v26_2_R1", provider), - "revision", directory.toFile(), rules); + public void describesTheEmbeddedProvidersAndRetainsTheirSharedReferences() throws Exception { + Map natives = natives(); + File output = temporary.newFolder("generated"); + File rules = new File(output, "keep.pro"); + NativeRuntimeArtifacts.generate(natives, MODULES, output, rules); Properties manifest = new Properties(); - try (Reader reader = Files.newBufferedReader(directory.resolve(NativeRuntimeArtifacts.MANIFEST))) { + try (Reader reader = Files.newBufferedReader(output.toPath().resolve(NativeRuntimeArtifacts.MANIFEST))) { manifest.load(reader); } - String digest = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256") - .digest(Files.readAllBytes(provider.toPath()))); - assertEquals(digest, manifest.getProperty("native-common.sha256")); - assertEquals(digest, manifest.getProperty("native-v26_2_R1.sha256")); + assertEquals("native-common,native-v26_2_R1", manifest.getProperty("modules")); + assertEquals("embedded", manifest.getProperty("storage")); + assertFalse(manifest.containsKey("repository")); + for (String module : MODULES) { + String digest = digest(Files.readAllBytes(natives.get(module).toPath())); + assertEquals(digest, manifest.getProperty(module + ".sha256")); + assertEquals("com.github.VolmitSoftware.VolmLib:" + module + ":embedded-" + digest, + manifest.getProperty(module + ".coordinate")); + } assertTrue(Files.readString(rules.toPath()).contains("-keep class art.arcane.volmlib.util.collection.KList { *; }")); - File plugin = directory.resolve("plugin.jar").toFile(); - try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(plugin.toPath()))) { - jar.putNextEntry(new JarEntry(NativeRuntimeArtifacts.MANIFEST)); - Files.copy(directory.resolve(NativeRuntimeArtifacts.MANIFEST), jar); - jar.closeEntry(); - } - NativeRuntimeArtifacts.verify(plugin, List.of("v26_2_R1"), "revision"); - assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verify(plugin, List.of("v26_3_R1"), "revision")); - assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verify(plugin, List.of("v26_2_R1"), "other")); + assertEquals(List.of("art/arcane/volmlib/util/collection/KList"), + Files.readAllLines(output.toPath().resolve("META-INF/volmit/runtime-roots.list"))); } - private static ModuleComponentIdentifier module(String module, String version) { - return new PublishedModule("com.github.VolmitSoftware.VolmLib", module, version); + @Test + public void rejectsAProviderSetThatDiffersFromTheRuntimeModules() throws Exception { + Map natives = natives(); + natives.remove("native-v26_2_R1"); + File output = temporary.newFolder("generated"); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.generate(natives, MODULES, output, + new File(output, "keep.pro"))); } - private record PublishedModule(String group, String module, String version) - implements ModuleComponentIdentifier, ModuleIdentifier { - @Override - public String getGroup() { - return group; - } + @Test + public void verifiesThePluginManifestAgainstTheProvidersBeingEmbedded() throws Exception { + Map natives = natives(); + File plugin = plugin(natives, Map.of()); + NativeRuntimeArtifacts.verify(plugin, MODULES, natives); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verify(plugin, + List.of("native-common", "native-v26_3_R1"), natives)); + Map rebuilt = new TreeMap<>(natives); + rebuilt.put("native-v26_2_R1", provider("rebuilt.jar", "art/arcane/volmlib/util/collection/KMap")); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verify(plugin, MODULES, rebuilt)); + } - @Override - public String getModule() { - return module; - } + @Test + public void rejectsProviderImplementationsBundledOutsideTheEmbeddedJars() throws Exception { + Map natives = natives(); + File plugin = plugin(natives, Map.of("art/arcane/volmlib/nativelib/v26_2_R1/terrain/Provider.class", new byte[0])); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verify(plugin, MODULES, natives)); + } - @Override - public String getVersion() { - return version; - } + @Test + public void verifiesTheEmbeddedProviderBytesInsideThePackedRuntime() throws Exception { + Map natives = natives(); + Map runtime = runtime(natives, natives); + NativeRuntimeArtifacts.verifyPacked(packed(runtime), MODULES, natives); + NativeRuntimeArtifacts.verifyPacked(jar("ordinary.jar", runtime), MODULES, natives); + Map rebuilt = new TreeMap<>(natives); + rebuilt.put("native-v26_2_R1", provider("rebuilt.jar", "art/arcane/volmlib/util/collection/KMap")); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verifyPacked(packed(runtime(natives, rebuilt)), + MODULES, natives)); + Map missing = new TreeMap<>(runtime); + missing.remove(NativeRuntimeArtifacts.RESOURCES + "native-v26_2_R1.jar"); + assertThrows(GradleException.class, () -> NativeRuntimeArtifacts.verifyPacked(packed(missing), MODULES, natives)); + } - @Override - public ModuleIdentifier getModuleIdentifier() { - return this; - } + private Map natives() throws IOException { + Map natives = new TreeMap<>(); + natives.put("native-common", provider("common.jar", "java/lang/Object")); + natives.put("native-v26_2_R1", provider("v26_2_R1.jar", "art/arcane/volmlib/util/collection/KList")); + return natives; + } - @Override - public String getName() { - return module; - } + private File provider(String name, String superclass) throws IOException { + ClassWriter writer = new ClassWriter(0); + writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "art/arcane/volmlib/nativelib/v26_2_R1/terrain/Provider", null, + superclass, null); + writer.visitEnd(); + return jar(name, Map.of("art/arcane/volmlib/nativelib/v26_2_R1/terrain/Provider.class", writer.toByteArray())); + } - @Override - public String getDisplayName() { - return group + ":" + module + ":" + version; + private File plugin(Map natives, Map extra) throws IOException { + File generated = temporary.newFolder(); + NativeRuntimeArtifacts.generate(natives, MODULES, generated, new File(generated, "keep.pro")); + Map contents = new TreeMap<>(extra); + contents.put(NativeRuntimeArtifacts.MANIFEST, + Files.readAllBytes(generated.toPath().resolve(NativeRuntimeArtifacts.MANIFEST))); + return jar("plugin.jar", contents); + } + + private Map runtime(Map described, Map embedded) throws IOException { + File generated = temporary.newFolder(); + NativeRuntimeArtifacts.generate(described, MODULES, generated, new File(generated, "keep.pro")); + Map contents = new TreeMap<>(); + contents.put(NativeRuntimeArtifacts.MANIFEST, + Files.readAllBytes(generated.toPath().resolve(NativeRuntimeArtifacts.MANIFEST))); + for (Map.Entry provider : embedded.entrySet()) { + contents.put(NativeRuntimeArtifacts.RESOURCES + provider.getKey() + ".jar", + Files.readAllBytes(provider.getValue().toPath())); } + return contents; + } + + private File packed(Map runtime) throws IOException { + ByteArrayOutputStream compressed = new ByteArrayOutputStream(); + try (XZOutputStream xz = new XZOutputStream(compressed, new LZMA2Options(1))) { + xz.write(Files.readAllBytes(jar("payload.jar", runtime).toPath())); + } + Map outer = new TreeMap<>(); + outer.put(NativeRuntimeArtifacts.MANIFEST, runtime.get(NativeRuntimeArtifacts.MANIFEST)); + outer.put("META-INF/volmit/runtime.jar.xz", compressed.toByteArray()); + return jar("packed.jar", outer); + } + + private File jar(String name, Map contents) throws IOException { + Path file = temporary.newFolder().toPath().resolve(name); + try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(file))) { + for (Map.Entry entry : contents.entrySet()) { + jar.putNextEntry(new JarEntry(entry.getKey())); + jar.write(entry.getValue()); + jar.closeEntry(); + } + } + return file.toFile(); + } + + private static String digest(byte[] bytes) throws Exception { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)); } } diff --git a/core/build.gradle b/core/build.gradle index 3f47c75b7..edc046f0c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -38,9 +38,6 @@ plugins { apply from: rootProject.file('gradle/runtime-relocations.gradle') def lib = 'art.arcane.iris.util' -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') - .get() String languageSourceReference = providers.gradleProperty('languageRevision').orElse('master').get() List languageSourceFiles = fileTree('src/main/resources/languages') { @@ -93,10 +90,10 @@ tasks.named('processResources').configure { */ dependencies { api(project(':spi')) - api("com.github.VolmitSoftware.VolmLib:native-api:${volmLibCoordinate.tokenize(':').last()}") { + api('com.github.VolmitSoftware.VolmLib:native-api:master-SNAPSHOT') { transitive = false } - api("com.github.VolmitSoftware.VolmLib:native-terrain-api:${volmLibCoordinate.tokenize(':').last()}") { + api('com.github.VolmitSoftware.VolmLib:native-terrain-api:master-SNAPSHOT') { transitive = false } @@ -129,7 +126,7 @@ dependencies { // Shaded implementation('de.crazydev22.slimjar.helper:spigot:2.1.9') - implementation(volmLibCoordinate) { + implementation('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } implementation('com.moandjiezana.toml:toml4j:0.7.2') { diff --git a/gradle.properties b/gradle.properties index d1dde0a20..46532118c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,4 +28,3 @@ apiVersion=26.1 fabricLoaderVersion=0.19.5 forgeVersion=26.3-66.0.8 neoForgeVersion=26.3.0.33-beta -volmLibCoordinate=com.github.VolmitSoftware.VolmLib:volmlib-shared:d24d28acddbf3555c0c30ac99569ebacd476ce27 diff --git a/gradle/generation-build-revision.gradle b/gradle/generation-build-revision.gradle index 8785ce3a2..eee1cb288 100644 --- a/gradle/generation-build-revision.gradle +++ b/gradle/generation-build-revision.gradle @@ -13,13 +13,12 @@ Configuration kernelDependencies = configurations.create('generationKernelDepend extendsFrom(configurations.implementation, configurations.slim, configurations.annotationProcessor) } dependencies.add(kernelDependencies.name, libs.guava) -String nativeLibraryVersion = providers.gradleProperty('volmLibCoordinate').get().tokenize(':').last() List nativeRevisionModules = ['native-common', 'native-minecraft26_2-sources'] for (String adapter : rootProject.extensions.extraProperties.get('nativeDevelopmentAdapters')) { nativeRevisionModules.add('native-' + adapter) } for (String module : nativeRevisionModules) { - dependencies.add(kernelDependencies.name, "com.github.VolmitSoftware.VolmLib:${module}:${nativeLibraryVersion}") { + dependencies.add(kernelDependencies.name, "com.github.VolmitSoftware.VolmLib:${module}:master-SNAPSHOT") { transitive = false } } diff --git a/gradle/native-minecraft-sources.gradle b/gradle/native-minecraft-sources.gradle index 4cc8201af..38ab738e0 100644 --- a/gradle/native-minecraft-sources.gradle +++ b/gradle/native-minecraft-sources.gradle @@ -3,16 +3,12 @@ Configuration nativeMinecraftSources = configurations.create('nativeMinecraftSou canBeResolved = true } -Properties nativeLibraryProperties = new Properties() -file('../../gradle.properties').withInputStream { InputStream stream -> nativeLibraryProperties.load(stream) } -String nativeLibraryCoordinate = providers.gradleProperty('volmLibCoordinate') - .getOrElse(nativeLibraryProperties.getProperty('volmLibCoordinate')) String nativeSourceLoader = extensions.extraProperties.get('nativeSourceLoader') String nativeSourceImplementation = 'minecraft26_2' String nativeSourceMinecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.3') extensions.extraProperties.set('nativeSourceImplementation', nativeSourceImplementation) dependencies.add(nativeMinecraftSources.name, - "com.github.VolmitSoftware.VolmLib:native-${nativeSourceImplementation}-sources:${nativeLibraryCoordinate.tokenize(':').last()}") + "com.github.VolmitSoftware.VolmLib:native-${nativeSourceImplementation}-sources:master-SNAPSHOT") TaskProvider unpackNativeMinecraftSources = tasks.register('unpackNativeMinecraftSources', Sync) { dependsOn(nativeMinecraftSources) diff --git a/gradle/volmlib-resolution.settings.gradle b/gradle/volmlib-resolution.settings.gradle index 02ffef79d..6e2f580dc 100644 --- a/gradle/volmlib-resolution.settings.gradle +++ b/gradle/volmlib-resolution.settings.gradle @@ -40,10 +40,17 @@ * otherwise walk up from settingsDir looking for a `VolmLib` directory * that contains a settings script. * - * When nothing is found the build falls back to the published `volmLibCoordinate` from - * gradle.properties. + * When nothing is found, or substitution is disabled, every VolmLib coordinate resolves the published + * master-SNAPSHOT from JitPack instead, re-checked on every build like the rest of the plugin fleet. + * + * The outcome is logged once per top-level build and handed to the build scripts through `gradle` + * extra properties: `volmLibDirectory` (the substituted checkout, or null) and `volmLibStamp` (the + * content of META-INF/volmit/volmlib.properties in every shipped jar). */ +import org.gradle.api.artifacts.Configuration +import org.gradle.process.ExecOutput + import java.io.File boolean irisHasVolmLibSettings(File directory) { @@ -85,9 +92,47 @@ boolean irisUseLocalVolmLib = providers.gradleProperty('useLocalVolmLib') .orElse('true') .map { String value -> value.equalsIgnoreCase('true') } .get() -File irisLocalVolmLibDirectory = irisResolveLocalVolmLibDirectory() +File irisLocalVolmLibDirectory = irisUseLocalVolmLib ? irisResolveLocalVolmLibDirectory() : null -if (irisUseLocalVolmLib && irisLocalVolmLibDirectory != null) { +String irisGit(File directory, String... arguments) { + try { + ExecOutput result = providers.exec { + commandLine(['git', '-C', directory.absolutePath] + arguments.toList()) + ignoreExitValue = true + } + return result.result.get().exitValue == 0 ? result.standardOutput.asText.get().trim() : null + } catch (Exception ignored) { + return null + } +} + +Map irisVolmLibStamp +String irisVolmLibDescription +if (irisLocalVolmLibDirectory == null) { + irisVolmLibStamp = [source: 'published master-SNAPSHOT'] + irisVolmLibDescription = 'published master-SNAPSHOT' + gradle.allprojects { Project project -> + project.buildscript.configurations.configureEach { Configuration configuration -> + configuration.resolutionStrategy.cacheChangingModulesFor(0, 'seconds') + } + project.configurations.configureEach { Configuration configuration -> + configuration.resolutionStrategy.cacheChangingModulesFor(0, 'seconds') + } + } +} else { + String commit = irisGit(irisLocalVolmLibDirectory, 'rev-parse', 'HEAD') + String status = commit == null ? null : irisGit(irisLocalVolmLibDirectory, 'status', '--porcelain') + String dirty = status == null ? 'unknown' : String.valueOf(!status.isEmpty()) + irisVolmLibStamp = [source: 'local', commit: commit ?: 'unknown', dirty: dirty] + irisVolmLibDescription = "${irisLocalVolmLibDirectory.canonicalPath} @ ${commit == null ? 'unknown' : commit.take(7)}${dirty == 'true' ? '+dirty' : ''}" +} +gradle.extensions.extraProperties.set('volmLibDirectory', irisLocalVolmLibDirectory) +gradle.extensions.extraProperties.set('volmLibStamp', irisVolmLibStamp) +if (gradle.parent == null) { + logger.lifecycle("VolmLib: ${irisVolmLibDescription}") +} + +if (irisLocalVolmLibDirectory != null) { includeBuild(irisLocalVolmLibDirectory) { dependencySubstitution { substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared')) diff --git a/gradle/volmlib-stamp.gradle b/gradle/volmlib-stamp.gradle new file mode 100644 index 000000000..f655c8df8 --- /dev/null +++ b/gradle/volmlib-stamp.gradle @@ -0,0 +1,26 @@ +/* + * Iris is a World Generator for Minecraft Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// Writes the VolmLib provenance that gradle/volmlib-resolution.settings.gradle resolved. Every shipped jar +// carries it as META-INF/volmit/volmlib.properties. +Map volmLibStamp = (Map) gradle.extensions.extraProperties.get('volmLibStamp') + +tasks.register('generateVolmLibStamp', WriteProperties) { + destinationFile.set(layout.buildDirectory.file('generated/volmlib-stamp/volmlib.properties')) + properties(volmLibStamp) +} diff --git a/jitpack.yml b/jitpack.yml index 8b9002186..9f0056e7f 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - openjdk25 install: - - ./gradlew --no-daemon build publishToMavenLocal -PuseLocalVolmLib=false -PuseMavenLocal=false + - ./gradlew --no-daemon build publishToMavenLocal -PuseLocalVolmLib=false diff --git a/probe/build.gradle b/probe/build.gradle index 512c6cd26..75fb1ca1a 100644 --- a/probe/build.gradle +++ b/probe/build.gradle @@ -9,10 +9,6 @@ application { mainClass = 'art.arcane.iris.probe.ClassloadProbe' } -String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') - .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') - .get() - configurations { studioProbeApi } @@ -23,10 +19,10 @@ dependencies { testImplementation('junit:junit:4.13.2') testImplementation(libs.gson) testImplementation(testFixtures(project(':core'))) - testImplementation(volmLibCoordinate) { + testImplementation('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } - compileOnly(volmLibCoordinate) { + compileOnly('com.github.VolmitSoftware.VolmLib:volmlib-shared:master-SNAPSHOT') { transitive = false } runtimeOnly(libs.paralithic) diff --git a/publish-volmlib.bat b/publish-volmlib.bat deleted file mode 100644 index 8095840c0..000000000 --- a/publish-volmlib.bat +++ /dev/null @@ -1,59 +0,0 @@ -@echo off -REM Publishes VolmLib to the local Maven repository under the coordinate this project -REM resolves by default: com.github.VolmitSoftware:VolmLib:master-SNAPSHOT. -REM -REM VolmLib is normally picked up as a composite build straight from source, so this is -REM only needed when you build with -PuseLocalVolmLib=false, or when another tool has to -REM resolve VolmLib from the local Maven repository rather than the included build. -REM -REM The lookup mirrors settings.gradle: it walks up from this script looking for a -REM VolmLib checkout sitting beside the project root. Set VOLMLIB_DIR to override. -REM Any extra arguments are forwarded to gradle. - -setlocal - -set "SCRIPT_DIR=%~dp0" -if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" - -set "VOLMLIB_DIRECTORY=" - -if defined VOLMLIB_DIR call :check "%VOLMLIB_DIR%" -if not defined VOLMLIB_DIRECTORY call :search "%SCRIPT_DIR%" - -if not defined VOLMLIB_DIRECTORY ( - echo Could not find a VolmLib checkout. 1>&2 - echo Expected a VolmLib directory beside this project root, or VOLMLIB_DIR pointing at one. 1>&2 - exit /b 1 -) - -echo ==^> Publishing VolmLib from %VOLMLIB_DIRECTORY% - -pushd "%VOLMLIB_DIRECTORY%" -if errorlevel 1 exit /b 1 - -call gradlew.bat publishToMavenLocal -Pgroup=com.github.VolmitSoftware -Pversion=master-SNAPSHOT -PvolmLibArtifactId=VolmLib %* -set "PUBLISH_RESULT=%ERRORLEVEL%" - -popd -exit /b %PUBLISH_RESULT% - -:check -if exist "%~1\settings.gradle" set "VOLMLIB_DIRECTORY=%~f1" -if exist "%~1\settings.gradle.kts" set "VOLMLIB_DIRECTORY=%~f1" -goto :eof - -:search -set "CURRENT=%~f1" -:search_loop -if exist "%CURRENT%\VolmLib\settings.gradle" ( - set "VOLMLIB_DIRECTORY=%CURRENT%\VolmLib" - goto :eof -) -if exist "%CURRENT%\VolmLib\settings.gradle.kts" ( - set "VOLMLIB_DIRECTORY=%CURRENT%\VolmLib" - goto :eof -) -for %%I in ("%CURRENT%\..") do set "PARENT=%%~fI" -if /i "%PARENT%"=="%CURRENT%" goto :eof -set "CURRENT=%PARENT%" -goto :search_loop diff --git a/publish-volmlib.sh b/publish-volmlib.sh deleted file mode 100644 index 322de2278..000000000 --- a/publish-volmlib.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# -# Publishes VolmLib to the local Maven repository under the coordinate this project -# resolves by default: com.github.VolmitSoftware:VolmLib:master-SNAPSHOT. -# -# VolmLib is normally picked up as a composite build straight from source, so this is -# only needed when you build with -PuseLocalVolmLib=false, or when another tool has to -# resolve VolmLib from ~/.m2 rather than from the included build. -# -# The lookup mirrors settings.gradle: it walks up from this script looking for a -# VolmLib checkout sitting beside the project root. Set VOLMLIB_DIR to override. -# Any extra arguments are forwarded to gradle. - -set -o pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" - -has_volmlib_settings() { - [[ -f "$1/settings.gradle" || -f "$1/settings.gradle.kts" ]] -} - -resolve_volmlib_directory() { - if [[ -n "$VOLMLIB_DIR" ]] && has_volmlib_settings "$VOLMLIB_DIR"; then - printf '%s' "$VOLMLIB_DIR" - return 0 - fi - - local current="$SCRIPT_DIR" - while [[ -n "$current" ]]; do - if has_volmlib_settings "$current/VolmLib"; then - printf '%s' "$current/VolmLib" - return 0 - fi - - local parent - parent="$(dirname -- "$current")" - [[ "$parent" == "$current" ]] && break - current="$parent" - done - - return 1 -} - -if ! VOLMLIB_DIRECTORY="$(resolve_volmlib_directory)"; then - printf 'Could not find a VolmLib checkout.\n' >&2 - printf 'Expected a VolmLib directory beside this project root, or VOLMLIB_DIR pointing at one.\n' >&2 - exit 1 -fi - -printf '==> Publishing VolmLib from %s\n' "$VOLMLIB_DIRECTORY" - -cd "$VOLMLIB_DIRECTORY" || exit 1 -./gradlew publishToMavenLocal \ - -Pgroup=com.github.VolmitSoftware \ - -Pversion=master-SNAPSHOT \ - -PvolmLibArtifactId=VolmLib \ - "$@" diff --git a/spi/build.gradle b/spi/build.gradle index cfd4f6732..6e4427943 100644 --- a/spi/build.gradle +++ b/spi/build.gradle @@ -12,7 +12,7 @@ java { } dependencies { - api("com.github.VolmitSoftware.VolmLib:native-api:${providers.gradleProperty('volmLibCoordinate').get().tokenize(':').last()}") { + api('com.github.VolmitSoftware.VolmLib:native-api:master-SNAPSHOT') { transitive = false } testImplementation('junit:junit:4.13.2') From 8d28ad4111e83d53d8eacadb86d8a2b581d594d2 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 09:33:30 -0400 Subject: [PATCH 2/3] Embedded-only native loader - Drop the repository storage and slimjar download path from NativeRuntimeLibraries - Reject native manifests that do not use embedded storage - Report provider hash mismatches as IOException --- .../bootstrap/NativeRuntimeLibraries.java | 123 ++++-------------- .../iris/platform/bootstrap/SlimJar.java | 4 +- .../bootstrap/NativeRuntimeLibrariesTest.java | 68 ++++------ 3 files changed, 52 insertions(+), 143 deletions(-) diff --git a/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java b/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java index e66d980c6..54619f8d9 100644 --- a/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java +++ b/core/src/main/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibraries.java @@ -2,26 +2,9 @@ package art.arcane.iris.platform.bootstrap; import art.arcane.volmlib.nativelib.NativeVersion; import art.arcane.volmlib.nativelib.NativeAdapters; -import io.github.slimjar.app.builder.ApplicationBuilder; -import io.github.slimjar.downloader.DependencyDownloader; -import io.github.slimjar.downloader.URLDependencyDownloader; -import io.github.slimjar.downloader.strategy.FilePathStrategy; -import io.github.slimjar.downloader.verify.FileChecksumCalculator; -import io.github.slimjar.relocation.helper.RelocationHelper; -import io.github.slimjar.relocation.helper.VerifyingRelocationHelperFactory; -import io.github.slimjar.relocation.meta.FlatFileMetaMediatorFactory; -import io.github.slimjar.exceptions.DownloaderException; -import io.github.slimjar.resolver.data.Dependency; -import io.github.slimjar.resolver.data.DependencyData; -import io.github.slimjar.resolver.data.Repository; -import io.github.slimjar.resolver.reader.dependency.DependencyDataProvider; -import io.github.slimjar.resolver.reader.dependency.DependencyReader; -import io.github.slimjar.resolver.reader.dependency.WrappingDependencyDataProviderFactory; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; @@ -31,20 +14,16 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HexFormat; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.Properties; public final class NativeRuntimeLibraries { private static final String MANIFEST = "META-INF/iris/native-runtime.properties"; - private final Map dependencies; - private final Repository repository; + private static final String GROUP = "com.github.VolmitSoftware.VolmLib"; + private final List providers; - private NativeRuntimeLibraries(Map dependencies, Repository repository) { - this.dependencies = dependencies; - this.repository = repository; + private NativeRuntimeLibraries(List providers) { + this.providers = providers; } public static NativeRuntimeLibraries load(String minecraftVersion) { @@ -60,60 +39,27 @@ public final class NativeRuntimeLibraries { } } - static NativeRuntimeLibraries from(Properties manifest, String minecraftVersion) throws IOException { - String storage = manifest.getProperty("storage", "repository"); - if (!storage.equals("repository") && !storage.equals("embedded")) { + static NativeRuntimeLibraries from(Properties manifest, String minecraftVersion) { + String storage = manifest.getProperty("storage"); + if (!"embedded".equals(storage)) { throw new IllegalStateException("Invalid native runtime storage: " + storage); } - boolean embedded = storage.equals("embedded"); NativeVersion version = NativeVersion.resolve(minecraftVersion).orElseThrow( () -> new UnsupportedOperationException("No native provider for Minecraft " + minecraftVersion)); List modules = List.of(manifest.getProperty("modules", "").split(",")); - Map selected = new LinkedHashMap<>(); + List selected = new ArrayList<>(2); for (String module : List.of("native-common", "native-" + version.packageName())) { if (!modules.contains(module)) { throw new UnsupportedOperationException("Native provider is not distributed: " + module); } - String[] coordinate = manifest.getProperty(module + ".coordinate", "").split(":"); String digest = manifest.getProperty(module + ".sha256", ""); - if (coordinate.length != 3 || !coordinate[0].equals("com.github.VolmitSoftware.VolmLib") - || !coordinate[1].equals(module) || coordinate[2].isBlank() || !digest.matches("[0-9a-f]{64}") - || embedded && !coordinate[2].equals("embedded-" + digest)) { + String coordinate = GROUP + ":" + module + ":embedded-" + digest; + if (!digest.matches("[0-9a-f]{64}") || !coordinate.equals(manifest.getProperty(module + ".coordinate"))) { throw new IllegalStateException("Invalid native dependency manifest entry: " + module); } - selected.put(new Dependency(coordinate[0], coordinate[1], coordinate[2], null, List.of()), digest); + selected.add(new Provider(module, "embedded-" + digest, digest)); } - Repository repository = embedded ? null : new Repository(URI.create(manifest.getProperty("repository")).toURL()); - return new NativeRuntimeLibraries(Map.copyOf(selected), repository); - } - - public > T configure(T builder, Path downloads) { - WrappingDependencyDataProviderFactory defaults = new WrappingDependencyDataProviderFactory(DependencyReader.DEFAULT); - builder.dataProviderFactory(url -> { - DependencyDataProvider provider = defaults.create(url); - return () -> merge(provider.get()); - }); - VerifyingRelocationHelperFactory relocation = new VerifyingRelocationHelperFactory( - new FileChecksumCalculator("SHA-256"), - FilePathStrategy.createRelocationStrategy(downloads.toFile(), "Iris"), - new FlatFileMetaMediatorFactory()); - builder.relocationHelperFactory(relocator -> { - RelocationHelper helper = relocation.create(relocator); - return (dependency, file) -> dependencies.containsKey(dependency) ? file : helper.relocate(dependency, file); - }); - builder.downloaderFactory((output, resolver, verifier) -> { - DependencyDownloader delegate = new URLDependencyDownloader(output, resolver, verifier); - return dependency -> { - Optional result = delegate.download(dependency); - String expected = dependencies.get(dependency); - if (expected != null) { - File file = result.orElseThrow(() -> new DownloaderException("Native dependency has no jar: " + dependency)); - verify(file, expected); - } - return result; - }; - }); - return builder; + return new NativeRuntimeLibraries(List.copyOf(selected)); } public URLClassLoader openProviderLoader(Path downloads) { @@ -128,31 +74,13 @@ public final class NativeRuntimeLibraries { } } - DependencyData merge(DependencyData original) { - if (repository == null) { - return original; - } - List combined = new ArrayList<>(original.dependencies()); - combined.addAll(dependencies.keySet()); - List repositories = new ArrayList<>(original.repositories()); - if (!repositories.contains(repository)) { - repositories.add(repository); - } - return new DependencyData(original.mirrors(), repositories, combined, original.relocations()); - } - List providerUrls(Path downloads, ClassLoader resources) throws IOException { - List urls = new ArrayList<>(dependencies.size()); - for (Map.Entry entry : dependencies.entrySet()) { - Dependency dependency = entry.getKey(); - Path artifact = downloads.resolve(dependency.groupId().replace('.', '/')) - .resolve(dependency.artifactId()).resolve(dependency.version()) - .resolve(dependency.artifactId() + "-" + dependency.version() + ".jar"); - if (repository == null) { - extractEmbedded(resources, dependency.artifactId(), artifact, entry.getValue()); - } else { - verify(artifact.toFile(), entry.getValue()); - } + List urls = new ArrayList<>(providers.size()); + for (Provider provider : providers) { + Path artifact = downloads.resolve(GROUP.replace('.', '/')) + .resolve(provider.module()).resolve(provider.version()) + .resolve(provider.module() + "-" + provider.version() + ".jar"); + extractEmbedded(resources, provider.module(), artifact, provider.sha256()); urls.add(artifact.toUri().toURL()); } return urls; @@ -171,7 +99,7 @@ public final class NativeRuntimeLibraries { Path temporary = Files.createTempFile(artifact.getParent(), module + "-", ".tmp"); try { Files.copy(input, temporary, StandardCopyOption.REPLACE_EXISTING); - verify(temporary.toFile(), expected); + verify(temporary, expected); Files.move(temporary, artifact, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); } finally { Files.deleteIfExists(temporary); @@ -187,15 +115,12 @@ public final class NativeRuntimeLibraries { } } - static void verify(File file, String expected) { - try { - String actual = digest(file.toPath()); - if (!actual.equals(expected)) { - throw new DownloaderException("Native dependency does not match this Iris build: " + file.getName()); - } - } catch (IOException exception) { - throw new DownloaderException("Cannot verify native dependency: " + file, exception); + static void verify(Path file, String expected) throws IOException { + if (!digest(file).equals(expected)) { + throw new IOException("Native dependency does not match this Iris build: " + file.getFileName()); } } + private record Provider(String module, String version, String sha256) { + } } diff --git a/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java b/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java index 437d4ed75..046c4a15b 100644 --- a/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java +++ b/core/src/main/java/art/arcane/iris/platform/bootstrap/SlimJar.java @@ -33,7 +33,7 @@ public class SlimJar { return; } NativeRuntimeLibraries libraries = NativeRuntimeLibraries.load(minecraftVersion); - libraries.configure(ApplicationBuilder.appending("Iris"), downloadPath) + ApplicationBuilder.appending("Iris") .injectableFactory(InjectableFactory.selecting( InjectableFactory.ERROR, InjectableFactory.INJECTABLE, @@ -78,7 +78,7 @@ public class SlimJar { Path downloadPath = plugin.getDataFolder("cache", "libraries").toPath(); debug(plugin, "Loading libraries..."); NativeRuntimeLibraries libraries = NativeRuntimeLibraries.load(Bukkit.getBukkitVersion()); - libraries.configure(ApplicationBuilder.appending(plugin.getName()), downloadPath) + ApplicationBuilder.appending(plugin.getName()) .injectableFactory(InjectableFactory.selecting(InjectableFactory.ERROR, InjectableFactory.INJECTABLE, InjectableFactory.WRAPPED, InjectableFactory.UNSAFE)) .downloadDirectoryPath(downloadPath) diff --git a/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java b/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java index e41353fd8..5ba346183 100644 --- a/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java +++ b/core/src/test/java/art/arcane/iris/platform/bootstrap/NativeRuntimeLibrariesTest.java @@ -1,8 +1,5 @@ package art.arcane.iris.platform.bootstrap; -import io.github.slimjar.exceptions.DownloaderException; -import io.github.slimjar.resolver.data.Dependency; -import io.github.slimjar.resolver.data.DependencyData; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -28,7 +25,6 @@ import java.util.stream.Stream; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -37,37 +33,36 @@ public class NativeRuntimeLibrariesTest { public TemporaryFolder temporary = new TemporaryFolder(); @Test - public void selectsOnlyCommonAndMatchingProvider() throws Exception { - Properties manifest = manifest(); - Dependency original = new Dependency("example", "library", "1", null, List.of()); - DependencyData data = new DependencyData(List.of(), List.of(), List.of(original), List.of()); - List selected = NativeRuntimeLibraries.from(manifest, "26.3.0").merge(data).dependencies() - .stream().map(Dependency::artifactId).sorted().toList(); - assertEquals(List.of("library", "native-common", "native-v26_3_R1"), selected); - assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(manifest, "1.21.11")); + public void providersMustBeEmbedded() throws Exception { + EmbeddedFixture fixture = embeddedFixture(); + fixture.manifest().setProperty("storage", "repository"); + assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0")); + fixture.manifest().remove("storage"); + assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0")); + } + + @Test + public void rejectsMinecraftVersionsWithoutProvider() throws Exception { + EmbeddedFixture fixture = embeddedFixture(); + assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "1.21.11")); + fixture.manifest().setProperty("modules", "native-common,native-v26_2_R1"); + assertThrows(UnsupportedOperationException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.3.0")); } @Test public void rejectsProviderBytesFromDifferentBuild() throws Exception { - Path jar = Files.createTempFile("native-runtime", ".jar"); - try { - Files.write(jar, new byte[0]); - NativeRuntimeLibraries.verify(jar.toFile(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - Files.writeString(jar, "different build"); - assertThrows(DownloaderException.class, () -> NativeRuntimeLibraries.verify(jar.toFile(), - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); - } finally { - Files.deleteIfExists(jar); - } + Path jar = temporary.newFile("native-runtime.jar").toPath(); + Files.write(jar, new byte[0]); + NativeRuntimeLibraries.verify(jar, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + Files.writeString(jar, "different build"); + assertThrows(IOException.class, () -> NativeRuntimeLibraries.verify(jar, + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); } @Test - public void embeddedProvidersLoadWithoutRepositoryAndSelectOnlyCurrentVersion() throws Exception { + public void embeddedProvidersSelectOnlyCommonAndCurrentVersion() throws Exception { EmbeddedFixture fixture = embeddedFixture(); NativeRuntimeLibraries libraries = NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0"); - Dependency original = new Dependency("example", "library", "1", null, List.of()); - DependencyData data = new DependencyData(List.of(), List.of(), List.of(original), List.of()); - assertSame(data, libraries.merge(data)); Path downloads = temporary.newFolder("downloads").toPath(); try (URLClassLoader resources = new URLClassLoader(new URL[]{fixture.resources().toUri().toURL()}, null)) { List providers = libraries.providerUrls(downloads, resources); @@ -125,7 +120,7 @@ public class NativeRuntimeLibrariesTest { NativeRuntimeLibraries libraries = NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0"); Path downloads = temporary.newFolder("downloads").toPath(); try (URLClassLoader resources = new URLClassLoader(new URL[]{fixture.resources().toUri().toURL()}, null)) { - DownloaderException failure = assertThrows(DownloaderException.class, () -> libraries.providerUrls(downloads, resources)); + IOException failure = assertThrows(IOException.class, () -> libraries.providerUrls(downloads, resources)); assertTrue(failure.getMessage().contains("native-v26_2_R1")); assertNoFailedExtract(downloads); } @@ -139,12 +134,13 @@ public class NativeRuntimeLibrariesTest { } private EmbeddedFixture embeddedFixture() throws Exception { - Properties properties = manifest(); + Properties properties = new Properties(); + List modules = List.of("native-common", "native-v26_2_R1", "native-v26_3_R1"); + properties.setProperty("modules", String.join(",", modules)); properties.setProperty("storage", "embedded"); - properties.remove("repository"); Path resources = temporary.newFolder("resources").toPath(); Path providers = Files.createDirectories(resources.resolve("META-INF/iris/native")); - for (String module : properties.getProperty("modules").split(",")) { + for (String module : modules) { ByteArrayOutputStream output = new ByteArrayOutputStream(); try (JarOutputStream jar = new JarOutputStream(output)) { jar.putNextEntry(new JarEntry("provider.txt")); @@ -169,16 +165,4 @@ public class NativeRuntimeLibrariesTest { private record EmbeddedFixture(Properties manifest, Path resources) { } - - private static Properties manifest() { - Properties properties = new Properties(); - List modules = List.of("native-common", "native-v26_2_R1", "native-v26_3_R1"); - properties.setProperty("modules", String.join(",", modules)); - properties.setProperty("repository", "https://jitpack.io/"); - for (String module : modules) { - properties.setProperty(module + ".coordinate", "com.github.VolmitSoftware.VolmLib:" + module + ":revision"); - properties.setProperty(module + ".sha256", "0".repeat(64)); - } - return properties; - } } From 15826162b495b1b44c2e7a92ce1a9c780ee6ad1c Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Tue, 29 Sep 2026 09:50:38 -0400 Subject: [PATCH 3/3] Loader task freshness - Always rerun the loader Exec tasks when VolmLib resolves from the published snapshot - Read the VolmLib dirty state without taking the optional index lock --- build.gradle | 14 ++++++++++++-- gradle/volmlib-resolution.settings.gradle | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 1295ab47b..0fb075442 100644 --- a/build.gradle +++ b/build.gradle @@ -323,6 +323,16 @@ if (localVolmLibCheckout != null) { }) } +// The published master-SNAPSHOT is resolved inside the nested build, so no outer input changes when it +// moves. Without a local checkout the Exec tasks always run and the nested build decides what is stale. +Closure declareVolmLibInputs = { Exec task -> + task.inputs.property('volmLibStamp', volmLibStamp) + if (localVolmLibCheckout == null) { + task.outputs.upToDateWhen { false } + } + return null +} + Closure declareModdedJarIo = { Exec task, String adapter, String artifactName, Map versions -> task.inputs.files(moddedSharedInputs).withPropertyName('moddedSharedSources') .withPathSensitivity(PathSensitivity.RELATIVE) @@ -332,7 +342,7 @@ Closure declareModdedJarIo = { Exec task, String adapter, String artifactN layout.projectDirectory.file("adapters/${adapter}/settings.gradle").asFile ).withPropertyName('adapterSources') versions.each { String key, String value -> task.inputs.property(key, value) } - task.inputs.property('volmLibStamp', volmLibStamp) + declareVolmLibInputs(task) task.outputs.file(layout.projectDirectory.file("adapters/${adapter}/build/libs/${artifactName}")) return null } @@ -495,7 +505,7 @@ tasks.register('moddedTest', Exec) { inputs.property('irisVersion', project.version.toString()) inputs.property('minecraftVersion', minecraftVersion) inputs.property('fabricLoaderVersion', fabricLoaderVersion) - inputs.property('volmLibStamp', volmLibStamp) + declareVolmLibInputs(it) outputs.dir(layout.projectDirectory.dir('adapters/fabric/build/test-results/test')) mustRunAfter(tasks.named('jar')) mustRunAfter(project(':core').tasks.named('check')) diff --git a/gradle/volmlib-resolution.settings.gradle b/gradle/volmlib-resolution.settings.gradle index 6e2f580dc..ca1f9e821 100644 --- a/gradle/volmlib-resolution.settings.gradle +++ b/gradle/volmlib-resolution.settings.gradle @@ -121,7 +121,7 @@ if (irisLocalVolmLibDirectory == null) { } } else { String commit = irisGit(irisLocalVolmLibDirectory, 'rev-parse', 'HEAD') - String status = commit == null ? null : irisGit(irisLocalVolmLibDirectory, 'status', '--porcelain') + String status = commit == null ? null : irisGit(irisLocalVolmLibDirectory, '--no-optional-locks', 'status', '--porcelain') String dirty = status == null ? 'unknown' : String.valueOf(!status.isEmpty()) irisVolmLibStamp = [source: 'local', commit: commit ?: 'unknown', dirty: dirty] irisVolmLibDescription = "${irisLocalVolmLibDirectory.canonicalPath} @ ${commit == null ? 'unknown' : commit.take(7)}${dirty == 'true' ? '+dirty' : ''}"