Jump to content

Forge for 1.17.1 does not include Kotlin Standard Library at runtime


MartinTheDragon

Recommended Posts

The issue

Updating from 1.16.5 to 1.17.1, a problem with the availability of the Kotlin stdlib at runtime emerged. Up until now, the following build script setup worked without issue (same thing, just different versions specified):

Getting the Kotlin Gradle plugin and applying it

buildscript {
    repositories {
        maven { url = 'https://maven.minecraftforge.net' }
        mavenCentral()
    }
    dependencies {
        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Kotlin version: 1.6.0
    }
}

apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'kotlin'

// Some other things

java.toolchain.languageVersion = JavaLanguageVersion.of(16) // This is new

Including the Standard Library as a dependency (actually unnecessary)

dependencies {
    minecraft "net.minecraftforge:forge:1.17.1-37.0.126" // Used to call for a Forge version for 1.16.5
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // This being jdk8 is not an issue

    // JEI would be here too
}

Packaging the Standard Library into the jar

jar {
    dependsOn(classes)
    duplicatesStrategy(DuplicatesStrategy.INCLUDE)
  
    // Manifest here

    configurations {
        kotlinstdlib
    }
    dependencies {
        kotlinstdlib "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    }
    from {
        configurations.kotlinstdlib.collect() { it.isDirectory() ? it : zipTree(it) }
    }
}

Setting the Kotlin JVM target version to 16 (used to be 1.8)

def kotlinCompilerArgs = ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]

compileKotlin {
    kotlinOptions {
        jvmTarget = "16"
        freeCompilerArgs += kotlinCompilerArgs
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = "16"
        freeCompilerArgs += kotlinCompilerArgs
    }
}

This still compiles the project correctly, running it however throws the following exception as soon as the mod annotated class gets loaded (when a Logger instance is requested to be more specific, probably does a null check under the hood - because Kotlin):

java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics

This class is part of the kotlin-stdlib-1.6.0.jar file. (Not kotlin-stdlib-jdk8-1.6.0.jar)
It shows up under "External Libraries", but appears to be missing at runtime for the workspace. Running 'build' outputs a jar that gets loaded correctly though, so the problem only appears when running in IDE using 'runClient'.

JEI does seem to get loaded though, only the Kotlin Standard Library does not.

Versions

Gradle - 7.2
ForgeGradle - 5.1.X
Java for building: 11 (1.8 when testing on a different computer)
Java for compiling and running: 16
Kotlin: 1.6.0
Minecraft: 1.17.1
Forge: 37.0.126 (Currently latest)

IDE is IntelliJ

What I already tried

  • Using kotlin-stdlib instead of kotlin-stdlib-jdk8
  • Setting the Java version to 16 for the 1.16.5 version of the project (worked, but not the goal)
  • Using a different PC (different caches, same output)
  • Running the 'clean' task
  • Using 'api, 'compileOnly' and 'runtimeOnly' in the dependencies block instead of 'implementation'.
  • Applying the Kotlin plugin using the plugins block
  • Creating a raw Kotlin project with JVM 16 target (not Forge; worked)
  • Creating a raw Forge mod project using the Forge MDK, adding the Kotlin plugin to it, and converting the ExampleMod class to Kotlin (failed with same output)
  • Removing the implementation line for the Standard Library in both the 1.16.5 version and the 1.17.1 (the 1.17.1 version failed, the 1.16.5 version ran successfully, which means that this line does not have any effect)
  • This abomination
    compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    runtimeOnly fg.deobf("org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version")
    runtimeOnly fg.deobf("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
    runtimeOnly fg.deobf("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version")
    runtimeOnly fg.deobf("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
  • Combinations of the things mentioned above.

My guess

As this problem only appears with a difference in the build script of the mappings and Forge version, the only thing I guess would be left is Forge having an issue itself. I do not know how Forge loads mods and libraries in the development environment, I have checked what System.getenv("CLASSPATH") outputs using a breakpoint, and it returned null. It is remapping and including JEI correctly though, so it confuses me why the Kotlin Standard Library would not be present. This makes me a bit unsure whether it is Forge's fault, but again, the only real change I did for the build script was changing the mappings and Forge version (and adding the Java 16 line, but I have already checked doing that for 1.16.5). It is also not an incompatibility between Java versions because building a mod jar and running Minecraft Forge 1.17.1 with it normally (outside of IDE) works.

Are there any workarounds/fixes for this? Of course, any help much appreciated!

Link to comment
Share on other sites

By looking at the log output for command 'gradlew --debug runData', it appears that the Standard Library jars get passed to Forge via the '-cp' command line parameter correctly in both versions. Trying to create a workaround earlier revealed that the module resolver is aware of the Standard Library's exports, and therefore conflicts with any attempt to manually copy stdlib over to 'build/classes/kotlin/main'. This means I cannot create a workaround that way. I have also switched to 1.18, and this issue still persists. This is most likely an issue regarding JVM options, which I do not have enough knowledge about.

The build.gradle snippets shown above (remember switching jvmTarget in kotlinOptions to 17 when building for 1.18), alongside with the following piece of code for a mod class, should be enough to reproduce:

File: src/main/kotlin/com/example/examplemod/ExampleMod.kt

package com.example.examplemod

import net.minecraftforge.fml.common.Mod
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger

@Mod("examplemod")
class ExampleMod {
    init {
        LOGGER.info("Hello World!")
    }

    companion object {
        private val LOGGER: Logger = LogManager.getLogger()
    }
}

Note that removing the explicit type Logger (so it becomes 'private val LOGGER = LogManager.getLogger()') loads successfully, because when there is no explicit type specified, Koltin implicitly infers a so-called platform type, shown in IDE as 'Logger!' (note the exclamation mark). This platform type does not get null checked by default, so there are no references to 'kotlin.jvm.internal.Intrinsics' for null checks. Specifying the type explicitly as not null however creates a null check under the hood, because in Kotlin, for a type to be nullable, it has to be suffixed with a question mark (Logger?). Otherwise it is guaranteed to be not null.

Explanation for those not knowing Kotlin:
The 'init' block is essentially a constructor block. The Java equivalent would be 'public ExampleMod() { LOGGER.info("Hello World!"); }'. Declarations inside 'companion object' basically behave like static declarations, so the Java equivalent would be 'private static Logger LOGGER = LogManager.getLogger();'.

Looking forward to a fix! Thank you for your time.

Edited by MartinTheDragon
Clarification
Link to comment
Share on other sites

Further testing revealed that Forge will not successfully load any library in the IDE at all, if the library is not a mod. This means this is either a Forge bug, or the way to include a library at runtime has been changed.

Tested with a random library for Java not included in Minecraft or Forge:

build.gradle

dependencies {
    minecraft 'net.minecraftforge:forge:1.18-38.0.8'
    implementation 'com.github.ben-manes.caffeine:caffeine:3.0.4'
}

ExampleMod.java

package com.example.examplemod;

import com.github.benmanes.caffeine.cache.Caffeine;
import net.minecraftforge.fml.common.Mod;

@Mod("examplemod")
public class ExampleMod {
    public ExampleMod() {
        Caffeine<Object, Object> graphs = Caffeine.newBuilder();
    }
}

Will result in

java.lang.NoClassDefFoundError: com/github/benmanes/caffeine/cache/Caffeine

Should I file a bug report?

Link to comment
Share on other sites

  • 1 month later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • KILAT77 : Waspada Situs Scam dengan Withdraw Yang Tidak Dibayar Di era digital saat ini, banyak orang yang mencari keberuntungan melalui situs perjudian online. Namun, dibalik gemerlap janji-janji manis kemenangan, ada bahaya yang mengintai. Salah satu situs yang patut diwaspadai adalah KILAT77. Situs ini mendapat reputasi buruk karena banyak laporan dari pengguna yang mengklaim bahwa mereka tidak bisa menarik dana kemenangan mereka. Dalam artikel ini, kita akan membahas mengapa ROTER88 dianggap sebagai situs scam dan bagaimana Anda bisa melindungi diri dari penipuan serupa. Pengalaman Pengguna: Penarikan Tidak Dibayar Beberapa pengguna telah melaporkan pengalaman buruk mereka dengan KILAT77. Mereka mengaku bahwa setelah memenangkan sejumlah uang dan mencoba menariknya, proses penarikan mereka ditolak tanpa alasan yang jelas. Bahkan, beberapa pengguna melaporkan bahwa akun mereka tiba-tiba diblokir setelah mencoba melakukan penarikan, sehingga mereka kehilangan akses ke dana mereka sama sekali.
    • It is a dupe mod issue Remove Rubidium - you are already using Embeddium which is a fork of Rubidium
    • I made a block entity in forge 1.20.1, I want to prevent hopper from taking input slot item, i tried to override the extractItem method, it prevented hopper from taking input slot item, but the player also unable to take/change the item in input slot unless the slot is empty. public class FluidSeparatorBlockEntity extends BlockEntity implements MenuProvider { private static final int INPUT_SLOT = 0; private final CustomItemHandler itemHandler = new CustomItemHandler(3){ @Override protected void onContentsChanged(int slot) { setChanged(); } @Override public boolean isItemValid(int slot, @NotNull ItemStack stack) { return slot == INPUT_SLOT; } @Override public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { if (slot == INPUT_SLOT) { return ItemStack.EMPTY; } return super.extractItem(slot, amount, simulate); } }; private LazyOptional<IItemHandler> lazyItemHandler = LazyOptional.empty(); protected final ContainerData data; private int progress = 0; private int maxProgress = 78; public FluidSeparatorBlockEntity(BlockPos pPos, BlockState pBlockState) { super(ModBlockEntities.FLUID_SEPARATOR_BE.get(), pPos, pBlockState); this.data = new ContainerData() { @Override public int get(int pIndex) { return switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress; default -> 0; }; } @Override public void set(int pIndex, int pValue) { switch (pIndex) { case 0 -> FluidSeparatorBlockEntity.this.progress = pValue; case 1, 2 -> FluidSeparatorBlockEntity.this.maxProgress = pValue; } } @Override public int getCount() { return 3; } }; } @Override public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) { if(cap == ForgeCapabilities.ITEM_HANDLER) { return lazyItemHandler.cast(); } return super.getCapability(cap, side); } @Override public void onLoad() { super.onLoad(); lazyItemHandler = LazyOptional.of(() -> itemHandler); } @Override public void invalidateCaps() { super.invalidateCaps(); lazyItemHandler.invalidate(); } public void drops() { SimpleContainer inventory = new SimpleContainer(itemHandler.getSlots()); for(int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, itemHandler.getStackInSlot(i)); } Containers.dropContents(this.level, this.worldPosition, inventory); } @Override public Component getDisplayName() { return Component.translatable("block.chemmaster.fluid_separator"); } @Nullable @Override public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) { return new FluidSeparatorMenu(pContainerId, pPlayerInventory, this, this.data); } @Override protected void saveAdditional(CompoundTag pTag) { pTag.put("inventory", itemHandler.serializeNBT()); pTag.putInt("fluid_separator.progress", progress); super.saveAdditional(pTag); } @Override public void load(CompoundTag pTag) { super.load(pTag); itemHandler.deserializeNBT(pTag.getCompound("inventory")); progress = pTag.getInt("fluid_separator.progress"); } public void tick(Level pLevel, BlockPos pPos, BlockState pState) { ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { resetProgress(); return; } if(hasRecipe()) { increaseCraftingProgress(); setChanged(pLevel, pPos, pState); if(hasProgressFinished()) { craftItem(); resetProgress(); } } else { resetProgress(); } } private void resetProgress() { progress = 0; } private void craftItem() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isPresent()) { List<ItemStack> results = recipe.get().getOutputs(); ItemStack inputStack = this.itemHandler.getStackInSlot(INPUT_SLOT); if (inputStack.getCount() < 2) { // If there are not enough items, do not proceed with crafting return; } // Extract the input item from the input slot this.itemHandler.internalExtractItem(INPUT_SLOT, 2, false); // Loop through each result item and find suitable output slots for (ItemStack result : results) { int outputSlot = findSuitableOutputSlot(result); if (outputSlot != -1) { this.itemHandler.setStackInSlot(outputSlot, new ItemStack(result.getItem(), this.itemHandler.getStackInSlot(outputSlot).getCount() + result.getCount())); } else { // Handle the case where no suitable output slot is found // This can be logging an error, throwing an exception, or any other handling logic System.err.println("No suitable output slot found for item: " + result); } } } } private int findSuitableOutputSlot(ItemStack result) { // Implement logic to find a suitable output slot for the given result // Return the slot index or -1 if no suitable slot is found for (int i = 0; i < this.itemHandler.getSlots(); i++) { // Ensure we do not place the output item in the input slot if (i == INPUT_SLOT) { continue; } ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return i; } } return -1; } private boolean hasRecipe() { Optional<FluidSeparatingRecipe> recipe = getCurrentRecipe(); if (recipe.isEmpty()) { return false; } List<ItemStack> results = recipe.get().getOutputs(); for (ItemStack result : results) { if (!canInsertAmountIntoOutputSlot(result) || !canInsertItemIntoOutputSlot(result.getItem())) { return false; } } return true; } private Optional<FluidSeparatingRecipe> getCurrentRecipe(){ SimpleContainer inventory = new SimpleContainer(this.itemHandler.getSlots()); for (int i = 0; i < itemHandler.getSlots(); i++) { inventory.setItem(i, this.itemHandler.getStackInSlot(i)); } return this.level.getRecipeManager().getRecipeFor(FluidSeparatingRecipe.Type.INSTANCE, inventory, level); } private boolean canInsertAmountIntoOutputSlot(ItemStack result) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || (stackInSlot.getItem() == result.getItem() && stackInSlot.getCount() + result.getCount() <= stackInSlot.getMaxStackSize())) { return true; } } return false; } private boolean canInsertItemIntoOutputSlot(Item item) { for (int i = 1; i < this.itemHandler.getSlots(); i++) { ItemStack stackInSlot = this.itemHandler.getStackInSlot(i); if (stackInSlot.isEmpty() || stackInSlot.getItem() == item) { return true; } } return false; } private boolean hasProgressFinished() { return progress >= maxProgress; } private void increaseCraftingProgress() { progress++; } }  
    • No dice. Unfortunately this fix didn't work, thank you though.
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.