diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java index cc78cea6..2153b225 100644 --- a/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java +++ b/src/main/java/net/neoforged/moddevgradle/dsl/NeoForgeExtension.java @@ -14,9 +14,6 @@ import javax.inject.Inject; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; /** * This is the top-level {@code neoForge} extension, used to configure the moddev plugin. @@ -33,6 +30,7 @@ public abstract class NeoForgeExtension { private final DataFileCollection accessTransformers; private final DataFileCollection interfaceInjectionData; + private final ListProperty sourceSetsWithDependency; @Inject public NeoForgeExtension(Project project) { @@ -46,6 +44,8 @@ public NeoForgeExtension(Project project) { accessTransformers = project.getObjects().newInstance(DataFileCollection.class); interfaceInjectionData = project.getObjects().newInstance(DataFileCollection.class); + sourceSetsWithDependency = project.getObjects().listProperty(SourceSet.class); + getAccessTransformers().getFiles().convention(project.provider(() -> { var collection = project.getObjects().fileCollection(); @@ -78,6 +78,12 @@ public void addModdingDependenciesTo(SourceSet sourceSet) { .extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_RUNTIME_DEPENDENCIES)); configurations.getByName(sourceSet.getCompileClasspathConfigurationName()) .extendsFrom(configurations.getByName(ModDevPlugin.CONFIGURATION_COMPILE_DEPENDENCIES)); + + this.sourceSetsWithDependency.add(sourceSet); + } + + public ListProperty getSourceSetsWithDependency() { + return sourceSetsWithDependency; } /** diff --git a/src/main/java/net/neoforged/moddevgradle/internal/AttachIntelliJArtifactsTask.java b/src/main/java/net/neoforged/moddevgradle/internal/AttachIntelliJArtifactsTask.java new file mode 100644 index 00000000..e7ce49f3 --- /dev/null +++ b/src/main/java/net/neoforged/moddevgradle/internal/AttachIntelliJArtifactsTask.java @@ -0,0 +1,148 @@ +package net.neoforged.moddevgradle.internal; + +import com.google.gson.Gson; +import groovy.namespace.QName; +import groovy.util.Node; +import groovy.util.NodeList; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.xml.XmlTransformer; +import org.jetbrains.gradle.ext.IdeaLayoutJson; + +import javax.inject.Inject; +import java.io.File; +import java.nio.file.Files; +import java.util.Map; +import java.util.Objects; + +abstract class AttachIntelliJArtifactsTask extends DefaultTask { + @Inject + public AttachIntelliJArtifactsTask() {} + + @Optional + @InputFile + public abstract RegularFileProperty getLayoutFile(); + + @Nested + public abstract ListProperty getEntries(); + + @TaskAction + public void exec() throws Exception { + var model = new Gson().fromJson(Files.readString(getLayoutFile().get().getAsFile().toPath()), IdeaLayoutJson.class); + + for (var entry : getEntries().get()) { + var classesLocation = entry.getClassesLocation().get().getAsFile(); + var sourcesLocation = entry.getSourcesLocation().get().getAsFile(); + + for (String srcSet : entry.getSourceSets().get()) { + var location = model.modulesMap.get(srcSet); + if (location != null) { + var file = new File(location); + if (file.exists()) { + var trans = new XmlTransformer(); + trans.addAction(xml -> { + System.out.println("hi!!!"); + var node = xml.asNode(); + var moduleRoot = (Node) node.getAt(QName.valueOf("component")).stream() + .filter(p -> Objects.equals(((Node) p).get("@name"), "NewModuleRootManager")) + .findFirst() + .orElse(null); + if (moduleRoot != null) { + var entries = moduleRoot.getAt(QName.valueOf("orderEntry")); + for (var e : entries) { + if (e instanceof Node enode && Objects.equals(enode.get("@type"), "module-library")) { + var lib = getNode(enode, "library"); + var classes = getNode(lib, "CLASSES"); + if (classes != null) { + var root = getNode(classes, "root"); + if (root != null) { + var url = root.get("@url"); + if (url != null) { + System.out.println(url + " vs " + classesLocation.getName()); + if (url.toString().endsWith(classesLocation.getName() + "!/")) { + var sources = getNode(lib, "SOURCES"); + if (sources != null) { + root = getNode(sources, "root"); + if (root != null) { + url = root.get("@url"); + if (url.toString().endsWith(sourcesLocation.getName() + "!/")) { + System.out.println("ok fine: " + sources); + return; + } + } + } else { + sources = lib.appendNode("SOURCES"); + } + sources.appendNode("root", Map.of("url", "jar://" + sourcesLocation.getAbsolutePath() + "!/")); + System.out.println("appending"); + return; + } + } + } + } + } + } + + var lib = moduleRoot.appendNode("orderEntry", Map.of("type", "module-library")) + .appendNode("library"); + lib.appendNode("CLASSES").appendNode("root", Map.of("url", "jar://" + classesLocation.getAbsolutePath() + "!/")); + lib.appendNode("SOURCES").appendNode("root", Map.of("url", "jar://" + sourcesLocation.getAbsolutePath() + "!/")); + System.out.println("appended"); + return; + } + + var lib = node.appendNode("component", Map.of("name", "NewModuleRootManager")).appendNode("orderEntry", Map.of("type", "module-library")) + .appendNode("library"); + lib.appendNode("CLASSES").appendNode("root", Map.of("url", "jar://" + classesLocation.getAbsolutePath() + "!/")); + lib.appendNode("SOURCES").appendNode("root", Map.of("url", "jar://" + sourcesLocation.getAbsolutePath() + "!/")); + }); + var text = Files.readString(file.toPath()); + try (var out = Files.newOutputStream(file.toPath())) { + trans.transform(text, out); + } + } + } + } + } + + Files.delete(getLayoutFile().get().getAsFile().toPath()); + } + + private groovy.util.Node getNode(groovy.util.Node node, String name) { + var list = (NodeList) node.get(name); + return list.isEmpty() ? null : (Node) list.get(0); + } + + static class Entry { + private final ListProperty sourceSets; + private final RegularFileProperty classes, sources; + + public Entry(ObjectFactory factory) { + this.sourceSets = factory.listProperty(String.class); + this.classes = factory.fileProperty(); + this.sources = factory.fileProperty(); + } + + @Input + public ListProperty getSourceSets() { + return sourceSets; + } + + @InputFile + public RegularFileProperty getClassesLocation() { + return classes; + } + + @InputFile + public RegularFileProperty getSourcesLocation() { + return sources; + } + } +} diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java index ebc5fb33..e0023128 100644 --- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java +++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java @@ -55,6 +55,7 @@ import org.jetbrains.gradle.ext.BeforeRunTask; import org.jetbrains.gradle.ext.IdeaExtPlugin; import org.jetbrains.gradle.ext.JUnit; +import org.jetbrains.gradle.ext.LayoutFileBuildService; import org.jetbrains.gradle.ext.ProjectSettings; import org.jetbrains.gradle.ext.RunConfigurationContainer; import org.slf4j.event.Level; @@ -443,7 +444,7 @@ public void apply(Project project) { minecraftClassesArtifact ); - configureIntelliJModel(project, ideSyncTask, extension, prepareRunTasks); + configureIntelliJModel(project, ideSyncTask, extension, prepareRunTasks, createArtifacts); configureEclipseModel(project, ideSyncTask, createArtifacts, extension, prepareRunTasks); } @@ -553,7 +554,7 @@ private List configureArtifactManifestConfigurations(Project proj private static boolean shouldUseCombinedSourcesAndClassesArtifact() { // Only IntelliJ needs the combined artifact // For Eclipse, we can attach the sources via the Eclipse project model. - return IdeDetection.isIntelliJ(); + return false; } public void setupTesting() { @@ -818,19 +819,47 @@ class GradleTasks extends BeforeRunTask { runConfigurations.add(appRun); } - private static void configureIntelliJModel(Project project, TaskProvider ideSyncTask, NeoForgeExtension extension, Map> prepareRunTasks) { + private static void configureIntelliJModel(Project project, TaskProvider ideSyncTask, NeoForgeExtension extension, Map> prepareRunTasks, TaskProvider createArtifacts) { var rootProject = project.getRootProject(); + var startParameter = project.getGradle().getStartParameter(); + if (startParameter.getTaskNames().contains("processIdeaSettings")) { + var attachArtifactsTask = rootProject.getTasks().register("attachIntellijArtifacts", AttachIntelliJArtifactsTask.class, task -> { + task.getLayoutFile().set(rootProject.getLayout().file(rootProject.getGradle().getSharedServices().getRegistrations().named("layoutFile") + .flatMap(s -> ((LayoutFileBuildService.Params)s.getParameters()).getLayoutFile()))); + }); + + var taskRequests = new ArrayList<>(startParameter.getTaskRequests()); + + taskRequests.removeIf(te -> te.getArgs().contains("processIdeaSettings")); + taskRequests.add(new DefaultTaskExecutionRequest(List.of(attachArtifactsTask.getName()))); + startParameter.setTaskRequests(taskRequests); + } + if (!rootProject.getPlugins().hasPlugin(IdeaExtPlugin.class)) { rootProject.getPlugins().apply(IdeaExtPlugin.class); } + rootProject.getTasks().withType(AttachIntelliJArtifactsTask.class) + .configureEach(task -> { + var entry = new AttachIntelliJArtifactsTask.Entry(project.getObjects()); + entry.getSourcesLocation().set(createArtifacts.flatMap(CreateMinecraftArtifactsTask::getSourcesArtifact)); + entry.getClassesLocation().set(createArtifacts.flatMap(shouldUseCombinedSourcesAndClassesArtifact() ? CreateMinecraftArtifactsTask::getCompiledWithSourcesArtifact : CreateMinecraftArtifactsTask::getCompiledArtifact)); + var path = project.getPath().equals(":") ? project.getName() : project.getPath(); + entry.getSourceSets().set(extension.getSourceSetsWithDependency() + .map(l -> l.stream().map(s -> path + ":" + s.getName()).toList())); + task.getEntries().add(entry); + }); + // IDEA Sync has no real notion of tasks or providers or similar project.afterEvaluate(ignored -> { var settings = getIntelliJProjectSettings(rootProject); + // Force IJ into thinking we need the layout file to do things + settings.withIDEADir(dir -> {}); + settings.setGenerateImlFiles(true); + if (settings != null && IdeDetection.isIntelliJSync()) { // Also run the sync task directly as part of the sync. (Thanks Loom). - var startParameter = project.getGradle().getStartParameter(); var taskRequests = new ArrayList<>(startParameter.getTaskRequests()); taskRequests.add(new DefaultTaskExecutionRequest(List.of(ideSyncTask.getName())));