#FML Clean-up
1 messages ยท Page 5 of 1
Are we leaking the runtimeClasspath maybe?
Apparently that's what we're already doing
We are not
Wait, so does this work when you runClient? Or what? ๐
As far as I can see it should
We should try that
Do we have an example for that?
Also as an aside: not having a named config to represent the runtime classpath of runs sucks
Because we cannot inspect it using gradlew dependencies
runtimeClasspath
It is extended though
Yeah
I am considering making it one
Because I want to use it in tests
So it will likely come
But first step is to inspec gradlew dependencies for the normal runtimeClasspath, yes
No
The way the error manifests is always in the IDE, whether eclipse or IDEA
Both of them track classpath contents via the path of the jar
Explicitly not the GAV
So because of that you get into a situation where when they construct a runs classpath for running through them, they add both elements to the classpath
This has been known for years
This is also not just limited to Minecraft or NeoForge
It is very much possible, yes. But we should confirm this and document it in the locator if that's the case for the hacks we use
This same problem happens with all dependencies
But TBH the IJ dependency model actually should support this Orion
not leaking a dependency from module A to module B
You would think so right
Since it knows it is a gradle dep
But it is does not accross module boundaries
That is the fucking problem
At least it was last time
And because multi project builds create multiple distinct modules in IDEA you still run into the issue
That is actually exactly what it is not doing
It is not leaking
That is the point
If it where leaking
It would use the dependency from module A in module B
But it is not
Yet because your run uses them both in the classpath
I just meant that the project model itself in IJ should support this case. It's still possible that their Gradle sync is unable to properly convert it.
In any case, we should test this first with gradlew runClient to check whether this issue is already present in the Gradle project model or not.
If all else fails, we reintroduce reading the MC dependency from legacy classpath and add a large comment explaining why the fuck we do that
If somebody gives me a repo project
I will do it right now
Because this is a really high prio for me to fix
I don't have one right now either
@restive stag Is the project where you ran into this public?
Not yet, unfortunately - I'm working on it ๐
Should a dummy workspace with two MDKs not work?
It might
Same for meetings ๐
Lol what. Apparently my suggestion of using two configurations worked ๐
Unfortunately I am at work and can't test anything before like 7 pm
That seems utterly weird, because that is what NG7 does internally........
Exactly for that reason
Why would extending runtimeClasspath from a configuration
Yeah I'm confused as well now
Vs
Adding the dependency directly to runtimeClasspath make any difference?
Because your suggesting is this:
// Sets up a dependency configuration called 'localRuntime'.
// This configuration should be used instead of 'runtimeOnly' to declare
// a dependency that will be present for runtime testing but that is
// "optional", meaning it will not be pulled by dependents of this mod.
configurations {
runtimeClasspath.extendsFrom localRuntime
}
dependencies {
compileOnly "net.neoforged.neoforge:neoforge:XXX"
localRuntime "net.neoforged.neoforge:neoforge:XXX"
}
While NG does by default:
Well I'm surprised it even works
dependencies {
compileOnly "net.neoforged.neoforge:neoforge:XXX"
runtimeClasspath "net.neoforged.neoforge:neoforge:XXX"
}
It works, but what I don't understand is why it fixes it
That is what makes like 0 sense to me
Wait you just tested and it works??
The top one works
The bottom one not
The bottom one is what NG7 does
When you do implementation "net.neoforged.neoforge:neoforge:XXX"
Weird
It removes it from implementation
And adds it to compileOnly and runtimeClasspath
Yeah
This whole idea of doing this
Came from luke
To prevent them from showing up in maven poms and gradle metadata
Well I think it's the correct end behavior
It is
For sure
Hence me implementing it based on his suggestion
What I don't understand is why it works with the intermediary config
But not with the direct add
The end result is the same
uhm the getsuperconfigs is very suspicious
Can you expand that statement?
runtimeClasspath extends from implementation
Where?
ConfigurationUtils
the logic is completely backwards and explains why tech's code works
I am going to write some unit tests
Sitting in teamchat
If somebody wants to figure this out with me
I'll be metting all day
the issue is getsuperconfigs, that doesn't need or make sense to exist
It does.......
I just wrote 4 unit tests to test it
And it works perfectly fine
Not sure what you think it is supposed to do
to test "what"?
broken behaviour?
Its intended behaviour
so it leaking deps is intended behaviour?
To find the runtime and compile classpath configurations
Can you stop with these strawman arguments......
The methods in configuration utils are finding the correct configurations
configurations {
myCustomConfig
runtimeClasspath.extendsFrom myCustomConfig
}
dependencies {
myCustomConfig 'net.neoforge:neoforge:'
}
now replace myCustomConfig with implementation since that's how implementation basically looks like
and that explains why it's leaking deps
runtimeClasspath extends from impl, and you're getting all the "super"s (which is wrong, since gradle is in reverse here) and adding the deps to them too
the dep ends up on impl again
Yet it does not
Because the dep does not show up in the gradle metadata
Nor in the maven pom
So it can't be in implementation
@chrome mortar You can run the tests in this class: https://github.com/neoforged/NeoGradle/blob/fix/multi-project-leaking/common/src/test/java/net/neoforged/gradle/common/util/ConfigurationUtilsTest.java#L19 and see that you are wrong
The assertions clearly show that the implementation configuration is not returned
So unless I am missing something in the tests (which is possible), your hypothesis that this is caused by this section of the code is not correct
And that means that the replacement configurations for "implementation" are properly selected
Correct, what this part of the code is doing, is take in a configuration and a project. Then take all sourcesets from the project, and take from that sourceset the runtimeclasspath configuration, and then finding all configurations that this runtimeclasspath extendsfrom, and then check if the input configuration is part of that extends from set
So as far as I can see it works as designed
And as planned
I can see how you think it works in the wrong direction
But it does not
well replacement seems to let implementation remain with a random unspecificed dep
How did you test this?
dependencies {
implementation "net.neoforged:neoforge:${neo_version}"
}
and ran ./gradlew dependencies
Ah that is the generation task
Which is technically fine
Because that has no dependencies that can leak
Still it likely should not be there I think
Let me see if I can write some tests that verify that
Did the system for targeting classes for transformation via the constant pool ever go anywhere?
I've been eyeing that idea for a while now; would honestly be quite useful
no, and without designing the existing transformers to not parse every class, it's of no use
the current big issue I encountered is that Mixin's ML hook parses every class unconditionally, and there is no obvious way I see to find out if a given class needs transformation
Mixin only does stuff to classes with mixins targetting them though?
And it knows that list, so it should just be able to only target those classes
yes in theory it should be possible to fix
I just didn't see any obvious way to, given my lack of knowledge of Mixin internals
Also it's not of no use even then -- folks doing their own custom stuff could add their own transformers using it, that will be future-proofed against whenever we figure out how to fix the mixin one
But... mixin internals. Lovely. Those are always a true nightmare to poke. Worth looking into I guess to see what we can figure out
Llama might know a way
this is really weird
this suggests that it will only handle classes if one of the "post processors" requests it
One of them always votes for it, forget which
I can check
MixinTransformationHandler
any non-empty class is always voted for
With the new changes, noticing some leaking of internal stuff. The LoadingModList leaks internal classes (namely, ModFileInfo, which isn't strictly internal but leaks ModFile which is marked as internal), leading to strange IDE warnings
See #modder-support-1โค20โค6 message
I am not terribly happy with the entire handover from FMLLoader->LoadingModList->ModLoader, to be frank
Sometimes I wish there was a single place to check mods like you can do on Fabric
Well that person is "abusing" a newly-introduced API ๐
Hmm? LoadingModList isn't new, and you've been able to grab stuff from it for a while, right?
Wasn't it always the recommended way for inspecting mod stuff at weirdly early times?
That new method is new
It is but it doesn't really have a well-defined API
Still not sure what thing here is new -- getModFiles() is not, nor is getFile(), and getting the loading mod list was easy before with methods that don't seem to be marked "internal". The bigger issue is that there's no good well-defined API here, and that @ApiStatus.Internal is used inconsistently throughout the codebase.
Note that no method should have a return or parameter type that involves an @ApiStatus.Internal class unless that method it, itself, @ApiStatus.Internal!
It might be a different method than what I have in mind then
But yes FML doesn't have a particularly clean design

F
on it
For a sec I thought to myself "hmmm do I need to rebase this? it's still green...."
@glacial crag Reclick please: https://github.com/neoforged/FancyModLoader/pull/157
done
ty
Alright, so current musings on startup:
JavaAgent is kinda awesome
Has perf-impact when transforming, but we can use it without a transformer and then it has no impact. That still allows us to completely remove all --add-opens args and nicely set up modules as we want at runtime, even for java.lang
For tests run from IDE, where we may not be able to control the javaagent arg easily at the moment, we can use dynamic attach until that gets yeeted from the JRE in likely the next LTS (Gradle has until then to figure their shit out)
But I don't think that'll be much of a problem since the enterprise world relies on Mockito and Powermock and I believe both use the Bytebuddy dynamic attach agent at the moment.
@haughty copper is this intended for dev or prod
I feel like attaching the java agent in prod will be messy
Why?
how will you get the path to the jar?
Same as with the modulepath?
Our installer already makes those assumptions for the module path
hmm k
Here's a snippet of the Vanilla Launcher profile
"jvm": [
// ...
"-p",
"${library_directory}/cpw/mods/securejarhandler/2.1.24/securejarhandler-2.1.24.jar...
// ...
]
// ...
"libraries": [
{
"name": "cpw.mods:securejarhandler:2.1.24@jar",
"downloads": {
"artifact": {
// ...
"path": "cpw/mods/securejarhandler/2.1.24/securejarhandler-2.1.24.jar"
}
}
},
}
The same approach should work for passing "-javaagent"
That's a new one.... TinyFD (or is it Windows?) complaining about quotes in my error message... what
TinyFD
Oh huh. That check is nowhere to be found on GH, but I noticed that mirror is extremely outdated

The checks are present in the version on sourceforge, which seems to be the primary source
(but at the same time not surprising that it does stuff like that -- making crossplatform dialogs is a deep can of worms)
lwjgl uses tinyfd
Yeah since this is for FML Startup fatal error reporting in case not even the early startup window can be shown/found
I don't have a lot of options
I could theoretically just use AWT, to be honest
doesn't lwjgl prevent awt from being used?
Nope
Since... if earlydisplay didn't even start and we're about to crash, I really don't need to care about messing up OpenGL for later ๐
in macos specifically
I thought it's more the other way around
if you use AWT you create an off-thread message loop
I think lwjgl intentionally breaks awt by forcing headless mode to be set
You can just unset that
Lwjgl has both
but maybe that only matters if you have actually initialized opengl XD
I think that's it
it does? huh.
lwjgl might, but does MC ship with both
MC doesn't use it, yes
Well yeah, but I think AWT is the better option, when I think about it
remember to test in mac
Though what does MC even use TinyFD for?
or have someone test in mac
Since assuming the FML main method is called and none of the MC libraries are on the path, I can only use AWT to show an error anyway
nothing, apparently 
oh I probably don't have sources downloaded here at work
yep.
grumbles about jetbrains
more like nobrains :V
Somehow my brain went to MessageBoxW
I wish showing a dialog box in a crossplatform way was actually as easy as it is in windows
I mean, windows - easy. other platforms - not so easy
Yup
Windows W
Does Linux even have such a concept?
Better it has many many many such concepts
which display server and window system do you want to support lol
So it doesn't
I believe in mac you need to have a GUI initialized in order to show a messagebox
in linux you have to pick a GUI framework and hope that whatever DE you use will work
oh it's apparently not that bad in mac. https://gist.github.com/brendandahl/9061968
unless they deleted that API and replaced it with something else, which is possible
tinyfd is certainly not using that
Uh
Mac has a C++ API?
It instead seems to build an applyscript file and executes that
Aren't they all on ObjC?
yesn't. there's bindings people have written
so it's basically doing "this" instead https://apple.stackexchange.com/questions/73290/display-dialog-from-command-line-like-xmessage-does
anyhow
Oh, so it's the low level upon which ObjC runs
tinyfd seems to try to support OSX<9
mac? well maybe you can call API, maybe you need to execute a dynamically created shell script
Shame on them not also having Win 3.11 support
linux? well you either depend on some random gui framework which may be 1000x bigger than your app, or run a shell command that calls xmessage
Run a shell command that installs xmessage by using apt
If you don't have apt, then install it via whatever way you do to install apy
Imagine using C APIs
There's a lot more checks than that
In a C application
thoguht I had pasted the others
// TinyFD refuses to let us use quotes
message = message.replace('"', '`');
message = message.replace('\'', '`');
Well, I'll just go with that for the time being!
but I only pasted one lol
until we know we can actually use AWT
If we can use AWT, we can also use Swing...
then we can make actually useful error dialogs ๐
Also earlydisplay still has a text-QR code embedded in it that's unused
leads to tinyurl.com/forgetest ๐
Leading to https://blog.minecraftforge.net/personal/matyrobbrt/optifine-alternatives/ ๐
@chrome mortar blogger!
if (getOs() != MAC) {
displayWithAwt();
} else {
tinyfd("An error occurred. Upgrade to a better OS to know what happened");
}
๐
But yeah, IIRC LWJGL now ships a patched GLFW that doesn't conflict with AWT on Mac
It should be tested, but I remember discussions about it on LWJGLCord with Spasi
I mean, is that just the GLFW used by LWJGL then?
I don't wanna buy a mac to test this shit ๐
LWJGL has its own fork of GLFW and various other libraries, yes
But I cannot confirm that it is the same file that is run on MC
You'd have to check the dylib name
And I can't ATM
I mean, they just pull in the LWJGL natives, so if LWJGL patched it, MC benefits
I don't know if LWJGL patches it in the main artifact or requires a different one
Hence why
[Reference to](#1187879036815417456 message) #1187879036815417456 [โค ](#1187879036815417456 message)Don't ask me about the artifact name
I write everything Windows exclusive, so I didn't interact with that
I simply remember a discussion and lengthy debugging talks
Hm yeah looking at that patch btw, that is not something we'd want to trigger ๐
Apparently it does a cross-thread dispatch then which I'd wager decreases perf
I mean... you can have fun with objc_sendMsg but I wouldn't recommend it ๐
Ugh I am still so annoyed at us shoehorning modules into this
The basic APIs we'd really want just aren't exposed by the JDK ๐
which ones?
Patch Modules
ah yes that one
And generally the infrastructure around constructing automatic modules from directories is... annoyingly internal
Cue me copy pasting the list of reserved java keywords from the JDK, just as SJH did ๐
The entire ecosystem is really... not there. STILL not there.
does every jar not have an automatic module name yet?
The problem here is in development I get the disjointed folders
Just like with mods
I.e. when developing the startup code itself, reloading "itself" into a module layer is more difficult than it seems
Since you have to first re-assemble the disjointed directories, then you have to scan which packages it includes to build the module descriptor
If you had access to the patch module infra, I suppose we could instead construct the module from the first folder and patch the resource folder into that module
I suppose a maintainer vote about dropping modules is not gonna fly (I doubt most people even care, though). So I'll suffer through it ๐
Urgh this might actually be the first approach that works at finding these directories by module in dev:
// We do not want to deal with that. The spec says use forward slashes.
surprised you put the contains check before the isEmpty check 
Yeah true, we might also consider if as a generic util, NF itself is the better place. ๐ค
Good point, actually ๐ค
@haughty copper There is ways to use the patch stuff
It now has public constructors
So you can create your own patching infrastructure and pass that to layers, modules and classloaders where needed
Well the constructor is public, but it's still all in jdk.internal ๐
Some of it yeah
But /sigh. At least I think I found a light-weight solution to making it viable for developing on FML or any other of the service-level projects in development
Where it will re-assemble a module out of the Gradle classes+resources directory
My approach is: I place a file in src/main/resources: <module_name>.resourceroot
Which contains a list of required Java packages that the module must have.
When I don't find required module X at startup, I look for <module_name>.resourceroot in all directories on the CP.
If I find it, I mark that directory down as the "resources" dir for module X.
Then I scan the other CP directories for Java packages and use the one that contains all packages required for module X as the "classes" dir for module X.
That way this works out of the box with Gradle, IJ, whatever and requires no further buildscript interaction
Would an incremental AP not work better here?
Hm, it's possible. The resourceroot file doesn't have to contain all packages of the module. Just what you need to identify the classes directory securely
I still need to scan that to get a full package list for the ModuleDescriptor. Sadly this is before SJH is even loaded
No AP 
So far this actually seems to work reasonably well.
In module-land, a package can only exist in one module anyway, so it seems to be sufficient for our use-case.
The use-case here is only for service-level modules in development, which never span more than one classes/resources directory (unlike mods)
@haughty copper https://github.com/neoforged/FancyModLoader/pull/170#discussion_r1659276890 you touched en_us and ignored all others ๐
all other langs still use {0} etc for the broken files
If the keys didnt change, would crowdin not update the placeholders? ๐ค
you need to take into consideration files that actually had it translated (are there any?)
but really, if you've already done a python
you can do more ๐
I already deleted the python ๐
But this was just a regexp replace anyway
Ugh now I also had to replicate the service-file scanning. I feel the SJH Pain ๐
TBF, if our boot/startup projects that we wanted to pull as source into the dev-env of FML had module-info.class, I'd not have to do any of that
Gnahahahaha .... fuck ๐
I am now getting earlydisplay... in a child classloader. And when I then try to show the tinyfd dialog back in my actual main method..............:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Native Library C:\Users\Sebastian\AppData\Local\Temp\lwjgl_Sebastian\3.3.3+5\x64\lwjgl.dll already loaded in another classloader
at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:167)
at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:139)
We also forgot updating ATs in FML itself: https://github.com/neoforged/FancyModLoader/blob/main/gradle.properties#L4C33-L4C34
god tinyfd sucks so much
it attaches its message box on windows to the "ForegroundWindow", even if that is from another process
if I kill the process via IntelliJ while it is showing the message box, it locks mouse input to IJ, forcing me to restart it...
That sounds like garbage
It is
Well multiple, I suppose:
If not on MacOS, we can just use Swing
When on MacOS, the caveat above doesn't apply since it launches a damn applescript process to show a messagebox...
Before LWJGL starts, we can also use Swing on MacOS
I think ultimately we will want earlydisplay to show such errors (after it has started), eliminating the need for tinyfd once earlydisplay runs
before earlydisplay runs, we can use Swing
And now I managed to break MDG ๐
Failed to pre-load artifact 'project :modlauncher' from path 'C:\Neo Forged\modlauncher\build\libs\modlauncher-11.0.5.jar': java.lang.IllegalArgumentException: Malformed Maven coordinate: project :modlauncher
Oeps
And now gradleutils is having a bit of a configuration cache fit again
Once more. Maddening. Gradle has the information I need, but choses not to expose it at all.
I'd love to get at that file path...................
Hmmm, I suppose I can get at the path, but not anything else. Oh well
Okay well, I finally got past that.... Now I can try building runs in FML that actually simulate userdev and production
I'll also have to see how I can simulate neodev
Gradle is driving me up a wall again...
Execution failed for task ':startup:dependencies'.
Could not resolve all dependencies for configuration ':startup:userdevTestRuntimeClasspath'.
java.util.ConcurrentModificationException (no error message)
Deep within Gradle when resolving dependencies โค๏ธ
Well NG is forced to not use modules anyway ๐
?
I am talking about NIO ! ๐
Ooooh those
UnionFS / JarJarFS
For fucks sake gradle
There is an actually object that is named that
Why do they suck?
I have never had an issue with them
Well obviously not the providers themselves
It's that the JDK only allows them to be on the system classloader
No i mean their api
Well no
That is a concequence of us loading them so early
They can technically be anywhere
Well "kinda", they need to be there for tha Path->URI roundtrip to work, no?
If I remember correctly
Yes, they need to be loaded by the time that the first conversion is done
the problem is that it caches that
Yes, not only that
it specifically does this:
ServiceLoader<FileSystemProvider> sl = ServiceLoader
.load(FileSystemProvider.class, ClassLoader.getSystemClassLoader());
I am now getting to the actual mod-loading part and it is only calling loadInstalledProviders for the first time, after I have already set up our module layer nicely
But it still loads them all from the app classpath
I'll see what I can do I suppose ๐
Yeah I know, I was hoping to not have to resolve to haxxx
Blame JDK
Performance probably
But in reality they should have done this in a classloader aware way
Where this cachable information is stored in some metadata attached to the classloader
Because else you get into hot water
But that opens a whole host of other problems
Maybe some day I can convince you that we don't have to use Modules ๐
But I know, that day is not today ๐
Eh, if we kept the constraints, it'd really be easy
Also the hacks that both Forge and Fabric apply will get ripped from Java eventually
The constraints being: no default package, packages must not overlap between jars
Which hacks?
Child-first class-loaders aren't hacks
The implicit reliance on the URL classloader mechanics
??? What do you mean
What is hacky about fabric loader?
I don't know the details of the fabric one, but they don't have to use add-opens ๐
Old FML (from before 1.12) relies heavily on implied URL classloader mechanics, so does the new MCF mechanic that they introduced after the split.
Fabric might not actually depend on those, but last time I checked, they still use URL classloaders
What mechanics though?
So here's what I know:
Ultimately all you need is a child-first class-loader that only loads child-first those classes that are subject to transformation, right?
Where the problem is: how do you know, which classes are subject to transformation
That behaviour is however only implicitly implemented in URL classloaders last I checked
No?
The module classloaders are the only ones that have that explicitly defined
Child-first is the default concept for any plugin classloader ever
Sure, but not by the JDK
Sure it is...
Or are you suggesting they'd introduce a constraint on a class by-name only ever being able to be loaded once per JVM?
That would break so much in this eco-system (Gradle COUGH) that there would be riot in the streets ๐
No, but modules partially enfore that no?
It is not a full enfore sure
But a partial
Well, we don't break that enforcement though
We only ever load classes in that child-first loader
if they have not been loaded before
from the app classloader
That is however not defined behaviour.......
WDYM? It is actually a preqreuisite for us and fabric both
Using child classloaders is an extremely standard Java feature
Personally, I understand btw, that extra care has to be taken to avoid loading the class from the wrong loader, although that is even possible today in dev
No they are not...... That is the whole point. And Thread can override any classloader, if modules is not specified. And force load any class any which way.
No they can't
Sorry you're just so wrong I can't continue discussing this
Yeah you can get the system classloader allright
But that does not matter for the transformation system
He means that the hierarchy has a package->module mapping
Exactly
I think if we ever decided to go back to non-modular classloading
Modules are the only system in the JVM that has the link Package -> Classloader
we would absolutly keep that constraint, by the way
And which are enforced by the JVM and JDK themselves
Except that there is no way to enforce that
because it also trivializes the filtering in the transforming classloader
sure there is
How?
we are loading the jars ourselves
we still index the packages they contain
and we would use that information to set up the filter in the transforming class-loader
to make the decision: does this package need to be transformed: yes / no
Well kinda but we haven't enforced it on a jar to jar basis
Simply by asking the parent of the transforming classloader to load the class
Well, in dev at least, I'd have a solution to prevent it from being bricked ๐
Which is not posisble in Module land
Because that module is not part of the layer and there is no way to get it there
Well you can get to the system CL but it'd be outside of your hierarchy
It also only ever matters in dev anyway
The systemcl can not load any random mod jar
The mods are not on the systems boot layer
You can add them via instrumentation ๐
Seriously?
Yes
It has a method to extend the app classloader classpath post-factum
Sadly, only for non-modular jars
Or rather, only adding to the unnamed module, to be precise
Your argument against modules (which have been declared as the way forward by the JVM developers themselves) is that somebody might add instrumentation?
But it doesn't matter. What I was getting at
In dev, we can actually check this (also using instrumentation)
instrumentation is not ment as a runtime tool
no Orion, my argument against modules is
that the JDK obviously did not have our use case in mind ๐
It clearly did
You cannot seriously think that
Sure i can
when they keep all the shit private that we actually have to use
when they do not offer a way to --add-opens / --add-export / --patch-module
any of the module layers a JVM user might construct
the entire mechanism ignores our use case completely
it does not have parity with non-module-land
But considering we might even be able to work around that
the biggest hurdles are actually still not the JDK itself
but rather the tooling around it, which to this day is atrocious
I have not seen IntelliJ make any moves towards more control over the module CP, sadly
Try to get IJ to add some things to the module-path, and not others when you work on a mod project and start anything ๐
Including runs, parsing and managing of the modulepath
That is the issue!
The JVM does not want you to do it half
The JVM supports that fine
It shoe horns it into the unnamed module
The unnamed module
is a well defined concept
Are you suggesting they'll remove it?
If you really are suggesting non-modular support is somehow going away in the JVM, please provide some actual hard evidence for it. Because then I can start sharpening my pitchforks at work since no one is using modules ๐
The only space where I've actually seen modules be used is in the client-side desktop app space that want to ship their embedded JRE
Well, except IJ ๐
Probably eventually.
To enable legacy access mode you have to supply --illegal-access=permit so that unnamed modules can work fully as if they are in classpath mode, for one
Second is the fact that unnamed modules can not access java.se.ee, at all. Period.
Not anytime soon though
unnamed modules, are a hack/bridge
To make migration easier
But I personally don't think they are intended to exist for ever, no
So. No evidence then...
It is not direct evidence
Everybody would climb the trees if Oracle said: -cp is getting yeeted
Use modules
It's none at all
There's no JEP, no proposal from them, nothing
Yet you keep claiming this
It's really riling me up, hrmpf
The original JEP that introduces this, clearly states that unnamed modules are not the same as the normal legacy classpath
Yes, and?
The legacy classpath is gone
Everything is in some form of module, I'd say
Even if it's the unnamed one
Since when does it do that?
Because I can't find any documentation on any of it
If you don't use the module path
Since the JEP?
No JEP 261 does not specify that
I mean there's a reason for having to --add-opens to unnamed
Yeah if you have a module path
If not there is no mention of it being needed
the runtime itself is always modular
But yeah, here in my experiment some random class loaded via -cp
Honestly, that was always my understanding of it.
The migration is that anything on the -cp turns up in the unnamed module of the app classloader
With some special rules of upgrading it, as far as I remember
It might have alwys been the case unsure
I am pretty sure it was
But that would mean that any JDK<8 lib that performed reflective access was at risk
Well yes, and they were
Since J16 ๐
Because that was the change
Strong enforcement of the actual constraints
Also, by the way. From the JDKs perspective, modules were still a success even if almost no one uses them directly.
Since it did allowe them to compartmentalize the JDK itself.
You should really reconsider if we need it, but I'll continue gritting my teeth at working around this problem with NIO ๐
I really think we do
I am also a modder that thinks we should enforce it better
And make more use of it
In the game layer?
Yeah
we offer asm + mixins + ATs
I don't really think it goes with the spirit
library layer is a bit of a different story
I know I am not in the majority with this opinion: My mod code is not to be touched. I spend a lot of time creating APIs that basically allow any kind of modification of behavior
Poeple mixing in, ASMing, or ATIng my shit should be kicked
But I am not in the majority here
As long as we offer those low-level transforms, any enforcement is kinda moot
No....
We can trivially filter the stuff we offer through transformation based on the module data
Aka: No open package -> No transform
Yes but if you had an event class in your mod
that would have meant it would have needed to be part of the transform
Anyway that is gone
Only to use it internally?
so no point in discussing that further
Yeah
There's @OnlyIn, but we want to be rid of that anyway
I actually think from a startup performance point of view, opting-out of being transformed would be nice lol ๐
but as you said, riots! think of the riots!
My point is this: I think the module system has one major advantage over normal CP:
Its enforcement mechanis, defining what loads what and how. It is enforced by the JVM it self
I mean in non-module-land
Yeah I think we can gain a decent amount of performance there
But indeed
it'd just be a classloader as it is now ๐
I'd have to opt in for people to transform AE2 anyway
Addons do so much shit I can't account for
Hm. I did discover something very interesting.
What I didn't realize is that you can kinda add modules to the system class-loader after the fact too ๐
Now this is some magic. With no module-path given to the JVM, and no reflection/unsafe
This is strictly about deep reflection, it's not related to unnamed modules
Unnamed modules are indeed a well-defined concept, used to load from from the classpath
Anyway it is staggering how much we have to fight the JDK and the tools to get to use modules, while getting very little in return right now. I suppose the API feels cool though
How did you do that?
I mean that's basically the issue, no? Someone thought it would be cool even though it makes our impl a lot more complicated
Unfortunately it doesn't look like a nice cost/benefit analysis was performed
And no instrumentation?
DefineModuleWithBlah and just pass it the system CL in the CL lookup function
That does add the layer to the CL
Yeah
But not the modules which should be good enough for the NIO FS
Only annoyance is that union is in SJH which depends on ASM
Then lets split that off
No it works with the SL if you load the provider class before
Any reason not to do the same for everything?
Resources don't work correctly if the module is not defined in the CL
We could reflect addModule
Then it would work yes
I think that we already do something like that in SJH
But for NIO FS it will work without any add opens or reflection
I am trying to understand the problem you are trying to solve
Except we still need instrumentation to add opens for the hacks SJH is using itself
Arg free startup
No module path
Why?
But keep it working with current APIs
Again Why? Why arg free startup.
Because module path is a blocker for supporting unit tests without hacks piled on hacks in the IDE
LCP is independent of that
You mean: run unit tests with the ide
Instead of run unit tests with gradle through the ide
?
To be specific?
Eclipse does not properly support that at all
Running through Gradle and getting in IDE reporting
Eclipse is indeed and will always indeed be a darkhorse
You just get console output
Does it sync test information at all
But it is possible to do it
IDEA at least looks it up
Not for Gradle no
Can't we configure eclipse to add these vm args?
I keep telling you it's not worth it doing modules but you can't claim I am not trying to make it work ๐ ๐
No
I wonder why we are still supporting an IDE, that is lacking major features with respect to integration to the
We can't even do it for IJ properly since it affects all IDE subprojects
No, eclipse really has no way to interact with at all in this area
IJ does not support our half module setup either
True
Question: When rebuilding the entire module stack ourselves, as far as I know FS lookup is the only major blocker
But those are too broadly scoped too, like the ones in IJ
So far yes
Jist a major annoyance
Yes
I am on the go but will try to get it to work with the modules we have and no reflect
I need to pull ASM into that layer but it has no resources either
And yes at some point we could pull out union into a dep free jar
But I am trying this without actually breaking current compat
I plan to test it with ATM10
Sadly we will never be able to just use the vanilla launchers {classpath} token and pass it to --module-path
it probably contains the MC jar
And that would block the three unobfuscated packages
We just need to write our own classpath block
And not inherit the config from vanilla
Not particularlly clean
But possible
we can't use the net.minecraft package in a module on a child layer though
No he means not using the token hehrhe
No i mean tha
But the launcher token {classpath} is currently the combination of our classpath array entries and the vanilla version launcher profile
Because we explicitly extend it
So it combines the classpaths of both
It will always add a client jar to it afaik
Not tested what happens if you don't define any client artifact in the profile
And that is not something we noticed
Let me see if the internet in the ICE is good enough to dig up that research for you
And the answer is no
Internet is too bad
It's okay, we might want it to out the client there later for neo as a mod
It would save us from.having to look for.it on disk
When I am home I will test that method of extending the system CL module path using reflection into addModule on all JDKs
that doesn't work with unit tests in eclipse, right?
Why not?
"reflection"
Wdym?
no --add-opens
Paired with instrumentation obviously ๐
how will you attach the agent?
that will break once dynamic attach is disabled
I am working under the assumption that we have absolutely no control over jvm args
Guess what happens when java breaks mockito hehehehe
(which is a ridiculous assumption IMO)
people get told to add it as a jvm arg and it's all good?
We really do not right now but IJ will have to budge for the java agent
There is already a Gradle issue for it
IntelliJ doesn't give control over jvm args either? ๐คจ
But we can always add.the agent in the JUnit defaults
Since it does nothing if unused
But adding our argfiles breaks tests in non mdg projects
yeah
I mean we are lucky in this case since mockito relies on it and so many paying gradle and IJ customers use it
There is a grade issue for it too
yes, that's why it's generally a good idea to use what the rest of the ecosystem uses
you gotta love IJ!
You create a module-info.java
Then delete it again
And it still tries to start it in module mode ๐
lol
Fuck me, even after closing / reopening the project
Does it store it in like the module.xml
Or something stupid?
Or where does it pull it from
Cause it did not do that in the past
Oh wait, it did that because of Automatic-Module-Name, what the fuck
If that is present in src/main/resources/MANIFEST.MF
If it's just present in the jar task it don't care
Yeah
But it's completely stupid
It doesn't put anything on the module path
it just invokes the main method using --module
which fails
lol
I wonder what the reasoning is behind that
Knowing that these IDEs are build for profit
There must be something behind that concept
I really don't think modules are used much, so not a lot of testing on that end
The most I've seen it used is JavaFX
And that also didn't work great for me in IJ
So... in Java 9, this works:
Method loadModule = systemCl.getClass().getMethod("loadModule", ModuleReference.class);
return moduleReference -> {
try {
loadModule.invoke(systemCl, moduleReference);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
Only 13 more JDKs to test ๐
(and yeah that needs either an add-opens or Instrumentation to be accessible). Or I suppose, Unsafe? Idk
Unsafe is going away in a few years
Yes instrumentation also does the trick, really
Full method:
private static Consumer<ModuleReference> getModuleAdder(Instrumentation instrumentation, ClassLoader systemCl) throws Exception {
instrumentation.redefineModule(
systemCl.getClass().getModule(),
Set.of(),
Map.of(),
Map.of(
systemCl.getClass().getPackageName(), Set.of(MpExtensionTest.class.getModule())
),
Set.of(),
Map.of()
);
Method loadModule = systemCl.getClass().getMethod("loadModule", ModuleReference.class);
return moduleReference -> {
try {
loadModule.invoke(systemCl, moduleReference);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
}
My JavaAgent:
public class MpAgent {
static Instrumentation instrumentation;
public static void premain(String arguments, Instrumentation instrumentation) {
MpAgent.instrumentation = instrumentation;
}
public static void agentmain(String arguments, Instrumentation instrumentation) {
MpAgent.instrumentation = instrumentation;
}
}
I don't actually need agentmain per se, but eh
Then I do this in my main:
Instrumentation instrumentation = MpAgent.instrumentation;
if (instrumentation == null) {
System.err.println("No agent present. Using dynamic attach.");
instrumentation = ByteBuddyAgent.install();
}
Hehehehe, setup-java on GH can't get java 9
Okay, I just used gradle toolchains instead.
I am happy to report that at least this reflected loadModule method works from java 9 to 22
made https://github.com/neoforged/NeoForge/pull/1207 and will special case that placeholder in the meantime
Also this seems really outdated:
@glad cobalt Found yet another reason why we're fucked with modules ๐
?
Vanilla Minecraft don't care that LWJGL native modules all have the same module name
Vanilla Minecraft also does not download all the native modules
since it's a legitimate configuration to just put ARM64, x64 and x86 native jars on the CP and launch
It has actuall selectors for that
No
It doesn'T
I assumed that until 5 minutes ago too
But there is only an OS filter, not an architecture filter
Does it have them for all 3 architectures then?
Yes
How has this worked then in the past?
And BSL implicitly takes care of that by auto-merging modules of the same name
Yeah
Which I found quite surprising
Since putting those three on the module-path instead just crashes
Cause putting all those three natives on the CP seems like a majorly bad idea
It's not, really
I think LWJGL here is.... weird
Because their code explicitly handles this and the packaged natives are in different subpackages
The LWJGL team clearly says: Don't do thgat
in JPMS ๐
Put one and only one on it
They also say that you should pre extract it
And not put it in there at all
Where are you finding that info ๐
The first comment of the lwjgl guy on that issue you linked
His account seems at least very active
And he links to a bunch of documents regarding this
He's only commenting on JPMS
Oh you mean the general comments around pre-extracting it
Yeah
yes, understandable. But it still is a supported configuration to have them all on the CP, while it's not for JPMS
Which kinda puts us in a bad spot
I mean, what's one more hack 
Their jar manifests have an attribute I'll handle specifically. LWJGL-Platform: macos/x64
I am pre-indexing manifests in the startup code and caching the results so this will be very fast
And I think filtering out unneeded natives this way is better than trying pathname patterns..
Yes 100%
Although we should report this to Mojang
Since the lwjgl guy is right
Mojang should not be depending on this mechanism in production
Hey but real-talk
They have millions of users
If this didn't work
They'd probably know that better than the LWJGL guy ๐
I'd almost bet they are the heaviest LWJGL users
I'll still put one stone into the "why I don't like us using modules bucket" ๐
And grumpingly fix it anyway
"grumpingly".... is that even a word
Alright:
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-freetype\3.3.3\82028265a0a2ff33523ca75137ada7dc176e5210\lwjgl-freetype-3.3.3-natives-windows-arm64.jar due to incompatible architecture
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-freetype\3.3.3\15a8c1de7f51d07a92eae7ce1222557073a0c0c3\lwjgl-freetype-3.3.3-natives-windows-x86.jar due to incompatible architecture
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-glfw\3.3.3\f27018dc74f6289574502b46cce55d52817554e2\lwjgl-glfw-3.3.3-natives-windows-arm64.jar due to incompatible architecture
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-glfw\3.3.3\32334f3fd5270a59bad9939a93115acb6de36dcf\lwjgl-glfw-3.3.3-natives-windows-x86.jar due to incompatible architecture
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-jemalloc\3.3.3\ba1f3fed0ee4be0217eaa41c5bbfb4b9b1383c33\lwjgl-jemalloc-3.3.3-natives-windows-arm64.jar due to incompatible architecture
[162] INF Skipping C:\Gradle Home\caches\modules-2\files-2.1\org.lwjgl\lwjgl-jemalloc\3.3.3\f6063b6e0f23be483c5c88d84ce51b39dc69126c\lwjgl-jemalloc-3.3.3-natives-windows-x86.jar due to incompatible architecture
Very nice
Yeah that makes the most sense
I personally feel that is also a better solution, regardless of CP or MP
There should simply be only one
And not many
Using the same JPMS name for different artifacts does not seem like a good practice
?
Ah yeah
It is n't
But also in this case it should not matter
Like why would you put the different modules on the CP if the architecture of the system is nknown
So having JPMS block you from doing that
Seems reasonable
I bet almost no one uses LWJGL modular
You noted that the dude said to pre extract the natives, right?
That was though in a general context
Unrelated to JPMS
They have hard deps from their platform independent JPMS modules on the native one
So you still have to include a native JPMS module on module path even if you had pre extracted
Yeah true
But again
I think having more then one instance of a different none-compatible arch on the cp is not a great idea
It likely won't hurt
Since it will do a scan
And filter out
But you still can run into issues
It doesn't scan and filter it just does getResource for the file it needs
It works perfectly fine
JNA does the same and just ships all arch's in its main file
His point about extraction of DLLs to temp dirs is correct however. But everyone does it
Note that the different arch jars do not have overlapping resource paths. They are all distinct and unique
Still
Yeah I understand that it is then unique at runtime
But still feels wonky
From an architectural perspective
It's platform independent
So I don't see the problem hehe
I bet their.natives don't even unpack.when used with jlink heh
Real talk: vanilla doesn't run with everything on the module path, right?
I.e. if you just -p everything
Vanilla doesn't even use the module path
Of course it doesn't. But I'm saying that it's not even a vaguely supported use case. It straight up doesn't work.
Nope
So remind me why we are wasting our time with modules again? ๐
When clearly Minecraft itself doesn't care
TBH modules provide a lot more benefits to the architecture
Especially if eventually you give us module-infos
Most of the Java community doesn't care about these supposed benefits. Which places us at odds with Gradle, IntelliJ, Minecraft, etc
The architectural benefits are largely outweighed by the downsides of having to work around most of the ecosystem
Most of the Java community still bothers supporting Java 8
With that logic, we should use that version
And TBH, IJ supports modules just fine; so does Gradle
Gradle and IntelliJ have full support for Java 21 yet shit support for modules
All applications I've been writing in Java ever since Java 9 came out were modular
And using IntelliJ and Gradle
I have to say, I have not experienced any issues whatsoever
Except for when I had to work around the system for an --add-exports
That's cool. If you write a simple modular app it will work. But anything nontrivial requires unofficial Gradle plugins and/or copious amounts of hacks
Then maybe have you considered that the problem here isn't modules, but the vast amount of non-standard stuff you gotta do to set up Minecraft?
We currently have 0
Modules are harder to set up than plain CP applications. We already have complexity coming in from Minecraft. We do not need additional self-imposed complexity from JPMS
I mean that is good for you, but doesn't really apply to modding a game that isn't modular
Let's go back to jar hell
We are in jar hell
All the groundwork for FML as far as I remember was for us to eventually be able to use proper module-infos
There is no no groundwork in FML
Whatever holds the architecture
Also who is "us" in that sentiment. Modders? Or us, the platform?
both
As far as I remember, cpw had mentioned intentions of getting module-info working for mods
If you are suggesting we should try to convert every modder to use a modular setup even at compile time, I think you underestimate the complexity significantly
If an individual modder wants to go module-info, they lose the ability to use any mod-apis of mods that aren't using module-info
And for what? "Doing cool stuff"?
People are already failing even at the basics of buildscripts
It's clear I'm in the minority here
In droves
So there's no point in defending my views TBH
Just to be sure my point is not misunderstood
I'd love for this stuff to be viable
But when weighing it E2E, I don't see the benefits outweighing the downsides
And you know, I've already wasted countless hours on trying even though I've forseen that it's a pain in the ass ๐
IMO it would be nice to have compile time modularity, but I don't see the point in runtime modularity enforcement
mostly because of how annoying it would make it to try to make addons of a mod without addon API
Well yes, it would be nice
Do you have any clue what needs to go into the Gradle setup to have compile time modularity?
Well it's an entirely separate topic ๐
Also how will that addon even compile if there is compile time modularity
Well if you compile your mod modular, I don't think you can access another mod that isn't modular
You might, if they declared an Automatic-Module-Name and that actually matches their mod-id, but no guarantees
But you're already dead in the water since the generated minecraft artifact doesnt have a module descriptor
and whats even worse, it won't work at all since it's a merged jar
We can relatively trivially create a module-info.java for minecraft
I mean if you completely special case it and then ignore the resulting mods module-info at runtime
sure
otherwise. how exactly are you planning to deal with runtime having two modules (minecraft + neoforge)
while compile-time only has one jar
That is an architectural decision that we have right now. We could trivially make that two modules
You already made the biggest step in NFRT, where you only recompile patched MC sources.
So you could trivially, create a minecraft jar and a neoforge jar
And actually have two modules
If we didn't actually do modular runtime, then yes, wouldn't matter. You could still do that at compile-time to get the benefits of better checking there
Modular runtime is of interest though
Not really though, anyone can trivially sidestep any checks anyway
By that argument again we should be back to java 8
If it's just a honor system
Then enforce it
You can also just do the compile-time checks




