11package net .neoforged .moddevgradle .internal ;
22
33import java .net .URI ;
4+ import java .net .URLConnection ;
45import java .nio .charset .StandardCharsets ;
56import java .util .HashSet ;
67import java .util .Set ;
@@ -124,40 +125,45 @@ public void enable(
124125 extension .getRuns ());
125126 }
126127
128+ // 10-second timeout for the .module HTTP requests (both connect and read).
129+ private static final int MODULE_FETCH_TIMEOUT_MS = 10_000 ;
130+
127131 /**
128132 * Discovers additional modules from the Gradle Module Metadata of the selected
129133 * NeoForge version (if any) and the NeoForm Runtime, then applies the content
130134 * filter to the NeoForge repository.
131135 * <p>
136+ * When the NeoForged repository was configured at the settings level there is
137+ * no project-scoped repository to apply the filter to — this method returns
138+ * early without touching the network.
139+ * <p>
132140 * Dynamic modules are collected in a local set and passed directly to
133141 * {@link NeoForgedRepositoryFilter#filter} — no static mutable state is shared
134142 * across projects, which is safe with parallel project configuration.
135- * <p>
136- * NFRT metadata is always fetched regardless of whether a NeoForge version was
137- * selected, since NFRT can introduce new direct dependencies over time even in
138- * vanilla-only mode.
139143 */
140144 private static void populateNeoForgeRepositoryFilter (Project project ,
141145 @ Nullable String neoForgeVersion ) {
146+ // When the repository is configured at the settings level there is no
147+ // project-scoped repository to install the filter on; the settings-level
148+ // filter is already in place and cannot be augmented per-project.
149+ if (RepositoriesPlugin .getNeoForgeRepository (project ) == null ) {
150+ return ;
151+ }
152+
142153 var dynamicModules = new HashSet <String >();
143- var depPattern = Pattern .compile ("\" group\" :\\ s*\" ([^\" ]+)\" ,\\ s*\" module\" :\\ s*\" ([^\" ]+)\" " );
144154
145155 if (neoForgeVersion != null ) {
146- // Discover game library modules from the NeoForge artifact metadata.
147- fetchModuleDependencies ("net/neoforged/neoforge/" + neoForgeVersion
156+ var depPattern = Pattern . compile ( " \" group \" : \\ s* \" ([^ \" ]+) \" , \\ s* \" module \" : \\ s* \" ([^ \" ]+) \" " );
157+ fetchModuleDependencies (project , "net/neoforged/neoforge/" + neoForgeVersion
148158 + "/neoforge-" + neoForgeVersion + ".module" , depPattern , dynamicModules );
149- }
150-
151- // Always discover build tool modules from the NeoForm Runtime metadata.
152- // NFRT ships external tools (DiffPatch, AutoRenamingTool, etc.) whose
153- // transitive dependencies are rehosted on the NeoForged Maven and may
154- // change across NFRT releases.
155- try {
156- var nfrtVersion = NeoFormRuntimeExtension .getVersion (project );
157- fetchModuleDependencies ("net/neoforged/neoform-runtime/" + nfrtVersion
158- + "/neoform-runtime-" + nfrtVersion + ".module" , depPattern , dynamicModules );
159- } catch (Exception e ) {
160- LOG .warn ("Failed to resolve NFRT version for dynamic filter discovery: {}" , e .getMessage ());
159+ // NFRT metadata fetch uses the same pattern.
160+ try {
161+ var nfrtVersion = NeoFormRuntimeExtension .getVersion (project );
162+ fetchModuleDependencies (project , "net/neoforged/neoform-runtime/" + nfrtVersion
163+ + "/neoform-runtime-" + nfrtVersion + ".module" , depPattern , dynamicModules );
164+ } catch (Exception e ) {
165+ LOG .warn ("Failed to resolve NFRT version for dynamic filter discovery: {}" , e .getMessage ());
166+ }
161167 }
162168
163169 // Apply the content filter now — before any dependency resolution uses
@@ -166,13 +172,30 @@ private static void populateNeoForgeRepositoryFilter(Project project,
166172 RepositoriesPlugin .applyContentFilter (project , dynamicModules );
167173 }
168174
169- private static void fetchModuleDependencies (String path , Pattern depPattern ,
170- Set <String > dynamicModules ) {
175+ /**
176+ * Downloads a single {@code .module} file from the NeoForged Maven and extracts
177+ * {@code group:module} pairs from it.
178+ * <p>
179+ * Uses {@link URLConnection} directly rather than Gradle dependency resolution
180+ * because Gradle locks the repository content descriptor on first use, which
181+ * would prevent us from installing the content filter afterward. Respects
182+ * {@code --offline} and applies explicit connect/read timeouts.
183+ */
184+ private static void fetchModuleDependencies (Project project , String path ,
185+ Pattern depPattern , Set <String > dynamicModules ) {
186+ if (project .getGradle ().getStartParameter ().isOffline ()) {
187+ return ;
188+ }
189+
171190 try {
172191 var moduleUrl = URI .create (
173192 "https://maven.neoforged.net/releases/" + path ).toURL ();
174193
175- try (var stream = moduleUrl .openStream ()) {
194+ var connection = moduleUrl .openConnection ();
195+ connection .setConnectTimeout (MODULE_FETCH_TIMEOUT_MS );
196+ connection .setReadTimeout (MODULE_FETCH_TIMEOUT_MS );
197+
198+ try (var stream = connection .getInputStream ()) {
176199 var content = new String (stream .readAllBytes (), StandardCharsets .UTF_8 );
177200 var matcher = depPattern .matcher (content );
178201 while (matcher .find ()) {
0 commit comments