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')