From 2378c04dd8bb4bacb0392bae4aa1d10904c5718b Mon Sep 17 00:00:00 2001 From: mezz Date: Wed, 8 Jul 2026 01:48:48 +0900 Subject: [PATCH 1/2] Add vanilla run support ## Context JEI client tests need to compare behavior across: - NeoForge client with JEI + NeoForge server with JEI - NeoForge client with JEI + NeoForge server without JEI - NeoForge client with JEI + vanilla server without JEI - vanilla client without JEI + NeoForge servers with JEI Before this change, a NeoForge-enabled project could create client or server runs with no loaded mods, but those runs still used the NeoForge userdev runtime. For true vanilla fixtures, JEI needed separate vanilla-mode projects or custom wiring. ## Changes This change adds two run helpers: ```groovy vanillaClient() vanillaServer() ``` These helpers: - use the vanilla run templates - clear loaded project mods by default - use a dedicated vanilla runtime classpath - avoid inheriting the NeoForge userdev runtime in NeoForge-enabled projects IDE integrations also avoid using the ordinary application classpath for dedicated vanilla runs: - IntelliJ and Eclipse generate Gradle-backed run configurations - VS Code uses the generated launch classpath argfile and disables the default Java extension classpath ## Why This lets a project declare vanilla client or server fixtures alongside its NeoForge runs. For JEI, that means vanilla client and server fixtures can live in the NeoForge project next to the other client-test fixtures, instead of needing separate vanilla-mode subprojects only for those runs. ## Testing - `./gradlew build --no-daemon` - Unit coverage for: - the vanilla runtime dependency model - empty loaded-mod defaults - IntelliJ/Eclipse/VS Code vanilla run IDE wiring - Functional coverage for: - evaluated vanilla run classpath wiring - vanilla run task graph scheduling with `runVanillaClient --dry-run` - preserved dev-launch wiring for ordinary runs that select a source set --- README.md | 2 + .../neoforged/moddevgradle/dsl/RunModel.java | 25 +++ .../internal/EclipseIntegration.java | 33 ++- .../moddevgradle/internal/IdeIntegration.java | 2 +- .../internal/IntelliJIntegration.java | 40 +++- .../internal/ModDevArtifactsWorkflow.java | 23 ++ .../internal/ModDevRunWorkflow.java | 132 ++++++++++-- .../internal/PrepareRunOrTest.java | 9 +- .../internal/VsCodeIntegration.java | 29 ++- .../functional/AbstractFunctionalTest.java | 10 + .../functional/VanillaRunFunctionalTest.java | 39 ++++ .../internal/ModDevPluginTest.java | 196 +++++++++++++++++- .../functional/vanilla-runs.gradle | 65 ++++++ 13 files changed, 561 insertions(+), 44 deletions(-) create mode 100644 src/test/java/net/neoforged/moddevgradle/functional/VanillaRunFunctionalTest.java create mode 100644 src/test/resources/net/neoforged/moddevgradle/functional/vanilla-runs.gradle diff --git a/README.md b/README.md index ae299905..02c78cb9 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,10 @@ neoForge { type = "gameTestServer" // Client, data and server runs can use a shorthand instead: // client() + // vanillaClient() // data() // server() + // vanillaServer() // Changes the working directory used for this run. // The default is the 'run' subdirectory of your project diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java index ce73f3f8..5f3d9ee0 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java @@ -1,6 +1,7 @@ package net.neoforged.moddevgradle.dsl; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; @@ -57,6 +58,7 @@ public RunModel(String name, Project project, Iterable defaultMods) { getLogLevel().convention(Level.INFO); getDevLogin().convention(false); + getUseVanillaRunTemplates().convention(false); // Build a nicer name for the IDE run configuration boolean isSubProject = project.getRootProject() != project; @@ -164,6 +166,15 @@ public void client() { getType().set("client"); } + /** + * Equivalent to setting {@code type = "client"} and using the vanilla client run template. + */ + public void vanillaClient() { + getType().set("client"); + getUseVanillaRunTemplates().set(true); + getLoadedMods().set(Collections.emptySet()); + } + /** * Equivalent to setting {@code type = "clientData"}. * @@ -191,6 +202,15 @@ public void server() { getType().set("server"); } + /** + * Equivalent to setting {@code type = "server"} and using the vanilla server run template. + */ + public void vanillaServer() { + getType().set("server"); + getUseVanillaRunTemplates().set(true); + getLoadedMods().set(Collections.emptySet()); + } + /** * Equivalent to setting {@code type = "serverData"}. * @@ -273,6 +293,11 @@ public Configuration getAdditionalRuntimeClasspathConfiguration() { */ public abstract Property getDevLogin(); + /** + * Use ModDevGradle's built-in vanilla run templates instead of the configured loader's run templates. + */ + public abstract Property getUseVanillaRunTemplates(); + @Override public String toString() { return "Run[" + getName() + "]"; diff --git a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java index 40e97557..e628af4a 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java @@ -20,7 +20,6 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.TaskProvider; import org.gradle.plugins.ide.eclipse.EclipsePlugin; import org.gradle.plugins.ide.eclipse.model.Classpath; import org.gradle.plugins.ide.eclipse.model.EclipseModel; @@ -70,13 +69,18 @@ public void attachSources(Map, Provider> jarT } @Override - public void configureRuns(Map> prepareRunTasks, + public void configureRuns(Map ideRunConfigurations, Iterable runs) { // Set up runs if running under buildship and in VS Code project.afterEvaluate(ignored -> { for (var run : runs) { - var prepareTask = prepareRunTasks.get(run).get(); - addEclipseLaunchConfiguration(project, run, prepareTask); + var ideRunConfiguration = ideRunConfigurations.get(run); + var prepareTask = ideRunConfiguration.prepareRunTask().get(); + if (ideRunConfiguration.usesDedicatedVanillaRuntime().get()) { + addEclipseGradleLaunchConfiguration(project, run, prepareTask, ideRunConfiguration.runTask().get()); + } else { + addEclipseLaunchConfiguration(project, run, prepareTask); + } } }); } @@ -166,6 +170,27 @@ private void addEclipseLaunchConfiguration(Project project, writeEclipseLaunchConfig(project, launchConfigName, config); } + private void addEclipseGradleLaunchConfiguration(Project project, + RunModel run, + PrepareRun prepareTask, + RunGameTask runTask) { + if (!prepareTask.getEnabled()) { + LOG.info("Not creating Eclipse run {} since its prepare task {} is disabled", run, prepareTask); + return; + } + if (!shouldGenerateConfigFor(run)) { + LOG.info("Not creating Eclipse run {} since it's explicitly disabled", run); + return; + } + + var runIdeName = run.getIdeName().get(); + var eclipseProjectName = Objects.requireNonNullElse(eclipseModel.getProject().getName(), project.getName()); + var config = GradleLaunchConfig.builder(eclipseProjectName) + .tasks(runTask.getPath()) + .build(); + writeEclipseLaunchConfig(project, runIdeName, config); + } + protected static ModFoldersProvider getModFoldersProvider(Project project, Provider> modsProvider, @Nullable Provider testedMod) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java index 8e38adea..bfc84f58 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java @@ -89,7 +89,7 @@ public final void runTaskOnProjectSync(Object task) { ideSyncTask.configure(ideSyncTask -> ideSyncTask.dependsOn(task)); } - void configureRuns(Map> prepareRunTasks, Iterable runs) {} + void configureRuns(Map ideRunConfigurations, Iterable runs) {} void configureTesting(Provider> loadedMods, Provider testedMod, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java index 50f08e80..9c175d4d 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java @@ -24,13 +24,13 @@ import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskProvider; import org.gradle.internal.DefaultTaskExecutionRequest; import org.gradle.plugins.ide.idea.IdeaPlugin; import org.gradle.plugins.ide.idea.model.IdeaModel; import org.jetbrains.annotations.Nullable; import org.jetbrains.gradle.ext.Application; import org.jetbrains.gradle.ext.BeforeRunTask; +import org.jetbrains.gradle.ext.Gradle; import org.jetbrains.gradle.ext.IdeaExtPlugin; import org.jetbrains.gradle.ext.JUnit; import org.jetbrains.gradle.ext.ModuleRef; @@ -81,7 +81,7 @@ public void attachSources(Map, Provider> jarT } @Override - public void configureRuns(Map> prepareRunTasks, Iterable runs) { + public void configureRuns(Map ideRunConfigurations, Iterable runs) { // IDEA Sync has no real notion of tasks or providers or similar project.afterEvaluate(ignored -> { @@ -93,7 +93,8 @@ public void configureRuns(Map> prepareRunTask var outputDirectory = IntelliJOutputDirectoryValueSource.getIntellijOutputDirectory(project); for (var run : runs) { - var prepareTask = prepareRunTasks.get(run).get(); + var ideRunConfiguration = ideRunConfigurations.get(run); + var prepareTask = ideRunConfiguration.prepareRunTask().get(); if (!prepareTask.getEnabled()) { LOG.info("Not creating IntelliJ run {} since its prepare task {} is disabled", run, prepareTask); continue; @@ -102,7 +103,11 @@ public void configureRuns(Map> prepareRunTask LOG.info("Not creating IntelliJ run {} since it's explicitly disabled", run); continue; } - addIntelliJRunConfiguration(project, runConfigurations, outputDirectory, run, prepareTask); + if (ideRunConfiguration.usesDedicatedVanillaRuntime().get()) { + addIntelliJGradleRunConfiguration(project, runConfigurations, run, ideRunConfiguration.runTask().get()); + } else { + addIntelliJRunConfiguration(project, runConfigurations, outputDirectory, run, prepareTask); + } } } }); @@ -220,6 +225,16 @@ class GradleTasks extends BeforeRunTask { runConfigurations.add(appRun); } + private static void addIntelliJGradleRunConfiguration(Project project, + RunConfigurationContainer runConfigurations, + RunModel run, + RunGameTask runTask) { + var gradleRun = new ExtendedGradle(run.getIdeName().get(), getExtraIntelijRunProperties(run)); + gradleRun.setProjectPath(project.getProjectDir().getAbsolutePath()); + gradleRun.setTaskNames(List.of(runTask.getPath())); + runConfigurations.add(gradleRun); + } + private static String buildRelativePath(Provider file, File workingDirectory) { return workingDirectory.toPath().relativize(file.get().getAsFile().toPath()).toString().replace("\\", "/"); } @@ -295,4 +310,21 @@ public ExtendedApplication(String name, Project project, Map ext return m; } } + + private static class ExtendedGradle extends Gradle { + private final Map extraProperties; + + public ExtendedGradle(String name, Map extraProperties) { + super(name); + this.extraProperties = extraProperties; + } + + @Override + public Map toMap() { + @SuppressWarnings("unchecked") + var m = (Map) super.toMap(); + m.putAll(extraProperties); + return m; + } + } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 40b1723d..bebb9c6d 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -44,13 +44,16 @@ public record ModDevArtifactsWorkflow( VersionCapabilitiesInternal versionCapabilities, TaskProvider createArtifacts, Provider minecraftClassesDependency, + Provider vanillaMinecraftClassesArtifact, TaskProvider downloadAssets, Configuration runtimeDependencies, Configuration compileDependencies, + Configuration vanillaRuntimeDependencies, Provider modDevBuildDir, Provider artifactsBuildDir) { private static final String EXTENSION_NAME = "__internal_modDevArtifactsWorkflow"; + private static final String VANILLA_CLASSES_RESULT_ID = "vanillaDeobfuscated"; public static ModDevArtifactsWorkflow get(Project project) { var result = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevArtifactsWorkflow.class); if (result == null) { @@ -193,6 +196,7 @@ public static ModDevArtifactsWorkflow create(Project project, } else { minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameJarArtifact())).map(dependencyFactory::create); } + var vanillaMinecraftClassesArtifact = artifactsBuildDir.map(dir -> dir.file("vanilla-runtime-" + versionCapabilities.minecraftVersion() + ".jar")); // Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath. // We cannot use "runtimeOnly", since the contents of that are published. @@ -225,6 +229,18 @@ public static ModDevArtifactsWorkflow create(Project project, } }); + var vanillaRuntimeDependencies = configurations.create("modDevVanillaRuntimeDependencies", config -> { + config.setDescription("The runtime dependencies to run vanilla Minecraft."); + config.setCanBeResolved(false); + config.setCanBeConsumed(false); + if (moddingDependencies.neoFormDependency() != null) { + config.getDependencies().add(moddingDependencies.neoFormDependency().copy() + .capabilities(c -> c.requireCapability("net.neoforged:neoform-dependencies"))); + } else { + config.getDependencies().add(dependencyFactory.create("net.neoforged:minecraft-dependencies:" + versionCapabilities.minecraftVersion())); + } + }); + // For IDEs that support it, link the source/binary artifacts if we use separated ones if (!disableRecompilation && !ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { ideIntegration.attachSources( @@ -239,9 +255,11 @@ public static ModDevArtifactsWorkflow create(Project project, versionCapabilities, createArtifacts, minecraftClassesDependency, + vanillaMinecraftClassesArtifact, downloadAssets, runtimeDependencies, compileDependencies, + vanillaRuntimeDependencies, modDevBuildDir, artifactsBuildDir); @@ -371,6 +389,11 @@ public Provider requestAdditionalMinecraftArtifact(String id, Provi createArtifacts.flatMap(task -> task.getAdditionalResults().getting(id))); } + public Provider requestVanillaMinecraftClassesDependency() { + createArtifacts.configure(task -> task.getAdditionalResults().put(VANILLA_CLASSES_RESULT_ID, vanillaMinecraftClassesArtifact.map(RegularFile::getAsFile))); + return vanillaMinecraftClassesArtifact.map(path -> project.getDependencyFactory().create(project.files(path).builtBy(createArtifacts))); + } + private static void setNamedAttribute(Project project, AttributeContainer attributes, Attribute attribute, String value) { attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index d6be0f49..46c52698 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -128,6 +128,7 @@ private ModDevRunWorkflow(Project project, setupRuns( project, branding, + artifactsWorkflow, artifactsWorkflow.modDevBuildDir(), runs, userDevConfigOnly, @@ -249,6 +250,30 @@ public static void setupRuns( Consumer configureLegacyClasspath, Provider assetPropertiesFile, VersionCapabilitiesInternal versionCapabilities) { + setupRuns( + project, + branding, + null, + argFileDir, + runs, + runTemplatesSourceFile, + configureModulePath, + configureLegacyClasspath, + assetPropertiesFile, + versionCapabilities); + } + + public static void setupRuns( + Project project, + Branding branding, + @Nullable ModDevArtifactsWorkflow artifactsWorkflow, + Provider argFileDir, + DomainObjectCollection runs, + Object runTemplatesSourceFile, + Consumer configureModulePath, + Consumer configureLegacyClasspath, + Provider assetPropertiesFile, + VersionCapabilitiesInternal versionCapabilities) { var dependencyFactory = project.getDependencyFactory(); var ideIntegration = IdeIntegration.of(project, branding); @@ -267,16 +292,17 @@ public static void setupRuns( task.setDescription("Creates batch files/shell scripts to launch the game from outside of Gradle (i.e. Renderdoc, NVidia Nsight, etc.)"); }); - Map> prepareRunTasks = new IdentityHashMap<>(); + Map ideRunConfigurations = new IdentityHashMap<>(); runs.all(run -> { if (!versionCapabilities.modLocatorRework()) { // TODO: do this properly now that we have a flag in the version capabilities // This will explicitly be replaced in RunUtils to make this work for IDEs run.getEnvironment().put("MOD_CLASSES", RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null).getClassesArgument()); } - var prepareRunTask = setupRunInGradle( + var ideRunConfiguration = setupRunInGradle( project, branding, + artifactsWorkflow, argFileDir, run, runTemplatesSourceFile, @@ -286,9 +312,9 @@ public static void setupRuns( devLaunchConfig, versionCapabilities, createLaunchScriptsTask); - prepareRunTasks.put(run, prepareRunTask); + ideRunConfigurations.put(run, ideRunConfiguration); }); - ideIntegration.configureRuns(prepareRunTasks, runs); + ideIntegration.configureRuns(ideRunConfigurations, runs); } /** @@ -297,9 +323,10 @@ public static void setupRuns( * @param configureLegacyClasspath Callback to add entries to the legacy classpath. * @param assetPropertiesFile File that contains the asset properties file produced by NFRT. */ - private static TaskProvider setupRunInGradle( + private static IdeRunConfiguration setupRunInGradle( Project project, Branding branding, + @Nullable ModDevArtifactsWorkflow artifactsWorkflow, Provider argFileDir, RunModel run, Object runTemplatesFile, @@ -317,19 +344,13 @@ private static TaskProvider setupRunInGradle( var runtimeClasspathConfig = run.getSourceSet().map(SourceSet::getRuntimeClasspathConfigurationName) .map(configurations::getByName); - // Sucks, but what can you do... Only at the end do we actually know which source set this run will use - project.afterEvaluate(ignored -> { - runtimeClasspathConfig.get().extendsFrom(devLaunchConfig); - }); - var type = RunUtils.getRequiredType(project, run); + var runtimeClasspath = project.files(); var modulePathConfiguration = project.getConfigurations().create(InternalModelHelper.nameOfRun(run, "", "modulesOnly"), spec -> { spec.setDescription("Libraries that should be placed on the JVMs boot module path for run " + run.getName() + "."); spec.setCanBeResolved(true); spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); - configureModulePath.accept(spec); }); Provider legacyClasspathFile; @@ -338,7 +359,6 @@ private static TaskProvider setupRunInGradle( spec.setDescription("Contains all dependencies of the " + run.getName() + " run that should not be considered boot classpath modules."); spec.setCanBeResolved(true); spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); spec.attributes(attributes -> { attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; @@ -346,8 +366,6 @@ private static TaskProvider setupRunInGradle( })); setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); }); - configureLegacyClasspath.accept(spec); - spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); }); var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { @@ -357,12 +375,38 @@ private static TaskProvider setupRunInGradle( writeLcp.addEntries(legacyClasspathConfiguration); }); legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); + + project.afterEvaluate(ignored -> { + if (!shouldUseDedicatedVanillaRuntime(artifactsWorkflow, run)) { + legacyClasspathConfiguration.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); + configureLegacyClasspath.accept(legacyClasspathConfiguration); + legacyClasspathConfiguration.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); + } + }); } else { // Disallow adding dependencies to the additional classpath configuration since it would have no effect. forbidAdditionalRuntimeDependencies(run.getAdditionalRuntimeClasspathConfiguration(), versionCapabilities); legacyClasspathFile = null; } + project.afterEvaluate(ignored -> { + if (shouldUseDedicatedVanillaRuntime(artifactsWorkflow, run)) { + runtimeClasspath.from(getDedicatedVanillaRuntimeClasspath( + project, + artifactsWorkflow, + run, + type, + devLaunchConfig)); + } else { + runtimeClasspathConfig.get().extendsFrom(devLaunchConfig); + modulePathConfiguration.shouldResolveConsistentlyWith(runtimeClasspathConfig.get()); + configureModulePath.accept(modulePathConfiguration); + // Note: this contains both the runtimeClasspath configuration and the sourceset's outputs. + // This records a dependency on compiling and processing the resources of the source set. + runtimeClasspath.from(run.getSourceSet().map(SourceSet::getRuntimeClasspath)); + } + }); + var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> { task.setGroup(branding.internalTaskGroup()); task.setDescription("Prepares all files needed to launch the " + run.getName() + " Minecraft run."); @@ -388,6 +432,7 @@ private static TaskProvider setupRunInGradle( task.getJvmArguments().set(run.getJvmArguments()); task.getGameLogLevel().set(run.getLogLevel()); task.getDevLogin().set(run.getDevLogin()); + task.getUseVanillaRunTemplates().set(run.getUseVanillaRunTemplates()); task.getVersionCapabilities().set(versionCapabilities); }); ideIntegration.runTaskOnProjectSync(prepareRunTask); @@ -397,9 +442,7 @@ private static TaskProvider setupRunInGradle( task.setDescription("Creates a bash/shell-script to launch the " + run.getName() + " Minecraft run from outside Gradle or your IDE."); task.getWorkingDirectory().set(run.getGameDirectory().map(d -> d.getAsFile().getAbsolutePath())); - // Note: this contains both the runtimeClasspath configuration and the sourceset's outputs. - // This records a dependency on compiling and processing the resources of the source set. - task.getRuntimeClasspath().from(run.getSourceSet().map(SourceSet::getRuntimeClasspath)); + task.getRuntimeClasspath().from(runtimeClasspath); task.getLaunchScript().set(RunUtils.getLaunchScript(argFileDir, run)); task.getClasspathArgsFile().set(RunUtils.getArgFile(argFileDir, run, RunUtils.RunArgFile.CLASSPATH)); task.getVmArgsFile().set(prepareRunTask.get().getVmArgsFile().map(d -> d.getAsFile().getAbsolutePath())); @@ -409,16 +452,14 @@ private static TaskProvider setupRunInGradle( }); createLaunchScriptsTask.configure(task -> task.dependsOn(launchScriptTask)); - tasks.register(InternalModelHelper.nameOfRun(run, "run", ""), RunGameTask.class, task -> { + var runTask = tasks.register(InternalModelHelper.nameOfRun(run, "run", ""), RunGameTask.class, task -> { task.setGroup(branding.publicTaskGroup()); task.setDescription("Runs the " + run.getName() + " Minecraft run configuration."); // Launch with the Java version used in the project var toolchainService = ExtensionUtils.findExtension(project, "javaToolchains", JavaToolchainService.class); task.getJavaLauncher().set(toolchainService.launcherFor(spec -> spec.getLanguageVersion().set(javaExtension.getToolchain().getLanguageVersion()))); - // Note: this contains both the runtimeClasspath configuration and the sourceset's outputs. - // This records a dependency on compiling and processing the resources of the source set. - task.getClasspathProvider().from(run.getSourceSet().map(SourceSet::getRuntimeClasspath)); + task.getClasspathProvider().from(runtimeClasspath); task.getGameDirectory().set(run.getGameDirectory()); task.getEnvironmentProperty().set(run.getEnvironment()); @@ -432,7 +473,46 @@ private static TaskProvider setupRunInGradle( task.getJvmArgumentProviders().add(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); }); - return prepareRunTask; + return new IdeRunConfiguration( + prepareRunTask, + runTask, + launchScriptTask, + project.provider(() -> shouldUseDedicatedVanillaRuntime(artifactsWorkflow, run))); + } + + private static ConfigurableFileCollection getDedicatedVanillaRuntimeClasspath( + Project project, + @Nullable ModDevArtifactsWorkflow artifactsWorkflow, + RunModel run, + Provider type, + Configuration devLaunchConfig) { + if (artifactsWorkflow == null) { + throw new IllegalStateException("A vanilla runtime classpath requires the ModDev artifacts workflow."); + } + + var vanillaRuntimeClasspath = project.getConfigurations().create(InternalModelHelper.nameOfRun(run, "", "vanillaRuntimeClasspath"), spec -> { + spec.setDescription("Contains the vanilla runtime classpath for run " + run.getName() + "."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.getDependencies().addLater(artifactsWorkflow.requestVanillaMinecraftClassesDependency()); + spec.extendsFrom(artifactsWorkflow.vanillaRuntimeDependencies()); + spec.extendsFrom(devLaunchConfig); + spec.attributes(attributes -> { + attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> { + var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER; + return project.getObjects().named(MinecraftDistribution.class, name); + })); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + }); + + return project.files(vanillaRuntimeClasspath); + } + + private static boolean shouldUseDedicatedVanillaRuntime(@Nullable ModDevArtifactsWorkflow artifactsWorkflow, RunModel run) { + return artifactsWorkflow != null + && artifactsWorkflow.dependencies().neoForgeDependency() != null + && run.getUseVanillaRunTemplates().getOrElse(false); } /** @@ -540,3 +620,9 @@ private static void setNamedAttribute(Project project, Attribu attributes.attribute(attribute, project.getObjects().named(attribute.getType(), value)); } } + +record IdeRunConfiguration( + TaskProvider prepareRunTask, + TaskProvider runTask, + TaskProvider launchScriptTask, + Provider usesDedicatedVanillaRuntime) {} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index 116bb72f..1ed72f3f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java @@ -124,12 +124,19 @@ abstract class PrepareRunOrTest extends DefaultTask { @Input public abstract Property getDevLogin(); + /** + * Use the built-in vanilla run templates even when loader-specific templates are available. + */ + @Input + public abstract Property getUseVanillaRunTemplates(); + private final ProgramArgsFormat programArgsFormat; protected PrepareRunOrTest(ProgramArgsFormat programArgsFormat) { this.programArgsFormat = programArgsFormat; getVersionCapabilities().convention(VersionCapabilitiesInternal.latest()); getDevLogin().convention(false); + getUseVanillaRunTemplates().convention(false); } protected abstract UserDevRunType resolveRunType(UserDevConfig userDevConfig); @@ -167,7 +174,7 @@ public void prepareRun() throws IOException { // If no NeoForge userdev config is set, we only support Vanilla run types UserDevRunType runConfig; - if (getRunTypeTemplatesSource().isEmpty()) { + if (getUseVanillaRunTemplates().get() || getRunTypeTemplatesSource().isEmpty()) { runConfig = resolveRunType(getSimulatedUserDevConfigForVanilla()); } else { var userDevConfig = loadUserDevConfig(getRunTypeTemplatesSource().getSingleFile()); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java index 342f042b..1328e52b 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java @@ -1,17 +1,18 @@ package net.neoforged.moddevgradle.internal; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import net.neoforged.moddevgradle.dsl.RunModel; import net.neoforged.vsclc.BatchedLaunchWriter; import net.neoforged.vsclc.attribute.ConsoleType; +import net.neoforged.vsclc.attribute.LocatorPathLike; import net.neoforged.vsclc.attribute.PathLike; import net.neoforged.vsclc.attribute.ShortCmdBehaviour; import net.neoforged.vsclc.writer.WritingMode; import org.gradle.api.Project; -import org.gradle.api.tasks.TaskProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,15 +27,16 @@ final class VsCodeIntegration extends EclipseIntegration { } @Override - public void configureRuns(Map> prepareRunTasks, + public void configureRuns(Map ideRunConfigurations, Iterable runs) { // Set up runs if running under buildship and in VS Code project.afterEvaluate(ignored -> { var launchWriter = new BatchedLaunchWriter(WritingMode.MODIFY_CURRENT); for (var run : runs) { - var prepareTask = prepareRunTasks.get(run).get(); - addVscodeLaunchConfiguration(project, run, prepareTask, launchWriter); + var ideRunConfiguration = ideRunConfigurations.get(run); + var prepareTask = ideRunConfiguration.prepareRunTask().get(); + addVscodeLaunchConfiguration(project, run, prepareTask, ideRunConfiguration, launchWriter); } try { @@ -48,6 +50,7 @@ public void configureRuns(Map> prepareRunTask private void addVscodeLaunchConfiguration(Project project, RunModel run, PrepareRun prepareTask, + IdeRunConfiguration ideRunConfiguration, BatchedLaunchWriter launchWriter) { if (!prepareTask.getEnabled()) { LOG.info("Not creating VSCode run {} since its prepare task {} is disabled", run, prepareTask); @@ -60,6 +63,15 @@ private void addVscodeLaunchConfiguration(Project project, var runIdeName = run.getIdeName().get(); var eclipseProjectName = Objects.requireNonNullElse(eclipseModel.getProject().getName(), project.getName()); + var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null); + var usesDedicatedVanillaRuntime = ideRunConfiguration.usesDedicatedVanillaRuntime().get(); + var additionalJvmArgs = new ArrayList(); + if (usesDedicatedVanillaRuntime) { + ideSyncTask.configure(task -> task.dependsOn(ideRunConfiguration.launchScriptTask())); + additionalJvmArgs.add(RunUtils.getArgFileParameter(ideRunConfiguration.launchScriptTask().get().getClasspathArgsFile().get())); + } + additionalJvmArgs.add(RunUtils.getArgFileParameter(prepareTask.getVmArgsFile().get())); + additionalJvmArgs.add(modFoldersProvider.getArgument()); // If the user wants to run tasks before the actual execution, we attach them to autoBuildTasks // Missing proper support - https://github.com/microsoft/vscode-java-debug/issues/1106 @@ -67,18 +79,19 @@ private void addVscodeLaunchConfiguration(Project project, eclipseModel.autoBuildTasks(run.getTasksBefore().toArray()); } - var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null); - launchWriter.createGroup("Mod Development - " + project.getName(), WritingMode.REMOVE_EXISTING) + var launchConfiguration = launchWriter.createGroup("Mod Development - " + project.getName(), WritingMode.REMOVE_EXISTING) .createLaunchConfiguration() .withName(runIdeName) .withProjectName(eclipseProjectName) .withArguments(List.of(RunUtils.getArgFileParameter(prepareTask.getProgramArgsFile().get()))) - .withAdditionalJvmArgs(List.of(RunUtils.getArgFileParameter(prepareTask.getVmArgsFile().get()), - modFoldersProvider.getArgument())) + .withAdditionalJvmArgs(additionalJvmArgs) .withEnvironmentVariables(RunUtils.replaceModClassesEnv(run, modFoldersProvider)) .withMainClass(RunUtils.DEV_LAUNCH_MAIN_CLASS) .withShortenCommandLine(ShortCmdBehaviour.NONE) .withConsoleType(ConsoleType.INTERNAL_CONSOLE) .withCurrentWorkingDirectory(PathLike.ofNio(run.getGameDirectory().get().getAsFile().toPath())); + if (usesDedicatedVanillaRuntime) { + launchConfiguration.withClassPathsOverride(List.of()); + } } } diff --git a/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java b/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java index c1a4c86e..8ae2b5ec 100644 --- a/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java +++ b/src/test/java/net/neoforged/moddevgradle/functional/AbstractFunctionalTest.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -38,6 +39,15 @@ protected final void writeProjectFile(String relativePath, String content) throw Files.writeString(destination, content); } + protected final String readTestResource(String resourcePath) throws IOException { + try (var stream = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + if (stream == null) { + throw new IOException("Missing test resource: " + resourcePath); + } + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } + void writeGroovySettingsScript(@Language("gradle") String text, Object... args) throws IOException { writeFile(settingsFile, interpolateTemplate(text, args)); } diff --git a/src/test/java/net/neoforged/moddevgradle/functional/VanillaRunFunctionalTest.java b/src/test/java/net/neoforged/moddevgradle/functional/VanillaRunFunctionalTest.java new file mode 100644 index 00000000..e7c57f8a --- /dev/null +++ b/src/test/java/net/neoforged/moddevgradle/functional/VanillaRunFunctionalTest.java @@ -0,0 +1,39 @@ +package net.neoforged.moddevgradle.functional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Test; + +public class VanillaRunFunctionalTest extends AbstractFunctionalTest { + @Test + public void testVanillaRunsUseDedicatedRuntimeClasspath() throws IOException { + writeFile(settingsFile, "rootProject.name = 'vanilla-runs'"); + writeGroovyBuildScript(readTestResource("net/neoforged/moddevgradle/functional/vanilla-runs.gradle")); + + var result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir) + .withArguments("assertVanillaRuns", "--stacktrace") + .build(); + + assertEquals(TaskOutcome.SUCCESS, result.task(":assertVanillaRuns").getOutcome()); + } + + @Test + public void testVanillaRunTaskGraphCanBeScheduled() throws IOException { + writeFile(settingsFile, "rootProject.name = 'vanilla-runs'"); + writeGroovyBuildScript(readTestResource("net/neoforged/moddevgradle/functional/vanilla-runs.gradle")); + + var result = GradleRunner.create() + .withPluginClasspath() + .withProjectDir(testProjectDir) + .withArguments("runVanillaClient", "--dry-run", "--stacktrace") + .build(); + + assertThat(result.getOutput()).contains(":runVanillaClient SKIPPED"); + } +} diff --git a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java index 09acc348..64f57ff6 100644 --- a/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java +++ b/src/test/java/net/neoforged/moddevgradle/internal/ModDevPluginTest.java @@ -4,17 +4,31 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; import java.util.Set; +import java.util.function.Supplier; import net.neoforged.moddevgradle.AbstractProjectBuilderTest; import net.neoforged.moddevgradle.dsl.NeoForgeExtension; import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; import net.neoforged.moddevgradle.internal.utils.VersionCapabilitiesInternal; +import net.neoforged.nfrtgradle.CreateMinecraftArtifacts; import org.gradle.api.InvalidUserCodeException; +import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.FileCollectionDependency; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.plugins.ide.idea.model.IdeaModel; import org.gradle.testfixtures.ProjectBuilder; +import org.jetbrains.gradle.ext.Gradle; +import org.jetbrains.gradle.ext.ProjectSettings; +import org.jetbrains.gradle.ext.RunConfigurationContainer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -33,9 +47,7 @@ public ModDevPluginTest() { mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME); - // Set the Java version to the currently running Java to make it use that - var java = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); - java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.current()); + configureCurrentJavaToolchain(project); } @Test @@ -78,6 +90,109 @@ void testAddModdingDependenciesTo() { assertContainsModdingRuntimeDependencies("21.11.0", testSourceSet.getRuntimeClasspathConfigurationName()); } + @Test + void testVanillaRuntimeDependencyUsesKnownArtifactPath() { + extension.setVersion("21.11.0"); + + var dependency = ModDevArtifactsWorkflow.get(project) + .requestVanillaMinecraftClassesDependency() + .get(); + + assertThat(describeDependency(dependency)) + .isEqualTo("build/moddev/artifacts/vanilla-runtime-1.21.11.jar"); + assertThat(dependency).isInstanceOf(FileCollectionDependency.class); + assertThat(((FileCollectionDependency) dependency).getFiles().getBuildDependencies().getDependencies(null)) + .extracting(Task::getName) + .containsOnly("createMinecraftArtifacts"); + + var createArtifacts = project.getTasks().named("createMinecraftArtifacts", CreateMinecraftArtifacts.class).get(); + assertThat(createArtifacts.getAdditionalResults().get()) + .containsOnlyKeys("vanillaDeobfuscated"); + } + + @Test + void testVanillaRuntimeDependenciesDoNotUseNeoForge() { + extension.setVersion("21.11.0"); + + assertThatDependencies("modDevVanillaRuntimeDependencies") + .containsOnly("net.neoforged:minecraft-dependencies:1.21.11"); + assertDoesNotContainNeoForgeDependency("modDevVanillaRuntimeDependencies"); + } + + @Test + void testVanillaServerRunDefaultsToDedicatedVanillaRuntime() { + extension.getMods().create("mainmod", mod -> mod.sourceSet(mainSourceSet)); + extension.getRuns().create("vanilla", run -> run.vanillaServer()); + + var run = extension.getRuns().getByName("vanilla"); + assertThat(run.getType().get()).isEqualTo("server"); + assertThat(run.getUseVanillaRunTemplates().get()).isTrue(); + assertThat(run.getLoadedMods().get()).isEmpty(); + } + + @Test + void testVanillaClientRunDefaultsToDedicatedVanillaRuntime() { + extension.getMods().create("mainmod", mod -> mod.sourceSet(mainSourceSet)); + extension.getRuns().create("vanilla", run -> run.vanillaClient()); + + var run = extension.getRuns().getByName("vanilla"); + assertThat(run.getType().get()).isEqualTo("client"); + assertThat(run.getUseVanillaRunTemplates().get()).isTrue(); + assertThat(run.getLoadedMods().get()).isEmpty(); + } + + @Test + void testVanillaIntelliJRunUsesGradleTaskClasspath() { + withSystemProperty("idea.sync.active", "true", () -> { + var ideProject = createProjectWithModDevPlugin(); + createVanillaServerRun(ideProject); + + evaluateProject(ideProject); + + var runConfiguration = getIntelliJRunConfigurations(ideProject).getByName("Vanilla"); + assertThat(runConfiguration).isInstanceOf(Gradle.class); + + var runConfigurationData = runConfiguration.toMap(); + assertThat(runConfigurationData.get("type")).isEqualTo("gradle"); + assertThat(runConfigurationData.get("taskNames")).isEqualTo(List.of(":runVanilla")); + }); + } + + @Test + void testVanillaEclipseRunUsesGradleTaskClasspath() throws IOException { + Project ideProject = withSystemProperty("eclipse.application", "org.eclipse.buildship.core", () -> { + var result = createProjectWithModDevPlugin(); + createVanillaServerRun(result); + + evaluateProject(result); + return result; + }); + + var launchConfig = Files.readString(ideProject.file(".eclipse/configurations/Vanilla.launch").toPath()); + assertThat(launchConfig) + .contains("org.eclipse.buildship.core.launch.runconfiguration") + .contains(":runVanilla") + .doesNotContain("org.eclipse.jdt.launching.localJavaApplication"); + } + + @Test + void testVanillaVsCodeRunUsesGeneratedClasspathArgfile() throws IOException { + var ideProject = createProjectWithVsCodeIntegration(); + createVanillaServerRun(ideProject); + + evaluateProject(ideProject); + + var launchJson = Files.readString(ideProject.file(".vscode/launch.json").toPath()); + assertThat(launchJson) + .contains("\"classPaths\": []") + .contains("vanillaRunClasspath.txt") + .doesNotContain("\"$Runtime\""); + + assertThat(ideProject.getTasks().named("neoForgeIdeSync").get().getTaskDependencies().getDependencies(null)) + .extracting(Task::getName) + .contains("createVanillaLaunchScript"); + } + @Test void testGetVersion() { extension.setVersion("2.3.0"); @@ -188,4 +303,79 @@ private void assertContainsModdingRuntimeDependencies(String version, String con "build/moddev/artifacts/neoforge-" + version + "-client-extra-aka-minecraft-resources.jar", "net.neoforged:neoforge:" + version + "[net.neoforged:neoforge-dependencies]"); } + + private void assertDoesNotContainNeoForgeDependency(String configurationName) { + var dependencies = project.getConfigurations().getByName(configurationName).getAllDependencies(); + assertThat(dependencies).allSatisfy(dependency -> { + if (dependency instanceof ExternalModuleDependency moduleDependency) { + assertThat(moduleDependency.getName()).isNotEqualTo("neoforge"); + assertThat(moduleDependency.getRequestedCapabilities()) + .extracting("name") + .doesNotContain("neoforge-dependencies", "neoforge-moddev-bundle", "neoforge-moddev-config", "neoforge-moddev-module-path"); + } + }); + } + + private static Project createProjectWithModDevPlugin() { + var result = ProjectBuilder.builder().build(); + result.getPlugins().apply(ModDevPlugin.class); + configureCurrentJavaToolchain(result); + return result; + } + + private static Project createProjectWithVsCodeIntegration() { + var result = ProjectBuilder.builder().build(); + result.getExtensions().add(IdeIntegration.class, "mdgInternalIdeIntegration", new VsCodeIntegration(result, Branding.MDG)); + result.getPlugins().apply(ModDevPlugin.class); + configureCurrentJavaToolchain(result); + return result; + } + + private static void configureCurrentJavaToolchain(Project project) { + var java = ExtensionUtils.getExtension(project, "java", JavaPluginExtension.class); + java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.current()); + } + + private static NeoForgeExtension getNeoForgeExtension(Project project) { + return ExtensionUtils.getExtension(project, "neoForge", NeoForgeExtension.class); + } + + private static void createVanillaServerRun(Project project) { + var extension = getNeoForgeExtension(project); + extension.setVersion("21.11.0"); + extension.getRuns().create("vanilla", run -> run.vanillaServer()); + } + + private static void evaluateProject(Project project) { + ((ProjectInternal) project).evaluate(); + } + + private static RunConfigurationContainer getIntelliJRunConfigurations(Project project) { + var rootIdeaModel = ExtensionUtils.getExtension(project.getRootProject(), "idea", IdeaModel.class); + var projectSettings = ((ExtensionAware) rootIdeaModel.getProject()).getExtensions().getByType(ProjectSettings.class); + var runConfigurations = ExtensionUtils.findExtension((ExtensionAware) projectSettings, "runConfigurations", RunConfigurationContainer.class); + assertThat(runConfigurations).isNotNull(); + return runConfigurations; + } + + private static void withSystemProperty(String name, String value, Runnable runnable) { + withSystemProperty(name, value, () -> { + runnable.run(); + return null; + }); + } + + private static T withSystemProperty(String name, String value, Supplier supplier) { + var previousValue = System.getProperty(name); + System.setProperty(name, value); + try { + return supplier.get(); + } finally { + if (previousValue == null) { + System.clearProperty(name); + } else { + System.setProperty(name, previousValue); + } + } + } } diff --git a/src/test/resources/net/neoforged/moddevgradle/functional/vanilla-runs.gradle b/src/test/resources/net/neoforged/moddevgradle/functional/vanilla-runs.gradle new file mode 100644 index 00000000..d57e8589 --- /dev/null +++ b/src/test/resources/net/neoforged/moddevgradle/functional/vanilla-runs.gradle @@ -0,0 +1,65 @@ +import org.gradle.api.artifacts.ExternalModuleDependency + +plugins { + id "net.neoforged.moddev" +} + +neoForge { + version = "{DEFAULT_NEOFORGE_VERSION}" + + mods { + testmod { + sourceSet sourceSets.main + } + } + + runs { + regularServer { + server() + sourceSet = sourceSets.test + } + vanillaClient { + vanillaClient() + } + vanillaServer { + vanillaServer() + } + } +} + +def assertNoNeoForgeDependencies = { dependencies -> + def neoForgeDependencies = dependencies.findAll { dependency -> + dependency instanceof ExternalModuleDependency + && (dependency.name == "neoforge" + || dependency.requestedCapabilities.any { it.name.startsWith("neoforge-") }) + } + if (!neoForgeDependencies.empty) { + throw new GradleException("Unexpected NeoForge dependencies: ${neoForgeDependencies}") + } +} + +tasks.register("assertVanillaRuns") { + doLast { + assert configurations.modDevVanillaRuntimeDependencies.allDependencies*.name == ["minecraft-dependencies"] + assertNoNeoForgeDependencies(configurations.modDevVanillaRuntimeDependencies.allDependencies) + + ["vanillaClient", "vanillaServer"].each { runName -> + def run = neoForge.runs.named(runName).get() + assert run.loadedMods.get().empty + + def vanillaRuntimeClasspath = configurations.getByName("${runName}VanillaRuntimeClasspath") + assert vanillaRuntimeClasspath.extendsFrom*.name.contains("modDevVanillaRuntimeDependencies") + assert vanillaRuntimeClasspath.extendsFrom*.name.contains("devLaunchConfig") + assert !vanillaRuntimeClasspath.extendsFrom*.name.contains("modDevRuntimeDependencies") + assert !vanillaRuntimeClasspath.extendsFrom*.name.contains("${runName}AdditionalRuntimeClasspath") + assertNoNeoForgeDependencies(vanillaRuntimeClasspath.allDependencies) + + def legacyClasspath = configurations.getByName("${runName}LegacyClasspath") + assert legacyClasspath.dependencies.empty + assert legacyClasspath.extendsFrom.empty + } + + def regularRuntimeClasspath = configurations.getByName(sourceSets.test.runtimeClasspathConfigurationName) + assert regularRuntimeClasspath.extendsFrom*.name.contains("devLaunchConfig") + } +} From 2d444f976528771db1b4628fe9bf57cf1687268a Mon Sep 17 00:00:00 2001 From: mezz Date: Wed, 8 Jul 2026 21:44:40 +0900 Subject: [PATCH 2/2] minor cleanup --- .../internal/ModDevRunWorkflow.java | 23 ------------------- .../moddevgradle/internal/NeoDevFacade.java | 1 + 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 46c52698..258c2948 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -240,29 +240,6 @@ private static void addClientResources(Project project, Configuration spec, Task project.files(createArtifacts.flatMap(CreateMinecraftArtifacts::getResourcesArtifact)))); } - public static void setupRuns( - Project project, - Branding branding, - Provider argFileDir, - DomainObjectCollection runs, - Object runTemplatesSourceFile, - Consumer configureModulePath, - Consumer configureLegacyClasspath, - Provider assetPropertiesFile, - VersionCapabilitiesInternal versionCapabilities) { - setupRuns( - project, - branding, - null, - argFileDir, - runs, - runTemplatesSourceFile, - configureModulePath, - configureLegacyClasspath, - assetPropertiesFile, - versionCapabilities); - } - public static void setupRuns( Project project, Branding branding, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index 9192640d..5b146cd4 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -35,6 +35,7 @@ public static void setupRuns(Project project, ModDevRunWorkflow.setupRuns( project, Branding.NEODEV, + null, argFileDir, runs, runTemplatesSourceFile,