net.minecraftforge.gradle.common.util.runs.RunConfigGenerator Maven / Gradle / Ivy
/*
* ForgeGradle
* Copyright (C) 2018 Forge Development LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package net.minecraftforge.gradle.common.util.runs;
import com.google.common.base.Suppliers;
import net.minecraftforge.gradle.common.util.MinecraftExtension;
import net.minecraftforge.gradle.common.util.RunConfig;
import net.minecraftforge.gradle.common.util.Utils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
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.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
public abstract class RunConfigGenerator
{
public abstract void createRunConfiguration(@Nonnull final MinecraftExtension minecraft, @Nonnull final File runConfigurationsDir, @Nonnull final Project project, List additionalClientArgs);
public static void createIDEGenRunsTasks(@Nonnull final MinecraftExtension minecraft, @Nonnull final TaskProvider prepareRuns, @Nonnull final TaskProvider makeSourceDirs, List additionalClientArgs) {
final Project project = minecraft.getProject();
final Map, File, Supplier>> ideConfigurationGenerators = ImmutableMap., File, Supplier>>builder()
.put("genIntellijRuns", ImmutableTriple.of(Collections.singletonList(prepareRuns.get()),
new File(project.getRootProject().getRootDir(), ".idea/runConfigurations"),
() -> new IntellijRunGenerator(project.getRootProject())))
.put("genEclipseRuns", ImmutableTriple.of(ImmutableList.of(prepareRuns.get(), makeSourceDirs.get()),
project.getProjectDir(),
EclipseRunGenerator::new))
.put("genVSCodeRuns", ImmutableTriple.of(ImmutableList.of(prepareRuns.get(), makeSourceDirs.get()),
new File(project.getProjectDir(), ".vscode"),
VSCodeRunGenerator::new))
.build();
ideConfigurationGenerators.forEach((taskName, configurationGenerator) -> {
project.getTasks().register(taskName, Task.class, task -> {
task.setGroup(RunConfig.RUNS_GROUP);
task.dependsOn(configurationGenerator.getLeft());
task.doLast(t -> {
final File runConfigurationsDir = configurationGenerator.getMiddle();
if (!runConfigurationsDir.exists()) {
runConfigurationsDir.mkdirs();
}
configurationGenerator.getRight().get().createRunConfiguration(minecraft, runConfigurationsDir, project, additionalClientArgs);
});
});
});
}
protected static void elementOption(@Nonnull Document document, @Nonnull final Element parent, @Nonnull final String name, @Nonnull final String value) {
final Element option = document.createElement("option");
{
option.setAttribute("name", name);
option.setAttribute("value", value);
}
parent.appendChild(option);
}
protected static void elementAttribute(@Nonnull Document document, @Nonnull final Element parent, @Nonnull final String attributeType, @Nonnull final String key, @Nonnull final String value) {
final Element attribute = document.createElement(attributeType + "Attribute");
{
attribute.setAttribute("key", key);
attribute.setAttribute("value", value);
}
parent.appendChild(attribute);
}
protected static String replaceRootDirBy(@Nonnull final Project project, String value, @Nonnull final String replacement) {
if (value == null || value.isEmpty()) {
return value;
}
return value.replace(project.getRootDir().toString(), replacement);
}
protected static Stream mapModClassesToGradle(Project project, RunConfig runConfig)
{
if (runConfig.getMods().isEmpty()) {
List sources = runConfig.getAllSources();
return Stream.concat(
sources.stream().map(source -> source.getOutput().getResourcesDir()),
sources.stream().map(source -> source.getOutput().getClassesDirs().getFiles()).flatMap(Collection::stream)
).map(File::getAbsolutePath);
} else {
final SourceSet main = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
return runConfig.getMods().stream()
.map(modConfig -> {
return (modConfig.getSources().isEmpty() ? Stream.of(main) : modConfig.getSources().stream())
.flatMap(source -> Streams
.concat(Stream.of(source.getOutput().getResourcesDir()),
source.getOutput().getClassesDirs().getFiles().stream()))
.map(File::getAbsolutePath)
.distinct()
.map(s -> modConfig.getName() + "%%" + s)
.collect(Collectors.joining(File.pathSeparator)); // :
});
}
}
/**
* @deprecated Use {@link #configureTokensLazy(Project, RunConfig, Stream)}
*/
@Deprecated
// TODO: remove this when we can break compat
protected static Map configureTokens(final Project project, @Nonnull RunConfig runConfig, Stream modClasses) {
project.getLogger().warn("WARNING: RunConfigGenerator#configureTokens was called instead of the lazy variant, configureTokensLazy.");
project.getLogger().warn("This means longer evaluation times as all tokens are evaluated. Please use configureTokensLazy.");
return configureTokensLazy(project, runConfig, modClasses)
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get()));
}
protected static Map> configureTokensLazy(final Project project, @Nonnull RunConfig runConfig, Stream modClasses) {
Map> tokens = new HashMap<>();
runConfig.getTokens().forEach((k, v) -> tokens.put(k, () -> v));
runConfig.getLazyTokens().forEach((k, v) -> tokens.put(k, Suppliers.memoize(v::get)));
tokens.compute("source_roots", (key, sourceRoots) -> Suppliers.memoize(() -> ((sourceRoots != null)
? Stream.concat(Arrays.stream(sourceRoots.get().split(File.pathSeparator)), modClasses)
: modClasses).distinct().collect(Collectors.joining(File.pathSeparator))));
BiFunction, String, String> classpathJoiner = (supplier, evaluated) -> {
if (supplier == null)
return evaluated;
String oldCp = supplier.get();
return oldCp == null || oldCp.isEmpty() ? evaluated : String.join(File.pathSeparator, oldCp, evaluated);
};
// Can't lazily evaluate these as they create tasks we have to do in the current context
String runtimeClasspath = classpathJoiner.apply(tokens.get("runtime_classpath"), createRuntimeClassPathList(project));
tokens.put("runtime_classpath", () -> runtimeClasspath);
String minecraftClasspath = classpathJoiner.apply(tokens.get("minecraft_classpath"), createMinecraftClassPath(project));
tokens.put("minecraft_classpath", () -> minecraftClasspath);
File classpathFolder = new File(project.getBuildDir(), "classpath");
BinaryOperator classpathFileWriter = (filename, classpath) -> {
if (!classpathFolder.isDirectory() && !classpathFolder.mkdirs())
throw new IllegalStateException("Could not create directory at " + classpathFolder.getAbsolutePath());
File outputFile = new File(classpathFolder, runConfig.getUniqueFileName() + "_" + filename + ".txt");
try (Writer classpathWriter = Files.newBufferedWriter(outputFile.toPath(), StandardCharsets.UTF_8)) {
IOUtils.write(String.join(System.lineSeparator(), classpath.split(File.pathSeparator)), classpathWriter);
} catch (IOException e) {
project.getLogger().error("Exception when writing classpath to file {}", outputFile, e);
}
return outputFile.getAbsolutePath();
};
tokens.put("runtime_classpath_file",
Suppliers.memoize(() -> classpathFileWriter.apply("runtimeClasspath", runtimeClasspath)));
tokens.put("minecraft_classpath_file",
Suppliers.memoize(() -> classpathFileWriter.apply("minecraftClasspath", minecraftClasspath)));
// *Grumbles about having to keep a workaround for a "dummy" hack that should have never existed*
runConfig.getEnvironment().compute("MOD_CLASSES", (key,value) ->
Strings.isNullOrEmpty(value) || "dummy".equals(value) ? "{source_roots}" : value);
return tokens;
}
public static TaskProvider createRunTask(final RunConfig runConfig, final Project project, final TaskProvider prepareRuns, final List additionalClientArgs) {
return createRunTask(runConfig, project, prepareRuns.get(), additionalClientArgs);
}
private static String getResolvedClasspath(Configuration toResolve) {
return toResolve.copyRecursive().resolve().stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
}
protected static String createRuntimeClassPathList(final Project project) {
ConfigurationContainer configurations = project.getConfigurations();
Configuration runtimeClasspath = configurations.getByName("runtimeClasspath");
return getResolvedClasspath(runtimeClasspath);
}
protected static String createMinecraftClassPath(final Project project) {
ConfigurationContainer configurations = project.getConfigurations();
Configuration minecraft = configurations.findByName("minecraft");
if (minecraft == null)
minecraft = configurations.findByName("minecraftImplementation");
if (minecraft == null)
throw new IllegalStateException("Could not find valid minecraft configuration!");
return getResolvedClasspath(minecraft);
}
public static TaskProvider createRunTask(final RunConfig runConfig, final Project project, final Task prepareRuns, final List additionalClientArgs) {
Map> updatedTokens = configureTokensLazy(project, runConfig, mapModClassesToGradle(project, runConfig));
TaskProvider prepareRun = project.getTasks().register("prepare" + Utils.capitalize(runConfig.getTaskName()), Task.class, task -> {
task.setGroup(RunConfig.RUNS_GROUP);
task.dependsOn(prepareRuns, runConfig.getAllSources().stream().map(SourceSet::getClassesTaskName).toArray());
File workDir = new File(runConfig.getWorkingDirectory());
if (!workDir.exists()) {
workDir.mkdirs();
}
});
return project.getTasks().register(runConfig.getTaskName(), JavaExec.class, task -> {
task.setGroup(RunConfig.RUNS_GROUP);
task.dependsOn(prepareRun.get());
File workDir = new File(runConfig.getWorkingDirectory());
if (!workDir.exists()) {
workDir.mkdirs();
}
task.setWorkingDir(workDir);
task.getMainClass().set(runConfig.getMain());
JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class);
task.getJavaLauncher().set(service.launcherFor(project.getExtensions().getByType(JavaPluginExtension.class).getToolchain()));
task.args(getArgsStream(runConfig, updatedTokens, false).toArray());
runConfig.getJvmArgs().forEach((arg) -> task.jvmArgs(runConfig.replace(updatedTokens, arg)));
if (runConfig.isClient()) {
additionalClientArgs.forEach((arg) -> task.jvmArgs(runConfig.replace(updatedTokens, arg)));
}
runConfig.getEnvironment().forEach((key,value) -> task.environment(key, runConfig.replace(updatedTokens, value)));
runConfig.getProperties().forEach((key,value) -> task.systemProperty(key, runConfig.replace(updatedTokens, value)));
runConfig.getAllSources().stream().map(SourceSet::getRuntimeClasspath).forEach(task::classpath);
// Stop after this run task so it doesn't try to execute the run tasks, and their dependencies, of sub projects
if (runConfig.getForceExit()) {
task.doLast(t -> System.exit(0)); // TODO: Find better way to stop gracefully
}
});
}
// Workaround for the issue where file paths with spaces are improperly split into multiple args.
protected static String fixupArg(String replace)
{
if (replace.startsWith("\""))
return replace;
if (!replace.contains(" "))
return replace;
return '"' + replace + '"';
}
protected static String getArgs(RunConfig runConfig, Map updatedTokens)
{
return getArgsStream(runConfig, updatedTokens, true).collect(Collectors.joining(" "));
}
protected static Stream getArgsStream(RunConfig runConfig, Map updatedTokens, boolean wrapSpaces)
{
Stream args = runConfig.getArgs().stream().map((value) -> runConfig.replace(updatedTokens, value));
return wrapSpaces ? args.map(RunConfigGenerator::fixupArg) : args;
}
protected static String getJvmArgs(@Nonnull RunConfig runConfig, List additionalClientArgs, Map updatedTokens)
{
return getJvmArgsStream(runConfig, additionalClientArgs, updatedTokens)
.collect(Collectors.joining(" "));
}
private static Stream getJvmArgsStream(@Nonnull RunConfig runConfig, List additionalClientArgs, Map updatedTokens)
{
final Stream propStream = Stream.concat(
runConfig.getProperties().entrySet().stream()
.map(kv -> String.format("-D%s=%s", kv.getKey(), runConfig.replace(updatedTokens, kv.getValue()))),
runConfig.getJvmArgs().stream().map(value -> runConfig.replace(updatedTokens, value))).map(RunConfigGenerator::fixupArg);
if (runConfig.isClient()) {
return Stream.concat(propStream, additionalClientArgs.stream());
}
return propStream;
}
static abstract class XMLConfigurationBuilder extends RunConfigGenerator {
@Nonnull
protected abstract Map createRunConfiguration(@Nonnull final Project project, @Nonnull final RunConfig runConfig, @Nonnull final DocumentBuilder documentBuilder, List additionalClientArgs);
@Override
public final void createRunConfiguration(@Nonnull final MinecraftExtension minecraft, @Nonnull final File runConfigurationsDir, @Nonnull final Project project, List additionalClientArgs) {
try {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
minecraft.getRuns().forEach(runConfig -> {
final Map documents = createRunConfiguration(project, runConfig, docBuilder, additionalClientArgs);
documents.forEach((fileName, document) -> {
final DOMSource source = new DOMSource(document);
final StreamResult result = new StreamResult(new File(runConfigurationsDir, fileName));
try {
transformer.transform(source, result);
} catch (TransformerException e) {
e.printStackTrace();
}
});
});
} catch (ParserConfigurationException | TransformerConfigurationException e) {
e.printStackTrace();
}
}
}
static abstract class JsonConfigurationBuilder extends RunConfigGenerator {
@Nonnull
protected abstract JsonObject createRunConfiguration(@Nonnull final Project project, @Nonnull final RunConfig runConfig, List additionalClientArgs);
@Override
public final void createRunConfiguration(@Nonnull final MinecraftExtension minecraft, @Nonnull final File runConfigurationsDir, @Nonnull final Project project, List additionalClientArgs) {
final JsonObject rootObject = new JsonObject();
rootObject.addProperty("version", "0.2.0");
JsonArray runConfigs = new JsonArray();
minecraft.getRuns().forEach(runConfig -> {
runConfigs.add(createRunConfiguration(project, runConfig, additionalClientArgs));
});
rootObject.add("configurations", runConfigs);
Writer writer;
try {
writer = new FileWriter(new File(runConfigurationsDir, "launch.json"));
Gson gson = new GsonBuilder().setPrettyPrinting().create();
writer.write(gson.toJson(rootObject));
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy