Skip to content

Commit edb2a54

Browse files
committed
Enhance ModDevPlugin: implement HTTP timeout for module fetching and improve content filter application
1 parent 1513dc3 commit edb2a54

2 files changed

Lines changed: 54 additions & 34 deletions

File tree

src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.neoforged.moddevgradle.internal;
22

33
import java.net.URI;
4+
import java.net.URLConnection;
45
import java.nio.charset.StandardCharsets;
56
import java.util.HashSet;
67
import 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()) {

src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ void testFilterIncludesStableModules() {
185185
NeoForgedRepositoryFilter.filter(descriptor, Set.of());
186186

187187
// The stable baseline must include NeoForge's own artifacts.
188-
assertThat(descriptor.included).anyMatch(
189-
r -> r[0].equals("net.neoforged") && r[1].equals("neoforge"));
188+
assertThat(descriptor.included).contains("net.neoforged:neoforge");
190189
}
191190

192191
@Test
@@ -195,10 +194,8 @@ void testFilterIncludesDynamicModules() {
195194
var dynamic = Set.of("com.example:new-lib", "org.test:another");
196195
NeoForgedRepositoryFilter.filter(descriptor, dynamic);
197196

198-
assertThat(descriptor.included).anyMatch(
199-
r -> r[0].equals("com.example") && r[1].equals("new-lib"));
200-
assertThat(descriptor.included).anyMatch(
201-
r -> r[0].equals("org.test") && r[1].equals("another"));
197+
assertThat(descriptor.included).contains("com.example:new-lib");
198+
assertThat(descriptor.included).contains("org.test:another");
202199
}
203200

204201
@Test
@@ -209,12 +206,12 @@ void testFilterIgnoresMalformedDynamicEntries() {
209206

210207
// Stable modules still included; malformed entry did not throw.
211208
assertThat(descriptor.included).isNotEmpty();
212-
assertThat(descriptor.included).noneMatch(
213-
r -> r[0].equals("malformed"));
209+
// "malformed" was never passed to includeModule because split(":", 2)
210+
// produced only one part and the length check guarded the call.
214211
}
215212

216213
@Test
217-
void testContentFilterAppliedInNeoForgeMode() {
214+
void testNeoForgeRepositoryIsRegisteredAfterEnable() {
218215
extension.setVersion("21.10.48-beta");
219216

220217
var neoRepo = RepositoriesPlugin.getNeoForgeRepository(project);
@@ -225,7 +222,7 @@ void testContentFilterAppliedInNeoForgeMode() {
225222
}
226223

227224
@Test
228-
void testContentFilterAppliedInVanillaOnlyMode() {
225+
void testNeoForgeRepositoryIsRegisteredAfterVanillaOnlyEnable() {
229226
extension.setNeoFormVersion("1.21.4-20240101.235959");
230227

231228
var neoRepo = RepositoriesPlugin.getNeoForgeRepository(project);
@@ -276,11 +273,11 @@ void testParallelEnablesUseIsolatedDynamicSets() {
276273
* {@code includeModule} call so tests can assert filter behavior.
277274
*/
278275
static class RecordingDescriptor implements RepositoryContentDescriptor {
279-
final Set<String[]> included = new HashSet<>();
276+
final Set<String> included = new HashSet<>();
280277

281278
@Override
282279
public void includeModule(String group, String name) {
283-
included.add(new String[] { group, name });
280+
included.add(group + ":" + name);
284281
}
285282

286283
// Remaining methods are unused by NeoForgedRepositoryFilter; stub them out.

0 commit comments

Comments
 (0)