From 8b21978895078b1fca2ae203536771bda9b87b6d Mon Sep 17 00:00:00 2001 From: Marc Hermans Date: Fri, 30 Aug 2024 18:39:58 +0200 Subject: [PATCH] Initial working and booting version --- .../specification/NeoFormSpecification.groovy | 11 + .../NeoFormProjectConfiguration.groovy | 19 + .../RuntimeProjectConfiguration.groovy | 55 ++ .../extensions/NeoFormRuntimeExtension.java | 78 +- .../NeoFormRuntimeSpecification.java | 24 +- .../runtime/tasks/DistOnlyInjector.java | 179 +++++ .../neoform/runtime/tasks/StripJar.java | 68 +- .../extensions/DynamicProjectExtension.java | 713 +++++++++++++----- .../RuntimeDevRuntimeDefinition.java | 24 +- .../tasks/SetupProjectFromRuntime.java | 12 - .../gradle/platform/util/SetupUtils.java | 17 +- 11 files changed, 957 insertions(+), 243 deletions(-) create mode 100644 dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/NeoFormProjectConfiguration.groovy create mode 100644 dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/RuntimeProjectConfiguration.groovy create mode 100644 neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/DistOnlyInjector.java diff --git a/dsl/neoform/src/main/groovy/net/neoforged/gradle/dsl/neoform/runtime/specification/NeoFormSpecification.groovy b/dsl/neoform/src/main/groovy/net/neoforged/gradle/dsl/neoform/runtime/specification/NeoFormSpecification.groovy index 448afde4e..70ba6b395 100644 --- a/dsl/neoform/src/main/groovy/net/neoforged/gradle/dsl/neoform/runtime/specification/NeoFormSpecification.groovy +++ b/dsl/neoform/src/main/groovy/net/neoforged/gradle/dsl/neoform/runtime/specification/NeoFormSpecification.groovy @@ -2,6 +2,7 @@ package net.neoforged.gradle.dsl.neoform.runtime.specification import groovy.transform.CompileStatic import net.neoforged.gradle.dsl.common.runtime.spec.Specification +import net.neoforged.gradle.dsl.common.util.DistributionType import org.gradle.api.file.FileCollection import org.jetbrains.annotations.NotNull; @@ -62,5 +63,15 @@ interface NeoFormSpecification extends Specification { */ @NotNull B withAdditionalDependencies(@NotNull final FileCollection files); + + /** + * Removes all common elements from the output of this runtime. + * This means that code shared between the client and server distribution types will be removed. + * + * @return The builder. + * @implNote If this is activated yet {@link DistributionType#CLIENT} is not used as the distribution type, then an exception will be thrown on spec construction. + */ + @NotNull + B removeCommonElements() } } diff --git a/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/NeoFormProjectConfiguration.groovy b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/NeoFormProjectConfiguration.groovy new file mode 100644 index 000000000..2f8bc242b --- /dev/null +++ b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/NeoFormProjectConfiguration.groovy @@ -0,0 +1,19 @@ +package net.neoforged.gradle.dsl.platform.dynamic + +import groovy.transform.CompileStatic +import net.minecraftforge.gdi.ConfigurableDSLElement +import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.provider.Property + +/** + * Represents the configuration for a NeoForm dynamic project. + */ +@CompileStatic +abstract class NeoFormProjectConfiguration implements ConfigurableDSLElement { + + /** + * @return True if the source sets should be split based on distribution, false otherwise. + */ + @DSLProperty + abstract Property getSplitSourceSets(); +} diff --git a/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/RuntimeProjectConfiguration.groovy b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/RuntimeProjectConfiguration.groovy new file mode 100644 index 000000000..0679bc80c --- /dev/null +++ b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/dynamic/RuntimeProjectConfiguration.groovy @@ -0,0 +1,55 @@ +package net.neoforged.gradle.dsl.platform.dynamic + +import groovy.transform.CompileStatic +import net.minecraftforge.gdi.BaseDSLElement +import net.minecraftforge.gdi.ConfigurableDSLElement +import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Optional + +import javax.inject.Inject + +@CompileStatic +abstract class RuntimeProjectConfiguration implements BaseDSLElement { + + private final Project project + + @Inject + RuntimeProjectConfiguration(Project project) { + this.project = project + } + + Project getProject() { + return project + } + + @DSLProperty + abstract Property getNeoFormVersion() + + /** + * @return The directory containing the patches to apply to the project. + */ + @DSLProperty + abstract DirectoryProperty getPatches() + + /** + * @return The directory containing the rejects from applying patches to the project. + */ + @DSLProperty + abstract DirectoryProperty getRejects() + + /** + * @return The directory containing the legacy patches to migrate when split source sets are enabled. + */ + @Optional + @DSLProperty + abstract DirectoryProperty getLegacyPatches() + + /** + * @return True if the source sets should be split based on distribution, false otherwise. + */ + @DSLProperty + abstract Property getSplitSourceSets(); +} diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java index 10702831e..f40f0af26 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java @@ -34,6 +34,7 @@ import net.neoforged.gradle.util.TransformerUtils; import org.apache.commons.lang3.StringUtils; import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileCollection; @@ -191,18 +192,12 @@ private static TaskProvider createDecompile(NeoFormRuntimeSpe } private static String getDecompilerLogLevelArg(DecompilerLogLevel logLevel, String version) { - switch (logLevel) { - case TRACE: - return "trace"; - case INFO: - return "info"; - case WARN: - return "warn"; - case ERROR: - return "error"; - default: - throw new GradleException("LogLevel " + logLevel + " not supported by " + version); - } + return switch (logLevel) { + case TRACE -> "trace"; + case INFO -> "info"; + case WARN -> "warn"; + case ERROR -> "error"; + }; } private TaskProvider createExecute(final NeoFormRuntimeSpecification spec, final NeoFormConfigConfigurationSpecV1.Step step, final NeoFormConfigConfigurationSpecV1.Function function) { @@ -283,7 +278,6 @@ protected NeoFormRuntimeDefinition doCreate(final NeoFormRuntimeSpecification sp spec.getProject().getDependencies().create(library) )); - final Map symbolicDataSources = buildDataFilesMap(neoFormConfig, spec.getDistribution()); final TaskProvider sourceJarTask = spec.getProject().getTasks().register("supplySourcesFor" + spec.getIdentifier(), ArtifactProvider.class, task -> { @@ -441,11 +435,24 @@ protected void bakeDefinition(NeoFormRuntimeDefinition definition) { context.getLibrariesTask().flatMap(WithOutput::getOutput) ); + if (spec.noCommonElements()) { + if (!spec.getDistribution().isClient()) { + throw new InvalidUserDataException("Common elements can only be stripped from the client or joined distribution"); + } + + TaskProvider distInjectionInput = recompileInput; + recompileInput = spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "injectDistMarkers"), DistOnlyInjector.class, task -> { + task.getInputFile().set(distInjectionInput.flatMap(WithOutput::getOutput)); + configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, task); + }); + } + final FileCollection recompileDependencies = spec.getAdditionalRecompileDependencies().plus(spec.getProject().files(definition.getMinecraftDependenciesConfiguration())); + TaskProvider unpackSourcesInput = recompileInput; final TaskProvider unpackSources = spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "unzipSources"), UnpackZip.class, task -> { task.getInput().from( - recompileInput.flatMap(WithOutput::getOutput) + unpackSourcesInput.flatMap(WithOutput::getOutput) .map(sourceJar -> task.getArchiveOperations().zipTree(sourceJar).matching(sp -> sp.include("**/*.java")).getAsFileTree()) ); }); @@ -476,21 +483,58 @@ protected void bakeDefinition(NeoFormRuntimeDefinition definition) { recompileTask.configure(neoFormRuntimeTask -> configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, neoFormRuntimeTask)); + TaskProvider packTaskInput = recompileInput; final TaskProvider packTask = spec.getProject() .getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "packRecomp"), PackJar.class, task -> { - task.getInputFiles().from(recompileInput.flatMap(WithOutput::getOutput).map(task.getArchiveOperations()::zipTree).map(zipTree -> zipTree.matching(sp -> sp.exclude("**/*.java")))); + task.getInputFiles().from(packTaskInput.flatMap(WithOutput::getOutput).map(task.getArchiveOperations()::zipTree).map(zipTree -> zipTree.matching(sp -> sp.exclude("**/*.java")))); task.getInputFiles().from(recompileTask.flatMap(AbstractCompile::getDestinationDirectory)); }); packTask.configure(neoFormRuntimeTask -> configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, neoFormRuntimeTask)); taskOutputs.put(recompileTask.getName(), packTask); + //When we are running in split sourceset mode then we need to strip the common elements from the source and raw jars + //This is indicated by the noCommonElements flag in the spec, however we can only do this for the client, any other + //Configuration is invalid. + final TaskProvider rawSource; + final TaskProvider sourceSource; + if (spec.noCommonElements()) { + if (!spec.getDistribution().isClient()) { + throw new InvalidUserDataException("Common elements can only be stripped from the client or joined distribution"); + } + + //Reuse existing split tasks, in blacklist mode. + rawSource = spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "removeCommonRaw"), StripJar.class, task -> { + task.getInput().set(packTask.flatMap(WithOutput::getOutput)); + task.getIsWhitelistMode().set(false); + task.getMappingsFiles().setFrom(definition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_MAPPINGS)); + task.getFileExtension().set("class"); + task.convertNamesToPaths(); + + configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, task); + }); + + TaskProvider sourceSourceInput = recompileInput; + sourceSource = spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, "removeCommonSource"), StripJar.class, task -> { + task.getInput().set(sourceSourceInput.flatMap(WithOutput::getOutput)); + task.getIsWhitelistMode().set(false); + task.getMappingsFiles().setFrom(definition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_MAPPINGS)); + task.getFileExtension().set("java"); + task.convertNamesToPaths(); + + configureMcpRuntimeTaskWithDefaults(spec, neoFormDirectory, symbolicDataSources, task); + }); + } else { + rawSource = packTask; + sourceSource = recompileInput; + } + definition.getSourceJarTask().configure(task -> { - task.getInputFiles().from(recompileInput); + task.getInputFiles().from(sourceSource.flatMap(WithOutput::getOutput)); task.dependsOn(remapTask); }); definition.getRawJarTask().configure(task -> { - task.getInputFiles().from(packTask.flatMap(WithOutput::getOutput)); + task.getInputFiles().from(rawSource.flatMap(WithOutput::getOutput)); task.dependsOn(packTask); }); } diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/specification/NeoFormRuntimeSpecification.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/specification/NeoFormRuntimeSpecification.java index 9fc5b45b7..1932b24ae 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/specification/NeoFormRuntimeSpecification.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/specification/NeoFormRuntimeSpecification.java @@ -38,6 +38,7 @@ public class NeoFormRuntimeSpecification extends CommonRuntimeSpecification impl private final Provider neoFormArchive; private final NeoFormConfigConfigurationSpecV2 config; private final FileCollection additionalRecompileDependencies; + private final boolean noCommonElements; private NeoFormRuntimeSpecification(Project project, String version, @@ -47,11 +48,12 @@ private NeoFormRuntimeSpecification(Project project, Multimap preTaskTypeAdapters, Multimap postTypeAdapters, Multimap> taskCustomizers, - FileCollection additionalRecompileDependencies) { + FileCollection additionalRecompileDependencies, boolean noCommonElements) { super(project, "neoForm", version, side, preTaskTypeAdapters, postTypeAdapters, taskCustomizers, NeoFormRuntimeExtension.class); this.neoFormArchive = neoFormArchive; this.config = config; this.additionalRecompileDependencies = additionalRecompileDependencies; + this.noCommonElements = noCommonElements; } public NeoFormConfigConfigurationSpecV2 getConfig() { @@ -80,6 +82,10 @@ public Provider getNeoFormArchive() { return additionalRecompileDependencies; } + public boolean noCommonElements() { + return noCommonElements; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -104,6 +110,7 @@ public static final class Builder extends CommonRuntimeSpecification.Builder getCacheService(); + + @TaskAction + protected void run() throws Throwable { + getCacheService().get().cached( + this, + ICacheableJob.Default.file(getOutput(), this::doRun) + ).execute(); + } + + private void doRun() throws Exception { + final File outputFile = ensureFileWorkspaceReady(getOutput()); + + final FileTree inputFiles = getArchiveOperations().zipTree(getInputFile()); + try (final FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + final ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) { + final AdaptingZipBuildingFileTreeVisitor visitor = new AdaptingZipBuildingFileTreeVisitor( + zipOutputStream, + new InjectingFileAdapter(getDistEnumClass().get(), getDistMarkerClass().get(), getDistEnumValue().get()) + ); + inputFiles.visit(visitor); + } + } + + @InputFile + @PathSensitive(PathSensitivity.NONE) + public abstract RegularFileProperty getInputFile(); + + @Input + public abstract Property getDistEnumClass(); + + @Input + public abstract Property getDistEnumValue(); + + @Input + public abstract Property getDistMarkerClass(); + + @Input + public abstract Property getDummy(); + + private static final class InjectingFileAdapter implements BiConsumer { + + private final String distEnumClass; + private final String distMarkerClass; + + private final String distEnumClassName; + private final String distMarkerClassName; + + private final String distEnumValue; + + private InjectingFileAdapter(String distEnumClass, String distMarkerClass, String distEnumValue) { + this.distEnumClass = distEnumClass; + this.distMarkerClass = distMarkerClass; + this.distEnumClassName = distEnumClass.substring(distEnumClass.lastIndexOf('.') + 1); + this.distMarkerClassName = distMarkerClass.substring(distMarkerClass.lastIndexOf('.') + 1); + this.distEnumValue = distEnumValue; + } + + @Override + public void accept(FileVisitDetails fileVisitDetails, OutputStream outputStream) { + try { + inject(fileVisitDetails.open(), outputStream); + } catch (IOException e) { + throw new IllegalStateException("Failed to inject into file: " + fileVisitDetails.getName(), e); + } + } + + private void inject(final InputStream inputStream, final OutputStream outputStream) throws IOException { + final InputStreamReader reader = new InputStreamReader(inputStream); + final BufferedReader bufferedReader = new BufferedReader(reader); + final List lines = bufferedReader.lines().toList(); + final List injectedLines = inject(lines); + + final OutputStreamWriter writer = new OutputStreamWriter(outputStream); + final BufferedWriter bufferedWriter = new BufferedWriter(writer); + for (String line : injectedLines) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + + bufferedWriter.flush(); + //Close is not needed due to how zip processing works. + } + + private List inject(final List lines) { + final List result = new ArrayList<>(); + + boolean firstDeclarationFound = false; + for (final String line : lines) { + final boolean isDeclarationLine = isDeclarationLine(line); + if (!firstDeclarationFound) { + if (isDeclarationLine) { + firstDeclarationFound = true; + injectBeforeLastMatching(result, "import " + distEnumClass + ";", InjectingFileAdapter::isInjectionPoint); + injectBeforeLastMatching(result, "import " + distMarkerClass + ";", InjectingFileAdapter::isInjectionPoint); + } + } + + if (isDeclarationLine) { + result.add("%s@%s(%s.%s)".formatted( + getIndent(line), + distMarkerClassName, + distEnumClassName, + distEnumValue + )); + } + + result.add(line); + } + + return result; + } + + private static String getIndent(String line) { + final StringBuilder indent = new StringBuilder(); + for (char c : line.toCharArray()) { + if (Character.isWhitespace(c)) { + indent.append(c); + } else { + break; + } + } + + return indent.toString(); + } + + private static void injectBeforeLastMatching(final List lines, final String lineToInject, Predicate matcher) { + for (int i = lines.size() - 1; i >= 0; i--) { + final String line = lines.get(i); + if (matcher.test(line)) { + lines.add(i + 1, lineToInject); + return; + } + } + + lines.add(lineToInject); + } + + private static boolean isInjectionPoint(final String line) { + return !line.isBlank() && !line.trim().startsWith("@") && !line.trim().startsWith("import org"); + } + + private static boolean isDeclarationLine(final String line) { + return line.contains(" class ") || line.contains(" interface ") || line.contains(" enum ") || line.contains(" record ") || + line.startsWith("class ") || line.startsWith("interface ") || line.startsWith("enum ") || line.startsWith("record "); + } + } +} diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/StripJar.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/StripJar.java index d5af14e27..bad00d88f 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/StripJar.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/tasks/StripJar.java @@ -1,5 +1,6 @@ package net.neoforged.gradle.neoform.runtime.tasks; +import groovy.cli.Option; import net.neoforged.gradle.common.runtime.tasks.DefaultRuntime; import net.neoforged.gradle.common.services.caching.CachedExecutionService; import net.neoforged.gradle.common.services.caching.jobs.ICacheableJob; @@ -27,22 +28,36 @@ public abstract class StripJar extends DefaultRuntime { public StripJar() { super(); + getFileExtension().convention("class"); getMappingsFiles().from(getRuntimeData().map(data -> data.get("mappings"))); getIsWhitelistMode().convention(true); getFilters().convention( - getProject().provider(() -> { - if (getMappingsFiles().isEmpty()) { - return null; - } - - return getMappingsFiles().getFiles() - .stream() - .flatMap(file -> FileUtils.readAllLines(file.toPath())) - .filter(l -> !l.startsWith("\t")) - .map(s -> s.split(" ")[0] + ".class") - .distinct() - .collect(Collectors.toList()); - }) + getSeparatorReplacements().flatMap(replacements -> + getFileExtension().map(extension -> { + if (getMappingsFiles().isEmpty()) { + return null; + } + + return getMappingsFiles().getFiles() + .stream() + .flatMap(file -> FileUtils.readAllLines(file.toPath())) + .filter(l -> !l.startsWith("\t")) //Filter all field and method mappings + .filter(l -> !l.startsWith("#")) //Filter out comments + .map(s -> s.split(" ")[0]) + .map(s -> { + if (replacements.isEmpty()) + return s; + + for (SeparatorReplacement replacement : replacements) { + s = s.replace(replacement.source(), replacement.replacement()); + } + + return s; + }) + .map(s -> s + "." + extension) + .distinct() + .collect(Collectors.toList()); + })) ); getIsWhitelistMode().finalizeValueOnRead(); @@ -110,6 +125,33 @@ private boolean isEntryValid(JarEntry entry, boolean whitelist) { @Optional public abstract ListProperty getFilters(); + @Input + @Optional + public abstract Property getFileExtension(); + @Input public abstract Property getIsWhitelistMode(); + + @Nested + @Optional + public abstract ListProperty getSeparatorReplacements(); + + public void convertNamesToPaths() { + getSeparatorReplacements().add(SeparatorReplacement.NAME_TO_PATH); + } + + public record SeparatorReplacement(String source, String replacement) { + + public static final SeparatorReplacement NAME_TO_PATH = new SeparatorReplacement(".", "/"); + + @Override + public String source() { + return source; + } + + @Override + public String replacement() { + return replacement; + } + } } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java index f60004e8d..d720733b9 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java @@ -4,6 +4,8 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import groovy.lang.Closure; +import groovy.transform.stc.ClosureParams; import net.minecraftforge.gdi.BaseDSLElement; import net.minecraftforge.gdi.annotations.DSLProperty; import net.minecraftforge.gdi.annotations.ProjectGetter; @@ -23,6 +25,7 @@ import net.neoforged.gradle.common.util.ToolUtilities; import net.neoforged.gradle.dsl.common.extensions.AccessTransformers; import net.neoforged.gradle.dsl.common.extensions.Mappings; +import net.neoforged.gradle.dsl.common.extensions.sourceset.SourceSetDependencyExtension; import net.neoforged.gradle.dsl.common.runs.run.Run; import net.neoforged.gradle.dsl.common.runs.run.RunManager; import net.neoforged.gradle.dsl.common.runs.type.RunType; @@ -31,6 +34,8 @@ import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime; import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.common.util.*; +import net.neoforged.gradle.dsl.platform.dynamic.NeoFormProjectConfiguration; +import net.neoforged.gradle.dsl.platform.dynamic.RuntimeProjectConfiguration; import net.neoforged.gradle.dsl.platform.model.InstallerProfile; import net.neoforged.gradle.dsl.platform.model.LauncherProfile; import net.neoforged.gradle.dsl.platform.model.Library; @@ -53,23 +58,29 @@ import net.neoforged.gradle.platform.tasks.*; import net.neoforged.gradle.platform.util.ArtifactPathsCollector; import net.neoforged.gradle.platform.util.SetupUtils; +import net.neoforged.gradle.util.FileUtils; import net.neoforged.gradle.util.TransformerUtils; import net.neoforged.gradle.vanilla.VanillaProjectPlugin; import net.neoforged.gradle.vanilla.runtime.VanillaRuntimeDefinition; import net.neoforged.gradle.vanilla.runtime.extensions.VanillaRuntimeExtension; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.Directory; import org.gradle.api.file.FileTree; +import org.gradle.api.file.FileTreeElement; import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.FeatureSpec; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; @@ -82,6 +93,7 @@ import java.io.File; import java.net.URI; import java.nio.file.Files; +import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -92,66 +104,86 @@ import static net.neoforged.gradle.dsl.common.util.Constants.DEFAULT_PARCHMENT_GROUP; public abstract class DynamicProjectExtension implements BaseDSLElement { - + private final Project project; - + @Nullable private DynamicProjectType type = null; - + @Inject public DynamicProjectExtension(Project project) { this.project = project; this.getIsUpdating().convention(getProviderFactory().gradleProperty("updating").map(Boolean::valueOf).orElse(false)); - + //All dynamic projects expose information from themselves as a library. Cause they are. project.getPlugins().apply("java-library"); } - + @ProjectGetter @Override public Project getProject() { return project; } - + public void clean() { clean("+"); } - + public void clean(final String minecraftVersion) { type = DynamicProjectType.CLEAN; - + project.getPlugins().apply(VanillaProjectPlugin.class); - + final JavaPluginExtension javaPluginExtension = getProject().getExtensions().getByType(JavaPluginExtension.class); final SourceSet mainSource = javaPluginExtension.getSourceSets().getByName("main"); - + final VanillaRuntimeExtension vanillaRuntimeExtension = project.getExtensions().getByType(VanillaRuntimeExtension.class); final VanillaRuntimeDefinition runtimeDefinition = vanillaRuntimeExtension.create(builder -> builder.withMinecraftVersion(minecraftVersion).withDistributionType(DistributionType.CLIENT).withFartVersion(vanillaRuntimeExtension.getFartVersion()).withForgeFlowerVersion(vanillaRuntimeExtension.getVineFlowerVersion()).withAccessTransformerApplierVersion(vanillaRuntimeExtension.getAccessTransformerApplierVersion())); - + project.getTasks().named(mainSource.getCompileJavaTaskName()).configure(task -> task.setEnabled(false)); - + configureSetupTasks(runtimeDefinition.getSourceJarTask().flatMap(WithOutput::getOutput), mainSource, runtimeDefinition.getMinecraftDependenciesConfiguration()); } - + public void neoform() { //Accept any version of NeoForm. Aka the latest will always work. neoform("+"); } - + public void neoform(final String neoFormVersion) { + neoform(neoFormVersion, null); + } + + public void neoform(final String neoFormVersion, @Nullable final Action configurator) { type = DynamicProjectType.NEO_FORM; - + + final NeoFormProjectConfiguration configuration = project.getObjects().newInstance(NeoFormProjectConfiguration.class); + configuration.getSplitSourceSets().convention(false); + if (configurator != null) { + configurator.execute(configuration); + } + + final boolean splitSourceSets = configuration.getSplitSourceSets().get(); + project.getPlugins().apply(NeoFormProjectPlugin.class); - + final JavaPluginExtension javaPluginExtension = getProject().getExtensions().getByType(JavaPluginExtension.class); final SourceSet mainSource = javaPluginExtension.getSourceSets().getByName("main"); - - final NeoFormRuntimeExtension neoFormRuntimeExtension = project.getExtensions().getByType(NeoFormRuntimeExtension.class); - final NeoFormRuntimeDefinition runtimeDefinition = neoFormRuntimeExtension.create(builder -> { - builder.withNeoFormVersion(neoFormVersion).withDistributionType(DistributionType.JOINED); - + final SourceSet clientSource = splitSourceSets ? javaPluginExtension.getSourceSets().maybeCreate("client") : mainSource; + + final NeoFormRuntimeExtension mainRuntimeExtension = project.getExtensions().getByType(NeoFormRuntimeExtension.class); + final NeoFormRuntimeDefinition runtimeDefinition = mainRuntimeExtension.create(builder -> { + builder.withNeoFormVersion(neoFormVersion).withDistributionType( + splitSourceSets ? DistributionType.SERVER : DistributionType.JOINED + ); + NeoFormRuntimeUtils.configureDefaultRuntimeSpecBuilder(project, builder); }); + final NeoFormRuntimeDefinition clientRuntimeDefinition = splitSourceSets ? mainRuntimeExtension.create(builder -> { + builder.withNeoFormVersion(neoFormVersion).withDistributionType(DistributionType.CLIENT).removeCommonElements(); + + NeoFormRuntimeUtils.configureDefaultRuntimeSpecBuilder(project, builder); + }) : runtimeDefinition; final var parchmentArtifact = getParchment().map(parch -> { var split = parch.split("-"); @@ -161,46 +193,114 @@ public void neoform(final String neoFormVersion) { + "@zip"; }); - TaskProvider sourcesTask = runtimeDefinition.getSourceJarTask(); + TaskProvider mainSourcesTask = runtimeDefinition.getSourceJarTask(); + TaskProvider clientSourcesTask = splitSourceSets ? clientRuntimeDefinition.getSourceJarTask() : mainSourcesTask; if (parchmentArtifact.isPresent()) { - sourcesTask = RuntimeDevRuntimeExtension.applyParchment( + mainSourcesTask = RuntimeDevRuntimeExtension.applyParchment( getProject(), "applyParchment", getProject().provider(() -> ToolUtilities.resolveTool(getProject(), parchmentArtifact.get())), getProject().provider(() -> "p_"), - sourcesTask.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile), + mainSourcesTask.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile), true, runtimeDefinition.getSpecification(), project.getLayout().getBuildDirectory().dir(String.format("neoForm/%s", neoFormVersion)).get().getAsFile(), null ); + + if (splitSourceSets) { + clientSourcesTask = RuntimeDevRuntimeExtension.applyParchment( + getProject(), + "applyParchment", + getProject().provider(() -> ToolUtilities.resolveTool(getProject(), parchmentArtifact.get())), + getProject().provider(() -> "p_"), + clientSourcesTask.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile), + true, + clientRuntimeDefinition.getSpecification(), + project.getLayout().getBuildDirectory().dir(String.format("neoForm/%s", neoFormVersion)).get().getAsFile(), + null + ); + } + } + + configureSetupTasks(mainSourcesTask.flatMap(WithOutput::getOutput), mainSource, runtimeDefinition.getMinecraftDependenciesConfiguration()); + if (splitSourceSets) { + configureSetupTasks(clientSourcesTask.flatMap(WithOutput::getOutput), clientSource, clientRuntimeDefinition.getMinecraftDependenciesConfiguration()); } - - configureSetupTasks(sourcesTask.flatMap(WithOutput::getOutput), mainSource, runtimeDefinition.getMinecraftDependenciesConfiguration()); } - + public void runtime(final String neoFormVersion) { runtime(neoFormVersion, project.getRootProject().getLayout().getProjectDirectory().dir("patches"), project.getRootProject().getLayout().getProjectDirectory().dir("rejects")); } - + public void runtime(final String neoFormVersion, Directory patches, Directory rejects) { + runtime(runtimeProjectConfiguration -> { + runtimeProjectConfiguration.getNeoFormVersion().set(neoFormVersion); + runtimeProjectConfiguration.getPatches().set(patches); + runtimeProjectConfiguration.getRejects().set(rejects); + }); + } + + public void runtime(final Closure configureClosure) { + this.runtime(configureClosure != null ? configureClosure::call : null); + } + + public void runtime(@Nullable final Action configurator) { type = DynamicProjectType.RUNTIME; - + + final RuntimeProjectConfiguration configuration = project.getObjects().newInstance(RuntimeProjectConfiguration.class); + configuration.getPatches().convention(project.getRootProject().getLayout().getProjectDirectory().dir("patches")); + configuration.getRejects().convention(project.getRootProject().getLayout().getProjectDirectory().dir("rejects")); + configuration.getSplitSourceSets().convention(false); + if (configurator != null) { + configurator.execute(configuration); + } + + final String neoFormVersion = configuration.getNeoFormVersion().get(); + final boolean splitSourceSets = configuration.getSplitSourceSets().get(); + project.getPlugins().apply(PlatformDevProjectPlugin.class); - + final JavaPluginExtension javaPluginExtension = getProject().getExtensions().getByType(JavaPluginExtension.class); + + javaPluginExtension.withSourcesJar(); + javaPluginExtension.withJavadocJar(); + final SourceSet mainSource = javaPluginExtension.getSourceSets().getByName("main"); + final SourceSet clientSource = splitSourceSets ? javaPluginExtension.getSourceSets().maybeCreate("client") : mainSource; + + if (splitSourceSets) { + javaPluginExtension.registerFeature("client", featureSpec -> { + featureSpec.usingSourceSet(clientSource); + featureSpec.withJavadocJar(); + featureSpec.withSourcesJar(); + }); + } // Maven coordinates for the NeoForm version this runtime uses final NeoFormRuntimeExtension neoFormRuntimeExtension = getProject().getExtensions().getByType(NeoFormRuntimeExtension.class); final NeoFormRuntimeDefinition neoFormRuntimeDefinition = neoFormRuntimeExtension.create(builder -> { - builder.withNeoFormVersion(neoFormVersion).withDistributionType(DistributionType.JOINED); + builder.withNeoFormVersion(neoFormVersion).withDistributionType( + splitSourceSets ? DistributionType.SERVER : DistributionType.JOINED + ); NeoFormRuntimeUtils.configureDefaultRuntimeSpecBuilder(project, builder); }); + final NeoFormRuntimeDefinition clientNeoFormRuntimeDefinition = splitSourceSets ? neoFormRuntimeExtension.create(builder -> { + builder.withNeoFormVersion(neoFormVersion).withDistributionType(DistributionType.CLIENT).removeCommonElements(); + + NeoFormRuntimeUtils.configureDefaultRuntimeSpecBuilder(project, builder); + }) : neoFormRuntimeDefinition; + final NeoFormRuntimeDefinition joinedRuntimeDefinition = !splitSourceSets ? neoFormRuntimeDefinition : + neoFormRuntimeExtension.create(builder -> { + builder.withNeoFormVersion(neoFormVersion).withDistributionType(DistributionType.JOINED); + + NeoFormRuntimeUtils.configureDefaultRuntimeSpecBuilder(project, builder); + }); + // The NeoForm version that's passed into this method can be a version range or '+', but to build userdev and installer profiles safely, - // we need the actual version it was resolved to. Otherwise the NeoForm version used by installer & userdev could change over time. - String neoformDependency = "net.neoforged:neoform:" + neoFormRuntimeDefinition.getSpecification().getVersion() + "@zip"; + // we need the actual version it was resolved to. Otherwise, the NeoForm version used by installer & userdev could change over time. + String neoFormDependency = "net.neoforged:neoform:" + neoFormRuntimeDefinition.getSpecification().getVersion() + "@zip"; final var parchmentArtifact = getParchment().map(parch -> { var split = parch.split("-"); @@ -210,47 +310,105 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re + "@zip"; }); + final Directory mainPatches = splitSourceSets ? project.getLayout().getProjectDirectory().dir("src/main/patches") : configuration.getPatches().get(); + final Directory clientPatches = splitSourceSets ? project.getLayout().getProjectDirectory().dir("src/client/patches") : mainPatches; + + final Directory mainRejects = splitSourceSets ? project.getLayout().getProjectDirectory().dir("rejects/main") : configuration.getRejects().get(); + final Directory clientRejects = splitSourceSets ? project.getLayout().getProjectDirectory().dir("rejects/client") : mainRejects; + final RuntimeDevRuntimeExtension runtimeDevRuntimeExtension = project.getExtensions().getByType(RuntimeDevRuntimeExtension.class); final RuntimeDevRuntimeDefinition runtimeDefinition = runtimeDevRuntimeExtension.create(builder -> { builder.withNeoFormRuntime(neoFormRuntimeDefinition) - .withPatchesDirectory(patches) - .withRejectsDirectory(rejects) - .withDistributionType(DistributionType.JOINED) + .withPatchesDirectory(mainPatches) + .withRejectsDirectory(mainRejects) + .withDistributionType(splitSourceSets ? DistributionType.SERVER : DistributionType.JOINED) .withParchment(parchmentArtifact) .isUpdating(getIsUpdating()); }); - + final RuntimeDevRuntimeDefinition clientRuntimeDefinition = splitSourceSets ? runtimeDevRuntimeExtension.create(builder -> { + builder.withNeoFormRuntime(clientNeoFormRuntimeDefinition) + .withPatchesDirectory(clientPatches) + .withRejectsDirectory(clientRejects) + .withDistributionType(DistributionType.CLIENT) + .withParchment(parchmentArtifact) + .isUpdating(getIsUpdating()); + }) : runtimeDefinition; + project.getExtensions().add("runtime", runtimeDefinition); - + project.getExtensions().add("mainRuntime", runtimeDefinition); + if (splitSourceSets) { + project.getExtensions().add("clientRuntime", clientRuntimeDefinition); + } + + if (splitSourceSets) { + final SourceSetDependencyExtension clientDependsOn = clientSource.getExtensions().getByType(SourceSetDependencyExtension.class); + clientDependsOn.on(mainSource); + } + final IdeManagementExtension ideManagementExtension = project.getExtensions().getByType(IdeManagementExtension.class); - ideManagementExtension.registerTaskToRun(runtimeDefinition.getAssets()); - ideManagementExtension.registerTaskToRun(runtimeDefinition.getNatives()); - - final File workingDirectory = getProject().getLayout().getBuildDirectory().dir(String.format("platform/%s", runtimeDefinition.getSpecification().getIdentifier())).get().getAsFile(); - + if (splitSourceSets) { + ideManagementExtension.registerTaskToRun(clientRuntimeDefinition.getAssets()); + ideManagementExtension.registerTaskToRun(clientRuntimeDefinition.getNatives()); + } else { + ideManagementExtension.registerTaskToRun(runtimeDefinition.getAssets()); + ideManagementExtension.registerTaskToRun(runtimeDefinition.getNatives()); + } + + final File mainWorkingDirectory = getProject().getLayout().getBuildDirectory().dir(String.format("platform/%s", runtimeDefinition.getSpecification().getIdentifier())).get().getAsFile(); + //If split source sets are not enabled then this will be the same as mainWorkingDirectory + final File clientWorkingDirectory = getProject().getLayout().getBuildDirectory().dir(String.format("platform/%s", clientRuntimeDefinition.getSpecification().getIdentifier())).get().getAsFile(); + + final Configuration mergedModulesConfiguration = project.getConfigurations().create("mergedModules"); final Configuration clientExtraConfiguration = project.getConfigurations().create("clientExtra"); final Configuration serverExtraConfiguration = project.getConfigurations().create("serverExtra"); final Configuration installerConfiguration = project.getConfigurations().create("installer"); final Configuration installerLibrariesConfiguration = project.getConfigurations().create("installerLibraries"); final Configuration moduleOnlyConfiguration = project.getConfigurations().create("moduleOnly").setTransitive(false); + final Configuration gameLayerLibraryConfiguration = project.getConfigurations().create("gameLayerLibrary").setTransitive(false); + final Configuration clientGameLayerLibraryConfiguration = splitSourceSets ? project.getConfigurations().create("clientGameLayerLibrary").setTransitive(false) : gameLayerLibraryConfiguration; + final Configuration pluginLayerLibraryConfiguration = project.getConfigurations().create("pluginLayerLibrary").setTransitive(false); + final Configuration clientPluginLayerLibraryConfiguration = splitSourceSets ? project.getConfigurations().create("clientPluginLayerLibrary").setTransitive(false) : pluginLayerLibraryConfiguration; + final Configuration userdevCompileOnlyConfiguration = project.getConfigurations().create("userdevCompileOnly").setTransitive(false); final Configuration userdevTestImplementationConfiguration = project.getConfigurations().create("userdevTestImplementation").setTransitive(true); final Configuration jarJarConfiguration = project.getConfigurations().create("jarJar"); - clientExtraConfiguration.getDependencies().add(project.getDependencies().create(ExtraJarDependencyManager.generateClientCoordinateFor(runtimeDefinition.getSpecification().getMinecraftVersion()))); - - serverExtraConfiguration.getDependencies().add(project.getDependencies().create(ExtraJarDependencyManager.generateServerCoordinateFor(runtimeDefinition.getSpecification().getMinecraftVersion()))); - + if (splitSourceSets) { + clientGameLayerLibraryConfiguration.extendsFrom(gameLayerLibraryConfiguration); + clientPluginLayerLibraryConfiguration.extendsFrom(pluginLayerLibraryConfiguration); + } + + clientExtraConfiguration.getDependencies().add( + project.getDependencies().create( + ExtraJarDependencyManager.generateClientCoordinateFor( + clientRuntimeDefinition.getSpecification().getMinecraftVersion() + ))); + + serverExtraConfiguration.getDependencies().add( + project.getDependencies().create( + ExtraJarDependencyManager.generateServerCoordinateFor( + runtimeDefinition.getSpecification().getMinecraftVersion() + ))); + installerLibrariesConfiguration.extendsFrom(installerConfiguration); - installerLibrariesConfiguration.getDependencies().add(project.getDependencyFactory().create(neoformDependency)); - + installerLibrariesConfiguration.getDependencies().add(project.getDependencyFactory().create(neoFormDependency)); + + //TODO: Deal with the installer configuration, split? not split? project.getConfigurations().getByName(mainSource.getApiConfigurationName()).extendsFrom(gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, installerConfiguration); - project.getConfigurations().getByName(mainSource.getRuntimeClasspathConfigurationName()).extendsFrom(clientExtraConfiguration); - - project.getExtensions().configure(RunTypeManager.class, types -> types.configureEach(type -> configureRunType(project, type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, runtimeDefinition))); - project.getExtensions().configure(RunManager.class, runs -> runs.configureAll(run -> configureRun(run, runtimeDefinition))); + project.getConfigurations().getByName(clientSource.getApiConfigurationName()).extendsFrom(clientGameLayerLibraryConfiguration, clientPluginLayerLibraryConfiguration, installerConfiguration); + + project.getConfigurations().getByName(mainSource.getRuntimeClasspathConfigurationName()).extendsFrom(splitSourceSets ? serverExtraConfiguration : clientExtraConfiguration); + if (splitSourceSets) { + project.getConfigurations().getByName(clientSource.getRuntimeClasspathConfigurationName()).extendsFrom(clientExtraConfiguration); + } + + project.getExtensions().configure(RunTypeManager.class, types -> + types.configureEach(type -> + configureRunType(project, type, splitSourceSets, mergedModulesConfiguration, moduleOnlyConfiguration, clientGameLayerLibraryConfiguration, clientPluginLayerLibraryConfiguration + ))); + project.getExtensions().configure(RunManager.class, runs -> runs.configureAll(run -> configureRun(run, splitSourceSets, clientRuntimeDefinition))); project.getExtensions().create(net.neoforged.gradle.dsl.common.extensions.JarJar.class, JarJarExtension.EXTENSION_NAME, JarJarExtension.class, project); @@ -259,65 +417,172 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re final UserdevProfile userdevProfile = project.getExtensions().create("userdevProfile", UserdevProfile.class, project.getObjects()); final TaskProvider setupTask = configureSetupTasks(runtimeDefinition.getSourceJarTask().flatMap(WithOutput::getOutput), mainSource, runtimeDefinition.getMinecraftDependenciesConfiguration()); + final TaskProvider clientSetupTask = splitSourceSets ? configureSetupTasks(clientRuntimeDefinition.getSourceJarTask().flatMap(WithOutput::getOutput), clientSource, clientRuntimeDefinition.getMinecraftDependenciesConfiguration()) : setupTask; setupTask.configure(task -> task.getShouldLockDirectories().set(false)); - + clientSetupTask.configure(task -> task.getShouldLockDirectories().set(false)); + + if (splitSourceSets && configuration.getLegacyPatches().isPresent()) { + final Directory legacyPatches = configuration.getLegacyPatches().get(); + + final TaskProvider serverMappings = runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_MAPPINGS); + final Provider> serverRelativePaths = serverMappings.flatMap(WithOutput::getOutput) + .map(RegularFile::getAsFile) + .map(File::toPath) + .map(FileUtils::readAllLines) + .map(lines -> lines + .filter(line -> !line.startsWith("\t")) + .filter(line -> !line.startsWith("#")) + .map(line -> line.split(" ")[0]) + .map(line -> line.replace(".", "/")) + .map(line -> line + ".java.patch") + .collect(Collectors.toSet())); + + getProject().getTasks().register("migrateClientPatches", Copy.class, migrateTask -> { + migrateTask.dependsOn(serverMappings); + migrateTask.into(clientPatches); + migrateTask.from(legacyPatches, copySpec -> { + copySpec.include(fileTreeElement -> { + if (fileTreeElement.isDirectory()) + return true; + + final String relativeFromRoot = fileTreeElement.getRelativePath().getPathString(); + return !serverRelativePaths.get().contains(relativeFromRoot); + }); + copySpec.setIncludeEmptyDirs(false); + }); + + migrateTask.getInputs().property("filter", serverRelativePaths); + }); + + getProject().getTasks().register("migrateMainPatches", Copy.class, migrateTask -> { + migrateTask.dependsOn(serverMappings); + migrateTask.into(mainPatches); + migrateTask.from(legacyPatches, copySpec -> { + copySpec.include(fileTreeElement -> { + if (fileTreeElement.isDirectory()) + return true; + + final String relativeFromRoot = fileTreeElement.getRelativePath().getPathString(); + return serverRelativePaths.get().contains(relativeFromRoot); + }); + copySpec.setIncludeEmptyDirs(false); + }); + + migrateTask.getInputs().property("filter", serverRelativePaths); + }); + } + project.afterEvaluate(evaledProject -> { final EnumMap> cleanProviders = new EnumMap<>(DistributionType.class); - cleanProviders.put(DistributionType.CLIENT, createCleanProvider(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.CLIENT_JAR), runtimeDefinition, workingDirectory)); - cleanProviders.put(DistributionType.SERVER, createCleanProvider(runtimeDefinition.getJoinedNeoFormRuntimeDefinition().getTask("extractServer"), runtimeDefinition, workingDirectory)); - cleanProviders.put(DistributionType.JOINED, runtimeDefinition.getJoinedNeoFormRuntimeDefinition().getTask("rename")); - + cleanProviders.put(DistributionType.CLIENT, createCleanProvider(clientRuntimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.CLIENT_JAR), clientRuntimeDefinition, clientWorkingDirectory)); + cleanProviders.put(DistributionType.SERVER, createCleanProvider(runtimeDefinition.getNeoFormRuntimeDefinition().getTask("extractServer"), runtimeDefinition, mainWorkingDirectory)); + cleanProviders.put(DistributionType.JOINED, joinedRuntimeDefinition.getTask("rename")); + final EnumMap> obfToMojMappingProviders = new EnumMap<>(DistributionType.class); - final TaskProvider clientInverseMappings = createFlippedMojMapProvider(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.CLIENT_MAPPINGS), runtimeDefinition, workingDirectory); - final TaskProvider serverInverseMappings = createFlippedMojMapProvider(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_MAPPINGS), runtimeDefinition, workingDirectory); + final TaskProvider clientInverseMappings = createFlippedMojMapProvider(clientRuntimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.CLIENT_MAPPINGS), clientRuntimeDefinition, clientWorkingDirectory); + final TaskProvider serverInverseMappings = createFlippedMojMapProvider(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_MAPPINGS), runtimeDefinition, mainWorkingDirectory); obfToMojMappingProviders.put(DistributionType.CLIENT, clientInverseMappings); obfToMojMappingProviders.put(DistributionType.SERVER, serverInverseMappings); obfToMojMappingProviders.put(DistributionType.JOINED, clientInverseMappings); - final TaskProvider neoFormSources = runtimeDefinition.getJoinedNeoFormRuntimeDefinition().getSourceJarTask(); - + final TaskProvider neoFormSources = runtimeDefinition.getNeoFormRuntimeDefinition().getSourceJarTask(); + final TaskProvider clientNeoFormSources = splitSourceSets ? clientRuntimeDefinition.getNeoFormRuntimeDefinition().getSourceJarTask() : neoFormSources; + final TaskProvider packChanges = project.getTasks().register("packForgeChanges", PackJar.class, task -> { - task.getInputFiles().from(SetupUtils.getSetupSourceTarget(getProject())); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + task.getInputFiles().from(SetupUtils.getSetupSourceTarget(mainSource)); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); }); - + + @Nullable TaskProvider clientPackChanges = splitSourceSets ? project.getTasks().register("packClientForgeChanges", PackJar.class, task -> { + task.getInputFiles().from(SetupUtils.getSetupSourceTarget(clientSource)); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }) : null; + final TaskProvider createPatches = project.getTasks().register("createSourcePatches", GenerateSourcePatches.class, task -> { task.getBase().set(runtimeDefinition.getPatchBase().flatMap(WithOutput::getOutput)); task.getModified().set(packChanges.flatMap(WithOutput::getOutput)); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - - final TaskProvider unpackZip = project.getTasks().register("unpackSourcePatches", UnpackZip.class, task -> { + + @Nullable final TaskProvider createClientPatches = splitSourceSets ? project.getTasks().register("createClientSourcePatches", GenerateSourcePatches.class, task -> { + Validate.notNull(clientPackChanges, "Client pack changes must be present when split source sets are enabled"); + + task.getBase().set(clientRuntimeDefinition.getPatchBase().flatMap(WithOutput::getOutput)); + task.getModified().set(clientPackChanges.flatMap(WithOutput::getOutput)); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }) : null; + + final TaskProvider unpackPatches = project.getTasks().register("unpackSourcePatches", UnpackZip.class, task -> { task.getInput().from(project.zipTree(createPatches.flatMap(WithOutput::getOutput))); - task.getUnpackingTarget().set(patches); + task.getUnpackingTarget().set(mainPatches); task.dependsOn(createPatches); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - var mergeMappings = runtimeDefinition.getJoinedNeoFormRuntimeDefinition().getTask("mergeMappings"); + @Nullable final TaskProvider unpackClientPatches = splitSourceSets ? project.getTasks().register("unpackClientSourcePatches", UnpackZip.class, task -> { + Validate.notNull(createClientPatches, "Client create patches must be present when split source sets are enabled"); + + task.getInput().from(project.zipTree(createClientPatches.flatMap(WithOutput::getOutput))); + task.getUnpackingTarget().set(clientPatches); + task.dependsOn(createClientPatches); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }) : null; + + var mergeMappings = clientRuntimeDefinition.getNeoFormRuntimeDefinition().getTask("mergeMappings"); Provider compiledJarProvider; + Provider compiledClientJarProvider; + Provider compiledJoinedJarProvider; if (parchmentArtifact.isPresent()) { - var officialWithParams = project.getTasks().register(CommonRuntimeUtils.buildTaskName(runtimeDefinition, "officialMappingsJustParameters"), OfficialMappingsJustParameters.class, tsk -> { + var officialWithParams = project.getTasks().register(CommonRuntimeUtils.buildTaskName(clientRuntimeDefinition, "officialMappingsJustParameters"), OfficialMappingsJustParameters.class, tsk -> { tsk.getInput().set(mergeMappings.flatMap(WithOutput::getOutput)); tsk.dependsOn(mergeMappings); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(tsk, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(tsk, clientRuntimeDefinition, clientWorkingDirectory); }); compiledJarProvider = renameCompiledJar( officialWithParams, project.getTasks().named(mainSource.getJarTaskName(), Jar.class), runtimeDefinition, - workingDirectory + mainWorkingDirectory + ); + compiledClientJarProvider = renameCompiledJar( + officialWithParams, + project.getTasks().named(clientSource.getJarTaskName(), Jar.class), + clientRuntimeDefinition, + clientWorkingDirectory ); + + compiledJoinedJarProvider = project.getTasks().register(CommonRuntimeUtils.buildTaskName(runtimeDefinition, "createJoinedJar"), Zip.class, task -> { + task.getDestinationDirectory().set(new File(mainWorkingDirectory, "joined")); + task.getArchiveFileName().set("joined.jar"); + + task.from(project.zipTree(compiledJarProvider)); + task.from(project.zipTree(compiledClientJarProvider)); + }).flatMap(Zip::getArchiveFile); } else { compiledJarProvider = project.getTasks().named(mainSource.getJarTaskName(), Jar.class).flatMap(Jar::getArchiveFile); + compiledClientJarProvider = splitSourceSets ? project.getTasks().named(clientSource.getJarTaskName(), Jar.class).flatMap(Jar::getArchiveFile) : compiledJarProvider; + compiledJoinedJarProvider = compiledJarProvider; } - javaPluginExtension.withSourcesJar(); + final EnumMap> compiledJars = new EnumMap<>(DistributionType.class); + compiledJars.put(DistributionType.CLIENT, compiledClientJarProvider); + compiledJars.put(DistributionType.SERVER, compiledJarProvider); + compiledJars.put(DistributionType.JOINED, compiledJoinedJarProvider); + final TaskProvider sourcesJarProvider = project.getTasks().named(mainSource.getSourcesJarTaskName(), Jar.class); - sourcesJarProvider.configure(task -> { + sourcesJarProvider.configure(task -> { + task.exclude("net/minecraft/**"); + task.exclude("com/**"); + task.exclude("mcp/**"); + }); + + final TaskProvider clientSourcesJarProvider = splitSourceSets ? project.getTasks().named(clientSource.getSourcesJarTaskName(), Jar.class) : sourcesJarProvider; + clientSourcesJarProvider.configure(task -> { task.exclude("net/minecraft/**"); task.exclude("com/**"); task.exclude("mcp/**"); @@ -328,23 +593,42 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re final TaskProvider cleanProvider = cleanProviders.get(distribution); final TaskProvider generateBinaryPatchesTask = project.getTasks().register(distribution.createTaskName("generate", "BinaryPatches"), GenerateBinaryPatches.class, task -> { task.getClean().set(cleanProvider.flatMap(WithOutput::getOutput)); - task.getPatched().set(compiledJarProvider); + task.getPatched().set(compiledJars.get(distribution)); task.getDistributionType().set(distribution); - task.getPatches().from(patches); + + if (distribution == DistributionType.JOINED) { + task.getPatches().from(mainPatches); + task.getPatches().from(clientPatches); + } else if (distribution == DistributionType.CLIENT) { + task.getPatches().from(clientPatches); + } else if (distribution == DistributionType.SERVER) { + task.getPatches().from(mainPatches); + } + task.getMappings().set(obfToMojMappingProviders.get(distribution).flatMap(WithOutput::getOutput)); - - task.mustRunAfter(unpackZip); + + task.mustRunAfter(unpackPatches); + if (unpackClientPatches != null) { + task.mustRunAfter(unpackClientPatches); + } task.mustRunAfter(setupTask); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + task.mustRunAfter(clientSetupTask); + + if (distribution != DistributionType.CLIENT) { + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); + } else { + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + } }); binaryPatchGenerators.put(distribution, generateBinaryPatchesTask); } final Configuration runtimeClasspath = project.getConfigurations().getByName( - project.getExtensions().getByType(SourceSetContainer.class).findByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getRuntimeClasspathConfigurationName() + mainSource.getRuntimeClasspathConfigurationName() ); + final Configuration clientRuntimeClasspath = splitSourceSets ? project.getConfigurations().getByName( + clientSource.getRuntimeClasspathConfigurationName() + ) : runtimeClasspath; launcherProfile.configure((Action) profile -> { profile.getId().set(String.format("%s-%s", project.getName(), project.getVersion())); @@ -353,25 +637,25 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re profile.getType().set("release"); profile.getMainClass().set("cpw.mods.bootstraplauncher.BootstrapLauncher"); profile.getInheritsFrom().set(runtimeDefinition.getSpecification().getMinecraftVersion()); - + //TODO: Deal with logging when model for it stands profile.getLoggingConfiguration().set(project.getObjects().newInstance(LauncherProfile.LoggingConfiguration.class)); - + final LauncherProfile.Arguments arguments = launcherProfile.getArguments().get(); - + arguments.game("--launchTarget"); arguments.game("forgeclient"); - + arguments.jvm("-Djava.net.preferIPv6Addresses=system"); arguments.jvm(createIgnoreList(project, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration).map(ignoreList -> "-DignoreList=" + ignoreList + ",${version_name}.jar")); - arguments.jvm("-DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar"); - arguments.jvm(collectFileNames(pluginLayerLibraryConfiguration, project).map(pluginLayerLibraries -> "-Dfml.pluginLayerLibraries=" + pluginLayerLibraries)); - arguments.jvm(collectFileNames(gameLayerLibraryConfiguration, project).map(gameLayerLibraries -> "-Dfml.gameLayerLibraries=" + gameLayerLibraries)); + arguments.jvm(collectFileNames(mergedModulesConfiguration, project).map(mergedModules -> "-DmergedModules=" + mergedModules)); + arguments.jvm(collectFileNames(clientPluginLayerLibraryConfiguration, project).map(pluginLayerLibraries -> "-Dfml.pluginLayerLibraries=" + pluginLayerLibraries)); + arguments.jvm(collectFileNames(clientGameLayerLibraryConfiguration, project).map(gameLayerLibraries -> "-Dfml.gameLayerLibraries=" + gameLayerLibraries)); arguments.jvm("-DlibraryDirectory=${library_directory}"); arguments.jvm("-p"); - + arguments.jvm(collectFilePaths(moduleOnlyConfiguration, "${library_directory}/", "${classpath_separator}", project)); - + arguments.jvm("--add-modules"); arguments.jvm("ALL-MODULE-PATH"); arguments.jvm("--add-opens"); @@ -382,30 +666,38 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re arguments.jvm("java.base/sun.security.util=cpw.mods.securejarhandler"); arguments.jvm("--add-exports"); arguments.jvm("jdk.naming.dns/com.sun.jndi.dns=java.naming"); - + launcherProfile.getArguments().set(arguments); }); + + final Configuration launcherJsonInstallerConfiguration = ConfigurationUtils.temporaryUnhandledConfiguration( + project.getConfigurations(), + "LauncherJsonInstallerConfiguration" + ); + launcherJsonInstallerConfiguration.extendsFrom(installerConfiguration); + launcherJsonInstallerConfiguration.shouldResolveConsistentlyWith(clientRuntimeClasspath); + final ListProperty repoCollection = new RepositoryCollection(project.getProviders(), project.getObjects(), project.getRepositories()).getURLs(); final TaskProvider createLauncherJson = project.getTasks().register("createLauncherJson", CreateLauncherJson.class, task -> { task.getProfile().set(launcherProfile); - task.getLibraries().from(installerConfiguration); - task.getLibraries().from(pluginLayerLibraryConfiguration); - task.getLibraries().from(gameLayerLibraryConfiguration); + task.getLibraries().from(launcherJsonInstallerConfiguration); + task.getLibraries().from(clientPluginLayerLibraryConfiguration); + task.getLibraries().from(clientGameLayerLibraryConfiguration); task.getLibraries().from(moduleOnlyConfiguration); task.getRepositoryURLs().set(repoCollection); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); }); - + final TaskProvider joinedCleanProvider = cleanProviders.get(DistributionType.JOINED); final TaskProvider strippedJar = project.getTasks().register("stripBinaryPatchedClasses", StripBinPatchedClasses.class, task -> { task.getCompiled().set(project.getTasks().named(mainSource.getJarTaskName(), Jar.class).flatMap(Jar::getArchiveFile)); task.getClean().set(joinedCleanProvider.flatMap(WithOutput::getOutput)); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider universalJar = project.getTasks().register("universalJar", JarJar.class, task -> { task.getArchiveClassifier().set("universal-unsigned"); task.getArchiveAppendix().set("universal-unsigned"); @@ -413,9 +705,9 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getArchiveBaseName().set(project.getName()); task.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("libs")); task.getArchiveFileName().set(project.provider(() -> String.format("%s-%s-universal-unsigned.jar", project.getName(), project.getVersion()))); - + task.dependsOn(strippedJar); - + task.from(project.zipTree(strippedJar.flatMap(WithOutput::getOutput))); task.manifest(manifest -> { manifest.attributes(ImmutableMap.of("FML-System-Mods", "neoforge")); @@ -425,11 +717,11 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.configuration(jarJarConfiguration); }); - + final TaskProvider signUniversalJar = project.getTasks().register("signUniversalJar", PotentiallySignJar.class, task -> { task.getInput().set(universalJar.flatMap(Jar::getArchiveFile)); task.getOutputFileName().set(project.provider(() -> String.format("%s-%s-universal.jar", project.getName(), project.getVersion()))); - + task.dependsOn(universalJar); }); @@ -451,15 +743,15 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re profile.processor(project, Constants.INSTALLERTOOLS, processor -> { processor.server(); processor.getArguments().addAll("--task", "EXTRACT_FILES", "--archive", "{INSTALLER}", - + "--from", "data/run.sh", "--to", "{ROOT}/run.sh", "--exec", "{ROOT}/run.sh", - + "--from", "data/run.bat", "--to", "{ROOT}/run.bat", - + "--from", "data/user_jvm_args.txt", "--to", "{ROOT}/user_jvm_args.txt", "--optional", "{ROOT}/user_jvm_args.txt", - + "--from", "data/win_args.txt", "--to", String.format("{ROOT}/libraries/%s/%s/%s/win_args.txt", project.getGroup().toString().replaceAll("\\.", "/"), project.getName(), project.getVersion()), - + "--from", "data/unix_args.txt", "--to", String.format("{ROOT}/libraries/%s/%s/%s/unix_args.txt", project.getGroup().toString().replaceAll("\\.", "/"), project.getName(), project.getVersion())); }); profile.processor(project, Constants.INSTALLERTOOLS, processor -> { @@ -471,7 +763,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re processor.getArguments().addAll("--task", "BUNDLER_EXTRACT", "--input", "{MINECRAFT_JAR}", "--output", "{MC_UNPACKED}", "--jar-only"); }); profile.processor(project, Constants.INSTALLERTOOLS, processor -> { - processor.getArguments().addAll("--task", "MCP_DATA", "--input", String.format("[%s]", neoformDependency), "--output", "{MAPPINGS}", "--key", "mappings"); + processor.getArguments().addAll("--task", "MCP_DATA", "--input", String.format("[%s]", neoFormDependency), "--output", "{MAPPINGS}", "--key", "mappings"); }); profile.processor(project, Constants.INSTALLERTOOLS, processor -> { processor.getArguments().addAll("--task", "DOWNLOAD_MOJMAPS", "--version", runtimeDefinition.getSpecification().getMinecraftVersion(), "--side", "{SIDE}", "--output", "{MOJMAPS}"); @@ -493,7 +785,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re profile.processor(project, Constants.BINARYPATCHER, processor -> { processor.getArguments().addAll("--clean", "{MC_SRG}", "--output", "{PATCHED}", "--apply", "{BINPATCH}"); }); - + profile.getLibraries().add(Library.fromOutput(signUniversalJar, project, "net.neoforged", "neoforge", project.getVersion().toString(), "universal")); //TODO: Abstract this away to some kind of DSL property @@ -523,10 +815,10 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getProfile().set(installerProfile); task.getLibraries().from(installerJsonInstallerLibrariesConfiguration); task.getRepositoryURLs().set(repoCollection); - + task.dependsOn(signUniversalJar); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); final Configuration installerToolConfiguration = ConfigurationUtils.temporaryConfiguration( @@ -535,9 +827,9 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re project.getDependencies().create("net.neoforged:legacyinstaller:3.0.+:shrunk")); final TaskProvider downloadInstaller = project.getTasks().register("downloadInstaller", Download.class, task -> { task.getInput().from(installerToolConfiguration); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider createWindowsServerArgsFile = project.getTasks().register("createWindowsServerArgsFile", CreateClasspathFiles.class, task -> { task.getModulePath().from(moduleOnlyConfiguration); task.getClasspath().from(installerConfiguration); @@ -546,12 +838,12 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getPathSeparator().set(";"); task.getServer().set(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_JAR).flatMap(WithOutput::getOutput)); task.getNeoFormVersion().set(neoFormVersion); - + configureInstallerTokens(task, runtimeDefinition, Lists.newArrayList(moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration), pluginLayerLibraryConfiguration, gameLayerLibraryConfiguration); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider createUnixServerArgsFile = project.getTasks().register("createUnixServerArgsFile", CreateClasspathFiles.class, task -> { task.getModulePath().from(moduleOnlyConfiguration); task.getClasspath().from(installerConfiguration); @@ -560,12 +852,12 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getPathSeparator().set(":"); task.getServer().set(runtimeDefinition.getGameArtifactProvidingTasks().get(GameArtifact.SERVER_JAR).flatMap(WithOutput::getOutput)); task.getNeoFormVersion().set(neoFormVersion); - + configureInstallerTokens(task, runtimeDefinition, Lists.newArrayList(moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration), pluginLayerLibraryConfiguration, gameLayerLibraryConfiguration); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider installerJar = project.getTasks().register("legacyInstallerJar", CreateLegacyInstaller.class, task -> { task.getInstallerCore().set(downloadInstaller.flatMap(WithOutput::getOutput)); task.getInstallerJson().set(createLegacyInstallerJson.flatMap(WithOutput::getOutput)); @@ -575,9 +867,9 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getWindowsServerArgs().set(createWindowsServerArgsFile.flatMap(WithOutput::getOutput)); task.getUnixServerArgs().set(createUnixServerArgsFile.flatMap(WithOutput::getOutput)); task.getData().from(project.getRootProject().fileTree("server_files/").exclude("args.txt")); - + configureInstallerTokens(task, runtimeDefinition, Lists.newArrayList(moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration), pluginLayerLibraryConfiguration, gameLayerLibraryConfiguration); - + if (project.getProperties().containsKey("neogradle.runtime.platform.installer.debug") && Boolean.parseBoolean(project.getProperties().get("neogradle.runtime.platform.installer.debug").toString())) { task.from(signUniversalJar.flatMap(WithOutput::getOutput), spec -> { spec.into(String.format("/maven/net/neoforged/neoforge/%s/", project.getVersion())); @@ -585,14 +877,14 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re }); } }); - + TaskProvider signInstallerJar = project.getTasks().register("signInstallerJar", PotentiallySignJar.class, task -> { task.getInput().set(installerJar.flatMap(Zip::getArchiveFile)); task.getOutputFileName().set(project.provider(() -> String.format("%s-%s-installer.jar", project.getName(), project.getVersion()))); - + task.dependsOn(installerJar); }); - + //Note the following runtypes are for now hardcoded, in the future they should be pulled from the runtime definition //Note: We can not use a 'configureEach' here, because this causes issues with the config cache. userdevProfile.runType("client", type -> { @@ -601,7 +893,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re type.getIsClient().set(true); type.getIsGameTest().set(true); type.getSystemProperties().put("neoforge.enableGameTest", "true"); - + type.getArguments().add("--launchTarget"); type.getArguments().add("forgeclientuserdev"); type.getArguments().add("--version"); @@ -610,7 +902,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re type.getArguments().add("{asset_index}"); type.getArguments().add("--assetsDir"); type.getArguments().add("{assets_root}"); - + configureUserdevRunType(type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, userdevCompileOnlyConfiguration, project); }); userdevProfile.runType("server", type -> { @@ -620,7 +912,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re type.getArguments().add("--launchTarget"); type.getArguments().add("forgeserveruserdev"); - + configureUserdevRunType(type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, userdevCompileOnlyConfiguration, project); }); userdevProfile.runType("gameTestServer", type -> { @@ -630,11 +922,11 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re type.getIsGameTest().set(true); type.getSystemProperties().put("neoforge.enableGameTest", "true"); type.getSystemProperties().put("neoforge.gameTestServer", "true"); - - + + type.getArguments().add("--launchTarget"); type.getArguments().add("forgeserveruserdev"); - + configureUserdevRunType(type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, userdevCompileOnlyConfiguration, project); }); userdevProfile.runType("data", type -> { @@ -648,7 +940,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re type.getArguments().add("{asset_index}"); type.getArguments().add("--assetsDir"); type.getArguments().add("{assets_root}"); - + configureUserdevRunType(type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, userdevCompileOnlyConfiguration, project); }); @@ -671,7 +963,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re configureUserdevRunType(type, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration, userdevCompileOnlyConfiguration, project); }); - userdevProfile.getNeoForm().set(neoformDependency); + userdevProfile.getNeoForm().set(neoFormDependency); userdevProfile.getSourcePatchesDirectory().set("patches/"); userdevProfile.getAccessTransformerDirectory().set("ats/"); userdevProfile.getBinaryPatchFile().set("joined.lzma"); @@ -716,30 +1008,44 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getTestLibraries().from(userdevJsonUserdevTestImplementationConfiguration); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider generateAts = project.getTasks().register("generateAccessTransformers", AccessTransformerFileGenerator.class, task -> { - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); - + final TaskProvider packPatches = project.getTasks().register("packPatches", PackJar.class, task -> { - task.getInputFiles().from(project.fileTree(patches).matching(filterable -> { + task.getInputFiles().from(project.fileTree(mainPatches).matching(filterable -> { filterable.include("**/*.patch"); })); - - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); + @Nullable final TaskProvider clientPackPatches = splitSourceSets ? project.getTasks().register("clientPackPatches", PackJar.class, task -> { + task.getInputFiles().from(project.fileTree(clientPackChanges).matching(filterable -> { + filterable.include("**/*.patch"); + })); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }) : null; + final TaskProvider bakePatches; + @Nullable final TaskProvider bakeClientPatches; if (!parchmentArtifact.isPresent()) { bakePatches = project.getTasks().register("bakePatches", BakePatches.class, task -> { task.getInput().set(packPatches.flatMap(WithOutput::getOutput)); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); + bakeClientPatches = splitSourceSets ? project.getTasks().register("bakeClientPatches", BakePatches.class, task -> { + task.getInput().set(clientPackPatches.flatMap(WithOutput::getOutput)); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }) : null; } else { - final var sourceSetDir = SetupUtils.getSetupSourceTarget(project); + final var sourceSetDir = SetupUtils.getSetupSourceTarget(mainSource); final var sourcesWithoutParchment = RuntimeDevRuntimeExtension.applyParchment( project, "reverseParchment", @@ -748,7 +1054,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re project.provider(() -> sourceSetDir), false, runtimeDefinition.getSpecification(), - workingDirectory, + mainWorkingDirectory, strippedJar ); sourcesWithoutParchment.configure(withOutput -> withOutput.getProgramArguments().add("--ignore-prefix=mcp/")); @@ -757,12 +1063,37 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getModified().set(sourcesWithoutParchment.flatMap(WithOutput::getOutput)); task.getShouldCreateAutomaticHeader().set(false); - CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, mainWorkingDirectory); }); + + if (splitSourceSets) { + final var sourceSetClientDir = SetupUtils.getSetupSourceTarget(clientSource); + final var sourcesWithoutParchmentClient = RuntimeDevRuntimeExtension.applyParchment( + project, + "reverseParchmentClient", + mergeMappings.flatMap(WithOutput::getOutput).map(RegularFile::getAsFile), + project.provider(() -> ""), + project.provider(() -> sourceSetClientDir), + false, + clientRuntimeDefinition.getSpecification(), + clientWorkingDirectory, + strippedJar + ); + sourcesWithoutParchmentClient.configure(withOutput -> withOutput.getProgramArguments().add("--ignore-prefix=mcp/")); + bakeClientPatches = project.getTasks().register("createRenamedSourcePatchesClient", GenerateSourcePatches.class, task -> { + task.getBase().set(clientNeoFormSources.flatMap(WithOutput::getOutput)); + task.getModified().set(sourcesWithoutParchmentClient.flatMap(WithOutput::getOutput)); + task.getShouldCreateAutomaticHeader().set(false); + + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, clientRuntimeDefinition, clientWorkingDirectory); + }); + } else { + bakeClientPatches = null; + } } - + final AccessTransformers accessTransformers = project.getExtensions().getByType(AccessTransformers.class); - + final TaskProvider userdevJar = project.getTasks().register("userdevJar", Jar.class, task -> { task.getArchiveClassifier().set("userdev"); task.getArchiveAppendix().set("userdev"); @@ -770,9 +1101,12 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.getArchiveBaseName().set(project.getName()); task.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("libs")); task.getArchiveFileName().set(project.provider(() -> String.format("%s-%s-userdev.jar", project.getName(), project.getVersion()))); - + task.dependsOn(bakePatches); - + if (bakeClientPatches != null) { + task.dependsOn(bakeClientPatches); + } + //We need to get a raw file tree here, because else we capture the task reference in copy spec. final FileTree bakedPatches = project.zipTree(bakePatches.get().getOutput().get().getAsFile()); task.from(createUserdevJson.flatMap(WithOutput::getOutput), spec -> { @@ -790,8 +1124,13 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re task.from(bakedPatches, spec -> { spec.into("patches/"); }); + if (bakeClientPatches != null) { + task.from(project.zipTree(bakeClientPatches.get().getOutput().get().getAsFile()), spec -> { + spec.into("client_patches/"); + }); + } }); - + final TaskProvider assembleTask = project.getTasks().named("assemble"); assembleTask.configure(task -> { task.dependsOn(signInstallerJar); @@ -801,43 +1140,49 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re }); }); } - - private TaskProvider configureSetupTasks(Provider rawJarProvider, SourceSet mainSource, Configuration minecraftDependencies) { + + private TaskProvider configureSetupTasks( + Provider rawJarProvider, + SourceSet mainSource, + Configuration minecraftDependencies) { final IdeManagementExtension ideManagementExtension = project.getExtensions().getByType(IdeManagementExtension.class); - + final TaskProvider ideImportTask = ideManagementExtension.getOrCreateIdeImportTask(); - - final TaskProvider projectSetup = project.getTasks().register("setup", SetupProjectFromRuntime.class, task -> { + + final TaskProvider projectSetup = project.getTasks().register(mainSource.getTaskName("setup", "Project"), SetupProjectFromRuntime.class, task -> { task.getSourcesFile().set(rawJarProvider); + task.getSourcesDirectory().set(SetupUtils.getSetupSourceTarget(mainSource)); + task.getResourcesDirectory().set(SetupUtils.getSetupResourcesTarget(mainSource)); task.dependsOn(ideImportTask); }); - + final Configuration apiConfiguration = project.getConfigurations().getByName(mainSource.getApiConfigurationName()); minecraftDependencies.getAllDependencies().forEach(dep -> apiConfiguration.getDependencies().add(dep)); - + final Project rootProject = project.getRootProject(); if (!rootProject.getTasks().getNames().contains("setup")) { rootProject.getTasks().create("setup"); } - + rootProject.getTasks().named("setup").configure(task -> task.dependsOn(projectSetup)); - + return projectSetup; } - private void configureRunType(final Project project, final RunType runType, final Configuration moduleOnlyConfiguration, final Configuration gameLayerLibraryConfiguration, final Configuration pluginLayerLibraryConfiguration, RuntimeDevRuntimeDefinition runtimeDefinition) { - final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - final SourceSet mainSourceSet = javaPluginExtension.getSourceSets().getByName("main"); - - final Configuration runtimeClasspath = project.getConfigurations().getByName(mainSourceSet.getRuntimeClasspathConfigurationName()); - + private void configureRunType(final Project project, + final RunType runType, + final boolean splitSourceSets, + final Configuration mergedModuleConfiguration, + final Configuration moduleOnlyConfiguration, + final Configuration gameLayerLibraryConfiguration, + final Configuration pluginLayerLibraryConfiguration) { runType.getMainClass().set("cpw.mods.bootstraplauncher.BootstrapLauncher"); runType.getSystemProperties().put("java.net.preferIPv6Addresses", "system"); runType.getJvmArguments().addAll("-p", moduleOnlyConfiguration.getAsPath()); runType.getSystemProperties().put("ignoreList", createIgnoreList(project, moduleOnlyConfiguration, gameLayerLibraryConfiguration, pluginLayerLibraryConfiguration)); - runType.getSystemProperties().put("mergeModules", "jna-5.10.0.jar,jna-platform-5.10.0.jar"); + runType.getSystemProperties().put("mergeModules", collectFileNames(mergedModuleConfiguration, project)); runType.getSystemProperties().put("fml.pluginLayerLibraries", collectFileNames(pluginLayerLibraryConfiguration, project)); runType.getSystemProperties().put("fml.gameLayerLibraries", collectFileNames(gameLayerLibraryConfiguration, project)); runType.getSystemProperties().put("legacyClassPath", project.getConfigurations().getByName("runtimeClasspath").getAsPath()); @@ -849,7 +1194,11 @@ private void configureRunType(final Project project, final RunType runType, fina runType.getEnvironmentVariables().put("NEOFORGE_SPEC", project.getVersion().toString().substring(0, project.getVersion().toString().lastIndexOf("."))); - runType.getClasspath().from(runtimeClasspath); + runType.getClasspath().from(runType.getIsClient().map(isClient -> { + final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + final SourceSet sourceSet = javaPluginExtension.getSourceSets().getByName(splitSourceSets ? "client" : "main"); + return sourceSet.getRuntimeClasspath(); + })); } private static void configureInstallerTokens(final TokenizedTask tokenizedTask, final RuntimeDevRuntimeDefinition runtimeDefinition, final Collection ignoreConfigurations, final Configuration pluginLayerLibraries, final Configuration gameLayerLibraries) { @@ -869,7 +1218,7 @@ private static void configureInstallerTokens(final TokenizedTask tokenizedTask, } private static String extractNeoformVersion(RuntimeDevRuntimeDefinition runtimeDefinition) { - final String completeVersion = runtimeDefinition.getJoinedNeoFormRuntimeDefinition().getSpecification().getNeoFormVersion(); + final String completeVersion = runtimeDefinition.getNeoFormRuntimeDefinition().getSpecification().getNeoFormVersion(); return completeVersion.substring(completeVersion.lastIndexOf("-") + 1); } @@ -899,7 +1248,7 @@ private static Provider collectFilePaths(Configuration config, String pr }); } - private void configureRun(final Run run, final RuntimeDevRuntimeDefinition runtimeDefinition) { + private void configureRun(final Run run, final boolean splitSourceSet, final RuntimeDevRuntimeDefinition runtimeDefinition) { final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); final SourceSet mainSourceSet = javaPluginExtension.getSourceSets().getByName("main"); @@ -949,6 +1298,16 @@ private void configureRun(final Run run, final RuntimeDevRuntimeDefinition runti ); }); + if (splitSourceSet) { + final SourceSet clientSourceSet = javaPluginExtension.getSourceSets().getByName("main"); + clientSourceSet.getResources().getSrcDirs().forEach(file -> { + run.getArguments().addAll( + TransformerUtils.ifTrue(run.getIsDataGenerator(), + "--existing", file.getAbsolutePath()) + ); + }); + } + Provider assetsDir = DownloadAssets.getAssetsDirectory(project).map(Directory::getAsFile).map(File::getAbsolutePath); Provider assetIndex = runtimeDefinition.getAssets().flatMap(DownloadAssets::getAssetIndex); @@ -982,7 +1341,7 @@ private TaskProvider createCleanProvider(final TaskProvide final Set> additionalRuntimeTasks = Sets.newHashSet(); final TaskBuildingContext context = new TaskBuildingContext(spec.getProject(), String.format("mapCleanFor%s", StringUtils.capitalize(jarProvider.getName())), taskName -> CommonRuntimeUtils.buildTaskName(spec, taskName), jarProvider, runtimeDefinition.getGameArtifactProvidingTasks(), versionData, additionalRuntimeTasks, runtimeDefinition); - + final TaskProvider remapTask = context.getNamingChannel().getApplyCompiledMappingsTaskBuilder().get().build(context); additionalRuntimeTasks.forEach(taskProvider -> taskProvider.configure(task -> CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory))); remapTask.configure(task -> CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory)); @@ -998,7 +1357,7 @@ private TaskProvider createFlippedMojMapProvider(final Tas return project.getTasks().register(taskName, WriteIMappingsFile.class, task -> { task.getMappings().set(mojmapProvider.flatMap(WithOutput::getOutput).map(TransformerUtils.guard(file -> IMappingFile.load(file.getAsFile()))).map(file -> new CacheableIMappingFile(file.reverse()))); - + CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); }); } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java b/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java index 246631484..e1747cfd1 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/runtime/runtime/definition/RuntimeDevRuntimeDefinition.java @@ -21,43 +21,43 @@ */ //TODO: Create DSL for runtime public final class RuntimeDevRuntimeDefinition extends CommonRuntimeDefinition implements IDelegatingRuntimeDefinition { - private final NeoFormRuntimeDefinition joinedNeoFormRuntimeDefinition; + private final NeoFormRuntimeDefinition neoFormRuntimeDefinition; private final TaskProvider patchBase; - public RuntimeDevRuntimeDefinition(@NotNull RuntimeDevRuntimeSpecification specification, NeoFormRuntimeDefinition joinedNeoFormRuntimeDefinition, TaskProvider sourcesProvider, TaskProvider patchBase) { - super(specification, joinedNeoFormRuntimeDefinition.getTasks(), sourcesProvider, joinedNeoFormRuntimeDefinition.getRawJarTask(), joinedNeoFormRuntimeDefinition.getGameArtifactProvidingTasks(), joinedNeoFormRuntimeDefinition.getMinecraftDependenciesConfiguration(), joinedNeoFormRuntimeDefinition::configureAssociatedTask, joinedNeoFormRuntimeDefinition.getVersionJson()); - this.joinedNeoFormRuntimeDefinition = joinedNeoFormRuntimeDefinition; + public RuntimeDevRuntimeDefinition(@NotNull RuntimeDevRuntimeSpecification specification, NeoFormRuntimeDefinition neoFormRuntimeDefinition, TaskProvider sourcesProvider, TaskProvider patchBase) { + super(specification, neoFormRuntimeDefinition.getTasks(), sourcesProvider, neoFormRuntimeDefinition.getRawJarTask(), neoFormRuntimeDefinition.getGameArtifactProvidingTasks(), neoFormRuntimeDefinition.getMinecraftDependenciesConfiguration(), neoFormRuntimeDefinition::configureAssociatedTask, neoFormRuntimeDefinition.getVersionJson()); + this.neoFormRuntimeDefinition = neoFormRuntimeDefinition; this.patchBase = patchBase; } - public NeoFormRuntimeDefinition getJoinedNeoFormRuntimeDefinition() { - return joinedNeoFormRuntimeDefinition; + public NeoFormRuntimeDefinition getNeoFormRuntimeDefinition() { + return neoFormRuntimeDefinition; } @Override public @NotNull TaskProvider getAssets() { - return joinedNeoFormRuntimeDefinition.getAssets(); + return neoFormRuntimeDefinition.getAssets(); } @Override public @NotNull TaskProvider getNatives() { - return joinedNeoFormRuntimeDefinition.getNatives(); + return neoFormRuntimeDefinition.getNatives(); } @Override public @NotNull Map getMappingVersionData() { - return joinedNeoFormRuntimeDefinition.getMappingVersionData(); + return neoFormRuntimeDefinition.getMappingVersionData(); } @NotNull @Override public TaskProvider getListLibrariesTaskProvider() { - return joinedNeoFormRuntimeDefinition.getListLibrariesTaskProvider(); + return neoFormRuntimeDefinition.getListLibrariesTaskProvider(); } @Override protected void buildRunInterpolationData(RunImpl run, MapProperty interpolationData) { - joinedNeoFormRuntimeDefinition.buildRunInterpolationData(run, interpolationData); + neoFormRuntimeDefinition.buildRunInterpolationData(run, interpolationData); } public TaskProvider getPatchBase() { @@ -66,6 +66,6 @@ public TaskProvider getPatchBase() { @Override public Definition getDelegate() { - return joinedNeoFormRuntimeDefinition; + return neoFormRuntimeDefinition; } } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/tasks/SetupProjectFromRuntime.java b/platform/src/main/java/net/neoforged/gradle/platform/tasks/SetupProjectFromRuntime.java index 94b8a58ef..e6537f3d5 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/tasks/SetupProjectFromRuntime.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/tasks/SetupProjectFromRuntime.java @@ -19,18 +19,6 @@ public SetupProjectFromRuntime() { super(); setGroup("setup"); - - final File defaultSourceTarget = SetupUtils.getSetupSourceTarget(getProject()); - final File defaultResourceTarget = SetupUtils.getSetupResourcesTarget(getProject()); - - getSourcesDirectory().convention( - getProject().getLayout().dir(getProviderFactory().provider(() -> defaultSourceTarget)) - ); - - getResourcesDirectory().convention( - getProject().getLayout().dir(getProviderFactory().provider(() -> defaultResourceTarget)) - ); - getShouldLockDirectories().convention(true); } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/util/SetupUtils.java b/platform/src/main/java/net/neoforged/gradle/platform/util/SetupUtils.java index 60723aa01..571aa22ad 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/util/SetupUtils.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/util/SetupUtils.java @@ -1,5 +1,6 @@ package net.neoforged.gradle.platform.util; +import net.neoforged.gradle.common.util.SourceSetUtils; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; @@ -12,15 +13,13 @@ private SetupUtils() { throw new IllegalStateException("Tried to create utility class!"); } - public static File getSetupSourceTarget(final Project project) { - final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - final SourceSet mainSource = javaPluginExtension.getSourceSets().getByName("main"); - return mainSource.getJava().getFiles().size() == 1 ? mainSource.getJava().getSourceDirectories().getSingleFile() : project.file("src/main/java"); + public static File getSetupSourceTarget(final SourceSet sourceSet) { + final Project project = SourceSetUtils.getProject(sourceSet); + return sourceSet.getJava().getFiles().size() == 1 ? sourceSet.getJava().getSourceDirectories().getSingleFile() : project.file("src/%s/java".formatted(sourceSet.getName())); } - - public static File getSetupResourcesTarget(final Project project) { - final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - final SourceSet mainSource = javaPluginExtension.getSourceSets().getByName("main"); - return mainSource.getResources().getFiles().size() == 1 ? mainSource.getResources().getSourceDirectories().getSingleFile() : project.file("src/main/resources"); + + public static File getSetupResourcesTarget(final SourceSet mainSource) { + final Project project = SourceSetUtils.getProject(mainSource); + return mainSource.getResources().getFiles().size() == 1 ? mainSource.getResources().getSourceDirectories().getSingleFile() : project.file("src/%s/resources".formatted(mainSource.getName())); } }