From 6a9f5cec3f11f071d79a828c2d6f8f2b0d173c79 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:08:31 +0200 Subject: [PATCH] Port Forge run templates to the new classpath group system --- .../loom/forge/config/ForgeRunTemplate.java | 3 +- .../dependency/ForgeModClassesService.java | 107 ++++++++++++++++++ .../forge/dependency/ForgeRunsProvider.java | 91 +++------------ .../loom/util/collection/Multimap.java | 8 ++ .../loom/LoomCompanionGradlePlugin.java | 1 + .../net/fabricmc/loom/api/ModSettings.java | 16 +++ .../classpathgroups/ClasspathGroup.java | 19 +++- .../loom/configuration/ide/RunConfig.java | 2 + .../configuration/ide/RunConfigSettings.java | 2 +- .../configuration/ide/idea/IdeaSyncTask.java | 29 +++++ .../fabricmc/loom/task/AbstractRunTask.java | 31 +++++ .../loom/task/GenEclipseRunsTask.java | 23 ++++ .../loom/task/GenVsCodeProjectTask.java | 25 ++++ .../fabricmc/loom/task/RenderDocRunTask.java | 1 + .../task/launch/GenerateDLIConfigTask.java | 7 +- .../task/service/ClasspathGroupService.java | 11 +- .../loom/util/gradle/GradleUtils.java | 3 +- 17 files changed, 291 insertions(+), 88 deletions(-) create mode 100644 src/main/java/dev/architectury/loom/forge/dependency/ForgeModClassesService.java diff --git a/src/main/java/dev/architectury/loom/forge/config/ForgeRunTemplate.java b/src/main/java/dev/architectury/loom/forge/config/ForgeRunTemplate.java index e69877ec..cce034d9 100644 --- a/src/main/java/dev/architectury/loom/forge/config/ForgeRunTemplate.java +++ b/src/main/java/dev/architectury/loom/forge/config/ForgeRunTemplate.java @@ -35,6 +35,7 @@ import java.util.stream.Collectors; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.architectury.loom.forge.dependency.ForgeModClassesService; import dev.architectury.loom.util.collection.CollectionUtil; import org.gradle.api.Named; @@ -118,7 +119,7 @@ public record ForgeRunTemplate( }); // Add MOD_CLASSES, this is something that ForgeGradle does - settings.getEnvironmentVariables().computeIfAbsent("MOD_CLASSES", $ -> ConfigValue.of("{source_roots}").resolve(configValueResolver)); + settings.getEnvironmentVariables().putIfAbsent(ForgeModClassesService.ENVIRONMENT_VARIABLE, ForgeModClassesService.VARIABLE_KEY); } public Resolved resolve(ConfigValue.Resolver configValueResolver) { diff --git a/src/main/java/dev/architectury/loom/forge/dependency/ForgeModClassesService.java b/src/main/java/dev/architectury/loom/forge/dependency/ForgeModClassesService.java new file mode 100644 index 00000000..783cadf1 --- /dev/null +++ b/src/main/java/dev/architectury/loom/forge/dependency/ForgeModClassesService.java @@ -0,0 +1,107 @@ +package dev.architectury.loom.forge.dependency; + +import java.io.File; +import java.util.stream.Collectors; + +import dev.architectury.loom.util.Version; +import dev.architectury.loom.util.collection.Multimap; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.ModSettings; +import net.fabricmc.loom.configuration.classpathgroups.ClasspathGroup; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.task.service.ClasspathGroupService; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.service.Service; +import net.fabricmc.loom.util.service.ServiceFactory; +import net.fabricmc.loom.util.service.ServiceType; + +public final class ForgeModClassesService extends Service { + public static final ServiceType TYPE = new ServiceType<>(ForgeModClassesService.Options.class, ForgeModClassesService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ForgeModClassesService.class); + + public static final String ENVIRONMENT_VARIABLE = "MOD_CLASSES"; + public static final String VARIABLE_KEY = "{source_roots}"; + + public interface Options extends Service.Options { + @Nested + MapProperty getClasspathGroupOptions(); + + @Input + Property getSourceRootsSeparator(); + } + + public static Provider createOptions(Project project) { + return TYPE.maybeCreate(project, options -> { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (!extension.isForgeLike()) { + return false; + } + + for (RunConfigSettings runConfigSettings : extension.getRunConfigs()) { + NamedDomainObjectContainer modOverrides = runConfigSettings.getMods(); + NamedDomainObjectContainer modSettings = !modOverrides.isEmpty() ? modOverrides : extension.getMods(); + options.getClasspathGroupOptions().put(runConfigSettings.getName(), ClasspathGroupService.create(project, modSettings)); + } + + options.getSourceRootsSeparator().set(getSourceRootsSeparator(project)); + return true; + }); + } + + private static String getSourceRootsSeparator(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + // Some versions of Forge 49+ requires a different separator + if (!extension.isForge() || extension.getForgeProvider().getVersion().getMajorVersion() < Constants.Forge.MIN_BOOTSTRAP_DEV_VERSION) { + return File.pathSeparator; + } + + for (Dependency dependency : project.getConfigurations().getByName(Constants.Configurations.FORGE_DEPENDENCIES).getDependencies()) { + if (dependency.getGroup().equals("net.minecraftforge") && dependency.getName().equals("bootstrap-dev")) { + Version version = Version.parse(dependency.getVersion()); + return version.compareTo(Version.parse("2.1.4")) >= 0 ? File.pathSeparator : ";"; + } + } + + LOGGER.warn("Failed to find bootstrap-dev in forge dependencies, using File.pathSeparator as separator"); + return File.pathSeparator; + } + + public ForgeModClassesService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + public String getModClasses(String runConfig) { + // Use a set-valued multimap for deduplicating paths. + Multimap modClasses = Multimap.setMultimap(); + String separator = getOptions().getSourceRootsSeparator().get(); + ClasspathGroupService classpathGroupService = getServiceFactory().get(getOptions().getClasspathGroupOptions().getting(runConfig)); + + for (ClasspathGroup group : classpathGroupService.getClasspathGroups()) { + // Note: In Forge 1.16.5, resources have to come first to find mods.toml + if (group.resourceDir() != null) { + modClasses.put(group.name(), group.resourceDir()); + } + + for (File file : classpathGroupService.getClasspath(group)) { + modClasses.put(group.name(), file.getAbsolutePath()); + } + } + + return modClasses.streamEntries() + .map(entry -> entry.left() + "%%" + entry.right()) + .collect(Collectors.joining(separator)); + } +} diff --git a/src/main/java/dev/architectury/loom/forge/dependency/ForgeRunsProvider.java b/src/main/java/dev/architectury/loom/forge/dependency/ForgeRunsProvider.java index 374475c1..3dd0d146 100644 --- a/src/main/java/dev/architectury/loom/forge/dependency/ForgeRunsProvider.java +++ b/src/main/java/dev/architectury/loom/forge/dependency/ForgeRunsProvider.java @@ -40,22 +40,13 @@ import dev.architectury.loom.forge.config.ConfigValue; import dev.architectury.loom.forge.config.ForgeRunTemplate; import dev.architectury.loom.forge.config.UserdevConfig; import dev.architectury.loom.util.DependencyDownloader; -import dev.architectury.loom.util.Version; -import dev.architectury.loom.util.collection.Multimap; -import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectSet; import org.gradle.api.Project; -import org.gradle.api.artifacts.Dependency; -import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.ModSettings; -import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.gradle.SourceSetHelper; -import net.fabricmc.loom.util.gradle.SourceSetReference; -public class ForgeRunsProvider { +public class ForgeRunsProvider implements ConfigValue.Resolver { private final Project project; private final LoomGradleExtension extension; private final JsonObject json; @@ -78,11 +69,8 @@ public class ForgeRunsProvider { return new ForgeRunsProvider(project, userdevProvider.getJson(), userdevProvider.getConfig()); } - public ConfigValue.Resolver getResolver(@Nullable RunConfigSettings runConfig) { - return variable -> resolve(runConfig, variable); - } - - private String resolve(@Nullable RunConfigSettings runConfig, ConfigValue.Variable variable) { + @Override + public String resolve(ConfigValue.Variable variable) { String key = variable.name(); String string = '{' + key + '}'; @@ -128,51 +116,25 @@ public class ForgeRunsProvider { } else if (key.equals("natives")) { string = extension.getFiles().getNativesDirectory(project).getAbsolutePath(); } else if (key.equals("source_roots")) { - // Use a set-valued multimap for deduplicating paths. - Multimap modClasses = Multimap.setMultimap(); - NamedDomainObjectContainer mods = extension.getMods(); - String separator = getSourceRootsSeparator(); - - if (runConfig != null && !runConfig.getMods().isEmpty()) { - mods = runConfig.getMods(); - } - - for (ModSettings mod : mods) { - // Note: In Forge 1.16.5, resources have to come first to find mods.toml - for (SourceSetReference modSourceSet : mod.getModSourceSets().get()) { - File resourcesDir = modSourceSet.sourceSet().getOutput().getResourcesDir(); - modClasses.put(mod.getName(), resourcesDir.getAbsolutePath()); - } - - for (File file : SourceSetHelper.getClasspath(mod, project)) { - modClasses.put(mod.getName(), file.getAbsolutePath()); - } - } - - string = modClasses.entrySet().stream() - .map(entry -> entry.getKey() + "%%" + entry.getValue()) - .collect(Collectors.joining(separator)); + // ignored, handled later using ForgeModClassesService } else if (key.equals("mcp_mappings")) { string = "loom.stub"; + } else if (key.equals("modules")) { + string = StreamSupport.stream(json.getAsJsonArray("modules").spliterator(), false) + .map(JsonElement::getAsString) + .flatMap(str -> { + if (str.contains(":")) { + return DependencyDownloader.download(project, str, false, false).getFiles().stream() + .map(File::getAbsolutePath) + .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack + } + + return Stream.of(str); + }) + .collect(Collectors.joining(File.pathSeparator)); } else if (json.has(key)) { JsonElement element = json.get(key); - - if (element.isJsonArray()) { - string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false) - .map(JsonElement::getAsString) - .flatMap(str -> { - if (str.contains(":")) { - return DependencyDownloader.download(project, str, false, false).getFiles().stream() - .map(File::getAbsolutePath) - .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack - } - - return Stream.of(str); - }) - .collect(Collectors.joining(File.pathSeparator)); - } else { - string = element.toString(); - } + string = element.toString(); } else { project.getLogger().warn("Unrecognized template! " + string); } @@ -188,21 +150,4 @@ public class ForgeRunsProvider { private Set minecraftClasspath() { return DependencyDownloader.resolveFiles(project, project.getConfigurations().getByName(Constants.Configurations.FORGE_RUNTIME_LIBRARY), true); } - - private String getSourceRootsSeparator() { - // Some versions of Forge 49+ requires a different separator - if (!extension.isForge() || extension.getForgeProvider().getVersion().getMajorVersion() < Constants.Forge.MIN_BOOTSTRAP_DEV_VERSION) { - return File.pathSeparator; - } - - for (Dependency dependency : project.getConfigurations().getByName(Constants.Configurations.FORGE_DEPENDENCIES).getDependencies()) { - if (dependency.getGroup().equals("net.minecraftforge") && dependency.getName().equals("bootstrap-dev")) { - Version version = Version.parse(dependency.getVersion()); - return version.compareTo(Version.parse("2.1.4")) >= 0 ? File.pathSeparator : ";"; - } - } - - project.getLogger().warn("Failed to find bootstrap-dev in forge dependencies, using File.pathSeparator as separator"); - return File.pathSeparator; - } } diff --git a/src/main/java/dev/architectury/loom/util/collection/Multimap.java b/src/main/java/dev/architectury/loom/util/collection/Multimap.java index fa63cba1..f4769f30 100644 --- a/src/main/java/dev/architectury/loom/util/collection/Multimap.java +++ b/src/main/java/dev/architectury/loom/util/collection/Multimap.java @@ -9,6 +9,9 @@ import java.util.List; import java.util.Map; import java.util.SequencedSet; import java.util.Set; +import java.util.stream.Stream; + +import net.fabricmc.loom.util.Pair; public interface Multimap { static Specialized> setMultimap() { @@ -26,6 +29,11 @@ public interface Multimap { Map> asMap(); Set>> entrySet(); + default Stream> streamEntries() { + return entrySet().stream() + .flatMap(entry -> entry.getValue().stream().map(value -> new Pair<>(entry.getKey(), value))); + } + interface Specialized> extends Multimap { @Override C get(K key); diff --git a/src/main/java/net/fabricmc/loom/LoomCompanionGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomCompanionGradlePlugin.java index b2b2123a..819b9fb2 100644 --- a/src/main/java/net/fabricmc/loom/LoomCompanionGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomCompanionGradlePlugin.java @@ -34,6 +34,7 @@ import net.fabricmc.loom.util.Constants; public class LoomCompanionGradlePlugin implements Plugin { public static final String NAME = "dev.architectury.loom-companion"; + public static final String UPSTREAM_NAME = "net.fabricmc.fabric-loom-companion"; @Override public void apply(@NotNull Project project) { diff --git a/src/main/java/net/fabricmc/loom/api/ModSettings.java b/src/main/java/net/fabricmc/loom/api/ModSettings.java index b2e0b300..927992eb 100644 --- a/src/main/java/net/fabricmc/loom/api/ModSettings.java +++ b/src/main/java/net/fabricmc/loom/api/ModSettings.java @@ -36,6 +36,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.ApiStatus; @@ -54,6 +55,13 @@ public abstract class ModSettings implements Named { */ public abstract ConfigurableFileCollection getModFiles(); + /** + * The main resource directory in the mod. + * On Forge and NeoForge, this is the resource directory that contains mods.toml. + */ + @ApiStatus.Experimental + public abstract DirectoryProperty getMainResourceDirectory(); + @Inject public ModSettings() { getExternalGroups().finalizeValueOnRead(); @@ -125,6 +133,14 @@ public abstract class ModSettings implements Named { SourceSetReference ref = new SourceSetReference(SourceSetHelper.getSourceSetByName(sourceSetName, getProject()), getProject()); List classpath = SourceSetHelper.getClasspath(ref, false); getModFiles().from(classpath); + + // Arch: store resource dir + File resourcesDir = ref.sourceSet().getOutput().getResourcesDir(); + + if (resourcesDir != null && !getMainResourceDirectory().isPresent()) { + getMainResourceDirectory().set(resourcesDir); + } + return; } diff --git a/src/main/java/net/fabricmc/loom/configuration/classpathgroups/ClasspathGroup.java b/src/main/java/net/fabricmc/loom/configuration/classpathgroups/ClasspathGroup.java index ae383520..3359dddc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classpathgroups/ClasspathGroup.java +++ b/src/main/java/net/fabricmc/loom/configuration/classpathgroups/ClasspathGroup.java @@ -30,15 +30,20 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import org.gradle.api.file.FileSystemLocation; +import org.jetbrains.annotations.Nullable; + import net.fabricmc.loom.api.ModSettings; -public record ClasspathGroup(List paths, List externalGroups) implements Serializable { +public record ClasspathGroup(String name, @Nullable String resourceDir, List paths, List externalGroups) implements Serializable { public static List fromModSettings(Set modSettings) { - return modSettings.stream().map(s -> new ClasspathGroup(getPaths(s), s.getExternalGroups().get())).toList(); + return modSettings.stream().map(s -> new ClasspathGroup(s.getName(), getAbsolutePath(s.getMainResourceDirectory().getOrNull()), getPaths(s), s.getExternalGroups().get())).toList(); } // TODO remove this constructor when updating to Gradle 9.0, works around an issue where config cache cannot serialize immutable lists - public ClasspathGroup(List paths, List externalGroups) { + public ClasspathGroup(String name, @Nullable String resourceDir, List paths, List externalGroups) { + this.name = name; + this.resourceDir = resourceDir; this.paths = new ArrayList<>(paths); this.externalGroups = new ArrayList<>(externalGroups); } @@ -50,4 +55,12 @@ public record ClasspathGroup(List paths, List ex .map(File::getAbsolutePath) .toList(); } + + private static @Nullable String getAbsolutePath(@Nullable FileSystemLocation location) { + if (location == null) { + return null; + } + + return location.getAsFile().getAbsolutePath(); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index 893e735a..d39ed3a8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -77,6 +77,7 @@ public class RunConfig { public Map environmentVariables; public String projectName; public String folderName; + public String name; // Turns camelCase/PascalCase into Capital Case // caseConversionExample -> Case Conversion Example @@ -135,6 +136,7 @@ public class RunConfig { boolean appendProjectPath = settings.getAppendProjectPathToConfigName().get(); RunConfig runConfig = new RunConfig(); runConfig.configName = configName; + runConfig.name = name; if (appendProjectPath && !GradleUtils.isRootProject(project)) { runConfig.configName += " (" + project.getPath() + ")"; diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java index 3774c02b..887e652c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -446,7 +446,7 @@ public abstract class RunConfigSettings implements Named { ForgeRunTemplate template = runsProvider.getTemplates().findByName(templateName); if (template != null) { - template.applyTo(this, runsProvider.getResolver(this)); + template.applyTo(this, runsProvider); } else { project.getLogger().warn("Could not find Forge run template with name '{}'", templateName); } diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java index f3a55e8a..70db8276 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java @@ -43,12 +43,14 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import dev.architectury.loom.forge.dependency.ForgeModClassesService; import org.gradle.api.Project; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.jetbrains.annotations.VisibleForTesting; @@ -64,6 +66,7 @@ import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.task.AbstractLoomTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.service.ScopedServiceFactory; public abstract class IdeaSyncTask extends AbstractLoomTask { private static final Logger LOGGER = LoggerFactory.getLogger(IdeaSyncTask.class); @@ -71,16 +74,29 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { @Nested protected abstract ListProperty getIdeaRunConfigs(); + @Nested + @Optional + protected abstract Property getModClassesOptions(); + @Inject public IdeaSyncTask() { setGroup(Constants.TaskGroup.IDE); getIdeaRunConfigs().set(getProject().provider(this::getRunConfigs)); + getModClassesOptions().set(ForgeModClassesService.createOptions(getProject())); } @TaskAction public void runTask() throws IOException { for (IntelijRunConfig config : getIdeaRunConfigs().get()) { config.writeLaunchFile(); + + if (getModClassesOptions().isPresent()) { + try (var serviceFactory = new ScopedServiceFactory()) { + ForgeModClassesService modClassesService = serviceFactory.get(getModClassesOptions()); + Path launchFile = config.getLaunchFile().get().getAsFile().toPath(); + setForgeModClasses(launchFile, modClassesService.getModClasses(config.getName().get())); + } + } } } @@ -109,6 +125,7 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { irc.getRunConfigXml().set(runConfigXml); irc.getExcludedLibraryPaths().set(excludedLibraryPaths); irc.getLaunchFile().set(runConfigFile); + irc.getName().set(settings.getName()); configs.add(irc); settings.makeRunDir(); @@ -127,6 +144,9 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { @OutputFile RegularFileProperty getLaunchFile(); + @Input + Property getName(); + default void writeLaunchFile() throws IOException { Path launchFile = getLaunchFile().get().getAsFile().toPath(); @@ -160,6 +180,15 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { } } + public static void setForgeModClasses(Path runConfig, String modClasses) throws IOException { + final String inputXml = Files.readString(runConfig, StandardCharsets.UTF_8); + final String outputXml = inputXml.replace(ForgeModClassesService.VARIABLE_KEY, modClasses); + + if (!inputXml.equals(outputXml)) { + Files.writeString(runConfig, outputXml, StandardCharsets.UTF_8); + } + } + @VisibleForTesting public static String setClasspathModificationsInXml(String input, List exclusions) throws Exception { final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 8a0a4b72..56975409 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import dev.architectury.loom.forge.dependency.ForgeModClassesService; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; @@ -48,6 +49,10 @@ import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; +import org.gradle.process.ProcessForkOptions; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +60,7 @@ import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.service.ScopedServiceFactory; public abstract class AbstractRunTask extends JavaExec { private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder(); @@ -78,6 +84,15 @@ public abstract class AbstractRunTask extends JavaExec { @InputFiles protected abstract ConfigurableFileCollection getInternalClasspath(); + @ApiStatus.Internal + @Nested + @Optional + protected abstract Property getModClassesOptions(); + + @ApiStatus.Internal + @Input + protected abstract Property getRunConfigName(); + public AbstractRunTask(Function configProvider) { super(); setGroup(Constants.TaskGroup.FABRIC); @@ -103,6 +118,9 @@ public abstract class AbstractRunTask extends JavaExec { File buildCache = LoomGradleExtension.get(getProject()).getFiles().getProjectBuildCache(); File argFile = new File(buildCache, "argFiles/" + getName()); getArgFilePath().set(argFile.getAbsolutePath()); + + getModClassesOptions().set(ForgeModClassesService.createOptions(getProject())); + getRunConfigName().set(config.map(runConfig -> runConfig.name)); } private boolean canUseArgFile() { @@ -134,10 +152,23 @@ public abstract class AbstractRunTask extends JavaExec { setWorkingDir(new File(getProjectDir().get(), getInternalRunDir().get())); environment(getInternalEnvironmentVars().get()); + configureForgeModClasses(this); super.exec(); } + protected void configureForgeModClasses(ProcessForkOptions forkOptions) { + try (var serviceFactory = new ScopedServiceFactory()) { + ForgeModClassesService service = serviceFactory.getOrNull(getModClassesOptions()); + + if (service != null && ForgeModClassesService.VARIABLE_KEY.equals(forkOptions.getEnvironment().get(ForgeModClassesService.ENVIRONMENT_VARIABLE))) { + forkOptions.environment(ForgeModClassesService.ENVIRONMENT_VARIABLE, service.getModClasses(getRunConfigName().get())); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + @Override public void setWorkingDir(File dir) { if (!dir.exists()) { diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java index 639461ac..63df903b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java @@ -35,35 +35,54 @@ import java.util.List; import javax.inject.Inject; +import dev.architectury.loom.forge.dependency.ForgeModClassesService; import org.gradle.api.Project; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.plugins.ide.eclipse.model.EclipseModel; +import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.service.ScopedServiceFactory; public abstract class GenEclipseRunsTask extends AbstractLoomTask { @Nested protected abstract ListProperty getEclipseRunConfigs(); + @ApiStatus.Internal + @Nested + @Optional + protected abstract Property getModClassesOptions(); + @Inject public GenEclipseRunsTask() { setGroup(Constants.TaskGroup.IDE); getEclipseRunConfigs().set(getProject().provider(() -> getRunConfigs(getProject()))); + getModClassesOptions().set(ForgeModClassesService.createOptions(getProject())); } @TaskAction public void genRuns() throws IOException { for (EclipseRunConfig runConfig : getEclipseRunConfigs().get()) { runConfig.writeLaunchFile(); + + if (getModClassesOptions().isPresent()) { + try (var serviceFactory = new ScopedServiceFactory()) { + ForgeModClassesService modClassesService = serviceFactory.get(getModClassesOptions()); + Path launchFile = runConfig.getLaunchFile().get().getAsFile().toPath(); + IdeaSyncTask.setForgeModClasses(launchFile, modClassesService.getModClasses(runConfig.getName().get())); + } + } } } @@ -92,6 +111,7 @@ public abstract class GenEclipseRunsTask extends AbstractLoomTask { EclipseRunConfig eclipseRunConfig = project.getObjects().newInstance(EclipseRunConfig.class); eclipseRunConfig.getLaunchContent().set(config); eclipseRunConfig.getLaunchFile().set(project.file(configs)); + eclipseRunConfig.getName().set(name); runConfigs.add(eclipseRunConfig); settings.makeRunDir(); @@ -107,6 +127,9 @@ public abstract class GenEclipseRunsTask extends AbstractLoomTask { @OutputFile RegularFileProperty getLaunchFile(); + @Input + Property getName(); + default void writeLaunchFile() throws IOException { Path launchFile = getLaunchFile().get().getAsFile().toPath(); diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index 30cf19c8..e1a8e61b 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -41,20 +41,25 @@ import javax.inject.Inject; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import dev.architectury.loom.forge.dependency.ForgeModClassesService; import org.gradle.api.Project; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.services.ServiceReference; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.gradle.SyncTaskBuildService; +import net.fabricmc.loom.util.service.ScopedServiceFactory; // Recommended vscode plugin pack: // https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack @@ -69,11 +74,17 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { @OutputFile protected abstract RegularFileProperty getLaunchJson(); + @ApiStatus.Internal + @Nested + @Optional + protected abstract Property getModClassesOptions(); + @Inject public GenVsCodeProjectTask() { setGroup(Constants.TaskGroup.IDE); getLaunchConfigurations().set(getProject().provider(this::getConfigurations)); getLaunchJson().convention(getProject().getRootProject().getLayout().getProjectDirectory().file(".vscode/launch.json")); + getModClassesOptions().set(ForgeModClassesService.createOptions(getProject())); } private List getConfigurations() { @@ -120,6 +131,18 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { for (VsCodeConfiguration configuration : getLaunchConfigurations().get()) { JsonObject configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration).getAsJsonObject(); configurationJson.remove("runDir"); + configurationJson.remove("id"); + + if (getModClassesOptions().isPresent()) { + try (var serviceFactory = new ScopedServiceFactory()) { + ForgeModClassesService service = serviceFactory.get(getModClassesOptions()); + JsonObject env = configurationJson.getAsJsonObject("env"); + + if (env != null && env.has(ForgeModClassesService.ENVIRONMENT_VARIABLE)) { + env.addProperty(ForgeModClassesService.ENVIRONMENT_VARIABLE, service.getModClasses(configuration.id)); + } + } + } final List toRemove = new LinkedList<>(); @@ -151,6 +174,7 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { public record VsCodeConfiguration( String type, String name, + String id, String request, String cwd, String console, @@ -168,6 +192,7 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { return new VsCodeConfiguration( "java", runConfig.configName, + runConfig.name, "launch", "${workspaceFolder}/" + relativeRunDir, "integratedTerminal", diff --git a/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java index e02b53f6..480935ec 100644 --- a/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java @@ -67,6 +67,7 @@ public abstract class RenderDocRunTask extends RunGameTask { ExecResult result = getExecOperations().exec(exec -> { exec.workingDir(new File(getProjectDir().get(), getInternalRunDir().get())); exec.environment(getInternalEnvironmentVars().get()); + configureForgeModClasses(exec); exec.commandLine(getRenderDocExecutable().get().getAsFile()); exec.args(getRenderDocArgs().get()); diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java index 3fd9fe45..3b28c996 100644 --- a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java @@ -40,7 +40,6 @@ import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; -import dev.architectury.loom.forge.config.ConfigValue; import dev.architectury.loom.forge.config.ForgeRunTemplate; import dev.architectury.loom.forge.dependency.ForgeRunsProvider; import org.gradle.api.Project; @@ -64,10 +63,9 @@ import net.fabricmc.loom.build.IntermediaryNamespaces; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.task.service.ClasspathGroupService; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ModPlatform; -import net.fabricmc.loom.util.gradle.SourceSetHelper; -import net.fabricmc.loom.task.service.ClasspathGroupService; import net.fabricmc.loom.util.service.ScopedServiceFactory; public abstract class GenerateDLIConfigTask extends AbstractLoomTask { @@ -155,10 +153,9 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask { if (getExtension().isForgeLike()) { getRunTemplates().addAll(getProject().provider(() -> { final ForgeRunsProvider forgeRunsProvider = getExtension().getForgeRunsProvider(); - final ConfigValue.Resolver configResolver = forgeRunsProvider.getResolver(null); return forgeRunsProvider.getTemplates() .stream() - .map(template -> template.resolve(configResolver)) + .map(template -> template.resolve(forgeRunsProvider)) .toList(); })); diff --git a/src/main/java/net/fabricmc/loom/task/service/ClasspathGroupService.java b/src/main/java/net/fabricmc/loom/task/service/ClasspathGroupService.java index c3af919d..fd7f8298 100644 --- a/src/main/java/net/fabricmc/loom/task/service/ClasspathGroupService.java +++ b/src/main/java/net/fabricmc/loom/task/service/ClasspathGroupService.java @@ -69,10 +69,11 @@ public class ClasspathGroupService extends Service create(Project project) { - return TYPE.create(project, options -> { - LoomGradleExtension extension = LoomGradleExtension.get(project); - NamedDomainObjectContainer modSettings = extension.getMods(); + return create(project, LoomGradleExtension.get(project).getMods()); + } + public static Provider create(Project project, NamedDomainObjectContainer modSettings) { + return TYPE.create(project, options -> { if (modSettings.isEmpty()) { return; } @@ -159,4 +160,8 @@ public class ClasspathGroupService extends Service getClasspathGroups() { + return getOptions().getClasspathGroups().get(); + } } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java index 0f342906..f28c12c3 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java @@ -35,7 +35,6 @@ import org.gradle.api.provider.Provider; import net.fabricmc.loom.LoomCompanionGradlePlugin; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.util.Constants; public final class GradleUtils { private GradleUtils() { @@ -66,7 +65,7 @@ public final class GradleUtils { } public static boolean isLoomCompanionProject(Project project) { - return project.getPluginManager().hasPlugin(LoomCompanionGradlePlugin.NAME); + return project.getPluginManager().hasPlugin(LoomCompanionGradlePlugin.NAME) || project.getPluginManager().hasPlugin(LoomCompanionGradlePlugin.UPSTREAM_NAME); } public static Provider getBooleanPropertyProvider(Project project, String key) {