-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
847 additions
and
542 deletions.
There are no files selected for viewing
197 changes: 197 additions & 0 deletions
197
src/main/java/net/neoforged/moddevgradle/internal/EclipseIntegration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package net.neoforged.moddevgradle.internal; | ||
|
||
import net.neoforged.elc.configs.GradleLaunchConfig; | ||
import net.neoforged.elc.configs.JavaApplicationLaunchConfig; | ||
import net.neoforged.elc.configs.LaunchConfig; | ||
import net.neoforged.elc.configs.LaunchGroup; | ||
import net.neoforged.moddevgradle.dsl.ModModel; | ||
import net.neoforged.moddevgradle.dsl.RunModel; | ||
import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; | ||
import org.gradle.api.GradleException; | ||
import org.gradle.api.Project; | ||
import org.gradle.api.file.Directory; | ||
import org.gradle.api.file.RegularFile; | ||
import org.gradle.api.provider.Property; | ||
import org.gradle.api.provider.Provider; | ||
import org.gradle.api.provider.SetProperty; | ||
import org.gradle.api.tasks.TaskProvider; | ||
import org.gradle.plugins.ide.eclipse.EclipsePlugin; | ||
import org.gradle.plugins.ide.eclipse.model.Classpath; | ||
import org.gradle.plugins.ide.eclipse.model.EclipseModel; | ||
import org.gradle.plugins.ide.eclipse.model.Library; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.xml.stream.XMLStreamException; | ||
import java.io.File; | ||
import java.io.FileWriter; | ||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
/** | ||
* Provides integration with Eclipse Buildship and VSCode extensions based on it. | ||
*/ | ||
sealed class EclipseIntegration extends IdeIntegration permits VsCodeIntegration { | ||
private static final Logger LOG = LoggerFactory.getLogger(EclipseIntegration.class); | ||
|
||
protected final EclipseModel eclipseModel; | ||
|
||
protected EclipseIntegration(Project project) { | ||
super(project); | ||
this.eclipseModel = getOrCreateEclipseModel(project); | ||
LOG.debug("Configuring Eclipse model for Eclipse project '{}'.", eclipseModel.getProject().getName()); | ||
} | ||
|
||
/** | ||
* Attach a source artifact to a binary artifact if the IDE supports it. | ||
* | ||
* @param jarToSourceJarMapping Maps a classpath location containing classes to their source location. | ||
* Locations are usually JAR files but may be folders. | ||
*/ | ||
@Override | ||
public void attachSources(Map<Provider<RegularFile>, Provider<RegularFile>> jarToSourceJarMapping) { | ||
|
||
var fileClasspath = eclipseModel.getClasspath().getFile(); | ||
fileClasspath.whenMerged((Classpath classpath) -> { | ||
for (var mapping : jarToSourceJarMapping.entrySet()) { | ||
var classesPath = mapping.getKey().get().getAsFile(); | ||
var sourcesPath = mapping.getValue().get().getAsFile(); | ||
|
||
for (var entry : classpath.getEntries()) { | ||
if (entry instanceof Library library && classesPath.equals(new File(library.getPath()))) { | ||
library.setSourcePath(classpath.fileReference(sourcesPath)); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
protected void registerProjectSyncTask(TaskProvider<?> task) { | ||
// Make sure our post-sync task runs on Eclipse | ||
eclipseModel.synchronizationTasks(task); | ||
} | ||
|
||
@Override | ||
public void configureRuns(Map<RunModel, TaskProvider<PrepareRun>> prepareRunTasks, | ||
Iterable<RunModel> runs) { | ||
// Set up runs if running under buildship and in VS Code | ||
project.afterEvaluate(ignored -> { | ||
for (var run : runs) { | ||
var prepareTask = prepareRunTasks.get(run).get(); | ||
addEclipseLaunchConfiguration(project, run, prepareTask); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public void configureTesting(SetProperty<ModModel> loadedMods, | ||
Property<ModModel> testedMod, | ||
Provider<Directory> runArgsDir, | ||
File gameDirectory, | ||
Provider<RegularFile> programArgsFile, | ||
Provider<RegularFile> vmArgsFile) { | ||
// Eclipse has no concept of JUnit run templates. We cannot configure VM args or similar for all JUnit runs. | ||
} | ||
|
||
private static EclipseModel getOrCreateEclipseModel(Project project) { | ||
// Set up stuff for Eclipse | ||
var eclipseModel = ExtensionUtils.findExtension(project, "eclipse", EclipseModel.class); | ||
if (eclipseModel == null) { | ||
project.getPlugins().apply(EclipsePlugin.class); | ||
eclipseModel = ExtensionUtils.findExtension(project, "eclipse", EclipseModel.class); | ||
if (eclipseModel == null) { | ||
throw new GradleException("Even after applying the Eclipse plugin, no 'eclipse' extension was present!"); | ||
} | ||
} | ||
return eclipseModel; | ||
} | ||
|
||
private void addEclipseLaunchConfiguration(Project project, | ||
RunModel run, | ||
PrepareRun prepareTask) { | ||
if (!prepareTask.getEnabled()) { | ||
LOG.info("Not creating Eclipse run {} since its prepare task {} is disabled", run, prepareTask); | ||
return; | ||
} | ||
|
||
// Grab the eclipse model so we can extend it. -> Done on the root project so that the model is available to all subprojects. | ||
// And so that post sync tasks are only run once for all subprojects. | ||
|
||
var runIdeName = run.getIdeName().get(); | ||
var launchConfigName = runIdeName; | ||
var eclipseProjectName = Objects.requireNonNullElse(eclipseModel.getProject().getName(), project.getName()); | ||
|
||
// If the user wants to run tasks before the actual execution, we create a launch group to facilitate that | ||
if (!run.getTasksBefore().isEmpty()) { | ||
// Rename the main launch to "Run " ... | ||
launchConfigName = "Run " + runIdeName; | ||
|
||
// Creates a launch config to run the preparation tasks | ||
var prepareRunConfig = GradleLaunchConfig.builder(eclipseProjectName) | ||
.tasks(run.getTasksBefore().stream().map(task -> task.get().getPath()).toArray(String[]::new)) | ||
.build(); | ||
var prepareRunLaunchName = "Prepare " + runIdeName; | ||
writeEclipseLaunchConfig(project, prepareRunLaunchName, prepareRunConfig); | ||
|
||
// This is the launch group that will first launch Gradle, and then the game | ||
var withGradleTasksConfig = LaunchGroup.builder() | ||
.entry(LaunchGroup.entry(prepareRunLaunchName) | ||
.enabled(true) | ||
.adoptIfRunning(false) | ||
.mode(LaunchGroup.Mode.RUN) | ||
// See https://github.com/eclipse/buildship/issues/1272 | ||
// for why we cannot just wait for termination | ||
.action(LaunchGroup.Action.delay(2))) | ||
.entry(LaunchGroup.entry(launchConfigName) | ||
.enabled(true) | ||
.adoptIfRunning(false) | ||
.mode(LaunchGroup.Mode.INHERIT) | ||
.action(LaunchGroup.Action.none())) | ||
.build(); | ||
writeEclipseLaunchConfig(project, runIdeName, withGradleTasksConfig); | ||
} | ||
|
||
// This is the actual main launch configuration that launches the game | ||
var config = JavaApplicationLaunchConfig.builder(eclipseProjectName) | ||
.vmArgs( | ||
RunUtils.escapeJvmArg(RunUtils.getArgFileParameter(prepareTask.getVmArgsFile().get())), | ||
RunUtils.escapeJvmArg(getModFoldersProvider(project, run.getLoadedMods(), null).getArgument()) | ||
) | ||
.args(RunUtils.escapeJvmArg(RunUtils.getArgFileParameter(prepareTask.getProgramArgsFile().get()))) | ||
.envVar(run.getEnvironment().get()) | ||
.workingDirectory(run.getGameDirectory().get().getAsFile().getAbsolutePath()) | ||
.build(RunUtils.DEV_LAUNCH_MAIN_CLASS); | ||
writeEclipseLaunchConfig(project, launchConfigName, config); | ||
} | ||
|
||
protected static ModFoldersProvider getModFoldersProvider(Project project, | ||
Provider<Set<ModModel>> modsProvider, | ||
@Nullable Provider<ModModel> testedMod) { | ||
var folders = RunUtils.buildModFolders(project, modsProvider, testedMod, (sourceSet, output) -> { | ||
output.from(RunUtils.findSourceSetProject(project, sourceSet).getProjectDir().toPath() | ||
.resolve("bin") | ||
.resolve(sourceSet.getName())); | ||
}); | ||
|
||
var modFoldersProvider = project.getObjects().newInstance(ModFoldersProvider.class); | ||
modFoldersProvider.getModFolders().set(folders); | ||
return modFoldersProvider; | ||
} | ||
|
||
private static void writeEclipseLaunchConfig(Project project, String name, LaunchConfig config) { | ||
var file = project.file(".eclipse/configurations/" + name + ".launch"); | ||
file.getParentFile().mkdirs(); | ||
try (var writer = new FileWriter(file, false)) { | ||
config.write(writer); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException("Failed to write launch file: " + file, e); | ||
} catch (XMLStreamException e) { | ||
throw new RuntimeException("Failed to write launch file: " + file, e); | ||
} | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
src/main/java/net/neoforged/moddevgradle/internal/IdeIntegration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package net.neoforged.moddevgradle.internal; | ||
|
||
import net.neoforged.moddevgradle.dsl.ModModel; | ||
import net.neoforged.moddevgradle.dsl.RunModel; | ||
import net.neoforged.moddevgradle.internal.utils.ExtensionUtils; | ||
import net.neoforged.moddevgradle.internal.utils.IdeDetection; | ||
import org.gradle.api.Project; | ||
import org.gradle.api.Task; | ||
import org.gradle.api.file.Directory; | ||
import org.gradle.api.file.RegularFile; | ||
import org.gradle.api.provider.Property; | ||
import org.gradle.api.provider.Provider; | ||
import org.gradle.api.provider.SetProperty; | ||
import org.gradle.api.tasks.TaskProvider; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.File; | ||
import java.util.Map; | ||
|
||
sealed abstract class IdeIntegration permits IntelliJIntegration, EclipseIntegration, NoIdeIntegration { | ||
private static final Logger LOG = LoggerFactory.getLogger(IdeIntegration.class); | ||
|
||
/** | ||
* A task we attach other tasks to that should run when the IDE reloads the projects. | ||
*/ | ||
private final TaskProvider<Task> ideSyncTask; | ||
|
||
protected final Project project; | ||
|
||
public IdeIntegration(Project project) { | ||
this.project = project; | ||
this.ideSyncTask = project.getTasks().register("neoForgeIdeSync", task -> { | ||
task.setGroup(ModDevPlugin.INTERNAL_TASK_GROUP); | ||
task.setDescription("A utility task that is used to create necessary files when the Gradle project is synchronized with the IDE project."); | ||
}); | ||
this.registerProjectSyncTask(ideSyncTask); | ||
} | ||
|
||
public static IdeIntegration of(Project project) { | ||
var ideIntegration = ExtensionUtils.findExtension(project, "mdgInternalIdeIntegration", IdeIntegration.class); | ||
if (ideIntegration == null) { | ||
ideIntegration = createForProject(project); | ||
project.getExtensions().add(IdeIntegration.class, "mdgInternalIdeIntegration", ideIntegration); | ||
} | ||
return ideIntegration; | ||
} | ||
|
||
private static IdeIntegration createForProject(Project project) { | ||
if (IdeDetection.isVsCode()) { | ||
// VSCode internally uses Eclipse and as such, we need to prioritize it over the pure Eclipse integration | ||
LOG.debug("Activating VSCode integration for project {}.", project.getPath()); | ||
return new VsCodeIntegration(project); | ||
} else if (IdeDetection.isEclipse()) { | ||
LOG.debug("Activating Eclipse integration for project {}.", project.getPath()); | ||
return new EclipseIntegration(project); | ||
} else if (IdeDetection.isIntelliJSync()) { | ||
LOG.debug("Activating IntelliJ integration for project {}.", project.getPath()); | ||
return new IntelliJIntegration(project); | ||
} else { | ||
return new NoIdeIntegration(project); | ||
} | ||
} | ||
|
||
/** | ||
* Attach a source artifact to a binary artifact if the IDE supports it. | ||
* | ||
* @param jarToSourceJarMapping Maps a classpath location containing classes to their source location. | ||
* Locations are usually JAR files but may be folders. | ||
* @see #shouldUseCombinedSourcesAndClassesArtifact() This method will not work if the IDE doesn't support attaching sources. | ||
*/ | ||
void attachSources(Map<Provider<RegularFile>, Provider<RegularFile>> jarToSourceJarMapping) { | ||
} | ||
|
||
/** | ||
* Only IntelliJ needs the combined artifact. | ||
* We also use this model when Gradle tasks are being run from IntelliJ, not only if IntelliJ is reloading | ||
* the project. | ||
* This prevents the classpath during debugging from the classpath detected by IntelliJ itself. | ||
* For Eclipse, we can attach the sources via the Eclipse project model. | ||
*/ | ||
boolean shouldUseCombinedSourcesAndClassesArtifact() { | ||
return false; | ||
} | ||
|
||
/** | ||
* Registers a task to be run when the IDE reloads the Gradle project. | ||
*/ | ||
public final void runTaskOnProjectSync(TaskProvider<?> task) { | ||
ideSyncTask.configure(ideSyncTask -> ideSyncTask.dependsOn(task)); | ||
} | ||
|
||
/** | ||
* To be implemented by specific IDE integrations to register a task to be run on reload with the IDE. | ||
*/ | ||
protected abstract void registerProjectSyncTask(TaskProvider<?> task); | ||
|
||
void configureRuns(Map<RunModel, TaskProvider<PrepareRun>> prepareRunTasks, Iterable<RunModel> runs) { | ||
} | ||
|
||
void configureTesting(SetProperty<ModModel> loadedMods, | ||
Property<ModModel> testedMod, | ||
Provider<Directory> runArgsDir, | ||
File gameDirectory, | ||
Provider<RegularFile> programArgsFile, | ||
Provider<RegularFile> vmArgsFile) { | ||
} | ||
|
||
} |
Oops, something went wrong.