Configuration cache support for IDE tasks (#1163)

* Configuration cache support for IDE tasks

* Fix some tests
This commit is contained in:
modmuss
2024-08-31 10:13:07 +01:00
committed by GitHub
parent f3a9a49c6f
commit 543e49dfe0
10 changed files with 139 additions and 286 deletions

View File

@@ -39,7 +39,6 @@ import net.fabricmc.loom.configuration.CompileConfiguration;
import net.fabricmc.loom.configuration.FabricApiExtension;
import net.fabricmc.loom.configuration.LoomConfigurations;
import net.fabricmc.loom.configuration.MavenPublication;
import net.fabricmc.loom.configuration.ide.IdeConfiguration;
import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration;
import net.fabricmc.loom.configuration.sandbox.SandboxConfiguration;
import net.fabricmc.loom.decompilers.DecompilerConfiguration;
@@ -64,7 +63,6 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
LoomTasks.class,
DecompilerConfiguration.class,
IdeaConfiguration.class,
IdeConfiguration.class,
SandboxConfiguration.class
);
@@ -84,7 +82,6 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
// Apply default plugins
project.apply(ImmutableMap.of("plugin", "java-library"));
project.apply(ImmutableMap.of("plugin", "eclipse"));
project.apply(ImmutableMap.of("plugin", "idea"));
// Setup extensions
project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project));

View File

@@ -131,9 +131,7 @@ public abstract class CompileConfiguration implements Runnable {
configureDecompileTasks(configContext);
});
finalizedBy("idea", "genIdeaWorkspace");
finalizedBy("eclipse", "genEclipseRuns");
finalizedBy("cleanEclipse", "cleanEclipseRuns");
// Add the "dev" jar to the "namedElements" configuration
getProject().artifacts(artifactHandler -> artifactHandler.add(Configurations.NAMED_ELEMENTS, getTasks().named("jar")));

View File

@@ -1,45 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2020 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.ide;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.plugins.ide.idea.model.IdeaModel;
public abstract class IdeConfiguration implements Runnable {
@Inject
protected abstract Project getProject();
@Override
public void run() {
IdeaModel ideaModel = (IdeaModel) getProject().getExtensions().getByName("idea");
ideaModel.getModule().getExcludeDirs().addAll(getProject().files(".gradle", "build", ".idea", "out").getFiles());
ideaModel.getModule().setDownloadJavadoc(true);
ideaModel.getModule().setDownloadSources(true);
ideaModel.getModule().setInheritOutputDirs(true);
}
}

View File

@@ -1,48 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2020 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
import org.gradle.api.tasks.TaskAction;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
public class CleanEclipseRunsTask extends AbstractLoomTask {
@TaskAction
public void cleanRuns() throws IOException {
EclipseModel eclipseModel = getProject().getExtensions().getByType(EclipseModel.class);
File clientRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_client.launch");
File serverRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_server.launch");
if (clientRunConfigs.exists()) {
clientRunConfigs.delete();
}
if (serverRunConfigs.exists()) {
serverRunConfigs.delete();
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
* Copyright (c) 2016-2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,9 +26,22 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import javax.inject.Inject;
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.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
@@ -36,28 +49,68 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
public class GenEclipseRunsTask extends AbstractLoomTask {
public abstract class GenEclipseRunsTask extends AbstractLoomTask {
@Nested
protected abstract ListProperty<EclipseRunConfig> getEclipseRunConfigs();
@Inject
public GenEclipseRunsTask() {
getEclipseRunConfigs().set(getProject().provider(() -> getRunConfigs(getProject())));
}
@TaskAction
public void genRuns() throws IOException {
EclipseModel eclipseModel = getProject().getExtensions().getByType(EclipseModel.class);
LoomGradleExtension extension = getExtension();
for (EclipseRunConfig runConfig : getEclipseRunConfigs().get()) {
runConfig.writeLaunchFile();
}
}
private static List<EclipseRunConfig> getRunConfigs(Project project) {
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
LoomGradleExtension extension = LoomGradleExtension.get(project);
List<EclipseRunConfig> runConfigs = new ArrayList<>();
for (RunConfigSettings settings : extension.getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
String name = settings.getName();
final String name = settings.getName();
final File configs = new File(project.getProjectDir(), eclipseModel.getProject().getName() + "_" + name + ".launch");
final RunConfig configInst = RunConfig.runConfig(project, settings);
final String config;
File configs = new File(getProject().getProjectDir(), eclipseModel.getProject().getName() + "_" + name + ".launch");
RunConfig configInst = RunConfig.runConfig(getProject(), settings);
String config = configInst.fromDummy("eclipse_run_config_template.xml", false, getProject());
if (!configs.exists()) {
FileUtils.writeStringToFile(configs, config, StandardCharsets.UTF_8);
try {
config = configInst.fromDummy("eclipse_run_config_template.xml", false, project);
} catch (IOException e) {
throw new UncheckedIOException("Failed to generate Eclipse run configuration", e);
}
EclipseRunConfig eclipseRunConfig = project.getObjects().newInstance(EclipseRunConfig.class);
eclipseRunConfig.getLaunchContent().set(config);
eclipseRunConfig.getLaunchFile().set(project.file(configs));
runConfigs.add(eclipseRunConfig);
settings.makeRunDir();
}
return runConfigs;
}
public interface EclipseRunConfig {
@Input
Property<String> getLaunchContent();
@OutputFile
RegularFileProperty getLaunchFile();
default void writeLaunchFile() throws IOException {
Path launchFile = getLaunchFile().get().getAsFile().toPath();
if (Files.notExists(launchFile)) {
Files.writeString(launchFile, getLaunchContent().get(), StandardCharsets.UTF_8);
}
}
}
}

View File

@@ -1,103 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2021 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
public class GenIdeaProjectTask extends AbstractLoomTask {
@TaskAction
public void genIdeaRuns() throws IOException, ParserConfigurationException, SAXException, TransformerException {
Project project = this.getProject();
LoomGradleExtension extension = getExtension();
// Only generate the idea runs on the root project
if (!extension.isRootProject()) {
return;
}
project.getLogger().lifecycle(":Building idea workspace");
File file = project.file(project.getName() + ".iws");
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(file);
NodeList list = doc.getElementsByTagName("component");
Element runManager = null;
for (int i = 0; i < list.getLength(); i++) {
Element element = (Element) list.item(i);
if (element.getAttribute("name").equals("RunManager")) {
runManager = element;
break;
}
}
if (runManager == null) {
throw new RuntimeException("Failed to generate IntelliJ run configurations (runManager was not found)");
}
for (RunConfigSettings settings : getExtension().getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
runManager.appendChild(RunConfig.runConfig(project, settings).genRuns(runManager));
settings.makeRunDir();
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(file);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(source, result);
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2018-2021 FabricMC
* Copyright (c) 2018-2024 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -25,9 +25,11 @@
package net.fabricmc.loom.task;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@@ -39,8 +41,13 @@ import javax.inject.Inject;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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.OutputFile;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.LoomGradlePlugin;
@@ -55,20 +62,43 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
@ServiceReference(SyncTaskBuildService.NAME)
abstract Property<SyncTaskBuildService> getSyncTask();
@Input
protected abstract ListProperty<VsCodeConfiguration> getLaunchConfigurations();
@OutputFile
protected abstract RegularFileProperty getLaunchJson();
@Inject
public GenVsCodeProjectTask() {
getLaunchConfigurations().set(getProject().provider(this::getConfigurations));
getLaunchJson().convention(getProject().getRootProject().getLayout().getProjectDirectory().file("vscode/launch.json"));
notCompatibleWithConfigurationCache("Not yet supported");
}
private List<VsCodeConfiguration> getConfigurations() {
List<VsCodeConfiguration> configurations = new ArrayList<>();
for (RunConfigSettings settings : getExtension().getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
final VsCodeConfiguration configuration = VsCodeConfiguration.fromRunConfig(getProject(), RunConfig.runConfig(getProject(), settings));
configurations.add(configuration);
}
return configurations;
}
@TaskAction
public void genRuns() throws IOException {
final Path projectDir = getProject().getRootDir().toPath().resolve(".vscode");
final Path launchJson = getLaunchJson().get().getAsFile().toPath();
if (Files.notExists(projectDir)) {
Files.createDirectories(projectDir);
if (Files.notExists(launchJson.getParent())) {
Files.createDirectories(launchJson.getParent());
}
final Path launchJson = projectDir.resolve("launch.json");
final JsonObject root;
if (Files.exists(launchJson)) {
@@ -87,12 +117,7 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
root.add("configurations", configurations);
}
for (RunConfigSettings settings : getExtension().getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
final VsCodeConfiguration configuration = new VsCodeConfiguration(RunConfig.runConfig(getProject(), settings));
for (VsCodeConfiguration configuration : getLaunchConfigurations().get()) {
final JsonElement configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration);
final List<JsonElement> toRemove = new LinkedList<>();
@@ -113,54 +138,43 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask {
}
toRemove.forEach(configurations::remove);
configurations.add(configurationJson);
settings.makeRunDir();
Files.createDirectories(Paths.get(configuration.runDir));
}
final String json = LoomGradlePlugin.GSON.toJson(root);
Files.writeString(launchJson, json, StandardCharsets.UTF_8);
}
private class VsCodeLaunch {
public String version = "0.2.0";
public List<VsCodeConfiguration> configurations = new ArrayList<>();
public void add(RunConfig runConfig) {
configurations.add(new VsCodeConfiguration(runConfig));
}
}
@SuppressWarnings("unused")
private class VsCodeConfiguration {
public String type = "java";
public String name;
public String request = "launch";
public String cwd;
public String console = "integratedTerminal";
public boolean stopOnEntry = false;
public String mainClass;
public String vmArgs;
public String args;
public Map<String, Object> env;
public String projectName;
VsCodeConfiguration(RunConfig runConfig) {
this.name = runConfig.configName;
this.mainClass = runConfig.mainClass;
this.vmArgs = RunConfig.joinArguments(runConfig.vmArgs);
this.args = RunConfig.joinArguments(runConfig.programArgs);
this.cwd = "${workspaceFolder}/" + runConfig.runDir;
this.env = new HashMap<>(runConfig.environmentVariables);
this.projectName = runConfig.projectName;
if (getProject().getRootProject() != getProject()) {
Path rootPath = getProject().getRootDir().toPath();
Path projectPath = getProject().getProjectDir().toPath();
String relativePath = rootPath.relativize(projectPath).toString();
this.cwd = "${workspaceFolder}/%s/%s".formatted(relativePath, runConfig.runDir);
}
public record VsCodeConfiguration(
String type,
String name,
String request,
String cwd,
String console,
boolean stopOnEntry,
String mainClass,
String vmArgs,
String args,
Map<String, Object> env,
String projectName,
String runDir) implements Serializable {
public static VsCodeConfiguration fromRunConfig(Project project, RunConfig runConfig) {
return new VsCodeConfiguration(
"java",
runConfig.configName,
"launch",
"${workspaceFolder}/" + runConfig.runDir,
"integratedTerminal",
false,
runConfig.mainClass,
RunConfig.joinArguments(runConfig.vmArgs),
RunConfig.joinArguments(runConfig.programArgs),
new HashMap<>(runConfig.environmentVariables),
runConfig.projectName,
project.getProjectDir().toPath().resolve(runConfig.runDir).toAbsolutePath().toString()
);
}
}
}

View File

@@ -67,8 +67,7 @@ public abstract class LoomTasks implements Runnable {
t.setDescription("Generate the DevLaunchInjector config file");
// Must allow these IDE files to be generated first
t.mustRunAfter(getTasks().named("eclipse"));
t.mustRunAfter(getTasks().named("idea"));
t.mustRunAfter("eclipse");
t.dependsOn(generateLog4jConfig);
t.getRemapClasspathFile().set(generateRemapClasspath.get().getRemapClasspathFile());
@@ -114,23 +113,12 @@ public abstract class LoomTasks implements Runnable {
}
private void registerIDETasks() {
getTasks().register("genIdeaWorkspace", GenIdeaProjectTask.class, t -> {
t.setDescription("Generates an IntelliJ IDEA workspace from this project.");
t.dependsOn("idea", getIDELaunchConfigureTaskName(getProject()));
t.setGroup(Constants.TaskGroup.IDE);
});
getTasks().register("genEclipseRuns", GenEclipseRunsTask.class, t -> {
t.setDescription("Generates Eclipse run configurations for this project.");
t.dependsOn(getIDELaunchConfigureTaskName(getProject()));
t.setGroup(Constants.TaskGroup.IDE);
});
getTasks().register("cleanEclipseRuns", CleanEclipseRunsTask.class, t -> {
t.setDescription("Removes Eclipse run configurations for this project.");
t.setGroup(Constants.TaskGroup.IDE);
});
getTasks().register("vscode", GenVsCodeProjectTask.class, t -> {
t.setDescription("Generates VSCode launch configurations.");
t.dependsOn(getIDELaunchConfigureTaskName(getProject()));

View File

@@ -41,10 +41,9 @@ class MultiProjectTest extends Specification implements GradleProjectTestTrait {
when:
def result = gradle.run(tasks: [
"build",
"eclipse",
"genEclipseRuns",
"vscode",
"idea"
], configurationCache: false)
])
then:
result.task(":build").outcome == SUCCESS

View File

@@ -72,14 +72,14 @@ class SimpleProjectTest extends Specification implements GradleProjectTestTrait
setup:
def gradle = gradleProject(project: "simple", sharedFiles: true)
when:
def result = gradle.run(task: ide, configurationCache: false)
def result = gradle.run(task: ide)
then:
result.task(":${ide}").outcome == SUCCESS
where:
ide | _
'idea' | _
'eclipse' | _
'vscode' | _
ide | _
'ideaSyncTask' | _
'genEclipseRuns' | _
'vscode' | _
}
@Unroll