Farama-Foundation / MicroRTS

A simple and highly efficient RTS-game-inspired environment for reinforcement learning
GNU General Public License v3.0
283 stars 103 forks source link

Jaxen library needed for loading bot jar file #99

Closed khisr0w closed 1 year ago

khisr0w commented 1 year ago

When trying to load a jar in the tournament bar, the classloader error is: Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/jaxen/SimpleNamespaceContext

Stuff already tried

  1. Using JDK 8, 9, 12, 11, 19
  2. removing all the code from my bot.

However, if I add jaxen as a dependency for the microrts then it loads my bot without error. I even tried it with the bare minimum needed to make a random bot: My bot code

package abid;

import ai.core.AI;
import ai.core.AIWithComputationBudget;
import ai.core.ParameterSpecification;
import java.util.ArrayList;
import java.util.List;
import rts.PlayerActionGenerator;
import rts.PlayerAction;
import rts.GameState;
import rts.units.UnitTypeTable;

public class MyBot extends AIWithComputationBudget {

    UnitTypeTable m_utt;

    public MyBot(UnitTypeTable utt) {
        super(-1, -1);
        m_utt = utt;
    }

    @Override
    public void reset() {
    }

    @Override
    public AI clone() {
        return new MyBot(m_utt);
    }

    @Override
    public PlayerAction getAction(int player, GameState gs) {
        try {
            if (!gs.canExecuteAnyAction(player)) return new PlayerAction();
            PlayerActionGenerator pag = new PlayerActionGenerator(gs, player);
            return pag.getRandom();
        }catch(Exception e) {
            return new PlayerAction();
        }
    }

    @Override
    public List<ParameterSpecification> getParameters()
    {
        return new ArrayList<>();
    }
}

The complete error is as follows:

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: org/jaxen/SimpleNamespaceContext
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:575)
        at java.base/java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:864)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at tournaments.LoadTournamentAIs.loadTournamentAIsFromJAR(LoadTournamentAIs.java:54)
        at gui.frontend.FETournamentPane$2.actionPerformed(FETournamentPane.java:139)
        at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1967)
        at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2308)
        at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:405)
        at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
        at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
        at java.desktop/java.awt.Component.processMouseEvent(Component.java:6632)
        at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3342)
        at java.desktop/java.awt.Component.processEvent(Component.java:6397)
        at java.desktop/java.awt.Container.processEvent(Container.java:2263)
        at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:5008)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2321)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4840)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4918)
        at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4547)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4488)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2307)
        at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2762)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4840)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
        at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
        at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
        at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.ClassNotFoundException: org.jaxen.SimpleNamespaceContext
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 48 more
santiontanon commented 1 year ago

Btw, where is the dependency with jaxen coming from? I am the author of the original microRTS, and I have never seen this problem before. I never used that library in the code. So, two possibilities:

Are we sure the problem comes from the microRTS side?

khisr0w commented 1 year ago

So, after poking around the problem, I figured the issue is with the build system. But I think it still requires some attention. If I build my bot using NetBeans IDE then everything is rosy (mind you, the newer versions straight up don't work, I had to use NetBeans 9.0). However, If I build my bot using ant build system from the command line, or IntelliJ IDE, then the error pops up. My guess is that these build systems add the jaxen on top of the jar file. For completeness, here is my ant build.xml file:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="export_jar" name="Aggrobot">
    <property environment="env"/>
    <property name="debuglevel" value="source,lines,vars"/>
    <property name="target" value="1.8"/>
    <property name="source" value="1.8"/>
    <path id="Aggrobot.classpath">
        <pathelement location="lib/microrts.jar"/>
        <pathelement location="lib/jdom.jar"/>
        <pathelement location="lib/minimal-json-0.9.4.jar"/>
        <pathelement location="lib/weka.jar"/>
    </path>
    <target name="init">
        <mkdir dir="bin"/>
        <copy includeemptydirs="false" todir="bin">
            <fileset dir="src">
                <exclude name="**/*.java"/>
            </fileset>
        </copy>
    </target>
    <target name="clean">
        <delete dir="bin"/>
    </target>
    <target depends="clean" name="cleanall"/>
    <target depends="build-project" name="build"/>
    <target depends="init" name="build-project">
        <echo message="${ant.project.name}: ${ant.file}"/>
        <javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
            <src path="src"/>
            <classpath refid="Aggrobot.classpath"/>
        </javac>
    </target>
    <target name="export_jar" depends="cleanall,build-project">
        <mkdir dir="build" />
        <jar destfile="build/Aggrobot.jar" basedir="bin" duplicate="preserve">
            <zipgroupfileset dir="./lib/" includes="**/*.jar" />
            <manifest>
                <attribute name="Main-Class" value="test.GameVisualSimulationTest" />
            </manifest>
        </jar>
    </target>
</project>

I'm not sure if this issue should be closed or not. If the goal of the project is to be dev environment agnostic then maybe this needs to be addressed; but if NetBeans is the goal then we can just close this issue. I leave it up to the contributors :)

santiontanon commented 1 year ago

Let me tag @DennisSoemers who recently updated the build system to see if he has any clues here.

DennisSoemers commented 1 year ago

I've just tested, and this Exception triggers also when trying to load the microrts.jar file itself (while it is already running, so that would be a weird thing to do, but anyway...) from the Tournament window, to search it for new AIs.

Specifically, MicroRTS' own .jar file includes the org.jdom.xpath.JaxenXPath$NSContext.class file, which cannot be loaded because it in turn has org.jaxen.SimpleNamespaceContext as a dependency, which is not included in the MicroRTS .jar file. This unloadable class file itself comes from jdom.jar, which is included inside MicroRTS mostly for XML parsing (I think).

Anyway, for @Khisrow1: it looks to me like you are probably packaging all of MicroRTS inside your own new .jar file, so it ends up also including this unloadable .class file. One solution could be to instead package only your AI code (plus any new dependencies you have) in your .jar file, but not include all the contents of microrts.jar. Then, loading AIs from it through the MicroRTS GUI should work fine (because all the MicroRTS classes will by definition already be loaded if you have its GUI opened).

@santiontanon On the MicroRTS side of things, I could update LoadTournamentAIs.loadTournamentAIsFromJAR (https://github.com/Farama-Foundation/MicroRTS/blob/77411e7d133820cd199a91382474e0f1bb3b7316/src/tournaments/LoadTournamentAIs.java#LL40) to catch any exceptions instead of throwing them, and have it simply skip any classes that it fails to load?

santiontanon commented 1 year ago

Ah, yes indeed, bot .jar files should NOT contain microRTS inside! Good catch!

And about catching the exception: sure, why not! It might be nice to at least give a meaningful error message (such as warning of not packaging microRTS within a bot jar file). I don't think there is any need for fancy error panels, probably just printing it to stderr would suffice.

DennisSoemers commented 1 year ago

With PR 101 now merged in, I'll close this issue.

@Khisrow1 With the latest MicroRTS code, the error should no longer happen even if you do package all of MicroRTS inside your own . jar file, but you probably shouldn't. It's probably better to have your bot's .jar file only containing new code for your bot(s).