diff --git a/README.md b/README.md index c58ebc4c..85d6d468 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,11 @@ To decrease the likelihood of conflicts if no explicit module name is set, we prefix the filename of embedded subprojects with the group id. ### External Dependencies: Runs +As of Minecraft 1.21.9, external dependencies do not need special handling anymore to be loaded in runs. + +
+Show information for 1.21.8 and older Minecraft versions + External dependencies will only be loaded in your runs if they are mods (with a `META-INF/neoforge.mods.toml` file), or if they have the `FMLModType` entry set in their `META-INF/MANIFEST.MF` file. Usually, Java libraries do not fit either of these requirements, @@ -340,6 +345,7 @@ dependencies { _Advanced_: The additional runtime classpath can be configured per-run. For example, to add a dependency to the `client` run only, it can be added to `clientAdditionalRuntimeClasspath`. +
### Isolated Source Sets diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index 6c0fdbbb..4ccaf0f3 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -59,7 +59,6 @@ public class ModDevRunWorkflow { @Nullable private final ModuleDependency testFixturesDependency; private final ModuleDependency gameLibrariesDependency; - private final Configuration additionalClasspath; private final Configuration userDevConfigOnly; /** @@ -97,19 +96,32 @@ private ModDevRunWorkflow(Project project, } }); - additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { - spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - - spec.getDependencies().add(gameLibrariesDependency); - addClientResources(project, spec, artifactsWorkflow.createArtifacts()); - if (!versionCapabilities.modLocatorRework()) { - // Forge expects to find the Forge and client-extra jar on the legacy classpath - // Newer FML versions also search for it on the java.class.path. - spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency()); - } - }); + Consumer configureLegacyClasspath; + if (versionCapabilities.legacyClasspath()) { + var additionalClasspath = configurations.create("additionalRuntimeClasspath", spec -> { + spec.setDescription("Contains dependencies of every run, that should not be considered boot classpath modules."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + + spec.getDependencies().add(gameLibrariesDependency); + addClientResources(project, spec, artifactsWorkflow.createArtifacts()); + if (!versionCapabilities.modLocatorRework()) { + // Forge expects to find the Forge and client-extra jar on the legacy classpath + // Newer FML versions also search for it on the java.class.path. + spec.getDependencies().addLater(artifactsWorkflow.minecraftClassesDependency()); + } + }); + configureLegacyClasspath = legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath); + } else { + // Create the configuration but disallow adding anything to it, to notify users about potential mistakes. + // We might decide to remove it entirely in the future + var additionalClasspath = configurations.create("additionalRuntimeClasspath"); + forbidAdditionalRuntimeDependencies(additionalClasspath, versionCapabilities); + + configureLegacyClasspath = legacyClassPath -> { + throw new IllegalStateException("There is no legacy classpath for Minecraft " + versionCapabilities.minecraftVersion()); + }; + } setupRuns( project, @@ -122,11 +134,24 @@ private ModDevRunWorkflow(Project project, modulePath.getDependencies().add(modulePathDependency); } }, - legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath), + configureLegacyClasspath, artifactsWorkflow.downloadAssets().flatMap(DownloadAssets::getAssetPropertiesFile), versionCapabilities); } + private static void forbidAdditionalRuntimeDependencies(Configuration configuration, VersionCapabilitiesInternal versionCapabilities) { + // We cannot use withDependencies() since the configuration should never get resolved, + // but we want to inform the user anyway. + configuration.getDependencies().all(dependency -> { + throw new IllegalStateException(String.format( + "Tried to add a dependency to configuration %s, but there is no additional classpath anymore for Minecraft %s. " + + "Add the dependency to a standard configuration such as implementation or runtimeOnly. Dependency: %s", + configuration, + versionCapabilities.minecraftVersion(), + dependency)); + }); + } + public static ModDevRunWorkflow get(Project project) { var workflow = ExtensionUtils.findExtension(project, EXTENSION_NAME, ModDevRunWorkflow.class); if (workflow == null) { @@ -198,7 +223,8 @@ public void configureTesting(Provider testedMod, Provider setupRunInGradle( configureModulePath.accept(spec); }); - var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { - 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; - return project.getObjects().named(MinecraftDistribution.class, name); - })); - setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + Provider legacyClasspathFile; + if (versionCapabilities.legacyClasspath()) { + var legacyClasspathConfiguration = configurations.create(InternalModelHelper.nameOfRun(run, "", "legacyClasspath"), spec -> { + 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; + return project.getObjects().named(MinecraftDistribution.class, name); + })); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + configureLegacyClasspath.accept(spec); + spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); }); - configureLegacyClasspath.accept(spec); - spec.extendsFrom(run.getAdditionalRuntimeClasspathConfiguration()); - }); - var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); + var writeLcpTask = tasks.register(InternalModelHelper.nameOfRun(run, "write", "legacyClasspath"), WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the " + run.getName() + " Minecraft run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().set(argFileDir.map(dir -> dir.file(InternalModelHelper.nameOfRun(run, "", "legacyClasspath") + ".txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); + } else { + // Disallow adding dependencies to the additional classpath configuration since it would have no effect. + forbidAdditionalRuntimeDependencies(run.getAdditionalRuntimeClasspathConfiguration(), versionCapabilities); + legacyClasspathFile = null; + } var prepareRunTask = tasks.register(InternalModelHelper.nameOfRun(run, "prepare", "run"), PrepareRun.class, task -> { task.setGroup(branding.internalTaskGroup()); @@ -337,7 +371,9 @@ private static TaskProvider setupRunInGradle( task.getRunType().set(run.getType()); task.getRunTypeTemplatesSource().from(runTemplatesFile); task.getModules().from(modulePathConfiguration); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + if (legacyClasspathFile != null) { + task.getLegacyClasspathFile().set(legacyClasspathFile); + } task.getAssetProperties().set(assetPropertiesFile); task.getSystemProperties().set(run.getSystemProperties().map(props -> { props = new HashMap<>(props); @@ -405,7 +441,8 @@ static void setupTestTask(Project project, Provider argFileDir, Consumer configureModulePath, Consumer configureLegacyClasspath, - Provider assetPropertiesFile) { + Provider assetPropertiesFile, + VersionCapabilitiesInternal versionCapabilities) { var gameDirectory = new File(project.getProjectDir(), JUNIT_GAME_DIR); var ideIntegration = IdeIntegration.of(project, branding); @@ -423,27 +460,33 @@ static void setupTestTask(Project project, configureModulePath.accept(spec); }); - var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { - spec.setDescription("Contains the legacy classpath of unit tests."); - spec.setCanBeResolved(true); - spec.setCanBeConsumed(false); - spec.shouldResolveConsistentlyWith(testRuntimeClasspath); - spec.attributes(attributes -> { - setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); - setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); - }); - configureLegacyClasspath.accept(spec); - }); - // Place files for junit runtime in a subdirectory to avoid conflicting with other runs var runArgsDir = argFileDir.map(dir -> dir.dir("junit")); - var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { - writeLcp.setGroup(branding.internalTaskGroup()); - writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); - writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); - writeLcp.addEntries(legacyClasspathConfiguration); - }); + Provider legacyClasspathFile; + if (versionCapabilities.legacyClasspath()) { + var legacyClasspathConfiguration = configurations.create("neoForgeTestLibraries", spec -> { + spec.setDescription("Contains the legacy classpath of unit tests."); + spec.setCanBeResolved(true); + spec.setCanBeConsumed(false); + spec.shouldResolveConsistentlyWith(testRuntimeClasspath); + spec.attributes(attributes -> { + setNamedAttribute(project, attributes, MinecraftDistribution.ATTRIBUTE, MinecraftDistribution.CLIENT); + setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME); + }); + configureLegacyClasspath.accept(spec); + }); + + var writeLcpTask = tasks.register("writeNeoForgeTestClasspath", WriteLegacyClasspath.class, writeLcp -> { + writeLcp.setGroup(branding.internalTaskGroup()); + writeLcp.setDescription("Writes the legacyClasspath file for the test run, containing all dependencies that shouldn't be considered boot modules."); + writeLcp.getLegacyClasspathFile().convention(runArgsDir.map(dir -> dir.file("legacyClasspath.txt"))); + writeLcp.addEntries(legacyClasspathConfiguration); + }); + legacyClasspathFile = writeLcpTask.get().getLegacyClasspathFile(); + } else { + legacyClasspathFile = null; + } var vmArgsFile = runArgsDir.map(dir -> dir.file("vmArgs.txt")); var programArgsFile = runArgsDir.map(dir -> dir.file("programArgs.txt")); @@ -457,7 +500,9 @@ static void setupTestTask(Project project, task.getLog4jConfigFile().set(log4j2ConfigFile); task.getRunTypeTemplatesSource().from(runTemplatesSourceFile); task.getModules().from(neoForgeModDevModules); - task.getLegacyClasspathFile().set(writeLcpTask.get().getLegacyClasspathFile()); + if (legacyClasspathFile != null) { + task.getLegacyClasspathFile().set(legacyClasspathFile); + } task.getAssetProperties().set(assetPropertiesFile); task.getGameLogLevel().set(Level.INFO); }); @@ -485,10 +530,6 @@ static void setupTestTask(Project project, ideIntegration.configureTesting(loadedMods, testedMod, runArgsDir, gameDirectory, programArgsFile, vmArgsFile); } - public Configuration getAdditionalClasspath() { - return additionalClasspath; - } - 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/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java index 272dfe3f..24692694 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java @@ -83,7 +83,33 @@ public static void setupTestTask(Project project, argFileDir, configureModulePath, configureAdditionalClasspath, - assetPropertiesFile); + assetPropertiesFile, + // This overload of the method was only used by NeoForge before 1.21.9 + VersionCapabilitiesInternal.ofMinecraftVersion("1.21.8")); + } + + public static void setupTestTask(Project project, + Provider argFileDir, + TaskProvider testTask, + Object runTemplatesSourceFile, + Provider> loadedMods, + Provider testedMod, + Consumer configureModulePath, + Consumer configureAdditionalClasspath, + Provider assetPropertiesFile, + Provider neoFormVersion) { + ModDevRunWorkflow.setupTestTask( + project, + Branding.NEODEV, + runTemplatesSourceFile, + testTask, + loadedMods, + testedMod, + argFileDir, + configureModulePath, + configureAdditionalClasspath, + assetPropertiesFile, + neoFormVersion.map(VersionCapabilitiesInternal::ofNeoFormVersion).getOrElse(VersionCapabilitiesInternal.latest())); } public static void runTaskOnProjectSync(Project project, Object task) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java index 0543e684..116bb72f 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java @@ -89,6 +89,7 @@ abstract class PrepareRunOrTest extends DefaultTask { @InputFile @PathSensitive(PathSensitivity.RELATIVE) + @Optional abstract RegularFileProperty getLegacyClasspathFile(); @Classpath diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java index 4921a31f..3cfcb31c 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionCapabilitiesInternal.java @@ -16,7 +16,7 @@ * @param testFixtures If the NeoForge version for this Minecraft version supports test fixtures. */ public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersion, boolean splitDataRuns, - boolean testFixtures, boolean modLocatorRework) implements VersionCapabilities, Serializable { + boolean testFixtures, boolean modLocatorRework, boolean legacyClasspath) implements VersionCapabilities, Serializable { private static final Logger LOG = Logging.getLogger(VersionCapabilitiesInternal.class); @@ -26,6 +26,7 @@ public record VersionCapabilitiesInternal(String minecraftVersion, int javaVersi // Strips NeoForm timestamp suffixes OR dynamic version markers private static final Pattern NEOFORM_PATTERN = Pattern.compile("^(.*)-(?:\\+|\\d{8}\\.\\d{6})$"); + private static final int MC_1_21_9_INDEX = getReferenceVersionIndex("1.21.9"); private static final int MC_24W45A_INDEX = getReferenceVersionIndex("24w45a"); private static final int MC_1_20_5_INDEX = getReferenceVersionIndex("1.20.5"); private static final int MC_24W14A_INDEX = getReferenceVersionIndex("24w14a"); @@ -56,8 +57,9 @@ public static VersionCapabilitiesInternal ofVersionIndex(int versionIndex, Strin var splitData = hasSplitDataEntrypoints(versionIndex); var testFixtures = hasTestFixtures(versionIndex); var modLocatorRework = hasModLocatorRework(versionIndex); + var legacyClasspath = hasLegacyClasspath(versionIndex); - return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework); + return new VersionCapabilitiesInternal(minecraftVersion, javaVersion, splitData, testFixtures, modLocatorRework, legacyClasspath); } static int getJavaVersion(int versionIndex) { @@ -84,6 +86,10 @@ static boolean hasModLocatorRework(int versionIndex) { return versionIndex <= MC_1_20_5_INDEX; } + static boolean hasLegacyClasspath(int versionIndex) { + return versionIndex > MC_1_21_9_INDEX; + } + static int indexOfNeoForgeVersion(String version) { // NeoForge omits the "1." at the start of the Minecraft version and just adds an incrementing last digit var matcher = NEOFORGE_PATTERN.matcher(version); @@ -168,6 +174,7 @@ public VersionCapabilitiesInternal withMinecraftVersion(String minecraftVersion) javaVersion, splitDataRuns, testFixtures, - modLocatorRework); + modLocatorRework, + legacyClasspath); } }