diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index ed65288cc..c6b3fdbf9 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -1,9 +1,39 @@ +/* + * 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 . + */ + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.toolchain.JavaLanguageVersion + plugins { id 'java' + id 'net.fabricmc.fabric-loom' version '1.17.11' + alias(libs.plugins.shadow) } +Properties rootProperties = new Properties() +file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) } +String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.1')) +String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.1.2')) +String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.3') + group = 'art.arcane' -version = rootProject.version +version = irisVersion java { toolchain { @@ -11,17 +41,85 @@ java { } } +repositories { + mavenCentral() + maven { + name = 'FabricMC' + url = uri('https://maven.fabricmc.net/') + } + maven { url = uri('https://repo.codemc.org/repository/maven-public/') } + maven { url = uri('https://jitpack.io') } +} + +configurations { + bundle { + canBeConsumed = false + canBeResolved = true + } +} + +configurations.named('bundle').configure { + exclude(group: 'com.google.code.gson') + exclude(group: 'com.google.guava') + exclude(group: 'commons-io') + exclude(group: 'org.apache.commons', module: 'commons-lang3') + exclude(group: 'it.unimi.dsi', module: 'fastutil') + exclude(group: 'org.slf4j') + exclude(group: 'org.apache.logging.log4j') + exclude(group: 'de.crazydev22.slimjar.helper') + exclude(group: 'de.crazydev22.slimjar') + exclude(group: 'io.github.slimjar') +} + dependencies { - implementation(project(':spi')) + minecraft("com.mojang:minecraft:${minecraftVersion}") + implementation("net.fabricmc:fabric-loader:${fabricLoaderVersion}") + compileOnly('org.slf4j:slf4j-api:2.0.17') + + bundle("art.arcane:core:${irisVersion}") + bundle("art.arcane:spi:${irisVersion}") + bundle('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + + bundle(libs.paralithic) + bundle(libs.lru) + bundle(libs.kotlin.stdlib) + bundle(libs.kotlin.coroutines) + bundle(libs.commons.lang) + bundle(libs.commons.math3) + bundle(libs.caffeine) + bundle(libs.lz4) + bundle(libs.zip) + bundle(libs.sentry) + bundle(libs.oshi) + bundle(libs.byteBuddy.core) + bundle(libs.byteBuddy.agent) +} + +tasks.named('compileJava', JavaCompile).configure { + options.encoding = 'UTF-8' + options.release.set(25) } processResources { inputs.property('version', project.version) + inputs.property('minecraftVersion', minecraftVersion) filesMatching('fabric.mod.json') { - expand('version': project.version) + expand('version': project.version, 'minecraftVersion': minecraftVersion) } } -tasks.named('jar', Jar).configure { - archiveFileName.set("Iris-${project.version}-fabric-skeleton.jar") +tasks.named('shadowJar', ShadowJar).configure { + archiveFileName.set("Iris-${project.version}+mc${minecraftVersion}-fabric.jar") + configurations = [project.configurations.named('bundle').get()] + mergeServiceFiles() + exclude('META-INF/maven/**') + exclude('META-INF/proguard/**') + exclude('META-INF/*.SF') + exclude('META-INF/*.DSA') + exclude('META-INF/*.RSA') + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +tasks.named('assemble').configure { + dependsOn(tasks.named('shadowJar')) } diff --git a/adapters/fabric/gradle.properties b/adapters/fabric/gradle.properties new file mode 100644 index 000000000..af258fbfc --- /dev/null +++ b/adapters/fabric/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.caching=true +org.gradle.configuration-cache=false diff --git a/adapters/fabric/settings.gradle b/adapters/fabric/settings.gradle new file mode 100644 index 000000000..c7106b7e7 --- /dev/null +++ b/adapters/fabric/settings.gradle @@ -0,0 +1,95 @@ +/* + * 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 . + */ + +import java.io.File + +pluginManagement { + repositories { + maven { + name = 'FabricMC' + url = uri('https://maven.fabricmc.net/') + } + gradlePluginPortal() + mavenCentral() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'iris-fabric' + +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files('../../gradle/libs.versions.toml')) + } + } +} + +boolean hasVolmLibSettings(File directory) { + new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists() +} + +File resolveLocalVolmLibDirectory() { + String configuredPath = providers.gradleProperty('localVolmLibDirectory') + .orElse(providers.environmentVariable('VOLMLIB_DIR')) + .orNull + if (configuredPath != null && !configuredPath.isBlank()) { + File configuredDirectory = file(configuredPath) + if (hasVolmLibSettings(configuredDirectory)) { + return configuredDirectory + } + } + + File currentDirectory = settingsDir + while (currentDirectory != null) { + File candidate = new File(currentDirectory, 'VolmLib') + if (hasVolmLibSettings(candidate)) { + return candidate + } + + currentDirectory = currentDirectory.parentFile + } + + null +} + +boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib') + .orElse('true') + .map { String value -> value.equalsIgnoreCase('true') } + .get() +File localVolmLibDirectory = resolveLocalVolmLibDirectory() + +if (useLocalVolmLib && localVolmLibDirectory != null) { + includeBuild(localVolmLibDirectory) { + dependencySubstitution { + substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared')) + substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared')) + substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared')) + } + } +} + +includeBuild('../..') { + dependencySubstitution { + substitute(module('art.arcane:core')).using(project(':core')) + substitute(module('art.arcane:spi')).using(project(':spi')) + } +} diff --git a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java index b3a1f7d55..226ec0c21 100644 --- a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java +++ b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricBootstrap.java @@ -18,8 +18,46 @@ package art.arcane.iris.fabric; -public final class IrisFabricBootstrap { +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class IrisFabricBootstrap implements ModInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); + private static final String[] CORE_SELF_TEST_CLASSES = { + "art.arcane.iris.engine.IrisEngine", + "art.arcane.iris.util.common.data.B", + "art.arcane.iris.core.loader.IrisData" + }; + + @Override public void onInitialize() { - throw new UnsupportedOperationException("The Iris Fabric adapter is a build skeleton; worldgen is not wired yet (see CROSSPLATFORM_PLAN.md Phase 4)."); + FabricLoader loader = FabricLoader.getInstance(); + String modVersion = loader.getModContainer("irisworldgen") + .map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString()) + .orElse("unknown"); + String minecraftVersion = loader.getModContainer("minecraft") + .map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString()) + .orElse("unknown"); + LOGGER.info("Iris {} bootstrapping on Minecraft {} (Fabric)", modVersion, minecraftVersion); + + int loadedClasses = 0; + for (String className : CORE_SELF_TEST_CLASSES) { + try { + Class.forName(className, true, IrisFabricBootstrap.class.getClassLoader()); + loadedClasses++; + } catch (Throwable error) { + LOGGER.error("Iris core self-test failed to initialize {}", className, error); + } + } + + if (loadedClasses != CORE_SELF_TEST_CLASSES.length) { + throw new IllegalStateException("Iris core self-test failed: only " + loadedClasses + " of " + CORE_SELF_TEST_CLASSES.length + " engine classes initialized"); + } + + LOGGER.info("Iris core loaded ({} classes ok)", loadedClasses); + LOGGER.info("Iris worldgen is not wired on Fabric yet; this build verifies packaging and engine classloading"); } } diff --git a/adapters/fabric/src/main/resources/fabric.mod.json b/adapters/fabric/src/main/resources/fabric.mod.json index 47ea06179..5767c5fbd 100644 --- a/adapters/fabric/src/main/resources/fabric.mod.json +++ b/adapters/fabric/src/main/resources/fabric.mod.json @@ -3,11 +3,19 @@ "id": "irisworldgen", "version": "${version}", "name": "Iris", - "description": "Iris World Generation Engine (Fabric adapter - SKELETON, worldgen not yet wired)", + "description": "Iris World Generation Engine (Fabric adapter - packaging + core bundle; worldgen wiring lands next milestone)", "authors": ["Arcane Arts (Volmit Software)"], + "contact": { + "sources": "https://github.com/VolmitSoftware/Iris" + }, "license": "GPL-3.0", - "environment": "server", + "environment": "*", "entrypoints": { "main": ["art.arcane.iris.fabric.IrisFabricBootstrap"] + }, + "depends": { + "fabricloader": ">=0.19.0", + "minecraft": "~${minecraftVersion}", + "java": ">=25" } } diff --git a/build.gradle b/build.gradle index ec028f5fd..a9d789497 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,11 @@ plugins { group = 'art.arcane' version = providers.gradleProperty('irisVersion').getOrElse('4.0.0-26.1') + +project(':core') { + group = 'art.arcane' + version = rootProject.version +} String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.1.2') String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') @@ -128,11 +133,17 @@ tasks.register('buildBukkit', Copy) { into(layout.projectDirectory.dir('dist')) } +tasks.register('fabricJar', Exec) { + group = 'iris' + workingDir = layout.projectDirectory.dir('adapters/fabric').asFile + String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew' + commandLine(layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain') +} + tasks.register('buildFabric', Copy) { group = 'iris' - dependsOn(':adapters:fabric:jar') - from(project(':adapters:fabric').layout.buildDirectory.file("libs/Iris-${project.version}-fabric-skeleton.jar")) - rename { "Iris-${project.version}+mc${minecraftVersion}-fabric-skeleton.jar" } + dependsOn('fabricJar') + from(layout.projectDirectory.file("adapters/fabric/build/libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar")) into(layout.projectDirectory.dir('dist')) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e1113280..5dd3c0121 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 07f33339e..de2abc625 100644 --- a/settings.gradle +++ b/settings.gradle @@ -74,6 +74,5 @@ include(':spi') include(':adapters:bukkit:plugin') include(':adapters:bukkit:nms:v1_21_R7') include(':adapters:bukkit:nms:v26_1_R1') -include(':adapters:fabric') include(':adapters:forge') include(':adapters:neoforge')