Files
Iris/core/build.gradle
T
Robert Stoneking 45407a7e3f Updated Iris language files -
All 17 translations now use editable TOML files
Language menus and debug messages include translations
Headers explain all 243 variables, prefixes, colors and formatting
Generated guides refresh without overwriting customized messages
Missing or invalid messages fall back to English
Language reloads preserve server and player selections
Jars include localized guides and required TOML support
2026-09-10 01:17:18 -06:00

560 lines
23 KiB
Groovy

import io.github.slimjar.resolver.data.Mirror
import org.gradle.api.file.FileTreeElement
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.jvm.toolchain.JavaLanguageVersion
import java.net.URI
import java.nio.charset.StandardCharsets
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 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 <https://www.gnu.org/licenses/>.
*/
plugins {
id 'java'
id 'java-library'
id 'java-test-fixtures'
alias(libs.plugins.shadow)
alias(libs.plugins.slimjar)
}
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<File> languageSourceFiles = fileTree('src/main/resources/languages') {
include('*.toml')
}.files.toList().sort { File left, File right -> left.name <=> right.name }
List<String> languageSourceLocales = languageSourceFiles.collect { File source ->
source.name.substring(0, source.name.length() - '.toml'.length())
}
def languageSourceManifestTask = tasks.register('generateLanguageSourceManifest', WriteProperties) {
destinationFile.set(layout.buildDirectory.file('generated/language-source/iris-language-source.properties'))
property('revision', languageSourceReference)
property('locales', languageSourceLocales.join(','))
}
def languageGuidesTask = tasks.register('generateLanguageGuides') {
inputs.files(languageSourceFiles)
def guideDirectory = layout.buildDirectory.dir('generated/language-guides')
outputs.dir(guideDirectory)
doLast {
File output = guideDirectory.get().asFile
output.mkdirs()
for (File source : languageSourceFiles) {
List<String> header = source.readLines('UTF-8').takeWhile { String line ->
line.isBlank() || line.stripLeading().startsWith('#')
}
String locale = source.name.substring(0, source.name.length() - '.toml'.length())
new File(output, locale + '.txt').setText(header.join('\n').stripTrailing() + '\n', 'UTF-8')
}
}
}
tasks.named('processResources').configure {
dependsOn(languageSourceManifestTask, languageGuidesTask)
exclude('languages/**')
from(languageSourceManifestTask)
from(languageGuidesTask) {
into('iris-language-guides')
}
}
/**
* Dependencies.
*
* Provided or classpath dependencies are not shaded and are available on the runtime classpath
*
* Shaded dependencies are not available at runtime, nor are they available on mvn central so they
* need to be shaded into the jar (increasing binary size)
*
* Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the
* plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare
* these dependencies if they are available on mvn central.
*/
dependencies {
api(project(':spi'))
// Provided or Classpath
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
compileOnly(libs.paper.api)
compileOnly(libs.log4j.api)
compileOnly(libs.log4j.core)
compileOnly(libs.guava)
// Third Party Integrations
compileOnly(libs.nexo)
compileOnly(libs.oraxen) {
transitive = false
}
compileOnly(libs.itemsadder)
compileOnly(libs.score)
compileOnly(libs.mmoitems)
compileOnly(libs.mythiclib)
compileOnly(libs.eco)
compileOnly(libs.mythic)
compileOnly(libs.mythicCrucible)
compileOnly(libs.kgenerators) {
transitive = false
}
compileOnly(libs.multiverseCore)
compileOnly(libs.craftengine.core)
compileOnly(libs.craftengine.bukkit)
// Shaded
implementation('de.crazydev22.slimjar.helper:spigot:2.1.9')
implementation(volmLibCoordinate) {
transitive = false
}
implementation('com.moandjiezana.toml:toml4j:0.7.2') {
transitive = false
}
// Dynamically Loaded
slim(libs.gson)
slim(libs.lru)
slim(libs.caffeine)
slim(libs.paralithic)
slim(libs.paperlib)
slim(libs.adventure.api)
slim(libs.adventure.minimessage)
slim(libs.adventure.serializer.legacy)
slim(libs.adventure.serializer.plain)
slim(libs.bstats)
slim(libs.commons.io)
slim(libs.commons.lang3)
slim(libs.oshi)
slim(libs.lz4)
slim(libs.fastutil)
slim(libs.zip) {
exclude(group: 'org.slf4j', module: 'slf4j-api')
}
slim(libs.asm)
slim(libs.byteBuddy.core)
slim(libs.byteBuddy.agent)
slim(libs.dom4j)
slim(libs.jaxen)
testFixturesImplementation('junit:junit:4.13.2')
testFixturesImplementation('org.mockito:mockito-core:5.23.0')
testFixturesCompileOnly(libs.paper.api)
testImplementation('junit:junit:4.13.2')
testImplementation('org.mockito:mockito-core:5.23.0')
testImplementation(libs.paper.api)
testImplementation(libs.log4j.core)
testRuntimeOnly(libs.paper.api)
testImplementation(libs.multiverseCore)
}
int coreTestForks = Math.max(1, Integer.parseInt(providers.gradleProperty('testForks')
.getOrElse(String.valueOf(Math.max(1, (int) (Runtime.runtime.availableProcessors() / 2))))))
tasks.named('test').configure {
maxHeapSize = '1g'
maxParallelForks = coreTestForks
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
slimJar {
mirrors = [
new Mirror(
URI.create('https://maven-central.storage-download.googleapis.com/maven2').toURL(),
URI.create('https://repo.maven.apache.org/maven2/').toURL()
)
]
for (Map.Entry<String, String> relocation : runtimeDownloadRelocations.entrySet()) {
relocate(relocation.getKey(), relocation.getValue())
}
}
def embeddedAgentJar = project(':core:agent').tasks.named('jar', Jar)
def templateSource = file('src/main/templates')
def templateDest = layout.buildDirectory.dir('generated/sources/templates')
def generateTemplates = tasks.register('generateTemplates', Copy) {
inputs.properties([
environment: providers.provider {
if (project.hasProperty('release')) {
return 'production'
}
if (project.hasProperty('argghh')) {
return 'Argghh!'
}
return 'development'
},
commit: providers.provider {
String commitId = null
Exception failure = null
try {
Process process = new ProcessBuilder('git', '-C', rootProject.projectDir.absolutePath, 'rev-parse', 'HEAD')
.redirectErrorStream(true)
.start()
String output = new String(process.inputStream.readAllBytes(), StandardCharsets.UTF_8).trim()
int exitCode = process.waitFor()
if (exitCode == 0) {
commitId = output
} else {
failure = new GradleException("git rev-parse exited with code ${exitCode}: ${output}")
}
} catch (Exception ex) {
failure = ex
}
if (commitId != null && commitId.length() == 40) {
return commitId
}
logger.error('Git commit hash not found', failure)
return 'unknown'
},
minecraftVersion: providers.gradleProperty('minecraftVersion'),
])
from(templateSource)
into(templateDest)
rename { String fileName -> "art/arcane/iris/${fileName}" }
expand(inputs.properties)
}
tasks.named('compileJava', JavaCompile).configure {
/**
* We need parameter meta for the decree command system
*/
options.compilerArgs.add('-parameters')
options.compilerArgs.addAll(['--add-modules', 'jdk.incubator.vector'])
options.encoding = 'UTF-8'
options.debugOptions.debugLevel = 'none'
// lombok.config decides how equals/hashCode/toString are generated (fields, never getters), so editing it
// changes this module's bytecode. Gradle does not know that on its own and would serve a stale up-to-date
// build until something else in the module changed.
inputs.file(rootProject.file('lombok.config'))
.withPropertyName('lombokConfig')
.withPathSensitivity(PathSensitivity.NONE)
}
tasks.named('test', Test).configure {
jvmArgs('--add-modules', 'jdk.incubator.vector')
dependsOn(languageSourceManifestTask)
classpath = files(layout.buildDirectory.dir('generated/language-source')) + files('src/main/resources') + sourceSets.main.output + classpath
}
configurations.matching { it.name.startsWith('slim') }.all { }
// VolmLib packages that Iris forked into art.arcane.iris.util.project.* or replaced outright. Every
// class in each of these packages is unreachable from every other class shipped in the Bukkit jar,
// and none of them is a reflective target: Matter.read() resolves only matter/slices, JarScanner
// only walks Iris' own packages, and the remaining VolmLib Class.forName calls name Bukkit/NMS or
// runtime-downloaded types. Package granularity is deliberate - a partially used package stays whole.
// verifyBukkitArtifact re-derives the reference graph over the shipped jar and fails on any dangling
// reference, so an exclude that stops being dead is a build failure rather than a runtime crash.
List<String> supersededVolmLibPackages = [
'art/arcane/volmlib/util/noise/**',
'art/arcane/volmlib/util/stream/ProceduralStream.class',
'art/arcane/volmlib/util/stream/BasicStream.class',
'art/arcane/volmlib/util/stream/arithmetic/**',
'art/arcane/volmlib/util/stream/convert/**',
'art/arcane/volmlib/util/stream/interpolation/**',
'art/arcane/volmlib/util/stream/sources/**',
'art/arcane/volmlib/util/stream/utility/**',
'art/arcane/volmlib/util/uniques/**',
'art/arcane/volmlib/util/bukkit/json/**',
'art/arcane/volmlib/util/bukkit/registry/**',
'art/arcane/volmlib/util/director/visual/**',
'art/arcane/volmlib/util/value/**',
'art/arcane/volmlib/util/api/**',
'art/arcane/volmlib/util/entity/**',
'art/arcane/volmlib/util/reflect/**',
'art/arcane/volmlib/util/documentation/**',
'art/arcane/iris/core/report/**',
'art/arcane/volmlib/integration/VaultEconomy*.class'
]
List<String> unusedVolmLibClasses = [
'art/arcane/volmlib/util/nbt/io/ParseException',
'art/arcane/volmlib/util/nbt/io/SNBTDeserializer',
'art/arcane/volmlib/util/nbt/io/SNBTParser',
'art/arcane/volmlib/util/nbt/io/SNBTSerializer',
'art/arcane/volmlib/util/nbt/io/SNBTUtil',
'art/arcane/volmlib/util/nbt/io/SNBTWriter',
'art/arcane/volmlib/util/nbt/io/StringPointer',
'art/arcane/volmlib/util/io/StringDeserializer',
'art/arcane/volmlib/util/io/StringSerializer',
'art/arcane/volmlib/util/json/EnumType',
'art/arcane/volmlib/util/json/HTTP',
'art/arcane/volmlib/util/json/HTTPTokener',
'art/arcane/volmlib/util/json/JSONML',
'art/arcane/volmlib/util/json/JSONStringer',
'art/arcane/volmlib/util/json/JSONWriter',
'art/arcane/volmlib/util/json/XML',
'art/arcane/volmlib/util/json/XMLTokener',
'art/arcane/volmlib/util/hud/HudBid',
'art/arcane/volmlib/util/hud/HudBidder',
'art/arcane/volmlib/util/hud/HudLocalLedger',
'art/arcane/volmlib/util/hud/HudTitleClaim',
'art/arcane/volmlib/util/hud/HudTitleService',
'art/arcane/volmlib/util/scheduling/GroupedExecutor',
'art/arcane/volmlib/util/scheduling/SchedulerRuntime',
'art/arcane/volmlib/util/scheduling/SchedulerUtils',
'art/arcane/volmlib/util/scheduling/TaskExecutor',
'art/arcane/volmlib/util/parallel/StreamUtilsSupport',
'art/arcane/volmlib/util/parallel/SyncExecutorSupport',
'art/arcane/volmlib/util/cache/ByteBitCache',
'art/arcane/volmlib/util/cache/DataBitCache',
'art/arcane/volmlib/util/cache/FloatBitCache',
'art/arcane/volmlib/util/cache/IntBitCache',
'art/arcane/volmlib/util/cache/ShortBitCache',
'art/arcane/volmlib/util/cache/UByteBitCache',
'art/arcane/volmlib/util/data/ChunkCache',
'art/arcane/volmlib/util/data/ComplexCache',
'art/arcane/volmlib/util/data/IrisBiomeStorage',
'art/arcane/volmlib/util/data/NibbleArray',
'art/arcane/volmlib/util/data/NibbleDataPalette',
'art/arcane/volmlib/util/data/Recycler',
'art/arcane/volmlib/util/data/VanillaBiomeMap',
'art/arcane/volmlib/util/data/Writable',
'art/arcane/volmlib/util/data/base/ComplexCacheBase',
'art/arcane/volmlib/util/director/DirectorNodeBase',
'art/arcane/volmlib/util/director/DirectorParameterBase',
'art/arcane/volmlib/util/director/theme/DirectorThemes',
'art/arcane/volmlib/util/math/INode',
'art/arcane/volmlib/util/math/IrisMathHelper',
'art/arcane/volmlib/util/math/KochanekBartelsInterpolation',
'art/arcane/volmlib/util/math/PathInterpolation',
'art/arcane/volmlib/util/math/Spiral',
'art/arcane/volmlib/util/board/BoardManager',
'art/arcane/volmlib/util/board/BoardUpdateTask',
'art/arcane/volmlib/util/bukkit/Placeholders',
'art/arcane/volmlib/util/hunk/HunkFactory',
'art/arcane/volmlib/util/hunk/view/BiomeForceBridge',
'art/arcane/volmlib/util/hunk/view/BiomeGridForceSupport',
'art/arcane/volmlib/util/hunk/view/BiomeGridHunkHolder',
'art/arcane/volmlib/util/hunk/view/BiomeGridHunkView',
'art/arcane/volmlib/util/hunk/view/ChunkDataHunkHolder',
'art/arcane/volmlib/util/hunk/view/RotatedXHunkView',
'art/arcane/volmlib/util/hunk/view/RotatedYHunkView',
'art/arcane/volmlib/util/hunk/view/RotatedZHunkView',
'art/arcane/volmlib/util/exceptions/MissingDimensionException',
'art/arcane/volmlib/util/function/Supplier2',
'art/arcane/volmlib/util/function/Supplier3',
'art/arcane/volmlib/util/hud/HudPriority',
'art/arcane/volmlib/util/interpolation/InterpolationMethod3D',
'art/arcane/volmlib/util/interpolation/InterpolationType',
'art/arcane/volmlib/util/inventorygui/UIRainbowDecorator',
'art/arcane/volmlib/util/io/Converter',
'art/arcane/volmlib/util/matter/MatterPlacer',
'art/arcane/volmlib/util/plugin/SplashScreenSupport',
'art/arcane/volmlib/util/scheduling/Contained',
'art/arcane/volmlib/util/scheduling/IrisLock',
'art/arcane/volmlib/util/scheduling/QueueExecutor',
'art/arcane/volmlib/util/scheduling/S',
'art/arcane/volmlib/util/scheduling/SBase',
'art/arcane/volmlib/util/scheduling/SlidingWindowRateLimiter',
'art/arcane/volmlib/util/scheduling/Switch',
'art/arcane/volmlib/util/scheduling/ThreadMonitor',
'art/arcane/volmlib/util/cache/ByteCache',
'art/arcane/volmlib/util/cache/ChunkCache2DSimple',
'art/arcane/volmlib/util/cache/IntCache',
'art/arcane/volmlib/util/cache/ShortCache',
'art/arcane/volmlib/util/cache/UByteCache',
'art/arcane/volmlib/util/cache/WorldCache2DSimple',
'art/arcane/volmlib/util/data/CuboidException',
'art/arcane/volmlib/util/data/DUTF',
'art/arcane/volmlib/util/data/Heafty',
'art/arcane/volmlib/util/data/InvertedBiomeGrid',
'art/arcane/volmlib/util/data/Shrinkwrap',
'art/arcane/volmlib/util/data/WeightMap',
'art/arcane/volmlib/util/data/WeightedRandom',
'art/arcane/volmlib/util/director/compat/BukkitDirectorContext',
'art/arcane/volmlib/util/director/handlers/base/DummyHandlerBase',
'art/arcane/volmlib/util/director/handlers/base/OptionalWorldHandlerBase',
'art/arcane/volmlib/util/parallel/BurstedHunk',
'art/arcane/volmlib/util/parallel/GridLockSupport',
'art/arcane/volmlib/util/parallel/NoopGridLockSupport',
'art/arcane/volmlib/util/bukkit/ChunkPositionSet',
'art/arcane/volmlib/util/bukkit/Events',
'art/arcane/volmlib/util/json/SingleCollectionTypeFactory',
'art/arcane/volmlib/util/collection/StateList',
'art/arcane/iris/core/pack/IrisPack',
'art/arcane/iris/core/pack/IrisPackRepository',
'art/arcane/iris/util/common/scheduling/jobs/DownloadJob',
'art/arcane/iris/util/common/scheduling/jobs/JobCollection',
'art/arcane/iris/util/common/scheduling/jobs/ParallelQueueJob',
'art/arcane/iris/util/common/scheduling/jobs/QueueJob',
'art/arcane/iris/engine/framework/placer/WorldObjectPlacer',
'art/arcane/iris/engine/object/IPostBlockAccess',
'art/arcane/iris/engine/object/IrisPotionEffect',
'art/arcane/iris/engine/platform/DummyChunkGenerator',
'art/arcane/iris/util/common/parallel/GridLock',
'art/arcane/iris/util/common/reflect/W',
'art/arcane/iris/util/slimjar/BuildConstants',
'art/arcane/iris/util/slimjar/relocation/meta/AttributeMetaMediator',
'art/arcane/iris/util/slimjar/relocation/meta/AttributeMetaMediatorFactory'
]
List<String> unusedVolmLibClassEntries = unusedVolmLibClasses.collectMany { String classPath ->
[classPath + '.class', classPath + '$*.class']
}
// Annotation-only artifacts pulled in transitively by Gson and Caffeine. Their types appear solely
// in annotation attributes, which the JVM skips silently when the type is absent, so nothing loads
// or links against them at runtime.
List<String> annotationOnlyArtifacts = [
'com/google/errorprone/**',
'org/jspecify/**'
]
// Publisher metadata for the shaded dependencies. Nothing reads these at runtime.
List<String> dependencyBuildMetadata = [
'META-INF/maven/**',
'META-INF/proguard/**',
'META-INF/versions/*/OSGI-INF/**'
]
String languageTomlSupportClassPattern = 'art/arcane/volmlib/util/config/(?:TomlCodec|ConfigJson|ConfigExposePolicy|ConfigDoc|ConfigDescription|ConfigDocumentation)(?:\\$[^/]+)?\\.class'
tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar).configure {
inputs.property('languageTomlSupportClassPattern', languageTomlSupportClassPattern)
dependsOn(embeddedAgentJar)
mergeServiceFiles()
// minimize() stays off. Caffeine reaches its 520 generated cache and node classes through
// MethodHandles.Lookup.findClass on a computed name, and the VolmLib command, GUI and hotload
// surfaces are only referenced from :adapters:bukkit:plugin, which is merged into the artifact
// after this task runs. Both would be stripped as unreachable. Excluding them from minimization
// leaves it reaching ~25KB of Gson internals, which is not worth a mode that fails silently.
relocate('com.dfsek.paralithic', "${lib}.paralithic")
relocate('io.github.slimjar', "${lib}.slimjar")
relocate('com.moandjiezana.toml', "${lib}.toml")
exclude('modules/loader-agent.isolated-jar')
exclude(supersededVolmLibPackages)
exclude { FileTreeElement entry ->
!entry.isDirectory() && entry.path.startsWith('art/arcane/volmlib/util/config/')
&& !entry.path.matches(languageTomlSupportClassPattern)
}
exclude(unusedVolmLibClassEntries)
exclude(annotationOnlyArtifacts)
exclude(dependencyBuildMetadata)
from(embeddedAgentJar.map { it.archiveFile }) {
rename { String ignored -> 'agent.jar' }
}
}
rootProject.tasks.matching {
it.name == 'prepareKotlinBuildScriptModel'
}.configureEach {
dependsOn(generateTemplates)
}
sourceSets {
main {
java {
srcDir(generateTemplates.map { it.outputs })
}
}
externalProviderTest {
compileClasspath += sourceSets.test.compileClasspath + sourceSets.main.output
runtimeClasspath += sourceSets.test.runtimeClasspath + sourceSets.main.output
}
}
dependencies {
externalProviderTestImplementation(libs.craftengine.core) {
transitive = false
}
externalProviderTestImplementation(libs.craftengine.bukkit) {
transitive = false
}
externalProviderTestImplementation('net.momirealms:sparrow-nbt:0.22')
externalProviderTestImplementation('net.momirealms:sparrow-util:0.121')
externalProviderTestImplementation('net.momirealms:sparrow-reflection:0.33')
externalProviderTestImplementation('net.momirealms:sparrow-yaml:1.0.18')
externalProviderTestImplementation('net.momirealms:antigrieflib:1.0.17')
externalProviderTestImplementation('org.incendo:cloud-core:2.0.0')
externalProviderTestImplementation(libs.oraxen) {
transitive = false
}
externalProviderTestImplementation(libs.itemsadder) {
transitive = false
}
}
tasks.register('externalProviderTest', Test) {
testClassesDirs = sourceSets.externalProviderTest.output.classesDirs
classpath = sourceSets.externalProviderTest.runtimeClasspath
jvmArgs('--add-modules', 'jdk.incubator.vector')
}
tasks.named('check').configure {
dependsOn('externalProviderTest')
}
Closure<Void> registerPurityRatchet = { String taskName, String label, String forbiddenPackage, String allowlistName ->
tasks.register(taskName) {
group = 'verification'
description = "Fails if files outside core/${allowlistName} import ${forbiddenPackage} (the allowlist only shrinks)."
File allowlistFile = project.file(allowlistName)
File sourceRoot = project.file('src/main/java')
inputs.file(allowlistFile)
inputs.dir(sourceRoot)
doLast {
Set<String> allowed = allowlistFile.readLines().findAll { String line -> !line.isBlank() }.toSet()
List<String> offenders = new ArrayList<>()
List<String> coupled = new ArrayList<>()
sourceRoot.eachFileRecurse { File f ->
if (!f.name.endsWith('.java')) {
return
}
if (f.text.contains(forbiddenPackage)) {
String rel = sourceRoot.toPath().relativize(f.toPath()).toString()
coupled.add(rel)
if (!allowed.contains(rel)) {
offenders.add(rel)
}
}
}
if (!offenders.isEmpty()) {
throw new GradleException(label + " purity ratchet violated - new " + forbiddenPackage + " coupling in:\n " + offenders.join('\n ') + "\nDecouple these files or (only with strong justification) add them to core/" + allowlistName)
}
int shrunk = allowed.size() - coupled.size()
if (shrunk > 0) {
println(label + " purity ratchet: " + coupled.size() + " coupled files (" + shrunk + " fewer than allowlist - consider tightening core/" + allowlistName + ")")
} else {
println(label + " purity ratchet: " + coupled.size() + " coupled files (at allowlist ceiling)")
}
}
}
return null
}
registerPurityRatchet('bukkitPurityRatchet', 'Bukkit', 'org.bukkit', 'purity-allowlist.txt')
registerPurityRatchet('minecraftPurityRatchet', 'Minecraft', 'net.minecraft', 'minecraft-purity-allowlist.txt')
tasks.named('check').configure {
dependsOn('bukkitPurityRatchet', 'minecraftPurityRatchet')
}
apply from: rootProject.file('gradle/generation-build-revision.gradle')