Skip to content

Generated IntelliJ run configuration has no resolvable module when a subproject name contains '.' (e.g. Stonecutter nodes) #353

Description

@Narcissus-tazetta

Title: Generated IntelliJ run configuration has no resolvable module when a subproject name contains . (e.g. Stonecutter nodes) → run falls back to whole-project classpath


Environment

ModDevGradle 2.0.144 (same code on main as of today)
Gradle 9.7.1
IntelliJ IDEA 2026.2 (IU-262.9437.185)
Multi-version tooling Stonecutter 0.9.7
Project shape root project + version subprojects :1.21.1-neoforge (moddev) and :1.21.1-fabric (fabric-loom), sharing one src/

What happens

MDG generates the IntelliJ run configuration 1.21.1-neoforge - Client (Application type, net.neoforged.devlaunch.Main). In .idea/workspace.xml it ends up with no <module> element — the IDE shows "No module" in Edit Configurations.

Because the run has no module, IntelliJ builds -classpath from the whole project. In a multi-loader Stonecutter setup that pulls in the other node's Minecraft too, and NeoForge dev launch aborts before the window opens:

net.neoforged.fml.ModLoadingException: Loading errors encountered:
  - Found multiple copies of net/minecraft/server/MinecraftServer.class on the classpath:
    [.../versions/1.21.1-neoforge/build/moddev/artifacts/neoforge-21.1.228-merged.jar,
     .../.gradle/loom-cache/minecraftMaven/net/minecraft/minecraft-merged-.../...-1.21.1-loom.mappings....jar]
	at fml_loader/net.neoforged.fml.util.DevEnvUtils.findFileSystemRootOfFileOnClasspath(DevEnvUtils.java:78)
	at fml_loader/net.neoforged.fml.loading.targets.CommonDevLaunchHandler.collectAdditionalModFileLocators(CommonDevLaunchHandler.java:59)

Running :1.21.1-neoforge:runClient as a Gradle task works fine — only the generated IDE Application config is affected.

Root cause

IntelliJIntegration.getIntellijModuleName:

moduleName.append(project.getRootProject().getName().replace(" ", "_"));   // "xaeronav"
if (project != project.getRootProject()) {
    moduleName.append(project.getPath().replaceAll(":", "."));             // ".1.21.1-neoforge"
}
moduleName.append(".").append(sourceSet.getName());                       // ".main"
// => "xaeronav.1.21.1-neoforge.main"

The actual IntelliJ module for that subproject is xaeronav.1_21_1-neoforge.main — IntelliJ's Gradle import replaces . inside each path segment with _ (1.21.1-neoforge1_21_1-neoforge). MDG only maps :. and does not sanitize the segments, so setModuleName(...) gets a name that doesn't exist, and the generated config ends up module-less.

The code comment already flags this:

// The actual IDEA logic is more complicated, but this should cover the majority of use cases.

Stonecutter node names are <mcVersion>-<loader>, so they always contain dots — every Stonecutter + MDG project hits this.

Why it's easy to miss / hard to recover

  • Single-loader projects don't notice: "whole project classpath" == the only loader, so it just works.
  • disableIdeRun() stops MDG re-adding it, but MDG never removes the already-written config, and IntelliJ doesn't either — it has to be deleted from .idea/workspace.xml by hand once.

Suggested fix

Sanitize each segment of project.getPath() the way IntelliJ does (._, matching PathUtilRt / GradleProjectResolverUtil), or derive the module name from the idea model / ModuleRef instead of reconstructing the string.

Workaround (for anyone searching)

neoForge {
    runs {
        create("client") {
            client()
            disableIdeRun()
        }
    }
}

then delete the stale <node> - Client Application config from .idea/workspace.xml, and use a Gradle run config that runs :<node>:runClient.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions