diff --git a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java index a3f7ef17..64e1c129 100644 --- a/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java +++ b/src/legacy/java/net/neoforged/moddevgradle/legacyforge/internal/LegacyForgeModDevPlugin.java @@ -154,7 +154,8 @@ public void enable(Project project, LegacyForgeModdingSettings settings, LegacyF configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), versionCapabilities, - settings.isDisableRecompilation()); + settings.isDisableRecompilation(), + false); var runs = ModDevRunWorkflow.create( project, diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java index 021719a8..8a071d12 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModDevExtension.java @@ -157,6 +157,10 @@ public void addModdingDependenciesTo(SourceSet sourceSet) { ModDevArtifactsWorkflow.get(project).addToSourceSet(sourceSet); } + public void addModdingDependenciesTo(SourceSet sourceSet,boolean includeClient) { + ModDevArtifactsWorkflow.get(project).addToSourceSet(sourceSet, includeClient); + } + /** * After enabling modding, you can retrieve the version of the modding platform you picked using this getter. * I.e. the NeoForge or Forge version. If you chose to enable vanilla-only mode, this getter will throw. diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModModel.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModModel.java index bc259127..7ef1e12d 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModModel.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModModel.java @@ -15,6 +15,8 @@ public ModModel() { // TODO: We could potentially do a bit of name validation getModSourceSets().convention(List.of()); getModSourceSets().finalizeValueOnRead(); + getModClientSourceSets().convention(List.of()); + getModClientSourceSets().finalizeValueOnRead(); } @Override @@ -23,7 +25,13 @@ public ModModel() { // Do not name getSourceSets or it will conflict with project.sourceSets in scripts. public abstract ListProperty getModSourceSets(); + public abstract ListProperty getModClientSourceSets(); + public void sourceSet(SourceSet sourceSet) { getModSourceSets().add(sourceSet); } + + public void clientSourceSet(SourceSet sourceSet) { + getModClientSourceSets().add(sourceSet); + } } diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java index d6a1da9b..f5282b39 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/ModdingVersionSettings.java @@ -15,6 +15,8 @@ public abstract class ModdingVersionSettings { @Nullable private String neoFormVersion; + private boolean splitDist = false; + private Set enabledSourceSets = new HashSet<>(); private boolean disableRecompilation = "true".equals(System.getenv("CI")); @@ -77,4 +79,16 @@ public boolean isDisableRecompilation() { public void setDisableRecompilation(boolean disableRecompilation) { this.disableRecompilation = disableRecompilation; } + + /** + * Enables the split-distribution source set layout. This creates a {@code client} + * source set and wires it for client-only mod classes. + */ + public boolean isSplitDist() { + return splitDist; + } + + public void setSplitDist(boolean splitDist) { + this.splitDist = splitDist; + } } diff --git a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java index 40e97557..f6e36087 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java @@ -154,7 +154,7 @@ private void addEclipseLaunchConfiguration(Project project, } // This is the actual main launch configuration that launches the game - var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null); + var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null, RunUtils.getRequiredType(project, run)); var config = JavaApplicationLaunchConfig.builder(eclipseProjectName) .vmArgs( RunUtils.escapeJvmArg(RunUtils.getArgFileParameter(prepareTask.getVmArgsFile().get())), @@ -168,12 +168,13 @@ private void addEclipseLaunchConfiguration(Project project, protected static ModFoldersProvider getModFoldersProvider(Project project, Provider> modsProvider, - @Nullable Provider testedMod) { + @Nullable Provider testedMod, + Provider runType) { var folders = RunUtils.buildModFolders(project, modsProvider, testedMod, (sourceSet, output) -> { output.from(RunUtils.findSourceSetProject(project, sourceSet).getProjectDir().toPath() .resolve("bin") .resolve(sourceSet.getName())); - }); + }, runType); var modFoldersProvider = project.getObjects().newInstance(ModFoldersProvider.class); modFoldersProvider.getModFolders().set(folders); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java index 50f08e80..257aa040 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/IntelliJIntegration.java @@ -122,7 +122,7 @@ public void configureTesting(Provider> loadedMods, var intellijVmArgsFile = runArgsDir.map(dir -> dir.file("intellijVmArgs.txt")); var outputDirectory = IntelliJOutputDirectoryValueSource.getIntellijOutputDirectory(project); - var ideSpecificVmArgs = RunUtils.escapeJvmArg(getModFoldersProvider(project, outputDirectory, loadedMods, testedMod).getArgument()); + var ideSpecificVmArgs = RunUtils.escapeJvmArg(getModFoldersProvider(project, outputDirectory, loadedMods, testedMod, project.provider(() -> "client")).getArgument()); try { var vmArgsFilePath = intellijVmArgsFile.get().getAsFile().toPath(); Files.createDirectories(vmArgsFilePath.getParent()); @@ -185,7 +185,7 @@ private static void addIntelliJRunConfiguration(Project project, } appRun.setModuleName(getIntellijModuleName(project, sourceSet)); appRun.setWorkingDirectory(run.getGameDirectory().get().getAsFile().getAbsolutePath()); - var modFoldersProvider = getModFoldersProvider(project, outputDirectory, run.getLoadedMods(), null); + var modFoldersProvider = getModFoldersProvider(project, outputDirectory, run.getLoadedMods(), null, RunUtils.getRequiredType(project, run)); appRun.setEnvs(RunUtils.replaceModClassesEnv(run, modFoldersProvider)); appRun.setJvmArgs( RunUtils.escapeJvmArg(RunUtils.getArgFileParameter(prepareTask.getVmArgsFile().get())) @@ -232,15 +232,16 @@ public boolean shouldUseCombinedSourcesAndClassesArtifact() { private static ModFoldersProvider getModFoldersProvider(Project project, @Nullable Function outputDirectory, Provider> modsProvider, - @Nullable Provider testedMod) { + @Nullable Provider testedMod, + Provider runType) { Provider> folders; if (outputDirectory != null) { folders = RunUtils.buildModFolders(project, modsProvider, testedMod, (sourceSet, output) -> { var sourceSetDir = outputDirectory.apply(RunUtils.findSourceSetProject(project, sourceSet)).toPath().resolve(getIdeaOutName(sourceSet)); output.from(sourceSetDir.resolve("classes"), sourceSetDir.resolve("resources")); - }); + }, runType); } else { - folders = RunUtils.getModFoldersForGradle(project, modsProvider, testedMod); + folders = RunUtils.getModFoldersForGradle(project, modsProvider, testedMod, runType); } var modFoldersProvider = project.getObjects().newInstance(ModFoldersProvider.class); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java index 40b1723d..b383df37 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevArtifactsWorkflow.java @@ -28,6 +28,7 @@ import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaToolchainService; @@ -47,6 +48,9 @@ public record ModDevArtifactsWorkflow( TaskProvider downloadAssets, Configuration runtimeDependencies, Configuration compileDependencies, + Configuration clientRuntimeDependencies, + Configuration clientCompileDependencies, + @Nullable SourceSet splitClientSourceSet, Provider modDevBuildDir, Provider artifactsBuildDir) { @@ -68,7 +72,8 @@ public static ModDevArtifactsWorkflow create(Project project, Configuration accessTransformers, Configuration interfaceInjectionData, VersionCapabilitiesInternal versionCapabilities, - boolean disableRecompilation) { + boolean disableRecompilation, + boolean splitDist) { if (project.getExtensions().findByName(EXTENSION_NAME) != null) { throw new InvalidUserCodeException("You cannot enable modding in the same project twice."); } @@ -115,6 +120,8 @@ public static ModDevArtifactsWorkflow create(Project project, spec.getDependencies().addLater(parchment.getParchmentArtifact().map(dependencyFactory::create)); }); + Function> artifactPathStrategy = artifact -> artifactsBuildDir.map(dir -> dir.file(artifactNamingStrategy.getFilename(artifact))); + // it has to contain client-extra to be loaded by FML, and it must be added to the legacy CP var createArtifacts = tasks.register("createMinecraftArtifacts", CreateMinecraftArtifacts.class, task -> { task.setGroup(branding.internalTaskGroup()); @@ -151,15 +158,26 @@ public static ModDevArtifactsWorkflow create(Project project, task.getParchmentEnabled().set(parchment.getEnabled()); task.getParchmentConflictResolutionPrefix().set(parchment.getConflictResolutionPrefix()); - Function> artifactPathStrategy = artifact -> artifactsBuildDir.map(dir -> dir.file(artifactNamingStrategy.getFilename(artifact))); - task.getIncludeNeoForgeInGameJar().set(versionCapabilities.needsNeoForgeInMinecraftJar()); - task.getGameJarArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); - if (disableRecompilation) { - task.getDisableRecompilation().set(true); + if (splitDist) { + task.getGameCommonJarArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMMON)); + task.getGameClientJarArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT)); + if (disableRecompilation) { + task.getDisableRecompilation().set(true); + } else { + task.getGameCommonJarWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMMON_WITH_SOURCES)); + task.getGameClientJarWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_WITH_SOURCES)); + task.getGameCommonSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMMON_SOURCES)); + task.getGameClientSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_SOURCES)); + } } else { - task.getGameJarWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); - task.getGameSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); + task.getGameJarArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED)); + if (disableRecompilation) { + task.getDisableRecompilation().set(true); + } else { + task.getGameJarWithSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.COMPILED_WITH_SOURCES)); + task.getGameSourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.SOURCES)); + } } if (versionCapabilities.needsNeoForgeInMinecraftJar()) { task.getResourcesArtifact().set(artifactPathStrategy.apply(WorkflowArtifact.CLIENT_RESOURCES)); @@ -188,10 +206,19 @@ public static ModDevArtifactsWorkflow create(Project project, // For IntelliJ, we attach a combined sources+classes artifact which enables an "Attach Sources..." link for IJ users // Otherwise, attaching sources is a pain for IJ users. Provider minecraftClassesDependency; - if (!disableRecompilation && ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + Provider minecraftClientClassesDependency; + if (splitDist && !disableRecompilation && ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { + minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameCommonJarWithSourcesArtifact())).map(dependencyFactory::create); + minecraftClientClassesDependency = createArtifacts.map(task -> project.files(task.getGameClientJarWithSourcesArtifact())).map(dependencyFactory::create); + } else if (splitDist) { + minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameCommonJarArtifact())).map(dependencyFactory::create); + minecraftClientClassesDependency = createArtifacts.map(task -> project.files(task.getGameClientJarArtifact())).map(dependencyFactory::create); + } else if (!disableRecompilation && ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameJarWithSourcesArtifact())).map(dependencyFactory::create); + minecraftClientClassesDependency = null; } else { minecraftClassesDependency = createArtifacts.map(task -> project.files(task.getGameJarArtifact())).map(dependencyFactory::create); + minecraftClientClassesDependency = null; } // Name of the configuration in which we place the required dependencies to develop mods for use in the runtime-classpath. @@ -202,9 +229,9 @@ public static ModDevArtifactsWorkflow create(Project project, config.setCanBeConsumed(false); config.getDependencies().addLater(minecraftClassesDependency); - if (versionCapabilities.needsNeoForgeInMinecraftJar()) { + if (!splitDist && versionCapabilities.needsNeoForgeInMinecraftJar()) { config.getDependencies().addLater(createArtifacts.map(task -> project.files(task.getResourcesArtifact())).map(dependencyFactory::create)); - } else if (moddingDependencies.neoForgeDependency() != null) { + } else if (!versionCapabilities.needsNeoForgeInMinecraftJar() && moddingDependencies.neoForgeDependency() != null) { config.getDependencies().add(moddingDependencies.neoForgeDependency()); } // Technically, the Minecraft dependencies do not strictly need to be on the classpath because they are pulled from the legacy class path. @@ -212,6 +239,18 @@ public static ModDevArtifactsWorkflow create(Project project, config.getDependencies().add(moddingDependencies.gameLibrariesDependency()); }); + var clientRuntimeDependencies = configurations.create("modDevClientRuntimeDependencies", config -> { + config.setDescription("The client-only runtime dependencies to develop a split-distribution mod."); + config.setCanBeResolved(false); + config.setCanBeConsumed(false); + if (splitDist) { + config.getDependencies().addLater(minecraftClientClassesDependency); + if (versionCapabilities.needsNeoForgeInMinecraftJar()) { + config.getDependencies().addLater(createArtifacts.map(task -> project.files(task.getResourcesArtifact())).map(dependencyFactory::create)); + } + } + }); + // Configuration in which we place the required dependencies to develop mods for use in the compile-classpath. // While compile only is not published, we also use a configuration here to be consistent. var compileDependencies = configurations.create("modDevCompileDependencies", config -> { @@ -225,12 +264,39 @@ public static ModDevArtifactsWorkflow create(Project project, } }); + var clientCompileDependencies = configurations.create("modDevClientCompileDependencies", config -> { + config.setDescription("The client-only compile-time dependencies to develop a split-distribution mod."); + config.setCanBeResolved(false); + config.setCanBeConsumed(false); + if (splitDist) { + config.getDependencies().addLater(minecraftClientClassesDependency); + } + }); + // For IDEs that support it, link the source/binary artifacts if we use separated ones if (!disableRecompilation && !ideIntegration.shouldUseCombinedSourcesAndClassesArtifact()) { - ideIntegration.attachSources( - Map.of( - createArtifacts.get().getGameJarArtifact(), - createArtifacts.get().getGameSourcesArtifact())); + if (splitDist) { + ideIntegration.attachSources( + Map.of( + createArtifacts.get().getGameCommonJarArtifact(), + createArtifacts.get().getGameCommonSourcesArtifact(), + createArtifacts.get().getGameClientJarArtifact(), + createArtifacts.get().getGameClientSourcesArtifact())); + } else { + ideIntegration.attachSources( + Map.of( + createArtifacts.get().getGameJarArtifact(), + createArtifacts.get().getGameSourcesArtifact())); + } + } + + SourceSet clientSourceSet = null; + if (splitDist) { + SourceSetContainer sourceSets = ExtensionUtils.getSourceSets(project); + var main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + clientSourceSet = sourceSets.maybeCreate("client"); + clientSourceSet.setCompileClasspath(clientSourceSet.getCompileClasspath().plus(main.getOutput())); + clientSourceSet.setRuntimeClasspath(clientSourceSet.getRuntimeClasspath().plus(main.getOutput())); } var result = new ModDevArtifactsWorkflow( @@ -242,13 +308,20 @@ public static ModDevArtifactsWorkflow create(Project project, downloadAssets, runtimeDependencies, compileDependencies, + clientRuntimeDependencies, + clientCompileDependencies, + clientSourceSet, modDevBuildDir, artifactsBuildDir); project.getExtensions().add(ModDevArtifactsWorkflow.class, EXTENSION_NAME, result); for (var sourceSets : enabledSourceSets) { - result.addToSourceSet(sourceSets); + result.addToSourceSet(sourceSets, !splitDist); + } + + if (clientSourceSet != null) { + result.addToSourceSet(clientSourceSet, true); } return result; @@ -351,14 +424,24 @@ private static List configureArtifactManifestConfigurations( * Adds the compile-time and runtime-dependencies needed to compile mod code to the source-set of the given name. */ public void addToSourceSet(SourceSet sourceSet) { + addToSourceSet(sourceSet, true); + } + + public void addToSourceSet(SourceSet sourceSet, boolean includeClient) { var configurations = project.getConfigurations(); var sourceSets = ExtensionUtils.getSourceSets(project); if (!sourceSets.contains(sourceSet)) { throw new GradleException("Cannot add to the source set in another project: " + sourceSet); } - configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).extendsFrom(runtimeDependencies); - configurations.getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(compileDependencies); + var runtimeClasspath = configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()); + runtimeClasspath.extendsFrom(runtimeDependencies); + var compileClasspath = configurations.getByName(sourceSet.getCompileClasspathConfigurationName()); + compileClasspath.extendsFrom(compileDependencies); + if (includeClient) { + runtimeClasspath.extendsFrom(clientRuntimeDependencies); + compileClasspath.extendsFrom(clientCompileDependencies); + } } public Provider requestAdditionalMinecraftArtifact(String id, String filename) { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index 97f0e7f5..2646c33a 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -98,7 +98,8 @@ public void enable( configurations.getByName(DataFileCollections.CONFIGURATION_ACCESS_TRANSFORMERS), configurations.getByName(DataFileCollections.CONFIGURATION_INTERFACE_INJECTION_DATA), versionCapabilities, - settings.isDisableRecompilation()); + settings.isDisableRecompilation(), + settings.isSplitDist()); ModDevRunWorkflow.create( project, diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java index d6be0f49..e9c8fe21 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevRunWorkflow.java @@ -125,6 +125,8 @@ private ModDevRunWorkflow(Project project, }; } + configureSplitRunSourceSets(project, artifactsWorkflow, runs); + setupRuns( project, branding, @@ -141,6 +143,18 @@ private ModDevRunWorkflow(Project project, versionCapabilities); } + private static void configureSplitRunSourceSets(Project project, ModDevArtifactsWorkflow artifactsWorkflow, DomainObjectCollection runs) { + var clientSourceSet = artifactsWorkflow.splitClientSourceSet(); + if (clientSourceSet == null) { + return; + } + + var mainSourceSet = ExtensionUtils.getSourceSets(project).getByName(SourceSet.MAIN_SOURCE_SET_NAME); + runs.all(run -> run.getSourceSet().convention( + run.getType().map(type -> RunUtils.isClientRunType(type) ? clientSourceSet : mainSourceSet) + .orElse(mainSourceSet))); + } + 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. @@ -272,7 +286,9 @@ public static void setupRuns( 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()); + run.getEnvironment().put( + "MOD_CLASSES", + RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null, RunUtils.getRequiredType(project, run)).getClassesArgument()); } var prepareRunTask = setupRunInGradle( project, @@ -405,7 +421,7 @@ private static TaskProvider setupRunInGradle( task.getVmArgsFile().set(prepareRunTask.get().getVmArgsFile().map(d -> d.getAsFile().getAbsolutePath())); task.getProgramArgsFile().set(prepareRunTask.get().getProgramArgsFile().map(d -> d.getAsFile().getAbsolutePath())); task.getEnvironment().set(run.getEnvironment()); - task.getModFolders().set(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); + task.getModFolders().set(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null, type)); }); createLaunchScriptsTask.configure(task -> task.dependsOn(launchScriptTask)); @@ -429,7 +445,7 @@ private static TaskProvider setupRunInGradle( task.dependsOn(prepareRunTask); task.dependsOn(run.getTasksBefore()); - task.getJvmArgumentProviders().add(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null)); + task.getJvmArgumentProviders().add(RunUtils.getGradleModFoldersProvider(project, run.getLoadedMods(), null, type)); }); return prepareRunTask; @@ -524,7 +540,7 @@ static void setupTestTask(Project project, task.systemProperty("fml.junit.argsfile", programArgsFile.get().getAsFile().getAbsolutePath()); task.jvmArgs(RunUtils.getArgFileParameter(vmArgsFile.get())); - var modFoldersProvider = RunUtils.getGradleModFoldersProvider(project, loadedMods, testedMod); + var modFoldersProvider = RunUtils.getGradleModFoldersProvider(project, loadedMods, testedMod, project.provider(() -> "client")); task.getJvmArgumentProviders().add(modFoldersProvider); }); diff --git a/src/main/java/net/neoforged/moddevgradle/internal/RunUtils.java b/src/main/java/net/neoforged/moddevgradle/internal/RunUtils.java index c913ce82..e81e815d 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/RunUtils.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/RunUtils.java @@ -171,9 +171,12 @@ public static String getArgFileParameter(RegularFile argFile) { return "@" + argFile.getAsFile().getAbsolutePath(); } - public static ModFoldersProvider getGradleModFoldersProvider(Project project, Provider> modsProvider, Provider testedMod) { + public static ModFoldersProvider getGradleModFoldersProvider(Project project, + Provider> modsProvider, + Provider testedMod, + Provider runType) { var modFoldersProvider = project.getObjects().newInstance(ModFoldersProvider.class); - modFoldersProvider.getModFolders().set(getModFoldersForGradle(project, modsProvider, testedMod)); + modFoldersProvider.getModFolders().set(getModFoldersForGradle(project, modsProvider, testedMod, runType)); return modFoldersProvider; } @@ -215,16 +218,18 @@ public static Map replaceModClassesEnv(RunModel model, ModFolder public static Provider> getModFoldersForGradle(Project project, Provider> modsProvider, - @Nullable Provider testedMod) { + @Nullable Provider testedMod, + Provider runType) { return buildModFolders(project, modsProvider, testedMod, (sourceSet, output) -> { output.from(sourceSet.getOutput()); - }); + }, runType); } public static Provider> buildModFolders(Project project, Provider> modsProvider, @Nullable Provider testedModProvider, - BiConsumer outputFolderResolver) { + BiConsumer outputFolderResolver, + Provider runType) { // Convert it to optional to ensure zip will be called even if no mod under test is present. if (testedModProvider == null) { testedModProvider = project.provider(() -> null); @@ -253,6 +258,17 @@ public static Provider> buildModFolders(Project project, outputFolderResolver.accept(sourceSet, modFolder.getFolders()); } + if (isClientRunType(runType.get())) { + var clientSourceSets = mod.getModClientSourceSets().get(); + for (int i = 0; i < clientSourceSets.size(); ++i) { + var sourceSet = clientSourceSets.get(i); + if (clientSourceSets.subList(0, i).contains(sourceSet)) { + throw new InvalidUserCodeException("Duplicate source set '%s' in mod '%s'".formatted(sourceSet.getName(), mod.getName())); + } + outputFolderResolver.accept(sourceSet, modFolder.getFolders()); + } + } + // Add the test source set to the mod under test and if unit tests are enabled if (testedMod.isPresent() && testedMod.get() == mod) { var testSourceSet = ExtensionUtils.getSourceSets(project).findByName(SourceSet.TEST_SOURCE_SET_NAME); @@ -265,6 +281,10 @@ public static Provider> buildModFolders(Project project, })); })); } + + static boolean isClientRunType(String runType) { + return runType.equals("client") || runType.equals("clientData") || runType.equals("data"); + } } abstract class ModFoldersProvider implements CommandLineArgumentProvider { diff --git a/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java b/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java index 342f042b..04073ec0 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/VsCodeIntegration.java @@ -67,7 +67,7 @@ private void addVscodeLaunchConfiguration(Project project, eclipseModel.autoBuildTasks(run.getTasksBefore().toArray()); } - var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null); + var modFoldersProvider = getModFoldersProvider(project, run.getLoadedMods(), null, RunUtils.getRequiredType(project, run)); launchWriter.createGroup("Mod Development - " + project.getName(), WritingMode.REMOVE_EXISTING) .createLaunchConfiguration() .withName(runIdeName) diff --git a/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java index 2d16251d..33c71380 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/WorkflowArtifact.java @@ -6,6 +6,12 @@ public enum WorkflowArtifact { COMPILED(""), COMPILED_WITH_SOURCES("-merged"), + COMMON("-common"), + COMMON_WITH_SOURCES("-common-merged"), + COMMON_SOURCES("-common-sources"), + CLIENT("-client"), + CLIENT_WITH_SOURCES("-client-merged"), + CLIENT_SOURCES("-client-sources"), SOURCES("-sources"), CLIENT_RESOURCES("-client-extra-aka-minecraft-resources"); diff --git a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java index d9f9eb54..d6b6f285 100644 --- a/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java +++ b/src/main/java/net/neoforged/nfrtgradle/CreateMinecraftArtifacts.java @@ -182,6 +182,20 @@ public CreateMinecraftArtifacts() { @Optional public abstract RegularFileProperty getGameJarWithSourcesArtifact(); + /** + * This retrieves the common classes equivalent of {@link #getGameJarWithSourcesArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameCommonJarWithSourcesArtifact(); + + /** + * This retrieves the client-only classes equivalent of {@link #getGameJarWithSourcesArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameClientJarWithSourcesArtifact(); + /** * This retrieves the same as {@link #getGameJarWithSourcesArtifact()}, but doesn't include the sources in the * Jar file. @@ -190,6 +204,20 @@ public CreateMinecraftArtifacts() { @Optional public abstract RegularFileProperty getGameJarArtifact(); + /** + * This retrieves the common classes equivalent of {@link #getGameJarArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameCommonJarArtifact(); + + /** + * This retrieves the client-only classes equivalent of {@link #getGameJarArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameClientJarArtifact(); + /** * This retrieves a Zip-File containing the sources used to compile {@link #getGameJarArtifact()}. */ @@ -197,6 +225,20 @@ public CreateMinecraftArtifacts() { @Optional public abstract RegularFileProperty getGameSourcesArtifact(); + /** + * This retrieves a Zip-File containing the common sources used to compile {@link #getGameCommonJarArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameCommonSourcesArtifact(); + + /** + * This retrieves a Zip-File containing the client-only sources used to compile {@link #getGameClientJarArtifact()}. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getGameClientSourcesArtifact(); + /** * Also known as "client-extra". Contains the non-class files from the original Minecraft jar (excluding META-INF). */ @@ -348,39 +390,41 @@ public void createArtifacts() { boolean includeNeoForgeInGameJar = getIncludeNeoForgeInGameJar().get(); if (getDisableRecompilation().get()) { - if (getGameJarArtifact().isPresent()) { - if (getNeoForgeArtifact().isPresent() && includeNeoForgeInGameJar) { - requestedResults.add(new RequestedResult("gameJarNoRecompWithNeoForge", getGameJarArtifact().get().getAsFile())); - } else { - requestedResults.add(new RequestedResult("gameJarNoRecomp", getGameJarArtifact().get().getAsFile())); - } - } - if (getGameSourcesArtifact().isPresent()) { - throw new IllegalArgumentException("Cannot request game sources if recompilation is disabled."); - } - if (getGameJarWithSourcesArtifact().isPresent()) { - throw new IllegalArgumentException("Cannot request game jar with sources if recompilation is disabled."); + if (getNeoForgeArtifact().isPresent() && includeNeoForgeInGameJar) { + requestIfPresent(requestedResults, "gameJarNoRecompWithNeoForge", getGameJarArtifact()); + requestIfPresent(requestedResults, "gameCommonJarNoRecompWithNeoForge", getGameCommonJarArtifact()); + requestIfPresent(requestedResults, "gameClientJarNoRecompWithNeoForge", getGameClientJarArtifact()); + } else { + requestIfPresent(requestedResults, "gameJarNoRecomp", getGameJarArtifact()); + requestIfPresent(requestedResults, "gameCommonJarNoRecomp", getGameCommonJarArtifact()); + requestIfPresent(requestedResults, "gameClientJarNoRecomp", getGameClientJarArtifact()); } + rejectIfPresent(getGameSourcesArtifact(), "Cannot request game sources if recompilation is disabled."); + rejectIfPresent(getGameCommonSourcesArtifact(), "Cannot request game common sources if recompilation is disabled."); + rejectIfPresent(getGameClientSourcesArtifact(), "Cannot request game client sources if recompilation is disabled."); + rejectIfPresent(getGameJarWithSourcesArtifact(), "Cannot request game jar with sources if recompilation is disabled."); + rejectIfPresent(getGameCommonJarWithSourcesArtifact(), "Cannot request game common jar with sources if recompilation is disabled."); + rejectIfPresent(getGameClientJarWithSourcesArtifact(), "Cannot request game client jar with sources if recompilation is disabled."); } else if (getNeoForgeArtifact().isPresent() && includeNeoForgeInGameJar) { - if (getGameJarArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameJarWithNeoForge", getGameJarArtifact().get().getAsFile())); - } - if (getGameSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameSourcesWithNeoForge", getGameSourcesArtifact().get().getAsFile())); - } - if (getGameJarWithSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameJarWithSourcesAndNeoForge", getGameJarWithSourcesArtifact().get().getAsFile())); - } + requestIfPresent(requestedResults, "gameJarWithNeoForge", getGameJarArtifact()); + requestIfPresent(requestedResults, "gameCommonJarWithNeoForge", getGameCommonJarArtifact()); + requestIfPresent(requestedResults, "gameClientJarWithNeoForge", getGameClientJarArtifact()); + requestIfPresent(requestedResults, "gameSourcesWithNeoForge", getGameSourcesArtifact()); + requestIfPresent(requestedResults, "gameCommonSourcesWithNeoForge", getGameCommonSourcesArtifact()); + requestIfPresent(requestedResults, "gameClientSourcesWithNeoForge", getGameClientSourcesArtifact()); + requestIfPresent(requestedResults, "gameJarWithSourcesAndNeoForge", getGameJarWithSourcesArtifact()); + requestIfPresent(requestedResults, "gameCommonJarWithSourcesAndNeoForge", getGameCommonJarWithSourcesArtifact()); + requestIfPresent(requestedResults, "gameClientJarWithSourcesAndNeoForge", getGameClientJarWithSourcesArtifact()); } else { - if (getGameJarArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameJar", getGameJarArtifact().get().getAsFile())); - } - if (getGameSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameSources", getGameSourcesArtifact().get().getAsFile())); - } - if (getGameJarWithSourcesArtifact().isPresent()) { - requestedResults.add(new RequestedResult("gameJarWithSources", getGameJarWithSourcesArtifact().get().getAsFile())); - } + requestIfPresent(requestedResults, "gameJar", getGameJarArtifact()); + requestIfPresent(requestedResults, "gameCommonJar", getGameCommonJarArtifact()); + requestIfPresent(requestedResults, "gameClientJar", getGameClientJarArtifact()); + requestIfPresent(requestedResults, "gameSources", getGameSourcesArtifact()); + requestIfPresent(requestedResults, "gameCommonSources", getGameCommonSourcesArtifact()); + requestIfPresent(requestedResults, "gameClientSources", getGameClientSourcesArtifact()); + requestIfPresent(requestedResults, "gameJarWithSources", getGameJarWithSourcesArtifact()); + requestIfPresent(requestedResults, "gameCommonJarWithSources", getGameCommonJarWithSourcesArtifact()); + requestIfPresent(requestedResults, "gameClientJarWithSources", getGameClientJarWithSourcesArtifact()); } // Request that NFRT write all these results where we want them to be written to @@ -418,5 +462,17 @@ private void reportProblems(File problemsReport) { } } + private static void requestIfPresent(List requestedResults, String id, RegularFileProperty property) { + if (property.isPresent()) { + requestedResults.add(new RequestedResult(id, property.get().getAsFile())); + } + } + + private static void rejectIfPresent(RegularFileProperty property, String message) { + if (property.isPresent()) { + throw new IllegalArgumentException(message); + } + } + record RequestedResult(String id, File destination) {} } diff --git a/testproject/settings.gradle b/testproject/settings.gradle index 952a551e..56ce33d8 100644 --- a/testproject/settings.gradle +++ b/testproject/settings.gradle @@ -21,5 +21,6 @@ include 'subproject' include 'common' include 'jijtest' include 'coremod' +include 'split' enableFeaturePreview "STABLE_CONFIGURATION_CACHE" diff --git a/testproject/split/build.gradle b/testproject/split/build.gradle new file mode 100644 index 00000000..8e954e92 --- /dev/null +++ b/testproject/split/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'net.neoforged.moddev' + id 'maven-publish' + id 'java' +} + +repositories { + mavenLocal() +} + +neoForge { + enable { + version = project.neoforge_version + splitDist = true + } + runs { + client { + client() + } + + server { + server() + } + } + mods { + splittest { + sourceSet sourceSets.main + clientSourceSet sourceSets.client + } + } +} + + +neoFormRuntime { + version = "2.0.19-split-dist" +} diff --git a/testproject/split/src/client/java/split/client/ClientMain.java b/testproject/split/src/client/java/split/client/ClientMain.java new file mode 100644 index 00000000..c9f6c398 --- /dev/null +++ b/testproject/split/src/client/java/split/client/ClientMain.java @@ -0,0 +1,16 @@ +package split.client; + +import net.minecraft.client.Minecraft; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.common.Mod; +import split.SplitMain; + +@Mod(value = "splittest", dist = Dist.CLIENT) +public class ClientMain { + + public ClientMain() { + Minecraft instance = Minecraft.getInstance(); + System.out.println("from client"); + System.out.println(SplitMain.class); + } +} diff --git a/testproject/split/src/main/java/split/SplitMain.java b/testproject/split/src/main/java/split/SplitMain.java new file mode 100644 index 00000000..e6a80eb5 --- /dev/null +++ b/testproject/split/src/main/java/split/SplitMain.java @@ -0,0 +1,8 @@ +package split; + +import net.neoforged.fml.common.Mod; + +@Mod("splittest") +public class SplitMain { + public SplitMain() {} +} diff --git a/testproject/split/src/main/resources/META-INF/neoforge.mods.toml b/testproject/split/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..5a0ed839 --- /dev/null +++ b/testproject/split/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,21 @@ +license="CC0" + +[[mods]] +modId="splittest" +version="0.0.0" +displayName="Test Split Project" +description='''A test project.''' + +[[dependencies.splittest]] +modId="neoforge" +type="required" +versionRange="[21.10.0-beta,)" +ordering="NONE" +side="BOTH" + +[[dependencies.splittest]] +modId="minecraft" +type="required" +versionRange="[1.21.10]" +ordering="NONE" +side="BOTH"