mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Add genForgePatchedSources task (#98)
* Add genForgePatchedSources task Closes #32. Also reworks the MCP executor system quite heavily: - Instead of executing all steps up to `x`, it now resolves dependencies based on step inputs - No-op steps are skipped - Steps can be overridden with custom logic by callers - You can add multiple desired steps to one round of execution - All step types in MCPConfig 1.14-1.19 are supported now - Variables that reference MCPConfig zip contents now work - Removed special case for `{mappings}` variable Other: - the MCPConfig zip is now completely extracted into the cache dir instead of just the config file
This commit is contained in:
@@ -127,6 +127,7 @@ dependencies {
|
||||
implementation ('org.cadixdev:lorenz-asm:0.5.3')
|
||||
implementation ('de.oceanlabs.mcp:mcinjector:3.8.0')
|
||||
implementation ('com.opencsv:opencsv:5.4')
|
||||
implementation ('net.minecraftforge:DiffPatch:2.0.7')
|
||||
|
||||
// Testing
|
||||
testImplementation(gradleTestKit())
|
||||
|
||||
@@ -31,7 +31,10 @@ import java.util.List;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
|
||||
import net.fabricmc.loom.task.GenerateForgePatchedSourcesTask;
|
||||
import net.fabricmc.loom.task.GenerateSourcesTask;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
|
||||
@@ -82,5 +85,16 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
|
||||
|
||||
task.dependsOn(project.getTasks().named("genSourcesWithCfr"));
|
||||
});
|
||||
|
||||
// TODO: Support for env-only jars?
|
||||
if (extension.isForge() && extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.MERGED) {
|
||||
project.getTasks().register("genForgePatchedSources", GenerateForgePatchedSourcesTask.class, task -> {
|
||||
task.setDescription("Decompile Minecraft using Forge's toolchain.");
|
||||
task.setGroup(Constants.TaskGroup.FABRIC);
|
||||
|
||||
task.getInputJar().set(MinecraftPatchedProvider.get(project).getMinecraftSrgJar().toFile());
|
||||
task.getRuntimeJar().set(inputJar);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigData;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigStep;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
@@ -177,10 +176,8 @@ public class MinecraftPatchedProvider {
|
||||
|
||||
if (Files.notExists(minecraftSrgJar)) {
|
||||
this.dirty = true;
|
||||
McpConfigData data = getExtension().getMcpConfigProvider().getData();
|
||||
List<McpConfigStep> steps = data.steps().get(type.mcpId);
|
||||
McpExecutor executor = new McpExecutor(project, minecraftProvider, Files.createTempDirectory("loom-mcp"), steps, data.functions());
|
||||
Path output = executor.executeUpTo("rename");
|
||||
McpExecutor executor = createMcpExecutor(Files.createTempDirectory("loom-mcp"));
|
||||
Path output = executor.enqueue("rename").execute();
|
||||
Files.copy(output, minecraftSrgJar);
|
||||
}
|
||||
|
||||
@@ -544,6 +541,15 @@ public class MinecraftPatchedProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public McpExecutor createMcpExecutor(Path cache) {
|
||||
McpConfigProvider provider = getExtension().getMcpConfigProvider();
|
||||
return new McpExecutor(project, minecraftProvider, cache, provider, type.mcpId);
|
||||
}
|
||||
|
||||
public Path getMinecraftSrgJar() {
|
||||
return minecraftSrgJar;
|
||||
}
|
||||
|
||||
public Path getMinecraftPatchedSrgJar() {
|
||||
return minecraftPatchedSrgJar;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import java.util.function.Function;
|
||||
public sealed interface ConfigValue {
|
||||
String OUTPUT = "output";
|
||||
String PREVIOUS_OUTPUT_SUFFIX = "Output";
|
||||
String SRG_MAPPINGS_NAME = "mappings";
|
||||
/**
|
||||
* A special config value that is the path to a log file if absent.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.providers.forge.mcpconfig;
|
||||
|
||||
import static net.fabricmc.loom.configuration.providers.forge.mcpconfig.ConfigValue.PREVIOUS_OUTPUT_SUFFIX;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
|
||||
public final class DependencySet {
|
||||
private final Map<String, McpConfigStep> allSteps;
|
||||
private final List<String> stepNames;
|
||||
private final List<Predicate<McpConfigStep>> skipRules = new ArrayList<>();
|
||||
private final Set<String> steps = new HashSet<>();
|
||||
private Predicate<McpConfigStep> ignoreDependenciesFilter = data -> false;
|
||||
|
||||
public DependencySet(List<McpConfigStep> allSteps) {
|
||||
this.allSteps = allSteps.stream().collect(Collectors.toMap(McpConfigStep::name, Function.identity()));
|
||||
this.stepNames = CollectionUtil.map(allSteps, McpConfigStep::name);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
steps.clear();
|
||||
}
|
||||
|
||||
public void add(String step) {
|
||||
if (!allSteps.containsKey(step)) {
|
||||
return;
|
||||
}
|
||||
|
||||
steps.add(step);
|
||||
}
|
||||
|
||||
public void skip(String step) {
|
||||
skip(data -> data.name().equals(step));
|
||||
}
|
||||
|
||||
public void skip(Predicate<McpConfigStep> rule) {
|
||||
skipRules.add(rule);
|
||||
}
|
||||
|
||||
public void setIgnoreDependenciesFilter(Predicate<McpConfigStep> ignoreDependenciesFilter) {
|
||||
this.ignoreDependenciesFilter = ignoreDependenciesFilter;
|
||||
}
|
||||
|
||||
public SortedSet<String> buildExecutionSet() {
|
||||
SortedSet<String> steps = new TreeSet<>(Comparator.comparingInt(stepNames::indexOf));
|
||||
Queue<String> queue = new ArrayDeque<>(this.steps);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
String step = queue.remove();
|
||||
McpConfigStep data = allSteps.get(step);
|
||||
if (!allSteps.containsKey(step) || skipRules.stream().anyMatch(rule -> rule.test(data))) continue;
|
||||
steps.add(step);
|
||||
|
||||
if (!ignoreDependenciesFilter.test(allSteps.get(step))) {
|
||||
allSteps.get(step).config().values().forEach(value -> {
|
||||
if (value instanceof ConfigValue.Variable var) {
|
||||
String name = var.name();
|
||||
|
||||
if (name.endsWith(PREVIOUS_OUTPUT_SUFFIX) && name.length() > PREVIOUS_OUTPUT_SUFFIX.length()) {
|
||||
String substep = name.substring(0, name.length() - PREVIOUS_OUTPUT_SUFFIX.length());
|
||||
queue.offer(substep);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,22 @@ import com.google.gson.JsonObject;
|
||||
/**
|
||||
* Data extracted from the MCPConfig JSON file.
|
||||
*
|
||||
* @param data the value of the {@code data} property
|
||||
* @param mappingsPath the path to srg mappings inside the MCP zip
|
||||
* @param official the value of the {@code official} property
|
||||
* @param steps the MCP step definitions by environment type
|
||||
* @param functions the MCP function definitions by name
|
||||
*/
|
||||
public record McpConfigData(String mappingsPath, boolean official, Map<String, List<McpConfigStep>> steps, Map<String, McpConfigFunction> functions) {
|
||||
public record McpConfigData(
|
||||
JsonObject data,
|
||||
String mappingsPath,
|
||||
boolean official,
|
||||
Map<String, List<McpConfigStep>> steps,
|
||||
Map<String, McpConfigFunction> functions
|
||||
) {
|
||||
public static McpConfigData fromJson(JsonObject json) {
|
||||
String mappingsPath = json.getAsJsonObject("data").get("mappings").getAsString();
|
||||
JsonObject data = json.getAsJsonObject("data");
|
||||
String mappingsPath = data.get("mappings").getAsString();
|
||||
boolean official = json.has("official") && json.getAsJsonPrimitive("official").getAsBoolean();
|
||||
|
||||
JsonObject stepsJson = json.getAsJsonObject("steps");
|
||||
@@ -65,6 +73,6 @@ public record McpConfigData(String mappingsPath, boolean official, Map<String, L
|
||||
functionsBuilder.put(key, McpConfigFunction.fromJson(functionsJson.getAsJsonObject(key)));
|
||||
}
|
||||
|
||||
return new McpConfigData(mappingsPath, official, stepsBuilder.build(), functionsBuilder.build());
|
||||
return new McpConfigData(data, mappingsPath, official, stepsBuilder.build(), functionsBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,10 @@
|
||||
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -38,12 +36,13 @@ import org.gradle.api.Project;
|
||||
import net.fabricmc.loom.configuration.DependencyInfo;
|
||||
import net.fabricmc.loom.configuration.providers.forge.DependencyProvider;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.DeletingFileVisitor;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
public class McpConfigProvider extends DependencyProvider {
|
||||
private Path mcp;
|
||||
private Path configJson;
|
||||
private Path mappings;
|
||||
private Path unpacked;
|
||||
private McpConfigData data;
|
||||
|
||||
public McpConfigProvider(Project project) {
|
||||
@@ -56,9 +55,16 @@ public class McpConfigProvider extends DependencyProvider {
|
||||
|
||||
Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath();
|
||||
|
||||
if (!Files.exists(mcp) || !Files.exists(configJson) || refreshDeps()) {
|
||||
if (!Files.exists(mcp) || !Files.exists(unpacked) || refreshDeps()) {
|
||||
Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
// Delete existing files
|
||||
if (Files.exists(unpacked)) {
|
||||
Files.walkFileTree(unpacked, new DeletingFileVisitor());
|
||||
}
|
||||
|
||||
Files.createDirectory(unpacked);
|
||||
ZipUtils.unpackAll(mcp, unpacked);
|
||||
}
|
||||
|
||||
JsonObject json;
|
||||
@@ -70,27 +76,19 @@ public class McpConfigProvider extends DependencyProvider {
|
||||
data = McpConfigData.fromJson(json);
|
||||
}
|
||||
|
||||
private void init(String version) throws IOException {
|
||||
private void init(String version) {
|
||||
Path dir = getMinecraftProvider().dir("mcp/" + version).toPath();
|
||||
mcp = dir.resolve("mcp.zip");
|
||||
configJson = dir.resolve("mcp-config.json");
|
||||
mappings = dir.resolve("mcp-config-mappings.txt");
|
||||
|
||||
if (refreshDeps()) {
|
||||
Files.deleteIfExists(mappings);
|
||||
}
|
||||
unpacked = dir.resolve("unpacked");
|
||||
configJson = unpacked.resolve("config.json");
|
||||
}
|
||||
|
||||
public Path getMappings() {
|
||||
if (Files.notExists(mappings)) {
|
||||
try {
|
||||
Files.write(mappings, ZipUtils.unpack(getMcp(), getMappingsPath()), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to find mappings '" + getMappingsPath() + "' in " + getMcp() + "!");
|
||||
}
|
||||
}
|
||||
return unpacked.resolve(getMappingsPath());
|
||||
}
|
||||
|
||||
return mappings;
|
||||
public Path getUnpackedZip() {
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
public Path getMcp() {
|
||||
|
||||
@@ -32,13 +32,18 @@ import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
@@ -50,8 +55,10 @@ import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ConstantLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.DownloadManifestFileLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.FunctionLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.InjectLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ListLibrariesLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.NoOpLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.PatchLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StepLogic;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StripLogic;
|
||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
|
||||
@@ -66,15 +73,55 @@ public final class McpExecutor {
|
||||
private final MinecraftProvider minecraftProvider;
|
||||
private final Path cache;
|
||||
private final List<McpConfigStep> steps;
|
||||
private final DependencySet dependencySet;
|
||||
private final Map<String, McpConfigFunction> functions;
|
||||
private final Map<String, String> config = new HashMap<>();
|
||||
private final Map<String, String> extraConfig = new HashMap<>();
|
||||
private @Nullable StepLogic.Provider stepLogicProvider = null;
|
||||
|
||||
public McpExecutor(Project project, MinecraftProvider minecraftProvider, Path cache, List<McpConfigStep> steps, Map<String, McpConfigFunction> functions) {
|
||||
public McpExecutor(Project project, MinecraftProvider minecraftProvider, Path cache, McpConfigProvider provider, String environment) {
|
||||
this.project = project;
|
||||
this.minecraftProvider = minecraftProvider;
|
||||
this.cache = cache;
|
||||
this.steps = steps;
|
||||
this.functions = functions;
|
||||
this.steps = provider.getData().steps().get(environment);
|
||||
this.functions = provider.getData().functions();
|
||||
this.dependencySet = new DependencySet(this.steps);
|
||||
this.dependencySet.skip(step -> getStepLogic(step.name(), step.type()) instanceof NoOpLogic);
|
||||
this.dependencySet.setIgnoreDependenciesFilter(step -> getStepLogic(step.name(), step.type()).hasNoContext());
|
||||
|
||||
addDefaultFiles(provider, environment);
|
||||
}
|
||||
|
||||
private void addDefaultFiles(McpConfigProvider provider, String environment) {
|
||||
for (Map.Entry<String, JsonElement> entry : provider.getData().data().entrySet()) {
|
||||
if (entry.getValue().isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), entry.getValue().getAsString());
|
||||
} else if (entry.getValue().isJsonObject()) {
|
||||
JsonObject json = entry.getValue().getAsJsonObject();
|
||||
|
||||
if (json.has(environment) && json.get(environment).isJsonPrimitive()) {
|
||||
addDefaultFile(provider, entry.getKey(), json.getAsJsonPrimitive(environment).getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultFile(McpConfigProvider provider, String key, String value) {
|
||||
Path path = provider.getUnpackedZip().resolve(value).toAbsolutePath();
|
||||
|
||||
if (!path.startsWith(provider.getUnpackedZip().toAbsolutePath())) {
|
||||
// This is probably not what we're looking for since it falls outside the directory.
|
||||
return;
|
||||
} else if (Files.notExists(path)) {
|
||||
// Not a real file, let's continue.
|
||||
return;
|
||||
}
|
||||
|
||||
addConfig(key, path.toString());
|
||||
}
|
||||
|
||||
public void addConfig(String key, String value) {
|
||||
config.put(key, value);
|
||||
}
|
||||
|
||||
private Path getDownloadCache() throws IOException {
|
||||
@@ -105,8 +152,8 @@ public final class McpExecutor {
|
||||
return resolve(step, valueFromStep);
|
||||
}
|
||||
|
||||
if (name.equals(ConfigValue.SRG_MAPPINGS_NAME)) {
|
||||
return LoomGradleExtension.get(project).getSrgProvider().getSrg().toAbsolutePath().toString();
|
||||
if (config.containsKey(name)) {
|
||||
return config.get(name);
|
||||
} else if (extraConfig.containsKey(name)) {
|
||||
return extraConfig.get(name);
|
||||
} else if (name.equals(ConfigValue.LOG)) {
|
||||
@@ -117,35 +164,78 @@ public final class McpExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
public Path executeUpTo(String step) throws IOException {
|
||||
/**
|
||||
* Enqueues a step and its dependencies to be executed.
|
||||
*
|
||||
* @param step the name of the step
|
||||
* @return this executor
|
||||
*/
|
||||
public McpExecutor enqueue(String step) {
|
||||
dependencySet.add(step);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all queued steps and their dependencies.
|
||||
*
|
||||
* @return the output file of the last executed step
|
||||
*/
|
||||
public Path execute() throws IOException {
|
||||
SortedSet<String> stepNames = dependencySet.buildExecutionSet();
|
||||
dependencySet.clear();
|
||||
List<McpConfigStep> toExecute = new ArrayList<>();
|
||||
|
||||
for (String stepName : stepNames) {
|
||||
McpConfigStep step = CollectionUtil.find(steps, s -> s.name().equals(stepName))
|
||||
.orElseThrow(() -> new NoSuchElementException("Step '" + stepName + "' not found in MCP config"));
|
||||
toExecute.add(step);
|
||||
}
|
||||
|
||||
return executeSteps(toExecute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified steps.
|
||||
*
|
||||
* @param steps the steps to execute
|
||||
* @return the output file of the last executed step
|
||||
*/
|
||||
public Path executeSteps(List<McpConfigStep> steps) throws IOException {
|
||||
extraConfig.clear();
|
||||
|
||||
// Find the total number of steps we need to execute.
|
||||
int totalSteps = CollectionUtil.find(steps, s -> s.name().equals(step))
|
||||
.map(s -> steps.indexOf(s) + 1)
|
||||
.orElse(steps.size());
|
||||
int totalSteps = steps.size();
|
||||
int currentStepIndex = 0;
|
||||
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":executing {} MCP steps", totalSteps);
|
||||
|
||||
for (McpConfigStep currentStep : steps) {
|
||||
currentStepIndex++;
|
||||
StepLogic stepLogic = getStepLogic(currentStep.type());
|
||||
StepLogic stepLogic = getStepLogic(currentStep.name(), currentStep.type());
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":step {}/{} - {}", currentStepIndex, totalSteps, stepLogic.getDisplayName(currentStep.name()));
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
stepLogic.execute(new ExecutionContextImpl(currentStep));
|
||||
project.getLogger().log(STEP_LOG_LEVEL, ":{} done in {}", currentStep.name(), stopwatch.stop());
|
||||
|
||||
if (currentStep.name().equals(step)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Path.of(extraConfig.get(ConfigValue.OUTPUT));
|
||||
}
|
||||
|
||||
private StepLogic getStepLogic(String type) {
|
||||
/**
|
||||
* Sets the custom step logic provider of this executor.
|
||||
*
|
||||
* @param stepLogicProvider the provider, or null to disable
|
||||
*/
|
||||
public void setStepLogicProvider(@Nullable StepLogic.Provider stepLogicProvider) {
|
||||
this.stepLogicProvider = stepLogicProvider;
|
||||
}
|
||||
|
||||
private StepLogic getStepLogic(String name, String type) {
|
||||
if (stepLogicProvider != null) {
|
||||
final @Nullable StepLogic custom = stepLogicProvider.getStepLogic(name, type).orElse(null);
|
||||
if (custom != null) return custom;
|
||||
}
|
||||
|
||||
return switch (type) {
|
||||
case "downloadManifest", "downloadJson" -> new NoOpLogic();
|
||||
case "downloadClient" -> new ConstantLogic(() -> minecraftProvider.getMinecraftClientJar().toPath());
|
||||
@@ -154,6 +244,8 @@ public final class McpExecutor {
|
||||
case "listLibraries" -> new ListLibrariesLogic();
|
||||
case "downloadClientMappings" -> new DownloadManifestFileLogic(minecraftProvider.getVersionInfo().download("client_mappings"));
|
||||
case "downloadServerMappings" -> new DownloadManifestFileLogic(minecraftProvider.getVersionInfo().download("server_mappings"));
|
||||
case "inject" -> new InjectLogic();
|
||||
case "patch" -> new PatchLogic();
|
||||
default -> {
|
||||
if (functions.containsKey(type)) {
|
||||
yield new FunctionLogic(functions.get(type));
|
||||
@@ -178,8 +270,7 @@ public final class McpExecutor {
|
||||
|
||||
@Override
|
||||
public Path setOutput(String fileName) throws IOException {
|
||||
createStepCache(step.name());
|
||||
return setOutput(getStepCache(step.name()).resolve(fileName));
|
||||
return setOutput(cache().resolve(fileName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,6 +281,11 @@ public final class McpExecutor {
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path cache() throws IOException {
|
||||
return createStepCache(step.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path mappings() {
|
||||
return LoomGradleExtension.get(project).getMcpConfigProvider().getMappings();
|
||||
|
||||
@@ -24,7 +24,5 @@
|
||||
|
||||
/**
|
||||
* A simple implementation for executing MCPConfig steps.
|
||||
* Doesn't support all steps, just the ones up to {@code rename}
|
||||
* and all custom functions.
|
||||
*/
|
||||
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.ConfigValue;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
|
||||
public final class InjectLogic implements StepLogic {
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
Path injectedFiles = Path.of(context.resolve(new ConfigValue.Variable("inject")));
|
||||
Path input = Path.of(context.resolve(new ConfigValue.Variable("input")));
|
||||
Path output = context.setOutput("output.jar");
|
||||
Files.copy(input, output, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
try (FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(output, false)) {
|
||||
FileSystem fs = targetFs.get();
|
||||
|
||||
try (Stream<Path> paths = Files.walk(injectedFiles)) {
|
||||
Iterator<Path> iter = paths.filter(Files::isRegularFile).iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
Path from = iter.next();
|
||||
Path relative = injectedFiles.relativize(from);
|
||||
Path to = fs.getPath(relative.toString().replace(relative.getFileSystem().getSeparator(), "/"));
|
||||
Files.createDirectories(to.getParent());
|
||||
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.providers.forge.mcpconfig.steplogic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import codechicken.diffpatch.cli.CliOperation;
|
||||
import codechicken.diffpatch.cli.PatchOperation;
|
||||
import codechicken.diffpatch.util.LoggingOutputStream;
|
||||
import codechicken.diffpatch.util.PatchMode;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.ConfigValue;
|
||||
|
||||
public final class PatchLogic implements StepLogic {
|
||||
@Override
|
||||
public void execute(ExecutionContext context) throws IOException {
|
||||
Path input = Path.of(context.resolve(new ConfigValue.Variable("input")));
|
||||
Path patches = Path.of(context.resolve(new ConfigValue.Variable("patches")));
|
||||
Path output = context.setOutput("output.jar");
|
||||
Path rejects = context.cache().resolve("rejects");
|
||||
|
||||
CliOperation.Result<PatchOperation.PatchesSummary> result = PatchOperation.builder()
|
||||
.logTo(new LoggingOutputStream(context.logger(), LogLevel.INFO))
|
||||
.basePath(input)
|
||||
.patchesPath(patches)
|
||||
.outputPath(output)
|
||||
.mode(PatchMode.OFFSET)
|
||||
.rejectsPath(rejects)
|
||||
.build()
|
||||
.operate();
|
||||
|
||||
if (result.exit != 0) {
|
||||
throw new RuntimeException("Could not patch " + input + "; rejects saved to " + rejects.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
@@ -48,10 +49,15 @@ public interface StepLogic {
|
||||
return stepName;
|
||||
}
|
||||
|
||||
default boolean hasNoContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
interface ExecutionContext {
|
||||
Logger logger();
|
||||
Path setOutput(String fileName) throws IOException;
|
||||
Path setOutput(Path output);
|
||||
Path cache() throws IOException;
|
||||
/** Mappings extracted from {@code data.mappings} in the MCPConfig JSON. */
|
||||
Path mappings();
|
||||
String resolve(ConfigValue value);
|
||||
@@ -64,4 +70,9 @@ public interface StepLogic {
|
||||
return CollectionUtil.map(configValues, this::resolve);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Provider {
|
||||
Optional<StepLogic> getStepLogic(String name, String type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import codechicken.diffpatch.cli.CliOperation;
|
||||
import codechicken.diffpatch.cli.PatchOperation;
|
||||
import codechicken.diffpatch.util.LoggingOutputStream;
|
||||
import codechicken.diffpatch.util.PatchMode;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.logging.LogLevel;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutor;
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.ConstantLogic;
|
||||
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
|
||||
import net.fabricmc.loom.util.SourceRemapper;
|
||||
|
||||
public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
|
||||
/**
|
||||
* The SRG Minecraft file produced by the MCP executor.
|
||||
*/
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getInputJar();
|
||||
|
||||
/**
|
||||
* The runtime Minecraft file.
|
||||
*/
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getRuntimeJar();
|
||||
|
||||
/**
|
||||
* The source jar.
|
||||
*/
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getOutputJar();
|
||||
|
||||
public GenerateForgePatchedSourcesTask() {
|
||||
getOutputs().upToDateWhen((o) -> false);
|
||||
getOutputJar().fileProvider(getProject().provider(() -> GenerateSourcesTask.getMappedJarFileWithSuffix(getRuntimeJar(), "-sources.jar")));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() throws IOException {
|
||||
Path cache = Files.createTempDirectory("loom-decompilation");
|
||||
// Step 1: decompile and patch with MCP patches
|
||||
Path rawDecompiled = decompileAndPatch(cache);
|
||||
// Step 2: patch with Forge patches
|
||||
getLogger().lifecycle(":applying Forge patches");
|
||||
Path patched = sourcePatch(cache, rawDecompiled);
|
||||
// Step 3: remap
|
||||
remap(patched);
|
||||
// Step 4: add Forge's own sources
|
||||
ForgeSourcesRemapper.addForgeSources(getProject(), getOutputJar().get().getAsFile().toPath());
|
||||
}
|
||||
|
||||
private Path decompileAndPatch(Path cache) throws IOException {
|
||||
Path mcpCache = cache.resolve("mcp");
|
||||
Files.createDirectory(mcpCache);
|
||||
|
||||
MinecraftPatchedProvider patchedProvider = MinecraftPatchedProvider.get(getProject());
|
||||
McpExecutor mcp = patchedProvider.createMcpExecutor(mcpCache);
|
||||
mcp.setStepLogicProvider((name, type) -> {
|
||||
if (name.equals("rename")) {
|
||||
return Optional.of(new ConstantLogic(() -> getInputJar().get().getAsFile().toPath()));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
mcp.enqueue("decompile");
|
||||
mcp.enqueue("patch");
|
||||
return mcp.execute();
|
||||
}
|
||||
|
||||
private Path sourcePatch(Path cache, Path rawDecompiled) throws IOException {
|
||||
ForgeUserdevProvider userdev = getExtension().getForgeUserdevProvider();
|
||||
String patchPathInZip = userdev.getJson().getAsJsonPrimitive("patches").getAsString();
|
||||
Path output = cache.resolve("patched.jar");
|
||||
Path rejects = cache.resolve("rejects");
|
||||
|
||||
CliOperation.Result<PatchOperation.PatchesSummary> result = PatchOperation.builder()
|
||||
.logTo(new LoggingOutputStream(getLogger(), LogLevel.INFO))
|
||||
.basePath(rawDecompiled)
|
||||
.patchesPath(userdev.getUserdevJar().toPath())
|
||||
.patchesPrefix(patchPathInZip)
|
||||
.outputPath(output)
|
||||
.mode(PatchMode.ACCESS)
|
||||
.rejectsPath(rejects)
|
||||
.aPrefix(userdev.getJson().getAsJsonPrimitive("patchesOriginalPrefix").getAsString())
|
||||
.bPrefix(userdev.getJson().getAsJsonPrimitive("patchesModifiedPrefix").getAsString())
|
||||
.build()
|
||||
.operate();
|
||||
|
||||
if (result.exit != 0) {
|
||||
throw new RuntimeException("Could not patch " + rawDecompiled + "; rejects saved to " + rejects.toAbsolutePath());
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private void remap(Path input) {
|
||||
SourceRemapper remapper = new SourceRemapper(getProject(), "srg", "named");
|
||||
remapper.scheduleRemapSources(input.toFile(), getOutputJar().get().getAsFile(), false, true, () -> {
|
||||
});
|
||||
remapper.remapAll();
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,24 @@ public final class CollectionUtil {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the first element matching the predicate.
|
||||
*
|
||||
* @param list the list to be searched
|
||||
* @param filter the predicate to be matched
|
||||
* @param <E> the element type
|
||||
* @return the index of the first matching element, or -1 if none match
|
||||
*/
|
||||
public static <E> int indexOf(List<? extends E> list, Predicate<? super E> filter) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (filter.test(list.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the collection with a function.
|
||||
*
|
||||
|
||||
@@ -41,7 +41,7 @@ class ForgeTest extends Specification implements GradleProjectTestTrait {
|
||||
.replace('@MAPPINGS@', mappings)
|
||||
|
||||
when:
|
||||
def result = gradle.run(task: "build", args: "")
|
||||
def result = gradle.run(task: "build")
|
||||
|
||||
then:
|
||||
result.task(":build").outcome == SUCCESS
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.integration.forge
|
||||
|
||||
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.DEFAULT_GRADLE
|
||||
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
|
||||
|
||||
class PatchedDecompileTest extends Specification implements GradleProjectTestTrait {
|
||||
@Unroll
|
||||
def "decompile #mcVersion #forgeVersion"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "forge/simple", version: DEFAULT_GRADLE)
|
||||
gradle.buildGradle.text = gradle.buildGradle.text.replace('@MCVERSION@', mcVersion)
|
||||
.replace('@FORGEVERSION@', forgeVersion)
|
||||
.replace('@MAPPINGS@', 'loom.officialMojangMappings()')
|
||||
|
||||
when:
|
||||
def result = gradle.run(task: "genForgePatchedSources")
|
||||
|
||||
then:
|
||||
result.task(":genForgePatchedSources").outcome == SUCCESS
|
||||
|
||||
where:
|
||||
mcVersion | forgeVersion
|
||||
'1.19.2' | "43.1.1"
|
||||
'1.18.1' | "39.0.63"
|
||||
'1.17.1' | "37.0.67"
|
||||
'1.16.5' | "36.2.4"
|
||||
'1.14.4' | "28.2.23"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2022 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.forge
|
||||
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.ConfigValue
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.DependencySet
|
||||
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigStep
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
class DependencySetTest extends Specification {
|
||||
/*
|
||||
orphanA
|
||||
orphanB
|
||||
root
|
||||
-> childA1 -> childA2 --> childAB
|
||||
-> childB /
|
||||
*/
|
||||
@Shared List<McpConfigStep> allSteps = [
|
||||
new McpConfigStep('foo', 'orphanA', [:]),
|
||||
new McpConfigStep('foo', 'orphanB', [:]),
|
||||
new McpConfigStep('bar', 'root', [:]),
|
||||
new McpConfigStep('bar', 'childA1', [input: ConfigValue.of('{rootOutput}')]),
|
||||
new McpConfigStep('bar', 'childA2', [input: ConfigValue.of('{childA1Output}')]),
|
||||
new McpConfigStep('bar', 'childB', [input: ConfigValue.of('{rootOutput}')]),
|
||||
new McpConfigStep(
|
||||
'bar', 'childAB',
|
||||
[inputA: ConfigValue.of('{childA2Output}'), inputB: ConfigValue.of("{childBOutput}")]
|
||||
),
|
||||
]
|
||||
|
||||
DependencySet dependencySet = new DependencySet(allSteps)
|
||||
|
||||
def "single child"() {
|
||||
when:
|
||||
dependencySet.add('childAB')
|
||||
def executedSteps = dependencySet.buildExecutionSet()
|
||||
then:
|
||||
executedSteps.toList() == ['root', 'childA1', 'childA2', 'childB', 'childAB']
|
||||
}
|
||||
|
||||
def "multiple children"() {
|
||||
when:
|
||||
dependencySet.add('childA1')
|
||||
dependencySet.add('orphanB')
|
||||
def executedSteps = dependencySet.buildExecutionSet()
|
||||
then:
|
||||
executedSteps.toList() == ['orphanB', 'root', 'childA1']
|
||||
}
|
||||
|
||||
def "skip rule"() {
|
||||
when:
|
||||
dependencySet.add('childAB')
|
||||
dependencySet.skip('childA2')
|
||||
def executedSteps = dependencySet.buildExecutionSet()
|
||||
then:
|
||||
executedSteps.toList() == ['root', 'childB', 'childAB']
|
||||
}
|
||||
|
||||
def "ignore dependencies filter"() {
|
||||
when:
|
||||
dependencySet.add('childAB')
|
||||
dependencySet.ignoreDependenciesFilter = { it.name() == 'childA2' }
|
||||
def executedSteps = dependencySet.buildExecutionSet()
|
||||
then:
|
||||
executedSteps.toList() == ['root', 'childA2', 'childB', 'childAB']
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user