diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 97f0e7f5..6d8aea1e 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -1,17 +1,25 @@ package net.neoforged.moddevgradle.internal; +import java.net.URI; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin; import net.neoforged.moddevgradle.dsl.ModDevExtension; import net.neoforged.moddevgradle.dsl.ModdingVersionSettings; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; import net.neoforged.moddevgradle.internal.jarjar.JarJarPlugin; import net.neoforged.moddevgradle.internal.utils.VersionCapabilitiesInternal; +import net.neoforged.nfrtgradle.NeoFormRuntimeExtension; import net.neoforged.nfrtgradle.NeoFormRuntimePlugin; import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.plugins.JavaLibraryPlugin; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,9 +83,19 @@ public void enable( var configurations = project.getConfigurations(); - var dependencies = neoForge != null ? ModdingDependencies.create(neoForge, neoForgeNotation, neoForm, neoFormNotation, versionCapabilities) + var dependencies = neoForge != null ? ModdingDependencies.create( + neoForge, + neoForgeNotation, + neoForm, + neoFormNotation, + versionCapabilities) : ModdingDependencies.createVanillaOnly(neoForm, neoFormNotation); + // Always apply at least the stable baseline filter to the NeoForged + // repository. When a NeoForge version is selected we also discover + // additional game-library modules from its metadata. + populateNeoForgeRepositoryFilter(project, neoForgeVersion); + ArtifactNamingStrategy artifactNamingStrategy; // It's helpful to be able to differentiate the Vanilla jar and the NeoForge jar in classic multiloader setups. if (neoForge == null) { @@ -106,4 +124,93 @@ public void enable( artifacts, extension.getRuns()); } + + // 10-second timeout for the .module HTTP requests (both connect and read). + private static final int MODULE_FETCH_TIMEOUT_MS = 10_000; + + /** + * Discovers additional modules from the Gradle Module Metadata of the selected + * NeoForge version (if any) and the NeoForm Runtime, then applies the content + * filter to the NeoForge repository. + *
+ * When the NeoForged repository was configured at the settings level there is + * no project-scoped repository to apply the filter to — this method returns + * early without touching the network. + *
+ * Dynamic modules are collected in a local set and passed directly to
+ * {@link NeoForgedRepositoryFilter#filter} — no static mutable state is shared
+ * across projects, which is safe with parallel project configuration.
+ */
+ private static void populateNeoForgeRepositoryFilter(Project project,
+ @Nullable String neoForgeVersion) {
+ // When the repository is configured at the settings level there is no
+ // project-scoped repository to install the filter on; the settings-level
+ // filter is already in place and cannot be augmented per-project.
+ if (RepositoriesPlugin.getNeoForgeRepository(project) == null) {
+ return;
+ }
+
+ var dynamicModules = new HashSet
+ * Uses {@link URLConnection} directly rather than Gradle dependency resolution
+ * because Gradle locks the repository content descriptor on first use, which
+ * would prevent us from installing the content filter afterward. Respects
+ * {@code --offline} and applies explicit connect/read timeouts.
+ */
+ private static void fetchModuleDependencies(Project project, String path,
+ Pattern depPattern, Set
+ * The NeoForged Maven mirrors many Maven Central artifacts. Without a content filter,
+ * Gradle could resolve arbitrary third-party artifacts from it.
+ *
+ * This filter is built from two layers:
+ *
+ * Must be called before any dependency resolution uses this repository —
+ * Gradle locks the content descriptor on first use.
+ *
+ * When the repository was configured at the settings level this is a safe
+ * no-op — the content filter is already installed from {@code apply()}.
+ *
+ * @param dynamicModules set of {@code "group:module"} strings discovered at
+ * configuration time; empty set when none were discovered
+ */
+ static void applyContentFilter(Project project, Set
+ *
+ */
+public class NeoForgedRepositoryFilter {
+ /**
+ * All modules currently known to be hosted on the NeoForged Maven.
+ * When the selected NeoForge version pulls in a library that is not yet listed here,
+ * the dynamic discovery path in {@link net.neoforged.moddevgradle.internal.ModDevPlugin}
+ * adds it at configuration time.
+ */
+ private static final String[][] STABLE_MODULES = {
+ // --- net.neoforged sub-groups ---
+ { "net.neoforged.accesstransformers", "at-modlauncher" },
+ { "net.neoforged.accesstransformers", "at-parser" },
+ { "net.neoforged.fancymodloader", "earlydisplay" },
+ { "net.neoforged.fancymodloader", "junit-fml" },
+ { "net.neoforged.fancymodloader", "loader" },
+ { "net.neoforged.installertools", "binarypatcher" },
+ { "net.neoforged.installertools", "cli-utils" },
+ { "net.neoforged.installertools", "installertools" },
+ { "net.neoforged.javadoctor", "gson-io" },
+ { "net.neoforged.javadoctor", "spec" },
+ { "net.neoforged.jst", "jst-cli-bundle" },
+ // --- main net.neoforged group ---
+ { "net.neoforged", "AutoRenamingTool" },
+ { "net.neoforged", "DevLaunch" },
+ { "net.neoforged", "JarJarFileSystems" },
+ { "net.neoforged", "JarJarMetadata" },
+ { "net.neoforged", "JarJarSelector" },
+ { "net.neoforged", "accesstransformers" },
+ { "net.neoforged", "bus" },
+ { "net.neoforged", "coremods" },
+ { "net.neoforged", "mergetool" },
+ { "net.neoforged", "minecraft-dependencies" },
+ { "net.neoforged", "neoforge" },
+ { "net.neoforged", "neoform" },
+ { "net.neoforged", "neoform-runtime" },
+ { "net.neoforged", "srgutils" },
+ { "net.neoforged", "testframework" },
+ // --- modding toolchain ---
+ { "cpw.mods", "bootstraplauncher" },
+ { "cpw.mods", "modlauncher" },
+ { "cpw.mods", "securejarhandler" },
+ { "net.minecraftforge", "mergetool" },
+ { "net.minecraftforge", "srgutils" },
+ { "net.minecrell", "terminalconsoleappender" },
+ { "net.covers1624", "DevLogin" },
+ { "net.covers1624", "Quack" },
+ { "io.codechicken", "DiffPatch" },
+ { "io.github.llamalad7", "mixinextras-neoforge" },
+ { "net.fabricmc", "sponge-mixin" },
+ { "de.siegmar", "fastcsv" },
+ { "com.machinezoo.noexception", "noexception" },
+ { "net.jodah", "typetools" },
+ { "com.nothome", "javaxdelta" },
+ { "trove", "trove" },
+ // --- NeoForge-maintained library forks ---
+ { "com.electronwill.night-config", "core" },
+ { "com.electronwill.night-config", "toml" },
+ // --- Transitive dependencies of NFRT external tools ---
+ { "it.unimi.dsi", "fastutil" },
+ { "org.apache.commons", "commons-compress" },
+ { "org.apache.commons", "commons-lang3" },
+ { "org.apache.commons", "commons-parent" },
+ { "org.tukaani", "xz" },
+ { "org.ow2.asm", "asm" },
+ { "org.ow2.asm", "asm-analysis" },
+ { "org.ow2.asm", "asm-commons" },
+ { "org.ow2.asm", "asm-tree" },
+ { "org.ow2.asm", "asm-util" },
+ { "org.ow2", "ow2" },
+ { "org.lz4", "lz4-java" },
+ { "org.jcraft", "jorbis" },
+ // --- Game libraries & their transitive dependencies ---
+ // Transitive dependencies of common game libraries are listed explicitly
+ // because the dynamic discovery only sees direct dependencies (a single
+ // module-metadata download), not the full transitive tree.
+ { "ca.weblite", "java-objc-bridge" },
+ { "com.fasterxml.jackson.core", "jackson-annotations" },
+ { "com.fasterxml.jackson.core", "jackson-core" },
+ { "com.fasterxml.jackson", "jackson-base" },
+ { "com.fasterxml.jackson", "jackson-bom" },
+ { "com.fasterxml.jackson", "jackson-parent" },
+ { "com.fasterxml", "oss-parent" },
+ { "com.github.oshi", "oshi-core" },
+ { "com.github.oshi", "oshi-parent" },
+ { "com.google.code.findbugs", "jsr305" },
+ { "com.google.code.gson", "gson" },
+ { "com.google.code.gson", "gson-parent" },
+ { "com.google.errorprone", "error_prone_annotations" },
+ { "com.google.errorprone", "error_prone_parent" },
+ { "com.google.guava", "failureaccess" },
+ { "com.google.guava", "guava" },
+ { "com.google.guava", "guava-parent" },
+ { "com.google.guava", "listenablefuture" },
+ { "com.google.j2objc", "j2objc-annotations" },
+ { "com.ibm.icu", "icu4j" },
+ { "com.mojang", "authlib" },
+ { "com.mojang", "blocklist" },
+ { "com.mojang", "brigadier" },
+ { "com.mojang", "datafixerupper" },
+ { "com.mojang", "logging" },
+ { "com.mojang", "patchy" },
+ { "com.mojang", "text2speech" },
+ { "commons-codec", "commons-codec" },
+ { "commons-io", "commons-io" },
+ { "commons-logging", "commons-logging" },
+ { "io.fabric8", "kubernetes-client-bom" },
+ { "io.netty", "netty-bom" },
+ { "io.netty", "netty-buffer" },
+ { "io.netty", "netty-codec" },
+ { "io.netty", "netty-common" },
+ { "io.netty", "netty-handler" },
+ { "io.netty", "netty-parent" },
+ { "io.netty", "netty-resolver" },
+ { "io.netty", "netty-transport" },
+ { "io.netty", "netty-transport-classes-epoll" },
+ { "io.netty", "netty-transport-native-unix-common" },
+ { "jakarta.platform", "jakarta.jakartaee-bom" },
+ { "jakarta.platform", "jakartaee-api-parent" },
+ { "net.java.dev.jna", "jna" },
+ { "net.java.dev.jna", "jna-platform" },
+ { "org.antlr", "antlr4-master" },
+ { "org.antlr", "antlr4-runtime" },
+ { "org.apache.groovy", "groovy-bom" },
+ { "org.apache.httpcomponents", "httpclient" },
+ { "org.apache.httpcomponents", "httpcomponents-client" },
+ { "org.apache.httpcomponents", "httpcomponents-core" },
+ { "org.apache.httpcomponents", "httpcomponents-parent" },
+ { "org.apache.httpcomponents", "httpcore" },
+ { "org.apache.logging.log4j", "log4j" },
+ { "org.apache.logging.log4j", "log4j-api" },
+ { "org.apache.logging.log4j", "log4j-bom" },
+ { "org.apache.logging.log4j", "log4j-core" },
+ { "org.apache.logging.log4j", "log4j-slf4j2-impl" },
+ { "org.apache.logging", "logging-parent" },
+ { "org.apache.maven", "maven" },
+ { "org.apache.maven", "maven-artifact" },
+ { "org.apache.maven", "maven-parent" },
+ { "org.apache", "apache" },
+ { "org.apiguardian", "apiguardian-api" },
+ { "org.checkerframework", "checker-qual" },
+ { "org.codehaus.groovy", "groovy-bom" },
+ { "org.codehaus.plexus", "plexus" },
+ { "org.codehaus.plexus", "plexus-utils" },
+ { "org.commonmark", "commonmark" },
+ { "org.commonmark", "commonmark-parent" },
+ { "org.eclipse.ee4j", "project" },
+ { "org.eclipse.jetty", "jetty-bom" },
+ { "org.jetbrains", "annotations" },
+ { "org.jline", "jline-parent" },
+ { "org.jline", "jline-reader" },
+ { "org.jline", "jline-terminal" },
+ { "org.joml", "joml" },
+ { "org.jspecify", "jspecify" },
+ { "org.junit.jupiter", "junit-jupiter" },
+ { "org.junit.jupiter", "junit-jupiter-api" },
+ { "org.junit.jupiter", "junit-jupiter-engine" },
+ { "org.junit.jupiter", "junit-jupiter-params" },
+ { "org.junit.platform", "junit-platform-commons" },
+ { "org.junit.platform", "junit-platform-engine" },
+ { "org.junit.platform", "junit-platform-launcher" },
+ { "org.junit", "junit-bom" },
+ { "org.lwjgl", "lwjgl" },
+ { "org.lwjgl", "lwjgl-bom" },
+ { "org.lwjgl", "lwjgl-freetype" },
+ { "org.lwjgl", "lwjgl-glfw" },
+ { "org.lwjgl", "lwjgl-jemalloc" },
+ { "org.lwjgl", "lwjgl-openal" },
+ { "org.lwjgl", "lwjgl-opengl" },
+ { "org.lwjgl", "lwjgl-stb" },
+ { "org.lwjgl", "lwjgl-tinyfd" },
+ { "org.mockito", "mockito-bom" },
+ { "org.opentest4j", "opentest4j" },
+ { "org.slf4j", "slf4j-api" },
+ { "org.slf4j", "slf4j-bom" },
+ { "org.slf4j", "slf4j-parent" },
+ { "org.sonatype.oss", "oss-parent" },
+ { "org.springframework", "spring-framework-bom" },
+ // --- Other NeoForge-hosted tooling ---
+ { "org.parchmentmc.data", "parchment-1.21" },
+ { "org.vineflower", "vineflower" },
+ { "org.openjdk.nashorn", "nashorn-core" },
+ { "net.sf.jopt-simple", "jopt-simple" },
+ };
+
+ /**
+ * Applies the full filter to the given repository content descriptor: stable
+ * known modules plus caller-supplied dynamically discovered modules.
+ *
+ * @param descriptor the repository content descriptor to configure
+ * @param dynamicModules set of {@code "group:module"} strings discovered at
+ * configuration time; may be empty but never null
+ */
+ public static void filter(RepositoryContentDescriptor descriptor, Set
- *
*/
public class RepositoriesPlugin implements Plugin