From e2467c62225cc9a367e8fac8b9d258b2ac5e52b2 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 26 Feb 2026 23:02:38 -0800 Subject: [PATCH 01/10] Add support for run tokens. --- .../gradle/MavenizerInstance.java | 8 +- .../internal/ForgeGradleFlowAction.java | 20 -- .../gradle/internal/ForgeGradleProblems.java | 21 -- .../minecraftforge/gradle/internal/Lazy.java | 32 +++ .../internal/MavenizerInstanceImpl.java | 6 +- .../internal/MinecraftDependencyImpl.java | 92 ++++--- .../internal/MinecraftDependencyInternal.java | 10 + .../internal/MinecraftExtensionImpl.java | 8 +- .../internal/MinecraftExtensionInternal.java | 3 + .../SlimeLauncherEclipseConfiguration.java | 168 ++++++++++--- .../gradle/internal/SlimeLauncherExec.java | 224 +++++++----------- .../internal/SlimeLauncherMetadata.java | 11 + .../internal/SlimeLauncherRunHelper.java | 71 ++++++ .../gradle/internal/SlimeLauncherRunTask.java | 23 ++ .../minecraftforge/gradle/internal/Util.java | 65 ++++- 15 files changed, 514 insertions(+), 248 deletions(-) create mode 100644 src/main/java/net/minecraftforge/gradle/internal/Lazy.java create mode 100644 src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java create mode 100644 src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java diff --git a/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java index 7eed9db40..5849f901d 100644 --- a/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java +++ b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java @@ -22,9 +22,15 @@ public interface MavenizerInstance extends ProviderConvertible asProvider(); + /// Gets the mappings channel used by the Mavenizer instance. + /// + /// @return The mappings channel used, after all sanitization has been done, should match exactly to the generated maven artifact + /// @see MinecraftMappings#getChannel() + Provider getMappingChannel(); + /// Gets the mappings version used by the Mavenizer instance. /// - /// @return The mappings version used + /// @return The mappings version used, after all sanitization has been done, should match exactly to the generated maven artifact /// @see MinecraftMappings#getVersion() Provider getMappingVersion(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleFlowAction.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleFlowAction.java index be438c498..c94f3ca3f 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleFlowAction.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleFlowAction.java @@ -119,24 +119,4 @@ protected void run(Parameters parameters) { parameters.problems().reportAccessTransformersNotApplied(e); } } - - static abstract class MavenizerSyncCheck extends ForgeGradleFlowAction { - static abstract class Parameters extends ForgeGradleFlowAction.Parameters { - final DirectoryProperty dependencyOutput = this.getObjects().directoryProperty(); - final Property dependency = this.getObjects().property(String.class); - - @Inject - public Parameters() { } - } - - @Inject - public MavenizerSyncCheck() { } - - @Override - protected void run(Parameters parameters) { - if (parameters.dependencyOutput.getAsFile().get().exists()) return; - - parameters.problems().mavenizerOutOfDate(parameters.getFailure().isPresent(), parameters.dependency.get()); - } - } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java index c099a6ec7..788666fde 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java @@ -129,27 +129,6 @@ RuntimeException changingMinecraftDependency(Dependency dependency) { //endregion //region Minecraft Maven - void mavenizerOutOfDate(boolean throwIt, Object dependency) { - String name = "mavenizer-out-of-date"; - String displayName = "Minecraft Mavenizer is out-of-date"; - Action problemSpec = spec -> spec - .details(""" - Gradle cannot compile your sources or run the game because the Minecraft Mavenizer is out-of-date. - The Mavenizer must be re-run in order for the changes made to the Minecraft dependency to take effect. - Affected dependency: '%s'""" - .formatted(dependency)) - .severity(Severity.ERROR) - .solution("Re-import your project in your IDE, as this will automatically synchronize the Mavenizer.") - .solution("Run `gradlew` with no arguments, as this will automatically synchronize the Mavenizer.") - .solution("Temporary revert any edits to the Minecraft dependency until the Mavenizer is re-run.") - .solution(HELP_MESSAGE); - - if (throwIt) - throw this.throwing(new IllegalStateException(displayName), name, displayName, problemSpec); - else - this.report(name, displayName, problemSpec); - } - void reportMavenizerNotHighestRepository() { this.report("mavenizer-not-highest-repo", "Mavenizer repository is not the highest", spec -> spec .details(""" diff --git a/src/main/java/net/minecraftforge/gradle/internal/Lazy.java b/src/main/java/net/minecraftforge/gradle/internal/Lazy.java new file mode 100644 index 000000000..d8d894127 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/internal/Lazy.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradle.internal; + +import org.jspecify.annotations.Nullable; + +import java.util.function.Supplier; + +class Lazy implements Supplier { + private final Supplier supplier; + private boolean computed = false; + private @Nullable T value = null; + + public Lazy(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public final @Nullable T get() { + if (this.computed) + return this.value; + + synchronized (this) { + if (this.computed) + return this.value; + this.computed = true; + return this.value = supplier.get(); + } + } +} diff --git a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java index ab57a7dad..704fd3bdc 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java @@ -6,7 +6,6 @@ import groovy.json.JsonSlurper; import net.minecraftforge.gradle.MavenizerInstance; -import org.gradle.api.Transformer; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Provider; @@ -60,6 +59,11 @@ public Provider asProvider() { return this.invoke.map(m -> this.dependency); } + @Override + public Provider getMappingChannel() { + return get("mappings.channel"); + } + @Override public Provider getMappingVersion() { return get("mappings.version"); diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java index feaf9a535..5b9e011cf 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java @@ -6,6 +6,7 @@ import groovy.lang.Closure; import groovy.transform.NamedVariant; +import net.minecraftforge.gradle.MavenizerInstance; import net.minecraftforge.gradle.MinecraftExtensionForProject; import net.minecraftforge.gradle.MinecraftMappings; import net.minecraftforge.gradle.SlimeLauncherOptions; @@ -18,9 +19,11 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.ModuleIdentifier; +import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; import org.gradle.api.file.ProjectLayout; import org.gradle.api.flow.FlowProviders; import org.gradle.api.flow.FlowScope; @@ -29,6 +32,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.jspecify.annotations.Nullable; import javax.inject.Inject; @@ -42,6 +46,10 @@ abstract class MinecraftDependencyImpl implements MinecraftDependencyInternal { // Minecraft extension private final MinecraftExtensionInternal.ForProject minecraft = ((MinecraftExtensionInternal.ForProject) getProject().getExtensions().getByType(MinecraftExtensionForProject.class)); + private final String mavenizerName; // Name for our mavenizer invocation in the Minecraft Extension + private @Nullable Configuration detatchedConfig; + private @Nullable Configuration metadataConfig; + private @Nullable TaskProvider metadataTask; // Access Transformers private final ConfigurableFileCollection accessTransformer = this.getObjects().fileCollection(); @@ -53,25 +61,19 @@ abstract class MinecraftDependencyImpl implements MinecraftDependencyInternal { private final Property module = getObjects().property(ModuleIdentifier.class); private final Property version = getObjects().property(String.class); - private final DirectoryProperty mavenizerOutput = getObjects().directoryProperty(); private final Property mappings = this.getObjects().property(MinecraftMappingsInternal.class); - private @Nullable String sourceSetName; private final ForgeGradleProblems problems = this.getObjects().newInstance(ForgeGradleProblems.class); protected abstract @Inject Project getProject(); - protected abstract @Inject FlowScope getFlowScope(); - - protected abstract @Inject FlowProviders getFlowProviders(); - protected abstract @Inject ObjectFactory getObjects(); protected abstract @Inject ProjectLayout getProjectLayout(); @Inject - public MinecraftDependencyImpl(Provider mavenizerOutput) { - this.mavenizerOutput.set(mavenizerOutput); + public MinecraftDependencyImpl(String mavenizerName) { + this.mavenizerName = mavenizerName; this.mappings.convention(minecraft.getMappingsProperty()); } @@ -96,6 +98,30 @@ public void mappings(String channel, String version) { this.mappings.set(this.getObjects().newInstance(MinecraftMappingsImpl.class, channel, version)); } + @Override + public MavenizerInstance getMavenizerInstance() { + return this.minecraft.getDependency(this.mavenizerName); + } + + @Override + public FileCollection getMinecraftDependencies() { + assert this.detatchedConfig != null; + return this.detatchedConfig; + } + + @Override + public FileCollection getMetadataDependency() { + assert this.metadataConfig != null; + return this.metadataConfig; + } + + @Override + public TaskProvider getMetadataTask() { + if (this.metadataTask == null) + this.metadataTask = SlimeLauncherMetadata.register(this.getProject(), this); + return this.metadataTask; + } + @Override public boolean hasAccessTransformersPlugin() { return minecraft.hasAccessTransformersPlugin(); } @@ -140,6 +166,14 @@ public ExternalModuleDependency init(Object dependencyNotation, Closure closu return module; })); + // Keep a standalone configuration of JUST this dependency, so we can fill in 'minecraft_classpath' for run configs. + this.detatchedConfig = this.getProject().getConfigurations().detachedConfiguration(dependency); + this.metadataConfig = this.getProject().getConfigurations().detachedConfiguration(getProject().getDependencyFactory().create( + dependency.getModule().getGroup(), dependency.getModule().getName(), dependency.getVersion(), "metadata", "zip" + )); + this.metadataConfig.setTransitive(false); + this.metadataConfig.attributes(a -> a.attribute(Usage.USAGE_ATTRIBUTE, a.named(Usage.class, "metadata"))); + this.asString.set(dependency.toString()); this.asPath.set(Util.pathify(dependency)); this.module.set(dependency.getModule()); @@ -148,6 +182,21 @@ public ExternalModuleDependency init(Object dependencyNotation, Closure closu return this.delegate = dependency; } + @Override + public String toString() { + return this.asString.getOrElse(super.toString()); + } + + @Override + public String getPath() { + return this.asPath.get(); + } + + @Override + public ModuleIdentifier getModule() { + return this.module.get(); + } + @Override public void handle(Configuration configuration) { if (configuration.isCanBeResolved()) { @@ -157,13 +206,14 @@ public void handle(Configuration configuration) { // Apply the dependency substitution for mappings attributes. if (this.mappings.isPresent()) { + var instance = this.getMavenizerInstance(); var module = dependencySubstitution.module(moduleSelector); try { dependencySubstitution .substitute(module) .using(dependencySubstitution.variant(module, variant -> variant.attributes(attributes -> { - attributes.attributeProvider(ForgeAttributes.MappingsChannel.ATTRIBUTE, this.mappings.map(MinecraftMappings::getChannel)); - attributes.attributeProvider(ForgeAttributes.MappingsVersion.ATTRIBUTE, this.mappings.map(MinecraftMappings::getVersion)); + attributes.attributeProvider(ForgeAttributes.MappingsChannel.ATTRIBUTE, instance.getMappingChannel()); + attributes.attributeProvider(ForgeAttributes.MappingsVersion.ATTRIBUTE, instance.getMappingVersion()); }))) .because("Accounts for declared mappings."); } catch (InvalidUserCodeException e) { @@ -175,30 +225,12 @@ public void handle(Configuration configuration) { @Override public void handle(NamedDomainObjectSet sourceSets, NamedDomainObjectSet allSourceSets) { - var asString = this.asString.get(); - var dependencyOutput = this.mavenizerOutput.dir(this.asPath); - getFlowScope().always(ForgeGradleFlowAction.MavenizerSyncCheck.class, spec -> - spec.parameters(parameters -> { - parameters.getFailure().set(getFlowProviders().getBuildWorkResult().map(r -> r.getFailure().orElse(null))); - parameters.dependencyOutput.set(dependencyOutput); - parameters.dependency.set(asString); - }) - ); - - if (!sourceSets.isEmpty() && this.sourceSetName == null) - this.sourceSetName = sourceSets.iterator().next().getName(); - var runs = Objects.requireNonNullElseGet(this.getRuns(), () -> getObjects().domainObjectContainer(SlimeLauncherOptionsImpl.class)); ((NamedDomainObjectContainer) runs).addAll((NamedDomainObjectContainer) minecraft.getRuns()); + allSourceSets.configureEach(sourceSet -> { - var single = getProject() - .getConfigurations() - .getByName(sourceSet.getRuntimeClasspathConfigurationName()) - .getAllDependencies() - .matching(MinecraftDependencyInternal::is) - .size() == 1; runs.forEach(options -> { - var task = SlimeLauncherExec.register(getProject(), sourceSet, (SlimeLauncherOptionsImpl) options, module.get(), version.get(), asPath.get(), asString, single); + var runTask = SlimeLauncherExec.register(getProject(), sourceSet, (SlimeLauncherOptionsImpl)options, this); }); }); diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java index 3a0d9d2d8..0803e928a 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java @@ -5,6 +5,7 @@ package net.minecraftforge.gradle.internal; import groovy.lang.Closure; +import net.minecraftforge.gradle.MavenizerInstance; import net.minecraftforge.gradle.MinecraftDependency; import net.minecraftforge.gradle.SlimeLauncherOptions; import org.gradle.api.Action; @@ -13,7 +14,9 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; @@ -59,6 +62,13 @@ default Closure closure(Closure closure) { return closure.rehydrate(closure.getDelegate(), new ClosureOwnerImpl.MinecraftDependencyImpl(closure.getOwner(), this), closure.getThisObject()); } + MavenizerInstance getMavenizerInstance(); + FileCollection getMinecraftDependencies(); + FileCollection getMetadataDependency(); + TaskProvider getMetadataTask(); + String getPath(); + ModuleIdentifier getModule(); + void handle(Configuration configuration); void handle(NamedDomainObjectSet sourceSets, NamedDomainObjectSet allSourceSets); diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java index adb6ab9d1..de29f3a89 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java @@ -46,6 +46,7 @@ import javax.inject.Inject; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -390,7 +391,7 @@ public MavenizerInstance dependency( if (value instanceof ExternalModuleDependencyBundle) throw new IllegalArgumentException("Minecraft dependency cannot be a bundle"); - var minecraftDependency = this.getObjects().newInstance(MinecraftDependencyImpl.class, this.getMavenizerOutput()); + var minecraftDependency = this.getObjects().newInstance(MinecraftDependencyImpl.class, name); this.minecraftDependencies.add(minecraftDependency); var dep = minecraftDependency.init(value, closure); var outputJson = this.plugin.rootProjectDirectory().file(".gradle/mavenizer/dependencies/" + name + ".json").get().getAsFile(); @@ -464,6 +465,11 @@ public MavenizerInstance getDependency(String name) { return ret; } + @Override + public Collection getDependencies() { + return Collections.unmodifiableCollection(this.mavenizerRegistry.values()); + } + private void checkRepos(List repos) { ToIntFunction indexOf = s -> { for (var i = 0; i < repos.size(); i++) { diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionInternal.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionInternal.java index 40d0d55e7..a1282b9fe 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionInternal.java @@ -13,6 +13,7 @@ import org.gradle.api.reflect.TypeOf; import org.jetbrains.annotations.UnmodifiableView; +import java.util.Collection; import java.util.List; interface MinecraftExtensionInternal extends MinecraftExtension, HasPublicType, MinecraftMappingsContainerInternal { @@ -34,5 +35,7 @@ default TypeOf getPublicType() { } @UnmodifiableView List getRepositories(); + + @UnmodifiableView Collection getDependencies(); } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java index af3a7b357..52c282e7d 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java @@ -4,12 +4,12 @@ */ package net.minecraftforge.gradle.internal; -import com.google.gson.JsonIOException; import com.google.gson.reflect.TypeToken; import net.minecraftforge.gradle.SlimeLauncherOptions; import net.minecraftforge.util.data.json.JsonData; import net.minecraftforge.util.data.json.RunConfig; import org.gradle.api.DefaultTask; +import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; @@ -28,13 +28,16 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.toolchain.JavaLauncher; import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.work.DisableCachingByDefault; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; import org.gradle.workers.WorkerExecutor; +import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -49,20 +52,98 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; // This is mostly taken from ForgeGradle 6 but slimmed down to what we need @DisableCachingByDefault(because = "ForgeGradle would require more information to cache this task") -abstract class SlimeLauncherEclipseConfiguration extends DefaultTask implements ForgeGradleTask { +abstract class SlimeLauncherEclipseConfiguration extends DefaultTask implements ForgeGradleTask, SlimeLauncherRunTask { + static TaskProvider register(Project project, SourceSet sourceSet, SlimeLauncherOptionsImpl options, MinecraftDependencyInternal mcdep, String runTaskName) { + var metadata = mcdep.getMetadataTask(); + var generateEclipseRunTaskName = sourceSet.getTaskName("genEclipseRun", options.getName()) + "For" + Util.dependencyToCamelCase(mcdep.getModule()); + + var genEclipseRun = project.getTasks().register(generateEclipseRunTaskName, SlimeLauncherEclipseConfiguration.class, task -> { + task.getRunName().set(options.getName()); + task.setDescription("Generates the '%s' Slime Launcher run configuration for Eclipse.".formatted(options.getName())); + task.getOutputFile().set(task.getProjectLayout().getProjectDirectory().file(runTaskName + ".launch")); + + + var inst = mcdep.getMavenizerInstance(); + var runtimeClasspath = task.getObjects().fileCollection().from(task.getProviders().provider(sourceSet::getRuntimeClasspath)); + task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy + task.getMinecraftClasspath().setFrom(mcdep.getMinecraftDependencies()); + task.getMappingChannel().set(inst.getMappingChannel()); + task.getMappingVersion().set(inst.getMappingVersion()); + // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. + //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); + + var runtimeClasspath2 = task.getObjects().fileCollection().from( + task.getProviders().provider(() -> { + var runtime = sourceSet.getRuntimeClasspath(); + var eclipseModel = project.getExtensions().findByType(EclipseModel.class); + if (eclipseModel == null) + return runtime.getFiles(); + + // We need to build a map of sourcesets to real output paths like Eclipse's plugin does. + // There is no known exposure of this stuff, so have to do it ourselves. + // https://github.com/gradle/gradle/blob/master/platforms/ide/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.java#L220 + var classpath = eclipseModel.getClasspath(); + var sortedSourceSets = sortSourceSets(classpath.getSourceSets()); + var replacements = new HashMap(); + var base = classpath.getBaseSourceOutputDir().getAsFile().get(); + var claimed = new HashSet(); + claimed.add(classpath.getDefaultOutputDir()); + + // Gather the output name eclipse will use, and all outputs gradle expects + for (var sources : sortedSourceSets) { + var name = sources.getName(); + var path = new File(base, name); + while (claimed.contains(path)) { + name += '_'; + path = new File(base, name); + } + claimed.add(path); + if (sources.getOutput().getResourcesDir() != null) + replacements.put(sources.getOutput().getResourcesDir(), path); + for (var dir : sources.getOutput().getClassesDirs().getFiles()) + replacements.put(dir, path); + } + + // Now replace the existing classpath with the ones eclipse will use + var ret = new LinkedHashSet(runtime.getFiles().size()); + for (var file : runtime.getFiles()) + ret.add(replacements.getOrDefault(file, file)); + return ret; + })); + task.getClasspath().from(runtimeClasspath); + task.getSourceSetName().set(sourceSet.getName()); + + task.getCacheDir().set(task.getObjects().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); + task.getLocalCacheDir().set(task.getObjects().directoryProperty().value(task.localCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); + task.getMetadata().setFrom(metadata.map(SlimeLauncherMetadata::getMetadata)); + task.getRunsJson().set(metadata.flatMap(SlimeLauncherMetadata::getRunsJson)); + + task.getOptions().set(options); + }); + + project.getTasks().named("genEclipseRuns", task -> task.dependsOn(genEclipseRun)); + return genEclipseRun; + } + + protected abstract @OutputFile RegularFileProperty getOutputFile(); protected abstract @Input Property getProjectName(); - protected abstract @Input Property getSourceSetName(); + public abstract @Input @Override Property getSourceSetName(); protected abstract @Input @Optional Property getEclipseProjectName(); @@ -77,8 +158,13 @@ abstract class SlimeLauncherEclipseConfiguration extends DefaultTask implements protected abstract @Nested Property getOptions(); protected abstract @Internal DirectoryProperty getCacheDir(); - - protected abstract @InputFiles ConfigurableFileCollection getMetadata(); + public abstract @Internal @Override DirectoryProperty getLocalCacheDir(); + public abstract @InputFiles @Override ConfigurableFileCollection getMetadata(); + public abstract @InputFiles @Override ConfigurableFileCollection getMinecraftClasspath(); + public abstract @InputFiles @Override ConfigurableFileCollection getRuntimeClasspath(); + public abstract @Input @Override Property getMappingChannel(); + public abstract @Input @Override Property getMappingVersion(); + //protected abstract @InputFile @Override RegularFileProperty getSrgToMcp(); protected abstract @InputFile @Optional RegularFileProperty getRunsJson(); @@ -111,29 +197,30 @@ protected void exec() { if (!this.getEclipseProjectName().isPresent()) problems.reportMissingEclipsePlugin(this.getName()); - List args; - List jvmArgs; - MapProperty environment; DirectoryProperty workingDir; //region Launcher Metadata Inheritance Map configs = Map.of(); var jsons = this.getRunsJson().getAsFile().getOrNull(); - if (jsons != null && jsons.exists()) { - try { - configs = JsonData.fromJson( - this.getRunsJson().getAsFile().get(), - new TypeToken<>() { } - ); - } catch (JsonIOException e) { - // continue - } - } + if (jsons != null && jsons.exists()) + configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); + var tokens = SlimeLauncherRunHelper.buildTokens(this); + var unknown = new HashSet(); - args = new ArrayList<>(options.getArgs().getOrElse(List.of())); - jvmArgs = new ArrayList<>(options.getJvmArgs().getOrElse(List.of())); + var args = new ArrayList<>(List.of( + "--main", options.getMainClass().get(), + "--cache", this.getCacheDir().get().getAsFile().getAbsolutePath(), + "--metadata", this.getMetadata().getSingleFile().getAbsolutePath(), + "--" + )); + for (var arg : options.getArgs().getOrElse(List.of())) + args.add(Util.replaceTokens(tokens, arg, unknown)); + + var jvmArgs = new ArrayList(); + for (var arg : options.getJvmArgs().getOrElse(List.of())) + jvmArgs.add(Util.replaceTokens(tokens, arg, unknown)); if (!options.getClasspath().isEmpty()) this.getClasspath().setFrom(options.getClasspath()); if (options.getMinHeapSize().filter(Util::isPresent).isPresent()) @@ -141,17 +228,21 @@ protected void exec() { if (options.getMaxHeapSize().filter(Util::isPresent).isPresent()) jvmArgs.add("-Xmx" + options.getMaxHeapSize().get()); for (var property : options.getSystemProperties().getOrElse(Map.of()).entrySet()) - jvmArgs.add("-D" + property.getKey() + '=' + property.getValue()); - environment = options.getEnvironment(); + jvmArgs.add("-D" + property.getKey() + '=' + Util.replaceTokens(tokens, property.getValue(), unknown)); + + var env = new HashMap(); + for (var entry : options.getEnvironment().get().entrySet()) { + var value = Util.replaceTokens(tokens, entry.getValue(), unknown); + env.put(entry.getKey(), value); + } + workingDir = options.getWorkingDir(); //endregion - //region Slime Launcher setup - args.addAll(0, List.of("--main", options.getMainClass().get(), - "--cache", this.getCacheDir().get().getAsFile().getAbsolutePath(), - "--metadata", this.getMetadata().getSingleFile().getAbsolutePath(), - "--")); + for (var token : unknown) + getLogger().debug("Unknown Run Token: {}", token); + //region Slime Launcher setup try { Files.createDirectories(workingDir.get().getAsFile().toPath()); } catch (IOException e) { @@ -169,11 +260,32 @@ protected void exec() { parameters.getArgs().set(args); parameters.getJvmArgs().set(jvmArgs); parameters.getWorkingDir().set(workingDir); - parameters.getEnvironment().set(environment); + parameters.getEnvironment().set(env); parameters.getJavaHome().set(this.getJavaLauncher().map(j -> j.getMetadata().getInstallationPath())); }); } + private static List sortSourceSets(@Nullable Iterable sourceSets) { + if (sourceSets == null) + return new ArrayList<>(0); + var ret = new ArrayList(); + for (var item : sourceSets) + ret.add(item); + ret.sort(Comparator.comparing(SlimeLauncherEclipseConfiguration::toComparable)); + return ret; + } + + private static Integer toComparable(SourceSet sourceSet) { + String name = sourceSet.getName(); + if (SourceSet.MAIN_SOURCE_SET_NAME.equals(name)) { + return 0; + } else if (SourceSet.TEST_SOURCE_SET_NAME.equals(name)) { + return 1; + } else { + return 2; + } + } + static abstract class Action implements WorkAction { interface Parameters extends WorkParameters { RegularFileProperty getOutputFile(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index 81f8f9cd4..e8b735729 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -4,15 +4,12 @@ */ package net.minecraftforge.gradle.internal; -import com.google.gson.JsonIOException; import com.google.gson.reflect.TypeToken; +import net.minecraftforge.gradle.MinecraftExtensionForProject; import net.minecraftforge.gradle.SlimeLauncherOptions; import net.minecraftforge.util.data.json.JsonData; import net.minecraftforge.util.data.json.RunConfig; import org.gradle.api.Project; -import org.gradle.api.UnknownDomainObjectException; -import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; @@ -30,116 +27,45 @@ import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; -import org.gradle.plugins.ide.eclipse.model.EclipseModel; import org.gradle.work.DisableCachingByDefault; -import org.jspecify.annotations.Nullable; import javax.inject.Inject; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @DisableCachingByDefault(because = "Running the game cannot be cached") -abstract class SlimeLauncherExec extends JavaExec implements ForgeGradleTask, HasPublicType { - static TaskProvider register(Project project, SourceSet sourceSet, SlimeLauncherOptionsImpl options, ModuleIdentifier module, String version, String asPath, String asString, boolean single) { - TaskProvider metadata; - { - TaskProvider t; - var taskName = "slimeLauncherMetadata" + (single ? "" : "for" + Util.dependencyToCamelCase(module)); - try { - t = project.getTasks().named(taskName, SlimeLauncherMetadata.class); - } catch (UnknownDomainObjectException e) { - var metadataConfiguration = project.getConfigurations().detachedConfiguration( - project.getDependencyFactory().create(module.getGroup(), module.getName(), version, "metadata", "zip") - ); - metadataConfiguration.setTransitive(false); - metadataConfiguration.attributes(a -> a.attribute(Usage.USAGE_ATTRIBUTE, a.named(Usage.class, "metadata"))); - - t = project.getTasks().register(taskName, SlimeLauncherMetadata.class, task -> { - task.setDescription("Extracts the Slime Launcher metadata%s.".formatted(single ? "" : " for '%s'".formatted(asString))); - - task.getMetadata().setFrom(metadataConfiguration); - }); - } - - metadata = t; - } +abstract class SlimeLauncherExec extends JavaExec implements ForgeGradleTask, HasPublicType, SlimeLauncherRunTask { + static TaskProvider register(Project project, SourceSet sourceSet, SlimeLauncherOptionsImpl options, MinecraftDependencyInternal mcdep) { + var minecraft = ((MinecraftExtensionInternal.ForProject)project.getExtensions().getByType(MinecraftExtensionForProject.class)); + var metadata = mcdep.getMetadataTask(); + var single = minecraft.getDependencies().size() > 1; - var taskNameSuffix = (single ? "" : "for" + Util.dependencyToCamelCase(module)); + var taskNameSuffix = (single ? "" : "For" + Util.dependencyToCamelCase(mcdep.getModule())); var runTaskName = sourceSet.getTaskName("run", options.getName()) + taskNameSuffix; - var generateEclipseRunTaskName = sourceSet.getTaskName("genEclipseRun", options.getName()) + taskNameSuffix; - - var genEclipseRun = project.getTasks().register(generateEclipseRunTaskName, SlimeLauncherEclipseConfiguration.class, task -> { - task.getRunName().set(options.getName()); - task.setDescription("Generates the '%s' Slime Launcher run configuration for Eclipse.".formatted(options.getName())); - task.getOutputFile().set(task.getProjectLayout().getProjectDirectory().file(runTaskName + ".launch")); - - var runtimeClasspath = task.getObjects().fileCollection().from( - task.getProviders().provider(() -> { - var runtime = sourceSet.getRuntimeClasspath(); - var eclipseModel = project.getExtensions().findByType(EclipseModel.class); - if (eclipseModel == null) - return runtime.getFiles(); - - // We need to build a map of sourcesets to real output paths like Eclipse's plugin does. - // There is no known exposure of this stuff, so have to do it ourselves. - // https://github.com/gradle/gradle/blob/master/platforms/ide/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.java#L220 - var classpath = eclipseModel.getClasspath(); - var sortedSourceSets = sortSourceSets(classpath.getSourceSets()); - var replacements = new HashMap(); - var base = classpath.getBaseSourceOutputDir().getAsFile().get(); - var claimed = new HashSet(); - claimed.add(classpath.getDefaultOutputDir()); - - // Gather the output name eclipse will use, and all outputs gradle expects - for (var sources : sortedSourceSets) { - var name = sources.getName(); - var path = new File(base, name); - while (claimed.contains(path)) { - name += '_'; - path = new File(base, name); - } - claimed.add(path); - if (sources.getOutput().getResourcesDir() != null) - replacements.put(sources.getOutput().getResourcesDir(), path); - for (var dir : sources.getOutput().getClassesDirs().getFiles()) - replacements.put(dir, path); - } - - // Now replace the existing classpath with the ones eclipse will use - var ret = new LinkedHashSet(runtime.getFiles().size()); - for (var file : runtime.getFiles()) - ret.add(replacements.getOrDefault(file, file)); - return ret; - })); - task.getClasspath().from(runtimeClasspath); - task.getSourceSetName().set(sourceSet.getName()); - - task.getCacheDir().set(task.getObjects().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(asPath)).map(task.problems.ensureFileLocation()))); - task.getMetadata().setFrom(metadata.map(SlimeLauncherMetadata::getMetadata)); - task.getRunsJson().set(metadata.flatMap(SlimeLauncherMetadata::getRunsJson)); - - task.getOptions().set(options); - }); - - project.getTasks().named("genEclipseRuns", task -> task.dependsOn(genEclipseRun)); + var genEclipse = SlimeLauncherEclipseConfiguration.register(project, sourceSet, options, mcdep, runTaskName); return project.getTasks().register(runTaskName, SlimeLauncherExec.class, task -> { task.getRunName().set(options.getName()); task.getSourceSetName().set(sourceSet.getName()); task.setDescription("Runs the '%s' Slime Launcher run configuration.".formatted(options.getName())); - task.classpath(task.getObjectFactory().fileCollection().from(task.getProviderFactory().provider(sourceSet::getRuntimeClasspath))); - - var caches = task.getObjectFactory().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(asPath))); - task.getCacheDir().set(caches.map(task.problems.ensureFileLocation())); + var inst = mcdep.getMavenizerInstance(); + var runtimeClasspath = task.getObjectFactory().fileCollection().from(task.getProviderFactory().provider(sourceSet::getRuntimeClasspath)); + task.classpath(runtimeClasspath); + task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy + task.getMinecraftClasspath().setFrom(mcdep.getMinecraftDependencies()); + task.getMappingChannel().set(inst.getMappingChannel()); + task.getMappingVersion().set(inst.getMappingVersion()); + // We need a way to reverse this file, cuz we want srg->mcp and this is mcp->srg + //task.getSrgToMcp().set(project.file(inst.getToSrgFile())); + + task.getCacheDir().set(task.getObjectFactory().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); + task.getLocalCacheDir().set(task.getObjectFactory().directoryProperty().value(task.localCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); task.getMetadata().setFrom(metadata.map(SlimeLauncherMetadata::getMetadata)); task.getRunsJson().set(metadata.flatMap(SlimeLauncherMetadata::getRunsJson)); @@ -149,13 +75,18 @@ static TaskProvider register(Project project, SourceSet sourc protected abstract @Input Property getRunName(); - protected abstract @Input Property getSourceSetName(); + public abstract @Input Property getSourceSetName(); protected abstract @Nested Property getOptions(); protected abstract @Internal DirectoryProperty getCacheDir(); - - protected abstract @InputFiles ConfigurableFileCollection getMetadata(); + public abstract @Internal @Override DirectoryProperty getLocalCacheDir(); + public abstract @InputFiles @Override ConfigurableFileCollection getMetadata(); + public abstract @InputFiles @Override ConfigurableFileCollection getMinecraftClasspath(); + public abstract @InputFiles @Override ConfigurableFileCollection getRuntimeClasspath(); + public abstract @Input @Override Property getMappingChannel(); + public abstract @Input @Override Property getMappingVersion(); + //protected abstract @InputFile @Override RegularFileProperty getSrgToMcp(); protected abstract @InputFile @Optional RegularFileProperty getRunsJson(); @@ -186,52 +117,73 @@ public SlimeLauncherExec() { @Override public void exec() { Provider mainClass; - List args; //region Launcher Metadata Inheritance Map configs = Map.of(); var jsons = this.getRunsJson().getAsFile().getOrNull(); - if (jsons != null && jsons.exists()) { - try { - configs = JsonData.fromJson( - this.getRunsJson().getAsFile().get(), - new TypeToken<>() { } - ); - } catch (JsonIOException e) { - // continue - } - } + if (jsons != null && jsons.exists()) + configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); + var tokens = SlimeLauncherRunHelper.buildTokens(this); + var unknown = new HashSet(); mainClass = options.getMainClass().filter(Util::isPresent); - args = new ArrayList<>(options.getArgs().getOrElse(List.of())); - this.jvmArgs(options.getJvmArgs().get()); + if (!this.getMainClass().get().startsWith("net.minecraftforge.launcher")) { + this.getLogger().warn("WARNING: Main class is not Slime Launcher! Skipping additional configuration."); + } else { + var slimeArgs = List.of( + "--main", mainClass.get(), + "--cache", this.getCacheDir().get().getAsFile().getAbsolutePath(), + "--metadata", this.getMetadata().getSingleFile().getAbsolutePath(), + "--" + ); + // Set need to add slime args first, so grab a copy and reset + var args = new ArrayList<>(slimeArgs); + args.addAll(this.getArgs()); + this.setArgs(args); + } + + var args = new ArrayList(); + for (var arg : options.getArgs().getOrElse(List.of())) + args.add(Util.replaceTokens(tokens, arg, unknown)); + this.args(args); + + var jvmArgs = new ArrayList(); + for (var arg : options.getJvmArgs().getOrElse(List.of())) + jvmArgs.add(Util.replaceTokens(tokens, arg, unknown)); + this.jvmArgs(jvmArgs); + if (!options.getClasspath().isEmpty()) this.setClasspath(options.getClasspath()); if (options.getMinHeapSize().filter(Util::isPresent).isPresent()) this.setMinHeapSize(options.getMinHeapSize().get()); if (options.getMaxHeapSize().filter(Util::isPresent).isPresent()) this.setMinHeapSize(options.getMaxHeapSize().get()); - this.systemProperties(options.getSystemProperties().get()); - this.environment(options.getEnvironment().get()); - this.workingDir(options.getWorkingDir().get()); - //endregion - if (!this.getMainClass().get().startsWith("net.minecraftforge.launcher")) { - this.getLogger().warn("WARNING: Main class is not Slime Launcher! Skipping additional configuration."); - } else { - this.args("--main", mainClass.get(), - "--cache", this.getCacheDir().get().getAsFile().getAbsolutePath(), - "--metadata", this.getMetadata().getSingleFile().getAbsolutePath(), - "--"); + var system = new HashMap(); + for (var entry : options.getSystemProperties().get().entrySet()) { + var value = Util.replaceTokens(tokens, entry.getValue(), unknown); + system.put(entry.getKey(), value); + this.systemProperty(entry.getKey(), value); } - this.args(args); + var env = new HashMap(); + for (var entry : options.getEnvironment().get().entrySet()) { + var value = Util.replaceTokens(tokens, entry.getValue(), unknown); + env.put(entry.getKey(), value); + this.environment(entry.getKey(), value); + } + + this.workingDir(options.getWorkingDir().get()); + //endregion if (!this.getClient().getOrElse(false)) this.setStandardInput(System.in); + for (var token : unknown) + getLogger().lifecycle("Unknown Run Token: {}", token); + try { Files.createDirectories(this.getWorkingDir().toPath()); } catch (IOException e) { @@ -243,29 +195,15 @@ public void exec() { } catch (Exception e) { this.getLogger().error("Something went wrong! Here is some debug info."); this.getLogger().error("Args: {}", this.getArgs()); + this.getLogger().error("JVM Args: {}", this.getJvmArgs()); + this.getLogger().error("Environment:"); + for (var entry : env.entrySet()) + this.getLogger().error("\t{}: {}", entry.getKey(), entry.getValue()); + this.getLogger().error("System:"); + for (var entry : system.entrySet()) + this.getLogger().error("\t{}: {}", entry.getKey(), entry.getValue()); this.getLogger().error("Options: {}", options); throw e; } } - - private static List sortSourceSets(@Nullable Iterable sourceSets) { - if (sourceSets == null) - return new ArrayList<>(0); - var ret = new ArrayList(); - for (var item : sourceSets) - ret.add(item); - ret.sort(Comparator.comparing(SlimeLauncherExec::toComparable)); - return ret; - } - - private static Integer toComparable(SourceSet sourceSet) { - String name = sourceSet.getName(); - if (SourceSet.MAIN_SOURCE_SET_NAME.equals(name)) { - return 0; - } else if (SourceSet.TEST_SOURCE_SET_NAME.equals(name)) { - return 1; - } else { - return 2; - } - } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java index 0f80d713f..8160cbd18 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java @@ -5,12 +5,15 @@ package net.minecraftforge.gradle.internal; import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.UnknownDomainObjectException; import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; import javax.inject.Inject; import java.io.FileInputStream; @@ -23,6 +26,14 @@ import java.util.zip.ZipInputStream; abstract class SlimeLauncherMetadata extends DefaultTask implements ForgeGradleTask { + static TaskProvider register(Project project, MinecraftDependencyInternal mcdep) { + var taskName = "slimeLauncherMetadatafor" + Util.dependencyToCamelCase(mcdep.getModule()); + return project.getTasks().register(taskName, SlimeLauncherMetadata.class, task -> { + task.setDescription("Extracts the Slime Launcher metadata for '%s'.".formatted(mcdep.toString())); + task.getMetadata().setFrom(mcdep.getMetadataDependency()); + }); + } + protected abstract @InputFiles ConfigurableFileCollection getMetadata(); protected abstract @OutputFile RegularFileProperty getRunsJson(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java new file mode 100644 index 000000000..ed9dee03b --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradle.internal; + +import org.gradle.api.file.FileCollection; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +class SlimeLauncherRunHelper { + // Legacy replacement tokens. See https://github.com/MinecraftForge/ForgeGradle/issues/1048 + static Map> buildTokens(SlimeLauncherRunTask task) { + var ret = new HashMap>(); + // Should be taken care of by SlimeLauncher + ret.put("asset_index", () -> "{asset_index}"); + ret.put("assets_root", () -> "{assets_root}"); + ret.put("natives", () -> "{natives}"); + + + ret.put("mcp_mappings", task.getMappingChannel().zip(task.getMappingVersion(), (c, v) -> c + '_' + v)::get); + var minecraft = getClasspath(task.getMinecraftClasspath()); + var runtime = getClasspath(task.getRuntimeClasspath()); + ret.put("minecraft_classpath", getClasspath(minecraft)); + ret.put("runtime_classpath", getClasspath(runtime)); + ret.put("minecraft_classpath_file", getClasspathFile(task, "minecraft", minecraft)); + ret.put("runtime_classpath_file", getClasspathFile(task, "runtime_" + task.getSourceSetName().get(), runtime)); + // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. + //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); + + // Pending: + //mc_version The simple vanilla Minecraft Version - 1.21.11 + //mcp_version The full MCP Config version - 1.21.11-000000000.000000 + //modules The classpath of a detached configuration containing the dependencies from UserDevV2.modules + //source_roots The destination directories of each sourceset added to this run config. This is the old hacky thing for people who don't merge their sourcesets. Or use older versions that manually builds them. May not actually need this. + return ret; + } + + private static Supplier> getClasspath(FileCollection files) { + return new Lazy<>(() -> { + var ret = new ArrayList(files.getFiles().size()); + for (var file : files.getFiles()) + ret.add(file.getAbsolutePath()); + return ret; + }); + } + + private static Supplier getClasspath(Supplier> files) { + return new Lazy<>(() -> String.join(File.pathSeparator, files.get())); + } + + private static Supplier getClasspathFile(SlimeLauncherRunTask task, String name, Supplier> files) { + var file = task.getLocalCacheDir().file(name + "_classpath.txt").get().getAsFile(); + return new Lazy<>(() -> { + try { + Files.writeString(file.toPath(), String.join(System.lineSeparator(), files.get()), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Error when writing classpath file: " + file.getAbsolutePath(), e); + } + return file.getAbsolutePath(); + }); + } +} diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java new file mode 100644 index 000000000..30bc292b8 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.gradle.internal; + +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; + +public interface SlimeLauncherRunTask { + Property getSourceSetName(); + DirectoryProperty getLocalCacheDir(); + ConfigurableFileCollection getMetadata(); + ConfigurableFileCollection getMinecraftClasspath(); + ConfigurableFileCollection getRuntimeClasspath(); + Property getMappingChannel(); + Property getMappingVersion(); + //RegularFileProperty getSrgToMcp(); +} diff --git a/src/main/java/net/minecraftforge/gradle/internal/Util.java b/src/main/java/net/minecraftforge/gradle/internal/Util.java index a9a026488..ed7f59264 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/Util.java +++ b/src/main/java/net/minecraftforge/gradle/internal/Util.java @@ -10,16 +10,15 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.Version; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; -import java.util.Locale; import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; final class Util extends SharedUtil { static String checkMappingsParam(ForgeGradleProblems problems, @Nullable Object param, String name) { @@ -65,4 +64,64 @@ static String dependencyToCamelCase(@Nullable String group, String name) { return null; } + + // Copied straight from FG6 + // Replace tokens in a string that are wrapped in {} + // Supports escaping {} or \ using \ + static String replaceTokens(Map tokens, String value, @Nullable Set unknown) { + if (value.length() <= 2 || value.indexOf('{') == -1) + return value; + + var buf = new StringBuilder(); + + for (int x = 0; x < value.length(); x++) { + char c = value.charAt(x); + if (c == '\\') { + if (x == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + buf.append(value.charAt(++x)); + } else if (c == '{' || c == '\'') { + StringBuilder key = new StringBuilder(); + for (int y = x + 1; y <= value.length(); y++) { + if (y == value.length()) + throw new IllegalArgumentException("Illegal pattern (Unclosed " + c + "): " + value); + char d = value.charAt(y); + if (d == '\\') { + if (y == value.length() - 1) + throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value); + key.append(value.charAt(++y)); + } else if (c == '{' && d == '}') { + //noinspection ReassignedVariable,SuspiciousNameCombination + x = y; + break; + } else if (c == '\'' && d == '\'') { + //noinspection ReassignedVariable,SuspiciousNameCombination + x = y; + break; + } else + key.append(d); + } + if (c == '\'') + buf.append(key); + else { + Object v = tokens.get(key.toString()); + if (v instanceof Supplier) + v = ((Supplier) v).get(); + + if (v == null) { + if (unknown != null) + unknown.add(key.toString()); + buf.append('{').append(key).append('}'); + } else { + buf.append(v); + } + } + } else { + buf.append(c); + } + } + + return buf.toString(); + } + } From 88a0cebfbdc75567b5c11a647d55655229d57cd4 Mon Sep 17 00:00:00 2001 From: LexManos Date: Fri, 27 Feb 2026 16:11:02 -0800 Subject: [PATCH 02/10] Bump Mavenizer to 0.4.33, exposes needed info for token replacements Port eclipse run config fixes from ForgeDev --- .../gradle/MavenizerInstance.java | 12 +++ .../internal/MavenizerInstanceImpl.java | 33 +++++++ .../internal/MinecraftDependencyImpl.java | 56 +++++++---- .../internal/MinecraftDependencyInternal.java | 2 + .../SlimeLauncherEclipseConfiguration.java | 95 ++++++++++++++----- .../gradle/internal/SlimeLauncherExec.java | 13 +-- .../internal/SlimeLauncherMetadata.java | 2 +- .../internal/SlimeLauncherRunHelper.java | 21 +++- .../gradle/internal/SlimeLauncherRunTask.java | 6 +- .../minecraftforge/gradle/internal/Tools.java | 2 +- .../minecraftforge/gradle/internal/Util.java | 9 +- 11 files changed, 186 insertions(+), 65 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java index 5849f901d..ebf3e4a36 100644 --- a/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java +++ b/src/main/java/net/minecraftforge/gradle/MavenizerInstance.java @@ -59,4 +59,16 @@ public interface MavenizerInstance extends ProviderConvertible getToObfFile(); + + /// Gets the Minecraft Version used by the Mavenizer instance. + /// Only returns a value when Mavenizer is above 0.4.33 + /// + /// @return The Minecraft Version + Provider getMinecraftVersion(); + + /// Gets the MCP Config version used by the Mavenizer instance. + /// Only returns a value when Mavenizer is above 0.4.33, and we're targeting + /// an artifact that uses MCP Config. So Forge for Minecraft 1.13+ + /// @return The MCP Version + Provider getMCPVersion(); } diff --git a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java index 704fd3bdc..f620dee64 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java @@ -7,14 +7,18 @@ import groovy.json.JsonSlurper; import net.minecraftforge.gradle.MavenizerInstance; import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Provider; import org.jspecify.annotations.Nullable; import java.io.File; +import java.util.List; import java.util.Map; class MavenizerInstanceImpl implements MavenizerInstance { + private static final Logger LOGGER = Logging.getLogger(MavenizerInstanceImpl.class); private final MinecraftExtensionImpl.ForProjectImpl extension; private final Provider valueSource; private final ExternalModuleDependency dependency; @@ -54,6 +58,19 @@ private Provider get(String key) { })); } + private Provider get(String key, @Nullable String _default, String requiredVersion) { + return this.invoke.getting(key) + .orElse(this.extension.getProviders().provider(() -> { + // This should only happen when someone hardcodes their tool version, warn them + var message = "Mavenizer did not output expected json data " + key +", Make sure you're using Mavenizer >= " + requiredVersion; + if (_default != null) { + LOGGER.warn(message); + return _default; + } + throw new IllegalStateException("Mavenizer did not output expected json data " + key +", Make sure you're using Mavenizer >= " + requiredVersion); + })); + } + @Override public Provider asProvider() { return this.invoke.map(m -> this.dependency); @@ -88,4 +105,20 @@ public Provider getToObf() { public Provider getToObfFile() { return get("mappings.obf.file").map(this.extension.getProject()::file); } + + @Override + public Provider getMinecraftVersion() { + return get("mc.version", "UNKNOWN", "0.4.33"); + } + + @Override + public Provider getMCPVersion() { + return get("mcp.version", "UNKNOWN", "0.4.33"); + } + + // Internal, not sure if I want to return + public Provider> getPatcherModules() { + return get("patcher.modules", "", "0.4.33") + .map(value -> List.of(value.split(","))); + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java index 5b9e011cf..4f22d2fc2 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyImpl.java @@ -6,7 +6,6 @@ import groovy.lang.Closure; import groovy.transform.NamedVariant; -import net.minecraftforge.gradle.MavenizerInstance; import net.minecraftforge.gradle.MinecraftExtensionForProject; import net.minecraftforge.gradle.MinecraftMappings; import net.minecraftforge.gradle.SlimeLauncherOptions; @@ -21,22 +20,18 @@ import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.Directory; -import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.ProjectLayout; -import org.gradle.api.flow.FlowProviders; -import org.gradle.api.flow.FlowScope; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.jspecify.annotations.Nullable; import javax.inject.Inject; import java.io.File; +import java.util.ArrayList; import java.util.Objects; abstract class MinecraftDependencyImpl implements MinecraftDependencyInternal { @@ -47,8 +42,9 @@ abstract class MinecraftDependencyImpl implements MinecraftDependencyInternal { // Minecraft extension private final MinecraftExtensionInternal.ForProject minecraft = ((MinecraftExtensionInternal.ForProject) getProject().getExtensions().getByType(MinecraftExtensionForProject.class)); private final String mavenizerName; // Name for our mavenizer invocation in the Minecraft Extension - private @Nullable Configuration detatchedConfig; + private @Nullable Configuration selfConfig; private @Nullable Configuration metadataConfig; + private @Nullable Configuration patcherModulesConfig; private @Nullable TaskProvider metadataTask; // Access Transformers @@ -99,14 +95,14 @@ public void mappings(String channel, String version) { } @Override - public MavenizerInstance getMavenizerInstance() { - return this.minecraft.getDependency(this.mavenizerName); + public MavenizerInstanceImpl getMavenizerInstance() { + return (MavenizerInstanceImpl)this.minecraft.getDependency(this.mavenizerName); } @Override public FileCollection getMinecraftDependencies() { - assert this.detatchedConfig != null; - return this.detatchedConfig; + assert this.selfConfig != null; + return this.selfConfig; } @Override @@ -115,6 +111,25 @@ public FileCollection getMetadataDependency() { return this.metadataConfig; } + @Override + public FileCollection getPatcherModules() { + if (this.patcherModulesConfig == null) { + var cfg = this.getProject().getConfigurations().detachedConfiguration(); + cfg.setCanBeResolved(true); + cfg.setTransitive(false); + cfg.getDependencies().addAllLater( + this.getMavenizerInstance().getPatcherModules().map(lst -> { + var ret = new ArrayList(lst.size()); + for (var gav : lst) + ret.add(getProject().getDependencyFactory().create(gav)); + return ret; + }) + ); + this.patcherModulesConfig = cfg; + } + return this.patcherModulesConfig; + } + @Override public TaskProvider getMetadataTask() { if (this.metadataTask == null) @@ -153,21 +168,21 @@ public ExternalModuleDependency init(Object dependencyNotation, Closure closu this.runs = getObjects().domainObjectContainer(SlimeLauncherOptionsImpl.class); var dependency = (ExternalModuleDependency) getProject().getDependencies().create(dependencyNotation, Closures.function(d -> { - if (!(d instanceof ExternalModuleDependency module)) + if (!(d instanceof ExternalModuleDependency external)) throw this.problems.invalidMinecraftDependencyType(d); - if (module.isChanging()) - throw this.problems.changingMinecraftDependency(module); + if (external.isChanging()) + throw this.problems.changingMinecraftDependency(external); - Closures.invoke(this.closure(closure), module); + Closures.invoke(this.closure(closure), external); - ((ExtensionAware) module).getExtensions().getExtraProperties().set(MC_EXT_NAME, this); + ((ExtensionAware) external).getExtensions().getExtraProperties().set(MC_EXT_NAME, this); - return module; + return external; })); // Keep a standalone configuration of JUST this dependency, so we can fill in 'minecraft_classpath' for run configs. - this.detatchedConfig = this.getProject().getConfigurations().detachedConfiguration(dependency); + this.selfConfig = this.getProject().getConfigurations().detachedConfiguration(dependency); this.metadataConfig = this.getProject().getConfigurations().detachedConfiguration(getProject().getDependencyFactory().create( dependency.getModule().getGroup(), dependency.getModule().getName(), dependency.getVersion(), "metadata", "zip" )); @@ -197,6 +212,11 @@ public ModuleIdentifier getModule() { return this.module.get(); } + @Override + public String getKey() { + return this.mavenizerName; + } + @Override public void handle(Configuration configuration) { if (configuration.isCanBeResolved()) { diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java index 0803e928a..dc72b8e2a 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java @@ -65,9 +65,11 @@ default Closure closure(Closure closure) { MavenizerInstance getMavenizerInstance(); FileCollection getMinecraftDependencies(); FileCollection getMetadataDependency(); + FileCollection getPatcherModules(); TaskProvider getMetadataTask(); String getPath(); ModuleIdentifier getModule(); + String getKey(); void handle(Configuration configuration); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java index 52c282e7d..9647c6c26 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java @@ -10,6 +10,8 @@ import net.minecraftforge.util.data.json.RunConfig; import org.gradle.api.DefaultTask; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; @@ -75,17 +77,26 @@ static TaskProvider register(Project project, task.setDescription("Generates the '%s' Slime Launcher run configuration for Eclipse.".formatted(options.getName())); task.getOutputFile().set(task.getProjectLayout().getProjectDirectory().file(runTaskName + ".launch")); + var configName = sourceSet.getRuntimeClasspathConfigurationName(); + var config = project.getConfigurations().getByName(configName); + task.getProjectDependencies().addAll(config.getIncoming().getArtifacts().getResolvedArtifacts() + .map(artifacts -> { + var ret = new ArrayList(); + // We need to reference our self as well + ret.add(getProjectEclipseName(project)); + + var root = project.getRootProject(); + for (var artifact : artifacts) { + var id = artifact.getId().getComponentIdentifier(); + if (id instanceof ProjectComponentIdentifier projectIdentifier) { + var dep = root.project(projectIdentifier.getProjectPath()); + ret.add(getProjectEclipseName(dep)); + } + } + return ret; + })); - var inst = mcdep.getMavenizerInstance(); - var runtimeClasspath = task.getObjects().fileCollection().from(task.getProviders().provider(sourceSet::getRuntimeClasspath)); - task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy - task.getMinecraftClasspath().setFrom(mcdep.getMinecraftDependencies()); - task.getMappingChannel().set(inst.getMappingChannel()); - task.getMappingVersion().set(inst.getMappingVersion()); - // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. - //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); - - var runtimeClasspath2 = task.getObjects().fileCollection().from( + var runtimeClasspath = task.getObjects().fileCollection().from( task.getProviders().provider(() -> { var runtime = sourceSet.getRuntimeClasspath(); var eclipseModel = project.getExtensions().findByType(EclipseModel.class); @@ -123,7 +134,7 @@ static TaskProvider register(Project project, ret.add(replacements.getOrDefault(file, file)); return ret; })); - task.getClasspath().from(runtimeClasspath); + SlimeLauncherRunHelper.configure(task, mcdep, runtimeClasspath); task.getSourceSetName().set(sourceSet.getName()); task.getCacheDir().set(task.getObjects().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); @@ -150,6 +161,7 @@ static TaskProvider register(Project project, protected abstract @Input Property getRunName(); protected abstract @Nested Property getJavaLauncher(); + protected abstract @Input ListProperty getProjectDependencies(); protected abstract @InputFiles @Classpath ConfigurableFileCollection getClasspath(); @@ -162,6 +174,9 @@ static TaskProvider register(Project project, public abstract @InputFiles @Override ConfigurableFileCollection getMetadata(); public abstract @InputFiles @Override ConfigurableFileCollection getMinecraftClasspath(); public abstract @InputFiles @Override ConfigurableFileCollection getRuntimeClasspath(); + public abstract @InputFiles @Override ConfigurableFileCollection getPatcherModules(); + public abstract @Input @Override Property getMinecraftVersion(); + public abstract @Input @Override Property getMCPVersion(); public abstract @Input @Override Property getMappingChannel(); public abstract @Input @Override Property getMappingVersion(); //protected abstract @InputFile @Override RegularFileProperty getSrgToMcp(); @@ -181,10 +196,7 @@ static TaskProvider register(Project project, @Inject public SlimeLauncherEclipseConfiguration() { this.getProjectName().convention(this.getProject().getName()); - this.getEclipseProjectName().convention(getProviders().provider(() -> { - var eclipse = getProject().getExtensions().findByType(EclipseModel.class); - return eclipse == null ? null : eclipse.getProject().getName(); - })); + this.getEclipseProjectName().convention(getProject().provider(() -> getProjectEclipseName(this.getProject()))); var tool = this.getTool(Tools.SLIMELAUNCHER); this.getClasspath().from(tool.getClasspath()); @@ -255,6 +267,7 @@ protected void exec() { queue.submit(Action.class, parameters -> { parameters.getOutputFile().set(this.getOutputFile()); parameters.getEclipseProjectName().set(this.getEclipseProjectName().orElse(this.getProjectName())); + parameters.getProjectDependencies().set(this.getProjectDependencies()); parameters.getClasspath().setFrom(this.getClasspath()); parameters.getMainClass().set(this.getMainClass().get()); parameters.getArgs().set(args); @@ -262,6 +275,7 @@ protected void exec() { parameters.getWorkingDir().set(workingDir); parameters.getEnvironment().set(env); parameters.getJavaHome().set(this.getJavaLauncher().map(j -> j.getMetadata().getInstallationPath())); + parameters.getJavaVersion().set(this.getJavaLauncher().map(j -> j.getMetadata().getLanguageVersion().toString())); }); } @@ -291,6 +305,7 @@ interface Parameters extends WorkParameters { RegularFileProperty getOutputFile(); Property getEclipseProjectName(); + ListProperty getProjectDependencies(); ConfigurableFileCollection getClasspath(); @@ -303,6 +318,7 @@ interface Parameters extends WorkParameters { DirectoryProperty getWorkingDir(); DirectoryProperty getJavaHome(); + Property getJavaVersion(); MapProperty getEnvironment(); } @@ -337,7 +353,10 @@ public void execute() { stringAttribute(launch, rootElement, "org.eclipse.jdt.launching.WORKING_DIRECTORY", parameters.getWorkingDir().getAsFile().get().getAbsolutePath()); //stringAttribute(launch, rootElement, "org.eclipse.jdt.launching.JRE_CONTAINER", parameters.getJavaHome().getAsFile().get().getAbsolutePath()); mapAttribute(launch, rootElement, "org.eclipse.debug.core.environmentVariables", parameters.getEnvironment().get()); - classpathAttribute(launch, rootElement, parameters.getClasspath()); + var classpathList = classpathList(rootElement); + addClasspathProjects(classpathList, parameters.getProjectDependencies()); + addClasspathLibraries(classpathList, parameters.getClasspath()); + addClasspathJava(classpathList, parameters.getJavaVersion().get()); booleanAttribute(launch, rootElement, "org.eclipse.jdt.launching.DEFAULT_CLASSPATH", false); launch.appendChild(rootElement); @@ -380,19 +399,39 @@ private static void listAttribute(Document document, Element parent, String key, parent.appendChild(attribute); } - private static final String CLASSPATH_ENTRY_PREFIX = " "; + private static final String CLASSPATH_ENTRY_PREFIX = ""; - private static void classpathAttribute(Document document, Element parent, FileCollection files) { - var attribute = document.createElement("listAttribute"); + private static Element addChild(Element parent, String name) { + var ret = parent.getOwnerDocument().createElement(name); + parent.appendChild(ret); + return ret; + } + + private static Element classpathList(Element parent) { + var attribute = addChild(parent, "listAttribute"); attribute.setAttribute("key", "org.eclipse.jdt.launching.CLASSPATH"); + return attribute; + } - for (var v : files.getFiles()) { - var listEntry = document.createElement("listEntry"); - listEntry.setAttribute("value", CLASSPATH_ENTRY_PREFIX + v + CLASSPATH_ENTRY_SUFFIX); - attribute.appendChild(listEntry); + private static void classpathEntry(Element parent, int type, String value) { + addChild(parent, "listEntry").setAttribute("value", CLASSPATH_ENTRY_PREFIX + value + " type=\"" + type + "\"" + CLASSPATH_ENTRY_SUFFIX); + } + + private static void addClasspathLibraries(Element parent, FileCollection files) { + for (var v : files.getFiles()) + classpathEntry(parent, 2, "externalArchive=\"" + v + "\""); + } + + private static void addClasspathProjects(Element parent, ListProperty projects) { + for (var v : projects.get()) { + classpathEntry(parent, 1, "projectName=\"" + v + "\""); + classpathEntry(parent, 4, "containerPath=\"org.eclipse.buildship.core.gradleclasspathcontainer\" javaProject=\"" + v + "\""); } - parent.appendChild(attribute); + } + + private static void addClasspathJava(Element parent, String version) { + classpathEntry(parent, 4, "containerPath=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-" + version + "\""); } private static void mapAttribute(Document document, Element parent, String key, Map map) { @@ -411,4 +450,10 @@ private static void mapAttribute(Document document, Element parent, String key, parent.appendChild(attribute); } } + + private static String getProjectEclipseName(Project project) { + var eclipse = project.getExtensions().findByType(EclipseModel.class); + var name = eclipse == null ? null : eclipse.getProject().getName(); + return name != null ? name : project.getName(); + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index e8b735729..b86f4d13f 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -43,7 +43,7 @@ abstract class SlimeLauncherExec extends JavaExec implements ForgeGradleTask, Ha static TaskProvider register(Project project, SourceSet sourceSet, SlimeLauncherOptionsImpl options, MinecraftDependencyInternal mcdep) { var minecraft = ((MinecraftExtensionInternal.ForProject)project.getExtensions().getByType(MinecraftExtensionForProject.class)); var metadata = mcdep.getMetadataTask(); - var single = minecraft.getDependencies().size() > 1; + var single = minecraft.getDependencies().size() == 1; var taskNameSuffix = (single ? "" : "For" + Util.dependencyToCamelCase(mcdep.getModule())); var runTaskName = sourceSet.getTaskName("run", options.getName()) + taskNameSuffix; @@ -54,15 +54,9 @@ static TaskProvider register(Project project, SourceSet sourc task.getSourceSetName().set(sourceSet.getName()); task.setDescription("Runs the '%s' Slime Launcher run configuration.".formatted(options.getName())); - var inst = mcdep.getMavenizerInstance(); var runtimeClasspath = task.getObjectFactory().fileCollection().from(task.getProviderFactory().provider(sourceSet::getRuntimeClasspath)); task.classpath(runtimeClasspath); - task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy - task.getMinecraftClasspath().setFrom(mcdep.getMinecraftDependencies()); - task.getMappingChannel().set(inst.getMappingChannel()); - task.getMappingVersion().set(inst.getMappingVersion()); - // We need a way to reverse this file, cuz we want srg->mcp and this is mcp->srg - //task.getSrgToMcp().set(project.file(inst.getToSrgFile())); + SlimeLauncherRunHelper.configure(task, mcdep, runtimeClasspath); task.getCacheDir().set(task.getObjectFactory().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); task.getLocalCacheDir().set(task.getObjectFactory().directoryProperty().value(task.localCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); @@ -84,6 +78,9 @@ static TaskProvider register(Project project, SourceSet sourc public abstract @InputFiles @Override ConfigurableFileCollection getMetadata(); public abstract @InputFiles @Override ConfigurableFileCollection getMinecraftClasspath(); public abstract @InputFiles @Override ConfigurableFileCollection getRuntimeClasspath(); + public abstract @InputFiles @Override ConfigurableFileCollection getPatcherModules(); + public abstract @Input @Override Property getMinecraftVersion(); + public abstract @Input @Override Property getMCPVersion(); public abstract @Input @Override Property getMappingChannel(); public abstract @Input @Override Property getMappingVersion(); //protected abstract @InputFile @Override RegularFileProperty getSrgToMcp(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java index 8160cbd18..16aef6172 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java @@ -27,7 +27,7 @@ abstract class SlimeLauncherMetadata extends DefaultTask implements ForgeGradleTask { static TaskProvider register(Project project, MinecraftDependencyInternal mcdep) { - var taskName = "slimeLauncherMetadatafor" + Util.dependencyToCamelCase(mcdep.getModule()); + var taskName = "slimeLauncherMetadataFor" + Util.dependencyToCamelCase(mcdep.getModule()); return project.getTasks().register(taskName, SlimeLauncherMetadata.class, task -> { task.setDescription("Extracts the Slime Launcher metadata for '%s'.".formatted(mcdep.toString())); task.getMetadata().setFrom(mcdep.getMetadataDependency()); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java index ed9dee03b..677d177fc 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java @@ -25,21 +25,21 @@ static Map> buildTokens(SlimeLauncherRunTask task) { ret.put("assets_root", () -> "{assets_root}"); ret.put("natives", () -> "{natives}"); - ret.put("mcp_mappings", task.getMappingChannel().zip(task.getMappingVersion(), (c, v) -> c + '_' + v)::get); var minecraft = getClasspath(task.getMinecraftClasspath()); var runtime = getClasspath(task.getRuntimeClasspath()); + var modules = getClasspath(task.getPatcherModules()); ret.put("minecraft_classpath", getClasspath(minecraft)); ret.put("runtime_classpath", getClasspath(runtime)); + ret.put("modules", getClasspath(modules)); ret.put("minecraft_classpath_file", getClasspathFile(task, "minecraft", minecraft)); ret.put("runtime_classpath_file", getClasspathFile(task, "runtime_" + task.getSourceSetName().get(), runtime)); + ret.put("mc_version", task.getMinecraftVersion()::get); + ret.put("mcp_version", task.getMCPVersion()::get); // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); // Pending: - //mc_version The simple vanilla Minecraft Version - 1.21.11 - //mcp_version The full MCP Config version - 1.21.11-000000000.000000 - //modules The classpath of a detached configuration containing the dependencies from UserDevV2.modules //source_roots The destination directories of each sourceset added to this run config. This is the old hacky thing for people who don't merge their sourcesets. Or use older versions that manually builds them. May not actually need this. return ret; } @@ -68,4 +68,17 @@ private static Supplier getClasspathFile(SlimeLauncherRunTask task, Stri return file.getAbsolutePath(); }); } + + static void configure(SlimeLauncherRunTask task, MinecraftDependencyInternal mcdep, FileCollection runtimeClasspath) { + var inst = mcdep.getMavenizerInstance(); + task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy + task.getMinecraftClasspath().setFrom(mcdep.getMinecraftDependencies()); + task.getPatcherModules().setFrom(mcdep.getPatcherModules()); + task.getMinecraftVersion().set(inst.getMinecraftVersion()); + task.getMCPVersion().set(inst.getMCPVersion()); + task.getMappingChannel().set(inst.getMappingChannel()); + task.getMappingVersion().set(inst.getMappingVersion()); + // We need a way to reverse this file, cuz we want srg->mcp and this is mcp->srg + //task.getSrgToMcp().set(project.file(inst.getToSrgFile())); + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java index 30bc292b8..6e8c04674 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -7,9 +7,6 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; public interface SlimeLauncherRunTask { Property getSourceSetName(); @@ -17,6 +14,9 @@ public interface SlimeLauncherRunTask { ConfigurableFileCollection getMetadata(); ConfigurableFileCollection getMinecraftClasspath(); ConfigurableFileCollection getRuntimeClasspath(); + ConfigurableFileCollection getPatcherModules(); + Property getMinecraftVersion(); + Property getMCPVersion(); Property getMappingChannel(); Property getMappingVersion(); //RegularFileProperty getSrgToMcp(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/Tools.java b/src/main/java/net/minecraftforge/gradle/internal/Tools.java index f5b84bbfe..cf69cfcde 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/Tools.java +++ b/src/main/java/net/minecraftforge/gradle/internal/Tools.java @@ -11,5 +11,5 @@ private Tools() { } static final Tool SLIMELAUNCHER = Tool.ofForge("slimelauncher", "net.minecraftforge:slime-launcher:0.1.8", 8, "net.minecraftforge.launcher.Main"); - static final Tool MAVENIZER = Tool.ofForge("mavenizer", "net.minecraftforge:minecraft-mavenizer:0.4.35", 25, "net.minecraftforge.mcmaven.cli.Main"); + static final Tool MAVENIZER = Tool.ofForge("mavenizer", "net.minecraftforge:minecraft-mavenizer:0.4.33", 25, "net.minecraftforge.mcmaven.cli.Main"); } diff --git a/src/main/java/net/minecraftforge/gradle/internal/Util.java b/src/main/java/net/minecraftforge/gradle/internal/Util.java index ed7f59264..8b97ab6fe 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/Util.java +++ b/src/main/java/net/minecraftforge/gradle/internal/Util.java @@ -32,10 +32,6 @@ static boolean isPresent(String c) { return !c.isBlank(); } - static String dependencyToCamelCase(Dependency dependency) { - return dependencyToCamelCase(dependency.getGroup(), dependency.getName()); - } - static String dependencyToCamelCase(ModuleIdentifier dependency) { return dependencyToCamelCase(dependency.getGroup(), dependency.getName()); } @@ -43,9 +39,12 @@ static String dependencyToCamelCase(ModuleIdentifier dependency) { static String dependencyToCamelCase(@Nullable String group, String name) { var list = new ArrayList(3); - if (group != null) + boolean isForge = "net.minecraftforge".equals(group) && "forge".equals(name); + + if (group != null && !isForge) list.addAll(Arrays.asList(group.split("\\."))); + // TODO: [ForgeGradle] Add version distinction for run task names list.add(name); var builder = new StringBuilder(64); From 69f39ccf5365b8e72ac7471e4021a29644db6c06 Mon Sep 17 00:00:00 2001 From: LexManos Date: Sun, 1 Mar 2026 17:44:49 -0800 Subject: [PATCH 03/10] Add support for legacy {source_roots} token. --- .../gradle/SlimeLauncherOptionsNested.java | 41 ++++++++++ .../internal/MinecraftExtensionImpl.java | 1 - .../SlimeLauncherEclipseConfiguration.java | 76 ++++++++++++------ .../gradle/internal/SlimeLauncherExec.java | 4 +- .../internal/SlimeLauncherOptionsImpl.java | 49 +++++++++++- .../internal/SlimeLauncherRunHelper.java | 77 ++++++++++++++++++- .../gradle/internal/SlimeLauncherRunTask.java | 6 +- 7 files changed, 221 insertions(+), 33 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index f57db983f..2235de04a 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -4,6 +4,13 @@ */ package net.minecraftforge.gradle; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import groovy.transform.stc.ClosureParams; +import groovy.transform.stc.FromString; +import net.minecraftforge.gradleutils.shared.Closures; +import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; @@ -11,8 +18,11 @@ import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.SourceSet; +import java.util.List; import java.util.Map; public interface SlimeLauncherOptionsNested { @@ -203,4 +213,35 @@ public interface SlimeLauncherOptionsNested { /// add a single variable, use [#environment(String,Object)]. /// @see #getEnvironment() void environment(Provider> properties); + + + @Internal + NamedDomainObjectContainer getMods(); + + default void mods( + @DelegatesTo(NamedDomainObjectContainer.class) + @ClosureParams(value = FromString.class, options = "org.gradle.api.NamedDomainObjectContainer") + Closure closure + ) { + this.getMods().configure(closure); + } + + default void mods(Action> action) { + this.mods(Closures.action(this, action)); + } + + interface ModConfig { + String getName(); + List getSources(); + void setSources(List sources); + default void sources(List sources) { + getSources().addAll(sources); + } + default void sources(SourceSet... sources) { + getSources().addAll(List.of(sources)); + } + default void source(SourceSet source) { + getSources().add(source); + } + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java index de29f3a89..73a74d205 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftExtensionImpl.java @@ -37,7 +37,6 @@ import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.reflect.TypeOf; import org.gradle.api.tasks.TaskProvider; diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java index 9647c6c26..c034ed53b 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java @@ -10,7 +10,6 @@ import net.minecraftforge.util.data.json.RunConfig; import org.gradle.api.DefaultTask; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; @@ -64,6 +63,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; // This is mostly taken from ForgeGradle 6 but slimmed down to what we need @DisableCachingByDefault(because = "ForgeGradle would require more information to cache this task") @@ -98,39 +98,28 @@ static TaskProvider register(Project project, var runtimeClasspath = task.getObjects().fileCollection().from( task.getProviders().provider(() -> { - var runtime = sourceSet.getRuntimeClasspath(); var eclipseModel = project.getExtensions().findByType(EclipseModel.class); - if (eclipseModel == null) - return runtime.getFiles(); + var eclipseOutputs = getOutputs(project); + if (eclipseModel == null || eclipseOutputs.isEmpty()) + return sourceSet.getRuntimeClasspath().getFiles(); // We need to build a map of sourcesets to real output paths like Eclipse's plugin does. - // There is no known exposure of this stuff, so have to do it ourselves. - // https://github.com/gradle/gradle/blob/master/platforms/ide/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.java#L220 - var classpath = eclipseModel.getClasspath(); - var sortedSourceSets = sortSourceSets(classpath.getSourceSets()); var replacements = new HashMap(); - var base = classpath.getBaseSourceOutputDir().getAsFile().get(); - var claimed = new HashSet(); - claimed.add(classpath.getDefaultOutputDir()); // Gather the output name eclipse will use, and all outputs gradle expects - for (var sources : sortedSourceSets) { - var name = sources.getName(); - var path = new File(base, name); - while (claimed.contains(path)) { - name += '_'; - path = new File(base, name); + for (var source : eclipseModel.getClasspath().getSourceSets()) { + var path = eclipseOutputs.get(source); + if (path != null) { + if (source.getOutput().getResourcesDir() != null) + replacements.put(source.getOutput().getResourcesDir(), path); + for (var dir : source.getOutput().getClassesDirs().getFiles()) + replacements.put(dir, path); } - claimed.add(path); - if (sources.getOutput().getResourcesDir() != null) - replacements.put(sources.getOutput().getResourcesDir(), path); - for (var dir : sources.getOutput().getClassesDirs().getFiles()) - replacements.put(dir, path); } // Now replace the existing classpath with the ones eclipse will use - var ret = new LinkedHashSet(runtime.getFiles().size()); - for (var file : runtime.getFiles()) + var ret = new LinkedHashSet(); + for (var file : sourceSet.getRuntimeClasspath().getFiles()) ret.add(replacements.getOrDefault(file, file)); return ret; })); @@ -218,7 +207,8 @@ protected void exec() { configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); - var tokens = SlimeLauncherRunHelper.buildTokens(this); + var eclipseOutputs = new Lazy<>(() -> getOutputs(getProject())); + var tokens = SlimeLauncherRunHelper.buildTokens(this, options, source -> getOutputs(eclipseOutputs.get(), source)); var unknown = new HashSet(); var args = new ArrayList<>(List.of( @@ -456,4 +446,40 @@ private static String getProjectEclipseName(Project project) { var name = eclipse == null ? null : eclipse.getProject().getName(); return name != null ? name : project.getName(); } + + private static Map getOutputs(Project project) { + var eclipseModel = project.getExtensions().findByType(EclipseModel.class); + if (eclipseModel == null) + return Map.of(); + + // We need to build a map of sourcesets to real output paths like Eclipse's plugin does. + // There is no known exposure of this stuff, so have to do it ourselves. + // https://github.com/gradle/gradle/blob/master/platforms/ide/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.java#L220 + var classpath = eclipseModel.getClasspath(); + var sortedSourceSets = sortSourceSets(classpath.getSourceSets()); + var base = classpath.getBaseSourceOutputDir().getAsFile().get(); + var claimed = new HashSet(); + claimed.add(classpath.getDefaultOutputDir()); + + // Gather the output name eclipse will use, and all outputs gradle expects + var ret = new HashMap(); + for (var sources : sortedSourceSets) { + var name = sources.getName(); + var path = new File(base, name); + while (claimed.contains(path)) { + name += '_'; + path = new File(base, name); + } + claimed.add(path); + ret.put(sources, path); + } + return ret; + } + + private static Set getOutputs(Map known, SourceSet sourceSet) { + var eclipse = known.get(sourceSet); + if (eclipse != null) + return Set.of(eclipse.getAbsolutePath()); + return SlimeLauncherRunHelper.getOutputs(sourceSet); + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index b86f4d13f..b4d86c159 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -35,8 +35,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; @DisableCachingByDefault(because = "Running the game cannot be cached") abstract class SlimeLauncherExec extends JavaExec implements ForgeGradleTask, HasPublicType, SlimeLauncherRunTask { @@ -122,7 +124,7 @@ public void exec() { configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); - var tokens = SlimeLauncherRunHelper.buildTokens(this); + var tokens = SlimeLauncherRunHelper.buildTokens(this, options, SlimeLauncherRunHelper::getOutputs); var unknown = new HashSet(); mainClass = options.getMainClass().filter(Util::isPresent); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java index 33e1a17c9..9bfc12b22 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java @@ -7,6 +7,7 @@ import net.minecraftforge.gradle.SlimeLauncherOptionsNested; import net.minecraftforge.util.data.json.RunConfig; import org.gradle.api.Action; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.ProjectLayout; @@ -17,6 +18,8 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.SourceSet; + import javax.inject.Inject; import java.util.ArrayList; import java.util.HashMap; @@ -44,6 +47,8 @@ public abstract class SlimeLauncherOptionsImpl implements SlimeLauncherOptionsIn private final MapProperty nested = this.getObjects().mapProperty(String.class, SlimeLauncherOptionsNested.class); + private final NamedDomainObjectContainer mods = this.getObjects().domainObjectContainer(ModConfigImpl.class); + protected abstract @Inject ProjectLayout getProjectLayout(); protected abstract @Inject ObjectFactory getObjects(); @@ -125,6 +130,11 @@ public MapProperty getNested() { return this.nested; } + @Override + public NamedDomainObjectContainer getMods() { + return this.mods; + } + /* NESTED */ @Override @@ -249,7 +259,7 @@ public void systemProperties(Provider> properties) { @Override public void environment(String name, Object value) { - this.getSystemProperties().put(name, this.getProviders().provider(() -> Util.unpack(value).toString())); + this.getEnvironment().put(name, this.getProviders().provider(() -> Util.unpack(value).toString())); } @Override @@ -409,6 +419,17 @@ private void inherit(SlimeLauncherOptionsNested target) { LOGGER.log(level, " WorkingDir: {}", target.getWorkingDir().get()); this.getWorkingDir().set(target.getWorkingDir()); } + + for (var mod : target.getMods()) { + LOGGER.log(level, " Mod: {}", mod.getName()); + var thisMod = this.getMods().maybeCreate(mod.getName()); + for (var source : mod.getSources()) { + if (thisMod.getSources().stream().noneMatch(targetMod -> targetMod.getName().equals(mod.getName()))) { + LOGGER.log(level, " Source: {}", source.getName()); + thisMod.source(source); + } + } + } } /* DEBUGGING */ @@ -429,4 +450,30 @@ public String toString() { ", client=" + client.getOrNull() + '}'; } + + public static abstract class ModConfigImpl implements ModConfig { + private final String name; + private final List sourceSets = new ArrayList<>(); + + @Inject + public ModConfigImpl(String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public List getSources() { + return this.sourceSets; + } + + @Override + public void setSources(List sources) { + this.sourceSets.clear(); + this.sourceSets.addAll(sources); + } + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java index 677d177fc..66fb0d37f 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java @@ -5,6 +5,8 @@ package net.minecraftforge.gradle.internal; import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; import java.io.File; import java.io.IOException; @@ -12,13 +14,17 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; import java.util.function.Supplier; class SlimeLauncherRunHelper { // Legacy replacement tokens. See https://github.com/MinecraftForge/ForgeGradle/issues/1048 - static Map> buildTokens(SlimeLauncherRunTask task) { + static Map> buildTokens(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, Function> sourceOutputs) { var ret = new HashMap>(); // Should be taken care of by SlimeLauncher ret.put("asset_index", () -> "{asset_index}"); @@ -36,11 +42,9 @@ static Map> buildTokens(SlimeLauncherRunTask task) { ret.put("runtime_classpath_file", getClasspathFile(task, "runtime_" + task.getSourceSetName().get(), runtime)); ret.put("mc_version", task.getMinecraftVersion()::get); ret.put("mcp_version", task.getMCPVersion()::get); + ret.put("source_roots", getSourceRoots(task, options, sourceOutputs)); // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); - - // Pending: - //source_roots The destination directories of each sourceset added to this run config. This is the old hacky thing for people who don't merge their sourcesets. Or use older versions that manually builds them. May not actually need this. return ret; } @@ -69,6 +73,71 @@ private static Supplier getClasspathFile(SlimeLauncherRunTask task, Stri }); } + private static List getDefaultSourceSets(SlimeLauncherRunTask task) { + var java = task.getProject().getExtensions().findByType(JavaPluginExtension.class); + if (java == null) + return List.of(); + + var ret = new ArrayList(); + var main = java.getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME); + if (main != null) + ret.add(main); + + var taskName = task.getSourceSetName().getOrNull(); + if (taskName != null && !SourceSet.MAIN_SOURCE_SET_NAME.equals(taskName)) { + var other = java.getSourceSets().findByName(taskName); + if (other != null) + ret.add(other); + } + + return ret; + } + + // Gets a list of all output directories for + static Set getOutputs(SourceSet sourceSet) { + var ret = new LinkedHashSet(); + if (sourceSet.getOutput().getResourcesDir() != null) + ret.add(sourceSet.getOutput().getResourcesDir().getAbsolutePath()); + for (var file : sourceSet.getOutput().getFiles()) + ret.add(file.getAbsolutePath()); + return ret; + } + + static Supplier getSourceRoots(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, Function> sourceOutputs) { + return new Lazy<>(() -> { + var mods = new TreeMap>(); + var defaults = getDefaultSourceSets(task); + + // If there are no mods defined, try and get the main sourceset + if (options.getMods().isEmpty()) { + mods.put("", defaults); + } else { + for (var mod : options.getMods()) + mods.put(mod.getName(), mod.getSources().isEmpty() ? defaults : mod.getSources()); + } + + var ret = new StringBuilder(); + + for (var entry : mods.entrySet()) { + var entries = new ArrayList(); + var prefix = entry.getKey().isEmpty() ? "" : entry.getKey() + "%%"; + for (var source : entry.getValue()) { + for (var dir : sourceOutputs.apply(source)) + entries.add(prefix + dir); + } + + if (entries.size() == 1) // FML Requires at least 2 directories, so duplicate, hacky, but got to love legacy shit + entries.add(entries.get(0)); + + if (!ret.isEmpty()) + ret.append(File.pathSeparatorChar); + ret.append(String.join(File.pathSeparator, entries)); + } + + return ret.toString(); + }); + } + static void configure(SlimeLauncherRunTask task, MinecraftDependencyInternal mcdep, FileCollection runtimeClasspath) { var inst = mcdep.getMavenizerInstance(); task.getRuntimeClasspath().setFrom(runtimeClasspath); // main classpath gets polluted by Slimelauncher so keep a copy diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java index 6e8c04674..d2f8a52e3 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -4,11 +4,15 @@ */ package net.minecraftforge.gradle.internal; +import org.gradle.api.Task; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -public interface SlimeLauncherRunTask { +import java.util.List; + +public interface SlimeLauncherRunTask extends Task { Property getSourceSetName(); DirectoryProperty getLocalCacheDir(); ConfigurableFileCollection getMetadata(); From 2cf8014922a92abac99e4581e33ad84fe5f94611 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 17:35:43 -0500 Subject: [PATCH 04/10] Make ModConfig implement Named --- .../net/minecraftforge/gradle/SlimeLauncherOptionsNested.java | 4 ++-- .../gradle/internal/SlimeLauncherOptionsImpl.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index 2235de04a..7277808bf 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -10,6 +10,7 @@ import groovy.transform.stc.FromString; import net.minecraftforge.gradleutils.shared.Closures; import org.gradle.api.Action; +import org.gradle.api.Named; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; @@ -230,8 +231,7 @@ default void mods(Action this.mods(Closures.action(this, action)); } - interface ModConfig { - String getName(); + interface ModConfig extends Named { List getSources(); void setSources(List sources); default void sources(List sources) { diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java index 9bfc12b22..721b7a522 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java @@ -453,7 +453,7 @@ public String toString() { public static abstract class ModConfigImpl implements ModConfig { private final String name; - private final List sourceSets = new ArrayList<>(); + private final List sourceSets = new ArrayList<>(); @Inject public ModConfigImpl(String name) { From e8db82eb1ffdc44e03029c65b785c4f59aa8a5a2 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 18:15:56 -0500 Subject: [PATCH 05/10] Fix missing patcher modules causing build failure on run --- .../net/minecraftforge/gradle/SlimeLauncherOptionsNested.java | 1 - .../minecraftforge/gradle/internal/MavenizerInstanceImpl.java | 2 +- .../gradle/internal/SlimeLauncherOptionsInternal.java | 4 ++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index 7277808bf..e44ac1189 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -216,7 +216,6 @@ public interface SlimeLauncherOptionsNested { void environment(Provider> properties); - @Internal NamedDomainObjectContainer getMods(); default void mods( diff --git a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java index f620dee64..7941cb11a 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MavenizerInstanceImpl.java @@ -119,6 +119,6 @@ public Provider getMCPVersion() { // Internal, not sure if I want to return public Provider> getPatcherModules() { return get("patcher.modules", "", "0.4.33") - .map(value -> List.of(value.split(","))); + .map(value -> value.isBlank() ? List.of() : List.of(value.split(","))); } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java index 9a98940f0..297457a9f 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java @@ -7,6 +7,7 @@ import net.minecraftforge.gradle.SlimeLauncherOptions; import net.minecraftforge.gradle.SlimeLauncherOptionsNested; import net.minecraftforge.util.data.json.RunConfig; +import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.provider.MapProperty; @@ -30,6 +31,9 @@ public interface SlimeLauncherOptionsInternal extends SlimeLauncherOptions, HasP @Input @Optional Property getClient(); + @Override + @Internal NamedDomainObjectContainer getMods(); + @Nested MapProperty getNested(); default SlimeLauncherOptionsInternal inherit(Map configs, String sourceSetName) { From 538f05e19b1a15f44c8bfcf161f71ea400a73766 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 20:07:47 -0500 Subject: [PATCH 06/10] Fix remaining project invocations and make nested types --- .../gradle/SlimeLauncherOptionsNested.java | 15 +-- .../SlimeLauncherEclipseConfiguration.java | 57 ++++++++++-- .../gradle/internal/SlimeLauncherExec.java | 7 +- .../internal/SlimeLauncherOptionsImpl.java | 60 ++++++++++-- .../SlimeLauncherOptionsInternal.java | 16 +++- .../internal/SlimeLauncherRunHelper.java | 21 ++--- .../gradle/internal/SlimeLauncherRunTask.java | 1 + .../gradle/internal/SourceSetNested.java | 92 +++++++++++++++++++ 8 files changed, 227 insertions(+), 42 deletions(-) create mode 100644 src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index e44ac1189..bda2370ed 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -216,7 +216,7 @@ public interface SlimeLauncherOptionsNested { void environment(Provider> properties); - NamedDomainObjectContainer getMods(); + @Nested NamedDomainObjectContainer getMods(); default void mods( @DelegatesTo(NamedDomainObjectContainer.class) @@ -231,16 +231,9 @@ default void mods(Action } interface ModConfig extends Named { - List getSources(); void setSources(List sources); - default void sources(List sources) { - getSources().addAll(sources); - } - default void sources(SourceSet... sources) { - getSources().addAll(List.of(sources)); - } - default void source(SourceSet source) { - getSources().add(source); - } + void sources(List sources); + void sources(SourceSet... sources); + void source(SourceSet source); } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java index c034ed53b..8daa4c186 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java @@ -21,6 +21,7 @@ import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; @@ -108,7 +109,14 @@ static TaskProvider register(Project project, // Gather the output name eclipse will use, and all outputs gradle expects for (var source : eclipseModel.getClasspath().getSourceSets()) { - var path = eclipseOutputs.get(source); + File path = null; + for (var eclipseOutput : eclipseOutputs) { + if (eclipseOutput.getSources().getName().equals(source.getName())) + path = new File(eclipseOutput.getFile()); + + break; + } + if (path != null) { if (source.getOutput().getResourcesDir() != null) replacements.put(source.getOutput().getResourcesDir(), path); @@ -124,6 +132,7 @@ static TaskProvider register(Project project, return ret; })); SlimeLauncherRunHelper.configure(task, mcdep, runtimeClasspath); + task.getEclipseOutputs().set(task.getProviders().provider(() -> getOutputs(project))); task.getSourceSetName().set(sourceSet.getName()); task.getCacheDir().set(task.getObjects().directoryProperty().value(task.globalCaches().dir("slime-launcher/cache/%s".formatted(mcdep.getPath())).map(task.problems.ensureFileLocation()))); @@ -144,8 +153,10 @@ static TaskProvider register(Project project, protected abstract @Input Property getProjectName(); public abstract @Input @Override Property getSourceSetName(); + protected abstract @Nested ListProperty getDefaultSourceSets(); protected abstract @Input @Optional Property getEclipseProjectName(); + protected abstract @Nested @Optional SetProperty getEclipseOutputs(); protected abstract @Input Property getRunName(); @@ -187,6 +198,8 @@ public SlimeLauncherEclipseConfiguration() { this.getProjectName().convention(this.getProject().getName()); this.getEclipseProjectName().convention(getProject().provider(() -> getProjectEclipseName(this.getProject()))); + this.getDefaultSourceSets().convention(getProviders().provider(() -> SlimeLauncherRunHelper.getDefaultSourceSets(this))); + var tool = this.getTool(Tools.SLIMELAUNCHER); this.getClasspath().from(tool.getClasspath()); this.getMainClass().set(tool.getMainClass()); @@ -207,8 +220,8 @@ protected void exec() { configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); - var eclipseOutputs = new Lazy<>(() -> getOutputs(getProject())); - var tokens = SlimeLauncherRunHelper.buildTokens(this, options, source -> getOutputs(eclipseOutputs.get(), source)); + var eclipseOutputs = new Lazy<>(() -> this.getEclipseOutputs().get()); + var tokens = SlimeLauncherRunHelper.buildTokens(this, options, this.getDefaultSourceSets().get(), source -> getOutputs(eclipseOutputs.get(), source)); var unknown = new HashSet(); var args = new ArrayList<>(List.of( @@ -447,10 +460,10 @@ private static String getProjectEclipseName(Project project) { return name != null ? name : project.getName(); } - private static Map getOutputs(Project project) { + private static Set getOutputs(Project project) { var eclipseModel = project.getExtensions().findByType(EclipseModel.class); if (eclipseModel == null) - return Map.of(); + return Set.of(); // We need to build a map of sourcesets to real output paths like Eclipse's plugin does. // There is no known exposure of this stuff, so have to do it ourselves. @@ -462,7 +475,7 @@ private static Map getOutputs(Project project) { claimed.add(classpath.getDefaultOutputDir()); // Gather the output name eclipse will use, and all outputs gradle expects - var ret = new HashMap(); + var ret = new HashSet(); for (var sources : sortedSourceSets) { var name = sources.getName(); var path = new File(base, name); @@ -471,15 +484,41 @@ private static Map getOutputs(Project project) { path = new File(base, name); } claimed.add(path); - ret.put(sources, path); + ret.add(project.getObjects().newInstance(EclipseOutput.class, project.getObjects().newInstance(SourceSetNested.class, sources), path)); } return ret; } - private static Set getOutputs(Map known, SourceSet sourceSet) { - var eclipse = known.get(sourceSet); + private static Set getOutputs(Iterable known, SourceSetNested sourceSet) { + File eclipse = null; + for (var entry : known) { + if (entry.getSources().getName().equals(sourceSet.getName())) + eclipse = new File(entry.getFile()); + + break; + } + if (eclipse != null) return Set.of(eclipse.getAbsolutePath()); return SlimeLauncherRunHelper.getOutputs(sourceSet); } + + static abstract class EclipseOutput { + private final SourceSetNested sources; + private final File file; + + @Inject + public EclipseOutput(SourceSetNested sources, File file) { + this.sources = sources; + this.file = file; + } + + public @Nested SourceSetNested getSources() { + return sources; + } + + public @Input String getFile() { + return file.getAbsolutePath(); + } + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index b4d86c159..3026e19f0 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -13,6 +13,7 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -75,6 +76,8 @@ static TaskProvider register(Project project, SourceSet sourc protected abstract @Nested Property getOptions(); + protected abstract @Nested ListProperty getDefaultSourceSets(); + protected abstract @Internal DirectoryProperty getCacheDir(); public abstract @Internal @Override DirectoryProperty getLocalCacheDir(); public abstract @InputFiles @Override ConfigurableFileCollection getMetadata(); @@ -99,6 +102,8 @@ static TaskProvider register(Project project, SourceSet sourc public SlimeLauncherExec() { this.setGroup("Slime Launcher"); + this.getDefaultSourceSets().convention(getProviderFactory().provider(() -> SlimeLauncherRunHelper.getDefaultSourceSets(this))); + var tool = this.getTool(Tools.SLIMELAUNCHER); this.setClasspath(tool.getClasspath()); if (tool.hasMainClass()) @@ -124,7 +129,7 @@ public void exec() { configs = JsonData.fromJson(jsons, new TypeToken<>() { }); var options = ((SlimeLauncherOptionsInternal) this.getOptions().get()).inherit(configs, this.getSourceSetName().get()); - var tokens = SlimeLauncherRunHelper.buildTokens(this, options, SlimeLauncherRunHelper::getOutputs); + var tokens = SlimeLauncherRunHelper.buildTokens(this, options, this.getDefaultSourceSets().get(), SlimeLauncherRunHelper::getOutputs); var unknown = new HashSet(); mainClass = options.getMainClass().filter(Util::isPresent); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java index 721b7a522..2141be98e 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsImpl.java @@ -18,10 +18,13 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.SourceSet; import javax.inject.Inject; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -420,13 +423,14 @@ private void inherit(SlimeLauncherOptionsNested target) { this.getWorkingDir().set(target.getWorkingDir()); } - for (var mod : target.getMods()) { + for (var mod : ((SlimeLauncherOptionsInternal) target).getMods()) { LOGGER.log(level, " Mod: {}", mod.getName()); var thisMod = this.getMods().maybeCreate(mod.getName()); + var thisModSources = thisMod.getSources(); for (var source : mod.getSources()) { - if (thisMod.getSources().stream().noneMatch(targetMod -> targetMod.getName().equals(mod.getName()))) { + if (thisModSources.stream().noneMatch(targetMod -> targetMod.getName().equals(mod.getName()))) { LOGGER.log(level, " Source: {}", source.getName()); - thisMod.source(source); + thisMod.sourceInternal(source); } } } @@ -451,9 +455,11 @@ public String toString() { '}'; } - public static abstract class ModConfigImpl implements ModConfig { + public static abstract class ModConfigImpl implements ModConfigInternal { private final String name; - private final List sourceSets = new ArrayList<>(); + private final ArrayList sourceSets = new ArrayList<>(); + + protected abstract @Inject ObjectFactory getObjects(); @Inject public ModConfigImpl(String name) { @@ -461,19 +467,57 @@ public ModConfigImpl(String name) { } @Override - public String getName() { + public @Internal String getName() { return this.name; } @Override - public List getSources() { + public @Nested List getSources() { return this.sourceSets; } @Override - public void setSources(List sources) { + public void setSourcesInternal(List sources) { this.sourceSets.clear(); + this.sourcesInternal(sources); + } + + @Override + public void sourcesInternal(List sources) { this.sourceSets.addAll(sources); } + + @Override + public void sourcesInternal(SourceSetNested... sources) { + this.sourceSets.addAll(Arrays.asList(sources)); + } + + @Override + public void sourceInternal(SourceSetNested source) { + this.sourceSets.add(source); + } + + @Override + public void setSources(List sources) { + this.sourceSets.clear(); + this.sources(sources); + } + + @Override + public void sources(List sources) { + for (var source : sources) + this.source(source); + } + + @Override + public void sources(SourceSet... sources) { + for (var source : sources) + this.source(source); + } + + @Override + public void source(SourceSet source) { + this.sourceSets.add(getObjects().newInstance(SourceSetNested.class, source)); + } } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java index 297457a9f..2de6c3dd4 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java @@ -10,6 +10,7 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.reflect.HasPublicType; @@ -18,10 +19,12 @@ import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.SourceSet; +import java.util.List; import java.util.Map; -public interface SlimeLauncherOptionsInternal extends SlimeLauncherOptions, HasPublicType { +interface SlimeLauncherOptionsInternal extends SlimeLauncherOptions, HasPublicType { Logger LOGGER = Logging.getLogger(SlimeLauncherOptions.class); @Override @@ -32,7 +35,7 @@ public interface SlimeLauncherOptionsInternal extends SlimeLauncherOptions, HasP @Input @Optional Property getClient(); @Override - @Internal NamedDomainObjectContainer getMods(); + NamedDomainObjectContainer getMods(); @Nested MapProperty getNested(); @@ -41,4 +44,13 @@ default SlimeLauncherOptionsInternal inherit(Map configs, Str } SlimeLauncherOptionsInternal inherit(Map configs, String sourceSetName, String name); + + @SuppressWarnings("ClassEscapesDefinedScope") // nested interfaces are always public even if within inaccessible package-private types + interface ModConfigInternal extends ModConfig { + List getSources(); + void setSourcesInternal(List sources); + void sourcesInternal(List sources); + void sourcesInternal(SourceSetNested... sources); + void sourceInternal(SourceSetNested source); + } } diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java index 66fb0d37f..57bda8df4 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunHelper.java @@ -24,7 +24,7 @@ class SlimeLauncherRunHelper { // Legacy replacement tokens. See https://github.com/MinecraftForge/ForgeGradle/issues/1048 - static Map> buildTokens(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, Function> sourceOutputs) { + static Map> buildTokens(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, List defaultSourceSets, Function> sourceOutputs) { var ret = new HashMap>(); // Should be taken care of by SlimeLauncher ret.put("asset_index", () -> "{asset_index}"); @@ -42,7 +42,7 @@ static Map> buildTokens(SlimeLauncherRunTask task, Slim ret.put("runtime_classpath_file", getClasspathFile(task, "runtime_" + task.getSourceSetName().get(), runtime)); ret.put("mc_version", task.getMinecraftVersion()::get); ret.put("mcp_version", task.getMCPVersion()::get); - ret.put("source_roots", getSourceRoots(task, options, sourceOutputs)); + ret.put("source_roots", getSourceRoots(task, options, defaultSourceSets, sourceOutputs)); // Despite the name this is set to createSrgToMcp.getOutput().get().getAsFile().getAbsolutePath() so.. Srg -> MCP .srg mapping file. //ret.put("mcp_to_srg", getSrgToMcp().getAsFile().map(File::getAbsolutePath)::get); return ret; @@ -73,40 +73,39 @@ private static Supplier getClasspathFile(SlimeLauncherRunTask task, Stri }); } - private static List getDefaultSourceSets(SlimeLauncherRunTask task) { + static List getDefaultSourceSets(SlimeLauncherRunTask task) { var java = task.getProject().getExtensions().findByType(JavaPluginExtension.class); if (java == null) return List.of(); - var ret = new ArrayList(); + var ret = new ArrayList(); var main = java.getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME); if (main != null) - ret.add(main); + ret.add(task.getProject().getObjects().newInstance(SourceSetNested.class, main)); var taskName = task.getSourceSetName().getOrNull(); if (taskName != null && !SourceSet.MAIN_SOURCE_SET_NAME.equals(taskName)) { var other = java.getSourceSets().findByName(taskName); if (other != null) - ret.add(other); + ret.add(task.getProject().getObjects().newInstance(SourceSetNested.class, other)); } return ret; } // Gets a list of all output directories for - static Set getOutputs(SourceSet sourceSet) { + static Set getOutputs(SourceSetNested sourceSet) { var ret = new LinkedHashSet(); if (sourceSet.getOutput().getResourcesDir() != null) ret.add(sourceSet.getOutput().getResourcesDir().getAbsolutePath()); - for (var file : sourceSet.getOutput().getFiles()) + for (var file : sourceSet.getOutput().getAsFileCollection()) ret.add(file.getAbsolutePath()); return ret; } - static Supplier getSourceRoots(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, Function> sourceOutputs) { + static Supplier getSourceRoots(SlimeLauncherRunTask task, SlimeLauncherOptionsInternal options, List defaults, Function> sourceOutputs) { return new Lazy<>(() -> { - var mods = new TreeMap>(); - var defaults = getDefaultSourceSets(task); + var mods = new TreeMap>(); // If there are no mods defined, try and get the main sourceset if (options.getMods().isEmpty()) { diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java index d2f8a52e3..9e40f10f8 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -7,6 +7,7 @@ import org.gradle.api.Task; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; diff --git a/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java b/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java new file mode 100644 index 000000000..d2b7ac800 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java @@ -0,0 +1,92 @@ +package net.minecraftforge.gradle.internal; + +import org.gradle.api.file.FileCollection; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetOutput; +import org.jspecify.annotations.Nullable; + +import javax.inject.Inject; +import java.io.File; + +abstract class SourceSetNested { + private final String name; + private final FileCollection compileClasspath; + private final FileCollection annotationProcessorPath; + private final FileCollection runtimeClasspath; + private final SourceSetOutputNested output; + + protected abstract @Inject ObjectFactory getObjects(); + + @Inject + public SourceSetNested(SourceSet sourceSet) { + this.name = sourceSet.getName(); + this.compileClasspath = getObjects().fileCollection().from(sourceSet.getCompileClasspath()); + this.annotationProcessorPath = getObjects().fileCollection().from(sourceSet.getAnnotationProcessorPath()); + this.runtimeClasspath = getObjects().fileCollection().from(sourceSet.getRuntimeClasspath()); + this.output = getObjects().newInstance(SourceSetOutputNested.class, sourceSet.getOutput()); + } + + protected @Internal String getName() { + return this.name; + } + + protected @InputFiles @Classpath FileCollection getCompileClasspath() { + return this.compileClasspath; + } + + protected @InputFiles @Classpath FileCollection getAnnotationProcessorPath() { + return this.annotationProcessorPath; + } + + protected @InputFiles @Classpath FileCollection getRuntimeClasspath() { + return this.runtimeClasspath; + } + + protected @Nested SourceSetOutputNested getOutput() { + return this.output; + } + + static abstract class SourceSetOutputNested { + private final FileCollection files; + private final FileCollection classesDirs; + private final @Nullable File resourcesDir; + private final FileCollection dirs; + private final FileCollection generatedSourcesDirs; + + protected abstract @Inject ObjectFactory getObjects(); + + @Inject + public SourceSetOutputNested(SourceSetOutput output) { + this.files = getObjects().fileCollection().from(output); + this.classesDirs = getObjects().fileCollection().from(output.getClassesDirs()); + this.resourcesDir = output.getResourcesDir(); + this.dirs = getObjects().fileCollection().from(output.getDirs()); + this.generatedSourcesDirs = getObjects().fileCollection().from(output.getGeneratedSourcesDirs()); + } + + protected @Internal FileCollection getAsFileCollection() { + return this.files; + } + + protected @Internal FileCollection getClassesDirs() { + return this.classesDirs; + } + + protected @Internal @Nullable File getResourcesDir() { + return this.resourcesDir; + } + + protected @Internal FileCollection getDirs() { + return this.dirs; + } + + protected @Internal FileCollection getGeneratedSourcesDirs() { + return this.generatedSourcesDirs; + } + } +} From 3b576e070d72b798cb617a452fd610b00a19287a Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 20:14:18 -0500 Subject: [PATCH 07/10] Fix incorrect for loop breaks --- .../gradle/SlimeLauncherOptionsNested.java | 1 - .../internal/SlimeLauncherEclipseConfiguration.java | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index bda2370ed..3c30a239f 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -215,7 +215,6 @@ public interface SlimeLauncherOptionsNested { /// @see #getEnvironment() void environment(Provider> properties); - @Nested NamedDomainObjectContainer getMods(); default void mods( diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java index 8daa4c186..cb1e2b594 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherEclipseConfiguration.java @@ -111,10 +111,10 @@ static TaskProvider register(Project project, for (var source : eclipseModel.getClasspath().getSourceSets()) { File path = null; for (var eclipseOutput : eclipseOutputs) { - if (eclipseOutput.getSources().getName().equals(source.getName())) + if (eclipseOutput.getSources().getName().equals(source.getName())) { path = new File(eclipseOutput.getFile()); - - break; + break; + } } if (path != null) { @@ -492,10 +492,10 @@ private static Set getOutputs(Project project) { private static Set getOutputs(Iterable known, SourceSetNested sourceSet) { File eclipse = null; for (var entry : known) { - if (entry.getSources().getName().equals(sourceSet.getName())) + if (entry.getSources().getName().equals(sourceSet.getName())) { eclipse = new File(entry.getFile()); - - break; + break; + } } if (eclipse != null) From fa2d7e8f5029d841b3e4df235e523d896de926f8 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 20:29:23 -0500 Subject: [PATCH 08/10] Add missing Javadocs --- .../gradle/SlimeLauncherOptions.java | 11 ++++++ .../gradle/SlimeLauncherOptionsNested.java | 36 +++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptions.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptions.java index 75bb03acc..44b0102b2 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptions.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptions.java @@ -20,10 +20,21 @@ public interface SlimeLauncherOptions extends SlimeLauncherOptionsNested, Named @Override @Input String getName(); + /// Configures source set-specific attributes for this run. + /// + /// Use this, for example, if you need to configure test-specific arguments, properties, or environment variables. + /// + /// @param sourceSet The source set to configure this run for + /// @param action The configuring action default void with(SourceSet sourceSet, Action action) { this.with(sourceSet.getName(), action); } + /// Configures source set-specific attributes for this run. + /// + /// @param sourceSetName The source set's name to configure this run for + /// @param action The configuring action + /// @see #with(SourceSet, Action) void with(String sourceSetName, Action action); /// The classpath to use. diff --git a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java index 3c30a239f..fbbe963c3 100644 --- a/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java +++ b/src/main/java/net/minecraftforge/gradle/SlimeLauncherOptionsNested.java @@ -35,7 +35,7 @@ public interface SlimeLauncherOptionsNested { /// @return A property for the main class @Input @Optional Property getMainClass(); - /// Wither or not to inherit arguments from the UserDev provided run configs. + /// Whether or not to inherit arguments from the UserDev provided run configs. /// /// If you set this to false you must specify all arguments to start the process manually. /// @@ -50,7 +50,7 @@ public interface SlimeLauncherOptionsNested { /// @return A property for the arguments to pass to the main class @Input @Optional ListProperty getArgs(); - /// Wither or not to inherit JVM arguments from the UserDev provided run configs. + /// Whether or not to inherit JVM arguments from the UserDev provided run configs. /// /// If you set this to false you must specify all JVM arguments to start the process manually. /// @@ -215,8 +215,18 @@ public interface SlimeLauncherOptionsNested { /// @see #getEnvironment() void environment(Provider> properties); + /// The legacy mod configurations to use. + /// + /// This is used to define the mod's source paths for legacy versions when they cannot be interpreted automatically. + /// Use this if you use non-standard source paths for your mod or source set. + /// + /// @return The legacy mod configurations. @Nested NamedDomainObjectContainer getMods(); + /// Configures the legacy mod configurations to use. + /// + /// @param closure The configuring closure + /// @see #getMods() default void mods( @DelegatesTo(NamedDomainObjectContainer.class) @ClosureParams(value = FromString.class, options = "org.gradle.api.NamedDomainObjectContainer") @@ -225,14 +235,36 @@ default void mods( this.getMods().configure(closure); } + /// Configures the legacy mod configurations to use. + /// + /// @param action The configuring action + /// @see #getMods() default void mods(Action> action) { this.mods(Closures.action(this, action)); } + /// Represents a legacy mod configuration for older versions of Forge. + /// + /// @see #getMods() interface ModConfig extends Named { + /// Sets the source sets to use for this configuration. + /// + /// @param sources The source sets to use void setSources(List sources); + + /// Adds to the source sets to use for this configuration. + /// + /// @param sources The source sets to use void sources(List sources); + + /// Adds to the source sets to use for this configuration. + /// + /// @param sources The source sets to use void sources(SourceSet... sources); + + /// Adds to the source sets to use for this configuration. + /// + /// @param source The source set to use void source(SourceSet source); } } From fc1466183b4d124f36ff308d6d4dea2f506cc041 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 20:29:31 -0500 Subject: [PATCH 09/10] Final cleanup --- .../minecraftforge/gradle/internal/ClosureOwnerInternal.java | 2 -- .../net/minecraftforge/gradle/internal/ForgeAttributes.java | 1 - .../net/minecraftforge/gradle/internal/ForgeGradlePlugin.java | 1 - .../minecraftforge/gradle/internal/ForgeGradleProblems.java | 2 -- .../gradle/internal/MinecraftDependencyInternal.java | 2 -- .../net/minecraftforge/gradle/internal/SlimeLauncherExec.java | 2 -- .../minecraftforge/gradle/internal/SlimeLauncherMetadata.java | 3 --- .../gradle/internal/SlimeLauncherOptionsInternal.java | 2 -- .../minecraftforge/gradle/internal/SlimeLauncherRunTask.java | 4 ---- .../net/minecraftforge/gradle/internal/SourceSetNested.java | 4 ++++ 10 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/internal/ClosureOwnerInternal.java b/src/main/java/net/minecraftforge/gradle/internal/ClosureOwnerInternal.java index aea7d060d..010f122cd 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ClosureOwnerInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ClosureOwnerInternal.java @@ -9,7 +9,6 @@ import groovy.transform.NamedParam; import groovy.transform.NamedParams; import net.minecraftforge.gradle.ClosureOwner; -import net.minecraftforge.gradle.MinecraftDependency; import net.minecraftforge.gradle.MinecraftMappings; import net.minecraftforge.gradle.SlimeLauncherOptions; import org.gradle.api.Action; @@ -27,7 +26,6 @@ import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.capabilities.Capability; import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; import org.jspecify.annotations.Nullable; diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeAttributes.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeAttributes.java index e7a8e288d..819481bf4 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeAttributes.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeAttributes.java @@ -5,7 +5,6 @@ package net.minecraftforge.gradle.internal; import net.minecraftforge.util.os.OS; -import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.gradle.api.Action; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeDisambiguationRule; diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradlePlugin.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradlePlugin.java index 78ca22f7d..6571029a6 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradlePlugin.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradlePlugin.java @@ -11,7 +11,6 @@ import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.plugins.ExtensionAware; -import org.gradle.util.GradleVersion; import org.jspecify.annotations.Nullable; import javax.inject.Inject; diff --git a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java index 788666fde..57b59f3c0 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java +++ b/src/main/java/net/minecraftforge/gradle/internal/ForgeGradleProblems.java @@ -6,10 +6,8 @@ import net.minecraftforge.gradle.MinecraftMappings; import net.minecraftforge.gradleutils.shared.EnhancedProblems; -import org.gradle.api.Action; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.problems.ProblemSpec; import org.gradle.api.problems.Problems; import org.gradle.api.problems.Severity; import org.gradle.api.provider.ProviderFactory; diff --git a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java index dc72b8e2a..ce10f4f18 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/MinecraftDependencyInternal.java @@ -8,14 +8,12 @@ import net.minecraftforge.gradle.MavenizerInstance; import net.minecraftforge.gradle.MinecraftDependency; import net.minecraftforge.gradle.SlimeLauncherOptions; -import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectSet; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.reflect.HasPublicType; diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index 3026e19f0..f77ee91d4 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -36,10 +36,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; @DisableCachingByDefault(because = "Running the game cannot be cached") abstract class SlimeLauncherExec extends JavaExec implements ForgeGradleTask, HasPublicType, SlimeLauncherRunTask { diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java index 16aef6172..828c44fe1 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherMetadata.java @@ -6,8 +6,6 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Project; -import org.gradle.api.UnknownDomainObjectException; -import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; @@ -17,7 +15,6 @@ import javax.inject.Inject; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java index 2de6c3dd4..64c4507bd 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherOptionsInternal.java @@ -10,7 +10,6 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; -import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.reflect.HasPublicType; @@ -19,7 +18,6 @@ import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.SourceSet; import java.util.List; import java.util.Map; diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java index 9e40f10f8..85c854471 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -7,12 +7,8 @@ import org.gradle.api.Task; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -import java.util.List; - public interface SlimeLauncherRunTask extends Task { Property getSourceSetName(); DirectoryProperty getLocalCacheDir(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java b/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java index d2b7ac800..44c933418 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SourceSetNested.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ package net.minecraftforge.gradle.internal; import org.gradle.api.file.FileCollection; From 02023335bb809ea5eb2da89fbf63be1691e0d041 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Tue, 3 Mar 2026 20:35:01 -0500 Subject: [PATCH 10/10] Fix validatePlugins violations --- .../gradle/internal/SlimeLauncherExec.java | 2 +- .../gradle/internal/SlimeLauncherRunTask.java | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java index f77ee91d4..b58fb1727 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherExec.java @@ -70,7 +70,7 @@ static TaskProvider register(Project project, SourceSet sourc protected abstract @Input Property getRunName(); - public abstract @Input Property getSourceSetName(); + public abstract @Input @Override Property getSourceSetName(); protected abstract @Nested Property getOptions(); diff --git a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java index 85c854471..dbc36c383 100644 --- a/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java +++ b/src/main/java/net/minecraftforge/gradle/internal/SlimeLauncherRunTask.java @@ -8,17 +8,20 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; public interface SlimeLauncherRunTask extends Task { - Property getSourceSetName(); - DirectoryProperty getLocalCacheDir(); - ConfigurableFileCollection getMetadata(); - ConfigurableFileCollection getMinecraftClasspath(); - ConfigurableFileCollection getRuntimeClasspath(); - ConfigurableFileCollection getPatcherModules(); - Property getMinecraftVersion(); - Property getMCPVersion(); - Property getMappingChannel(); - Property getMappingVersion(); - //RegularFileProperty getSrgToMcp(); + @Input Property getSourceSetName(); + @Internal DirectoryProperty getLocalCacheDir(); + @InputFiles ConfigurableFileCollection getMetadata(); + @InputFiles ConfigurableFileCollection getMinecraftClasspath(); + @InputFiles ConfigurableFileCollection getRuntimeClasspath(); + @InputFiles ConfigurableFileCollection getPatcherModules(); + @Input Property getMinecraftVersion(); + @Input Property getMCPVersion(); + @Input Property getMappingChannel(); + @Input Property getMappingVersion(); + //@InputFile RegularFileProperty getSrgToMcp(); }