From 15afb457690f76f6578c09382256391c6842a9ca Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 29 Dec 2021 23:52:13 +0000 Subject: [PATCH] Exclude client only libraries in the IntelliJ server run config. --- .../configuration/ide/idea/IdeaSyncTask.java | 82 +++++++++++++- .../IdeaClasspathModificationsTest.groovy | 104 ++++++++++++++++++ 2 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/IdeaClasspathModificationsTest.groovy 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 58a4224..ead3874 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 @@ -26,17 +26,33 @@ package net.fabricmc.loom.configuration.ide.idea; import java.io.File; import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.inject.Inject; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import org.apache.commons.io.FileUtils; import org.gradle.api.Project; import org.gradle.api.tasks.TaskAction; +import org.jetbrains.annotations.VisibleForTesting; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; @@ -95,7 +111,7 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { if (settings.getEnvironment().equals("server") && !excludedServerLibraries.isEmpty()) { try { - setClasspathModifications(runConfigs, excludedServerLibraries); + setClasspathModifications(runConfigs.toPath(), excludedServerLibraries); } catch (Exception e) { getProject().getLogger().error("Failed to modify run configuration xml", e); } @@ -124,7 +140,67 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { return clientOnlyLibraries; } - private void setClasspathModifications(File runConfig, List exclusions) throws Exception { - // TODO modify the xml + private void setClasspathModifications(Path runConfig, List exclusions) throws IOException { + if (!IdeaUtils.supportsCustomizableClasspath()) { + return; + } + + final String inputXml = Files.readString(runConfig, StandardCharsets.UTF_8); + final String outputXml; + + try { + outputXml = setClasspathModificationsInXml(inputXml, exclusions); + } catch (Exception e) { + getLogger().error("Failed to modify idea xml", e); + + return; + } + + 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(); + final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + final Document document = documentBuilder.parse(new InputSource(new StringReader(input))); + final Element root = document.getDocumentElement(); + + final NodeList nodeList = root.getElementsByTagName("configuration"); + assert nodeList.getLength() == 1; + + final Element configuration = (Element) nodeList.item(0); + final NodeList classpathModificationsList = configuration.getElementsByTagName("classpathModifications"); + + // Remove all the existing exclusions + for (int i = 0; i < classpathModificationsList.getLength(); i++) { + configuration.removeChild(classpathModificationsList.item(i)); + } + + final Element classpathModifications = document.createElement("classpathModifications"); + + for (String exclusionPath : exclusions) { + final Element exclusion = document.createElement("entry"); + + exclusion.setAttribute("exclude", "true"); + exclusion.setAttribute("path", exclusionPath); + + classpathModifications.appendChild(exclusion); + } + + configuration.appendChild(classpathModifications); + + final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + final Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + + final DOMSource source = new DOMSource(document); + + final StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + + return writer.toString().replace("\r", ""); } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/IdeaClasspathModificationsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/IdeaClasspathModificationsTest.groovy new file mode 100644 index 0000000..3167755 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/IdeaClasspathModificationsTest.groovy @@ -0,0 +1,104 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 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.test.unit + +import net.fabricmc.loom.configuration.ide.RunConfig +import net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask +import org.intellij.lang.annotations.Language +import spock.lang.Specification + +import java.nio.charset.StandardCharsets + +class IdeaClasspathModificationsTest extends Specification { + + def "configure exclusions"() { + when: + def input = fromDummy() + def output = IdeaSyncTask.setClasspathModificationsInXml(input, ["/path/to/file.jar"]) + + then: + output == EXPECTED + } + + def "re-configure exclusions"() { + when: + def input = fromDummy() + def output = IdeaSyncTask.setClasspathModificationsInXml(input, ["/path/to/file.jar"]) + output = IdeaSyncTask.setClasspathModificationsInXml(output, ["/path/to/file.jar", "/path/to/another.jar"]) + + then: + output == EXPECTED2 + } + + private String fromDummy() { + String dummyConfig + + IdeaSyncTask.class.getClassLoader().getResourceAsStream("idea_run_config_template.xml").withCloseable { + dummyConfig = new String(it.readAllBytes(), StandardCharsets.UTF_8) + } + + dummyConfig = dummyConfig.replace("%NAME%", "Minecraft Client") + dummyConfig = dummyConfig.replace("%MAIN_CLASS%", "net.minecraft.client.Main") + dummyConfig = dummyConfig.replace("%IDEA_MODULE%", "main.test") + dummyConfig = dummyConfig.replace("%RUN_DIRECTORY%", ".run") + dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", RunConfig.joinArguments([]).replaceAll("\"", """)) + dummyConfig = dummyConfig.replace("%VM_ARGS%", RunConfig.joinArguments([]).replaceAll("\"", """)) + + return dummyConfig + } + + @Language("XML") + private static final String EXPECTED = ''' + + + + +'''.trim() + + @Language("XML") + private static final String EXPECTED2 = ''' + + + + +'''.trim() + +}