Port Forge run templates to the new classpath group system

This commit is contained in:
Juuz
2025-10-29 15:08:31 +02:00
parent f038ad586f
commit 6a9f5cec3f
17 changed files with 291 additions and 88 deletions

View File

@@ -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) {

View File

@@ -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<ForgeModClassesService.Options> {
public static final ServiceType<ForgeModClassesService.Options, ForgeModClassesService> 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<String, ClasspathGroupService.Options> getClasspathGroupOptions();
@Input
Property<String> getSourceRootsSeparator();
}
public static Provider<Options> createOptions(Project project) {
return TYPE.maybeCreate(project, options -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (!extension.isForgeLike()) {
return false;
}
for (RunConfigSettings runConfigSettings : extension.getRunConfigs()) {
NamedDomainObjectContainer<ModSettings> modOverrides = runConfigSettings.getMods();
NamedDomainObjectContainer<ModSettings> 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<String, String> 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));
}
}

View File

@@ -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<String, String> modClasses = Multimap.setMultimap();
NamedDomainObjectContainer<ModSettings> 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<File> 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;
}
}

View File

@@ -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<K, V> {
static <K, V> Specialized<K, V, SequencedSet<V>> setMultimap() {
@@ -26,6 +29,11 @@ public interface Multimap<K, V> {
Map<K, ? extends Collection<V>> asMap();
Set<? extends Map.Entry<K, ? extends Collection<V>>> entrySet();
default Stream<Pair<K, V>> streamEntries() {
return entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(value -> new Pair<>(entry.getKey(), value)));
}
interface Specialized<K, V, C extends Collection<V>> extends Multimap<K, V> {
@Override
C get(K key);

View File

@@ -34,6 +34,7 @@ import net.fabricmc.loom.util.Constants;
public class LoomCompanionGradlePlugin implements Plugin<Project> {
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) {

View File

@@ -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<File> 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;
}

View File

@@ -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<String> paths, List<ExternalClasspathGroup> externalGroups) implements Serializable {
public record ClasspathGroup(String name, @Nullable String resourceDir, List<String> paths, List<ExternalClasspathGroup> externalGroups) implements Serializable {
public static List<ClasspathGroup> fromModSettings(Set<ModSettings> 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<String> paths, List<ExternalClasspathGroup> externalGroups) {
public ClasspathGroup(String name, @Nullable String resourceDir, List<String> paths, List<ExternalClasspathGroup> 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<String> paths, List<ExternalClasspathGroup> ex
.map(File::getAbsolutePath)
.toList();
}
private static @Nullable String getAbsolutePath(@Nullable FileSystemLocation location) {
if (location == null) {
return null;
}
return location.getAsFile().getAbsolutePath();
}
}

View File

@@ -77,6 +77,7 @@ public class RunConfig {
public Map<String, Object> 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() + ")";

View File

@@ -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);
}

View File

@@ -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<IntelijRunConfig> getIdeaRunConfigs();
@Nested
@Optional
protected abstract Property<ForgeModClassesService.Options> 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<String> 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<String> exclusions) throws Exception {
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

View File

@@ -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<ForgeModClassesService.Options> getModClassesOptions();
@ApiStatus.Internal
@Input
protected abstract Property<String> getRunConfigName();
public AbstractRunTask(Function<Project, RunConfig> 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()) {

View File

@@ -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<EclipseRunConfig> getEclipseRunConfigs();
@ApiStatus.Internal
@Nested
@Optional
protected abstract Property<ForgeModClassesService.Options> 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<String> getName();
default void writeLaunchFile() throws IOException {
Path launchFile = getLaunchFile().get().getAsFile().toPath();

View File

@@ -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<ForgeModClassesService.Options> 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<VsCodeConfiguration> 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<JsonElement> 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",

View File

@@ -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());

View File

@@ -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();
}));

View File

@@ -69,10 +69,11 @@ public class ClasspathGroupService extends Service<ClasspathGroupService.Options
}
public static Provider<Options> create(Project project) {
return TYPE.create(project, options -> {
LoomGradleExtension extension = LoomGradleExtension.get(project);
NamedDomainObjectContainer<ModSettings> modSettings = extension.getMods();
return create(project, LoomGradleExtension.get(project).getMods());
}
public static Provider<Options> create(Project project, NamedDomainObjectContainer<ModSettings> modSettings) {
return TYPE.create(project, options -> {
if (modSettings.isEmpty()) {
return;
}
@@ -159,4 +160,8 @@ public class ClasspathGroupService extends Service<ClasspathGroupService.Options
public boolean hasGroups() {
return getOptions().getClasspathGroups().isPresent() && !getOptions().getClasspathGroups().get().isEmpty();
}
public List<ClasspathGroup> getClasspathGroups() {
return getOptions().getClasspathGroups().get();
}
}

View File

@@ -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<Boolean> getBooleanPropertyProvider(Project project, String key) {