From 70def8a31d8a28667c490b83ba2dabc826545000 Mon Sep 17 00:00:00 2001 From: Juuxel <6596629+Juuxel@users.noreply.github.com> Date: Sun, 22 May 2022 18:41:27 +0300 Subject: [PATCH] Use MCPConfig data for processing Forge jar, support single-jar Forge (#87) * Use MCPConfig data for merging and remapping Forge jar * Make DependencyDownloader support multiple deps * Support server-/client-only Minecraft with Forge There's one slight caveat here: the server jar contains some client-only classes that have been patched. This also happens with the official Forge installer in production, so it's probably fine. * Remove binpatcher dep * Move McpConfigProvider to correct package * Print tool name for functions in McpExecutor * Fix Forge tools outputting verbose output at IDEA refresh * Fix certain Forge deps being excluded from run configs * Always produce the client extra jar * Add step count to McpExecutor logging * Allow missing args and jvmargs in MCP functions This should fix using 1.14.4 and 1.16.5, which don't have JVM args for everything. * Make MCP function downloads follow redirects * Refactor MCP step outputs, don't copy raw MC jars * Remove MinecraftProviderBridge --- build.gradle | 1 - .../fabricmc/loom/LoomGradleExtension.java | 2 +- .../configuration/CompileConfiguration.java | 12 +- .../AccessTransformerJarProcessor.java | 6 +- .../providers/forge/DependencyProvider.java | 3 +- .../forge/FieldMigratedMappingsProvider.java | 22 +- .../providers/forge/ForgeUserdevProvider.java | 23 +- .../providers/forge/McpConfigProvider.java | 231 ----------- .../forge/MinecraftPatchedProvider.java | 363 +++++++----------- .../forge/mcpconfig/ConfigValue.java | 66 ++++ .../forge/mcpconfig/McpConfigData.java | 70 ++++ .../forge/mcpconfig/McpConfigFunction.java | 81 ++++ .../forge/mcpconfig/McpConfigProvider.java | 116 ++++++ .../forge/mcpconfig/McpConfigStep.java | 49 +++ .../forge/mcpconfig/McpExecutor.java | 230 +++++++++++ .../providers/forge/mcpconfig/StepLogic.java | 223 +++++++++++ .../forge/mcpconfig/package-info.java | 30 ++ .../minecraft/ForgeMinecraftProvider.java | 52 +++ .../MergedForgeMinecraftProvider.java | 69 ++++ .../SingleJarForgeMinecraftProvider.java | 76 ++++ .../minecraft/MinecraftJarConfiguration.java | 8 +- .../minecraft/SingleJarMinecraftProvider.java | 13 +- .../sources/ForgeSourcesRemapper.java | 6 +- .../net/fabricmc/loom/util/Constants.java | 3 + .../loom/util/DependencyDownloader.java | 70 +++- .../fabricmc/loom/util/ForgeToolExecutor.java | 21 +- .../loom/util/srg/SpecialSourceExecutor.java | 136 ------- 27 files changed, 1339 insertions(+), 643 deletions(-) delete mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/ConfigValue.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigData.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigFunction.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigProvider.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigStep.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/StepLogic.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/package-info.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/ForgeMinecraftProvider.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/MergedForgeMinecraftProvider.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/SingleJarForgeMinecraftProvider.java delete mode 100644 src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java diff --git a/build.gradle b/build.gradle index 03a7afae..c3699421 100644 --- a/build.gradle +++ b/build.gradle @@ -125,7 +125,6 @@ dependencies { // Forge patches implementation ('net.minecraftforge:installertools:1.2.0') - implementation ('net.minecraftforge:binarypatcher:1.1.1') implementation ('org.cadixdev:lorenz:0.5.3') implementation ('org.cadixdev:lorenz-asm:0.5.3') implementation ('de.oceanlabs.mcp:mcinjector:3.8.0') diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index f73f2fa1..49da581a 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -47,9 +47,9 @@ import net.fabricmc.loom.configuration.providers.forge.DependencyProviders; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; -import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; import net.fabricmc.loom.configuration.providers.forge.PatchProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; +import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 35b71df1..a59c703f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -55,10 +55,10 @@ import net.fabricmc.loom.configuration.providers.forge.DependencyProviders; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; -import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; import net.fabricmc.loom.configuration.providers.forge.PatchProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; +import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider; +import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; @@ -276,8 +276,8 @@ public final class CompileConfiguration { // Provide the vanilla mc jars -- TODO share across projects. final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(project); - if (extension.isForge() && !(minecraftProvider instanceof MinecraftPatchedProvider)) { - throw new UnsupportedOperationException("Using Forge with split or server-only jars is not currently supported!"); + if (extension.isForge() && !(minecraftProvider instanceof ForgeMinecraftProvider)) { + throw new UnsupportedOperationException("Using Forge with split jars is not supported!"); } extension.setMinecraftProvider(minecraftProvider); @@ -288,8 +288,8 @@ public final class CompileConfiguration { extension.setMappingsProvider(mappingsProvider); mappingsProvider.applyToProject(project, mappingsDep); - if (minecraftProvider instanceof MinecraftPatchedProvider patched) { - patched.remapJar(); + if (minecraftProvider instanceof ForgeMinecraftProvider patched) { + patched.getPatchedProvider().remapJar(); } // Provide the remapped mc jars diff --git a/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java index c7cc57b1..69a0fcd1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java @@ -165,8 +165,10 @@ public final class AccessTransformerJarProcessor implements JarProcessor { public static void executeAt(Project project, Path input, Path output, AccessTransformerConfiguration configuration) throws IOException { boolean serverBundleMetadataPresent = LoomGradleExtension.get(project).getMinecraftProvider().getServerBundleMetadata() != null; - String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (serverBundleMetadataPresent ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); - FileCollection classpath = DependencyDownloader.download(project, atDependency); + FileCollection classpath = new DependencyDownloader(project) + .add(Constants.Dependencies.ACCESS_TRANSFORMERS + (serverBundleMetadataPresent ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS)) + .add(Constants.Dependencies.ASM + Constants.Dependencies.Versions.ASM) + .download(); List args = new ArrayList<>(); args.add("--inJar"); args.add(input.toAbsolutePath().toString()); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/DependencyProvider.java index 28913658..d6d4ff1e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/DependencyProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/DependencyProvider.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.configuration.providers.forge; import java.io.File; +import java.nio.file.Path; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; @@ -53,7 +54,7 @@ public abstract class DependencyProvider { } static Dependency addDependency(Project project, Object object, String target) { - if (object instanceof File) { + if (object instanceof File || object instanceof Path) { object = project.files(object); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index c4a816de..a3d59693 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -25,7 +25,6 @@ package net.fabricmc.loom.configuration.providers.forge; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.io.UncheckedIOException; @@ -203,19 +202,16 @@ public class FieldMigratedMappingsProvider extends MappingsProviderImpl { } Visitor visitor = new Visitor(Opcodes.ASM9); + Path patchedSrgJar = MinecraftPatchedProvider.get(project).getMinecraftPatchedSrgJar(); + FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); + completer.onComplete(value -> system.close()); - for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) { - File patchedSrgJar = environment.patchedSrgJar.apply(MinecraftPatchedProvider.get(project)); - FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); - completer.onComplete(value -> system.close()); - - for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { - if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { - completer.add(() -> { - byte[] bytes = Files.readAllBytes(fsPath); - new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - }); - } + for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { + completer.add(() -> { + byte[] bytes = Files.readAllBytes(fsPath); + new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + }); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index a1baf2bf..63def709 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2020-2021 FabricMC + * Copyright (c) 2020-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 @@ -58,6 +58,7 @@ import org.gradle.api.attributes.Attribute; import org.gradle.api.file.FileSystemLocation; import org.gradle.api.provider.Provider; +import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.configuration.DependencyInfo; import net.fabricmc.loom.configuration.ide.RunConfigSettings; @@ -66,10 +67,13 @@ import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.PropertyUtil; +import net.fabricmc.loom.util.ZipUtils; public class ForgeUserdevProvider extends DependencyProvider { private File userdevJar; private JsonObject json; + Path joinedPatches; + BinaryPatcherConfig binaryPatcherConfig; public ForgeUserdevProvider(Project project) { super(project); @@ -89,6 +93,7 @@ public class ForgeUserdevProvider extends DependencyProvider { } userdevJar = new File(getExtension().getForgeProvider().getGlobalCache(), "forge-userdev.jar"); + joinedPatches = getExtension().getForgeProvider().getGlobalCache().toPath().resolve("patches-joined.lzma"); Path configJson = getExtension().getForgeProvider().getGlobalCache().toPath().resolve("forge-config.json"); if (!userdevJar.exists() || Files.notExists(configJson) || isRefreshDeps()) { @@ -132,8 +137,12 @@ public class ForgeUserdevProvider extends DependencyProvider { } } - // TODO: Should I copy the patches from here as well? - // That'd require me to run the "MCP environment" fully up to merging. + if (Files.notExists(joinedPatches)) { + Files.write(joinedPatches, ZipUtils.unpack(userdevJar.toPath(), json.get("binpatches").getAsString())); + } + + binaryPatcherConfig = BinaryPatcherConfig.fromJson(json.getAsJsonObject("binpatcher")); + for (Map.Entry entry : json.getAsJsonObject("runs").entrySet()) { LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); @@ -307,4 +316,12 @@ public class ForgeUserdevProvider extends DependencyProvider { public String getTargetConfig() { return Constants.Configurations.FORGE_USERDEV; } + + public record BinaryPatcherConfig(String dependency, List args) { + public static BinaryPatcherConfig fromJson(JsonObject json) { + String dependency = json.get("version").getAsString(); + List args = List.of(LoomGradlePlugin.GSON.fromJson(json.get("args"), String[].class)); + return new BinaryPatcherConfig(dependency, args); + } + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java deleted file mode 100644 index eb399914..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2020-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.configuration.providers.forge; - -import java.io.ByteArrayInputStream; -import java.io.File; -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 java.util.List; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; - -import net.fabricmc.loom.configuration.DependencyInfo; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.DependencyDownloader; -import net.fabricmc.loom.util.ZipUtils; - -public class McpConfigProvider extends DependencyProvider { - private Path mcp; - private Path configJson; - private Path mappings; - private Boolean official; - private String mappingsPath; - private RemapAction remapAction; - - public McpConfigProvider(Project project) { - super(project); - } - - @Override - public void provide(DependencyInfo dependency) throws Exception { - init(dependency.getDependency().getVersion()); - - Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); - - if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) { - Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); - Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } - - JsonObject json; - - try (Reader reader = Files.newBufferedReader(configJson)) { - json = new Gson().fromJson(reader, JsonObject.class); - } - - official = json.has("official") && json.getAsJsonPrimitive("official").getAsBoolean(); - mappingsPath = json.get("data").getAsJsonObject().get("mappings").getAsString(); - - if (json.has("functions")) { - JsonObject functions = json.getAsJsonObject("functions"); - - if (functions.has("rename")) { - remapAction = new ConfigDefinedRemapAction(getProject(), functions.getAsJsonObject("rename")); - } - } - - if (remapAction == null) { - throw new RuntimeException("Could not find remap action, this is probably a version Architectury Loom does not support!"); - } - } - - public RemapAction getRemapAction() { - return remapAction; - } - - private void init(String version) throws IOException { - 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 (isRefreshDeps()) { - Files.deleteIfExists(mappings); - } - } - - 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 mappings; - } - - public Path getMcp() { - return mcp; - } - - public boolean isOfficial() { - return official; - } - - public String getMappingsPath() { - return mappingsPath; - } - - @Override - public String getTargetConfig() { - return Constants.Configurations.MCP_CONFIG; - } - - public interface RemapAction { - FileCollection getClasspath(); - - String getMainClass(); - - List getArgs(Path input, Path output, Path mappings, FileCollection libraries); - } - - public static class ConfigDefinedRemapAction implements RemapAction { - private final Project project; - private final String name; - private final File mainClasspath; - private final FileCollection classpath; - private final List args; - private boolean hasLibraries; - - public ConfigDefinedRemapAction(Project project, JsonObject json) { - this.project = project; - this.name = json.get("version").getAsString(); - this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) - .getSingleFile(); - this.classpath = DependencyDownloader.download(project, this.name, true, true); - this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false) - .map(JsonElement::getAsString) - .collect(Collectors.toList()); - for (int i = 1; i < this.args.size(); i++) { - if (this.args.get(i).equals("{libraries}")) { - this.args.remove(i); - this.args.remove(i - 1); - this.hasLibraries = true; - break; - } - } - } - - @Override - public FileCollection getClasspath() { - return classpath; - } - - @Override - public String getMainClass() { - try { - byte[] manifestBytes = ZipUtils.unpackNullable(mainClasspath.toPath(), "META-INF/MANIFEST.MF"); - - if (manifestBytes == null) { - throw new RuntimeException("Could not find MANIFEST.MF in " + mainClasspath + "!"); - } - - Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue(Attributes.Name.MAIN_CLASS); - - if (value == null) { - throw new RuntimeException("Could not find main class in " + mainClasspath + "!"); - } else { - return value; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getArgs(Path input, Path output, Path mappings, FileCollection libraries) { - List args = this.args.stream() - .map(str -> { - return switch (str) { - case "{input}" -> input.toAbsolutePath().toString(); - case "{output}" -> output.toAbsolutePath().toString(); - case "{mappings}" -> mappings.toAbsolutePath().toString(); - default -> str; - }; - }) - .collect(Collectors.toList()); - - if (hasLibraries) { - for (File file : libraries) { - args.add("-e=" + file.getAbsolutePath()); - } - } - - return args; - } - - @Override - public String toString() { - return this.name; - } - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 0527a298..12cadcc5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -29,7 +29,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintStream; import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.FileSystem; @@ -42,10 +41,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Objects; -import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.jar.Attributes; @@ -62,8 +60,6 @@ import dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.NonClassCopyMode; import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; -import net.minecraftforge.binarypatcher.ConsoleTool; -import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.Logger; @@ -76,175 +72,156 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor; -import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; +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.McpExecutor; +import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ForgeToolExecutor; import net.fabricmc.loom.util.MappingsProviderVerbose; import net.fabricmc.loom.util.ThreadingUtils; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.function.FsPathConsumer; import net.fabricmc.loom.util.srg.InnerClassRemapper; -import net.fabricmc.loom.util.srg.SpecialSourceExecutor; import net.fabricmc.mappingio.tree.MemoryMappingTree; -public class MinecraftPatchedProvider extends MergedMinecraftProvider { +public class MinecraftPatchedProvider { private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; - private static final String CURRENT_LOOM_PATCH_VERSION = "6"; + private static final String CURRENT_LOOM_PATCH_VERSION = "7"; private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; - // Step 1: Remap Minecraft to SRG - private File minecraftClientSrgJar; - private File minecraftServerSrgJar; + private final Project project; + private final Logger logger; + private final MinecraftProvider minecraftProvider; + private final Type type; + + // Step 1: Remap Minecraft to SRG, merge if needed + private Path minecraftSrgJar; // Step 2: Binary Patch - private File minecraftClientPatchedSrgJar; - private File minecraftServerPatchedSrgJar; - // Step 3: Merge (global) - private File minecraftMergedPatchedSrgJar; - // Step 4: Access Transform - private File minecraftMergedPatchedSrgAtJar; - // Step 5: Remap Patched AT & Forge to Official - private File minecraftMergedPatchedJar; - private File minecraftClientExtra; + private Path minecraftPatchedSrgJar; + // Step 3: Access Transform + private Path minecraftPatchedSrgAtJar; + // Step 4: Remap Patched AT & Forge to official + private Path minecraftPatchedJar; + private Path minecraftClientExtra; - private boolean dirty; - private boolean serverJarInitialized = false; - - public static MergedMinecraftProvider createMergedMinecraftProvider(Project project) { - return LoomGradleExtension.get(project).isForge() ? new MinecraftPatchedProvider(project) : new MergedMinecraftProvider(project); - } + private boolean dirty = false; public static MinecraftPatchedProvider get(Project project) { MinecraftProvider provider = LoomGradleExtension.get(project).getMinecraftProvider(); - if (provider instanceof MinecraftPatchedProvider patched) { - return patched; + if (provider instanceof ForgeMinecraftProvider patched) { + return patched.getPatchedProvider(); } else { throw new UnsupportedOperationException("Project " + project.getPath() + " does not use MinecraftPatchedProvider!"); } } - public MinecraftPatchedProvider(Project project) { - super(project); + public MinecraftPatchedProvider(Project project, MinecraftProvider minecraftProvider, Type type) { + this.project = project; + this.logger = project.getLogger(); + this.minecraftProvider = minecraftProvider; + this.type = type; + } + + private LoomGradleExtension getExtension() { + return LoomGradleExtension.get(project); } private void initPatchedFiles() { String forgeVersion = getExtension().getForgeProvider().getVersion().getCombined(); - File forgeWorkingDir = dir("forge/" + forgeVersion); + Path forgeWorkingDir = minecraftProvider.dir("forge/" + forgeVersion).toPath(); String patchId = "forge-" + forgeVersion + "-"; - setJarPrefix(patchId); + minecraftProvider.setJarPrefix(patchId); - minecraftClientSrgJar = new File(forgeWorkingDir, "minecraft-client-srg.jar"); - minecraftServerSrgJar = new File(forgeWorkingDir, "minecraft-server-srg.jar"); - minecraftClientPatchedSrgJar = new File(forgeWorkingDir, "client-srg-patched.jar"); - minecraftServerPatchedSrgJar = new File(forgeWorkingDir, "server-srg-patched.jar"); - minecraftMergedPatchedSrgJar = new File(forgeWorkingDir, "merged-srg-patched.jar"); - minecraftMergedPatchedSrgAtJar = new File(forgeWorkingDir, "merged-srg-at-patched.jar"); - minecraftMergedPatchedJar = new File(forgeWorkingDir, "merged-patched.jar"); - minecraftClientExtra = new File(forgeWorkingDir, "forge-client-extra.jar"); + minecraftSrgJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg.jar"); + minecraftPatchedSrgJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg-patched.jar"); + minecraftPatchedSrgAtJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg-at-patched.jar"); + minecraftPatchedJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-patched.jar"); + minecraftClientExtra = forgeWorkingDir.resolve("forge-client-extra.jar"); } - private File getEffectiveServerJar() throws IOException { - if (getServerBundleMetadata() != null) { - if (!serverJarInitialized) { - extractBundledServerJar(); - serverJarInitialized = true; - } - - return getMinecraftExtractedServerJar(); - } else { - return getMinecraftServerJar(); + private void cleanAllCache() throws IOException { + for (Path path : getGlobalCaches()) { + Files.deleteIfExists(path); } } - public void cleanAllCache() { - for (File file : getGlobalCaches()) { - file.delete(); - } - } - - private File[] getGlobalCaches() { - File[] files = { - minecraftClientSrgJar, - minecraftServerSrgJar, - minecraftClientPatchedSrgJar, - minecraftServerPatchedSrgJar, - minecraftMergedPatchedSrgJar, + private Path[] getGlobalCaches() { + Path[] files = { + minecraftSrgJar, + minecraftPatchedSrgJar, + minecraftPatchedSrgAtJar, + minecraftPatchedJar, minecraftClientExtra, - minecraftMergedPatchedSrgAtJar, - minecraftMergedPatchedJar }; return files; } private void checkCache() throws IOException { - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) - || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) { + if (LoomGradlePlugin.refreshDeps || Stream.of(getGlobalCaches()).anyMatch(Files::notExists) + || !isPatchedJarUpToDate(minecraftPatchedJar)) { cleanAllCache(); } } - @Override public void provide() throws Exception { - super.provide(); initPatchedFiles(); checkCache(); this.dirty = false; - if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { + if (Files.notExists(minecraftSrgJar)) { this.dirty = true; - // Remap official jars to MCPConfig remapped srg jars - createSrgJars(getProject().getLogger()); + McpConfigData data = getExtension().getMcpConfigProvider().getData(); + List steps = data.steps().get(type.mcpId); + McpExecutor executor = new McpExecutor(project, minecraftProvider, Files.createTempDirectory("loom-mcp"), steps, data.functions()); + Path output = executor.executeUpTo("rename"); + Files.copy(output, minecraftSrgJar); } - if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { + if (dirty || Files.notExists(minecraftPatchedSrgJar)) { this.dirty = true; - patchJars(getProject().getLogger()); + patchJars(); } - if (dirty || !minecraftMergedPatchedSrgJar.exists()) { - mergeJars(getProject().getLogger()); - } - - if (!minecraftMergedPatchedSrgAtJar.exists()) { + if (dirty || Files.notExists(minecraftPatchedSrgAtJar)) { this.dirty = true; - accessTransformForge(getProject().getLogger()); + accessTransformForge(); } } public void remapJar() throws Exception { if (dirty) { - remapPatchedJar(getProject().getLogger()); + remapPatchedJar(); fillClientExtraJar(); } this.dirty = false; - DependencyProvider.addDependency(getProject(), minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); - } - - @Override - protected void mergeJars() throws IOException { - // Don't merge jars in the superclass + DependencyProvider.addDependency(project, minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); } private void fillClientExtraJar() throws IOException { - Files.deleteIfExists(minecraftClientExtra.toPath()); + Files.deleteIfExists(minecraftClientExtra); FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); - copyNonClassFiles(getMinecraftClientJar(), minecraftClientExtra); + copyNonClassFiles(minecraftProvider.getMinecraftClientJar().toPath(), minecraftClientExtra); } private TinyRemapper buildRemapper(Path input) throws IOException { - Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); + Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(project); MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); TinyRemapper remapper = TinyRemapper.newRemapper() - .logger(getProject().getLogger()::lifecycle) + .logger(logger::lifecycle) .logUnknownInvokeDynamic(false) .withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true)) .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official")) @@ -252,7 +229,7 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { .rebuildSourceFilenames(true) .build(); - if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + if (project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { MappingsProviderVerbose.saveFile(remapper); } @@ -261,36 +238,11 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { return remapper; } - private void createSrgJars(Logger logger) throws Exception { - produceSrgJar(super.getMinecraftClientJar().toPath(), getEffectiveServerJar().toPath()); - } - - private void produceSrgJar(Path clientJar, Path serverJar) throws IOException { - Path tmpSrg = getToSrgMappings(); - Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - - // These can't be threaded because accessing getRemapAction().getMainClass() can cause a situation where - // 1. thread A has an FS open - // 2. thread B tries to open a new one, but fails - // 3. thread A closes its FS - // 4. thread B tries to get the already open one => crash - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); - } - - private Path getToSrgMappings() throws IOException { - if (getExtension().getSrgProvider().isTsrgV2()) { - return getExtension().getSrgProvider().getMergedMojangRaw(); - } else { - return getExtension().getMcpConfigProvider().getMappings(); - } - } - - private void fixParameterAnnotation(File jarFile) throws Exception { - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); + private void fixParameterAnnotation(Path jarFile) throws Exception { + logger.info(":fixing parameter annotations for " + jarFile.toAbsolutePath()); Stopwatch stopwatch = Stopwatch.createStarted(); - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toUri()), ImmutableMap.of("create", false))) { ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { @@ -317,14 +269,14 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { completer.complete(); } - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); + logger.info(":fixed parameter annotations for " + jarFile.toAbsolutePath() + " in " + stopwatch); } - private void deleteParameterNames(File jarFile) throws Exception { - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); + private void deleteParameterNames(Path jarFile) throws Exception { + logger.info(":deleting parameter names for " + jarFile.toAbsolutePath()); Stopwatch stopwatch = Stopwatch.createStarted(); - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toUri()), ImmutableMap.of("create", false))) { ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); @@ -371,7 +323,7 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { completer.complete(); } - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); + logger.info(":deleted parameter names for " + jarFile.toAbsolutePath() + " in " + stopwatch); } private File getForgeJar() { @@ -382,10 +334,10 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { return getExtension().getForgeUserdevProvider().getUserdevJar(); } - private boolean isPatchedJarUpToDate(File jar) throws IOException { - if (!jar.exists()) return false; + private boolean isPatchedJarUpToDate(Path jar) throws IOException { + if (Files.notExists(jar)) return false; - byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); + byte[] manifestBytes = ZipUtils.unpackNullable(jar, "META-INF/MANIFEST.MF"); if (manifestBytes == null) { return false; @@ -398,68 +350,46 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { return true; } else { - getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); + logger.lifecycle(":forge patched jars not up to date. current version: " + value); return false; } } - private void accessTransformForge(Logger logger) throws Exception { - List toDelete = new ArrayList<>(); + private void accessTransformForge() throws IOException { + List toDelete = new ArrayList<>(); Stopwatch stopwatch = Stopwatch.createStarted(); logger.lifecycle(":access transforming minecraft"); - File input = minecraftMergedPatchedSrgJar; - File target = minecraftMergedPatchedSrgAtJar; - Files.deleteIfExists(target.toPath()); + Path input = minecraftPatchedSrgJar; + Path target = minecraftPatchedSrgAtJar; + Files.deleteIfExists(target); - AccessTransformerJarProcessor.executeAt(getProject(), input.toPath(), target.toPath(), args -> { - for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + AccessTransformerJarProcessor.executeAt(project, input, target, args -> { + for (Path jar : ImmutableList.of(getForgeJar().toPath(), getExtension().getForgeUserdevProvider().getUserdevJar().toPath(), minecraftPatchedSrgJar)) { + byte[] atBytes = ZipUtils.unpackNullable(jar, Constants.Forge.ACCESS_TRANSFORMER_PATH); if (atBytes != null) { - File tmpFile = File.createTempFile("at-conf", ".cfg"); + Path tmpFile = Files.createTempFile("at-conf", ".cfg"); toDelete.add(tmpFile); - Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + Files.write(tmpFile, atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); args.add("--atFile"); - args.add(tmpFile.getAbsolutePath()); + args.add(tmpFile.toAbsolutePath().toString()); } } }); - for (File file : toDelete) { - file.delete(); + for (Path file : toDelete) { + Files.delete(file); } logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); } - public enum Environment { - CLIENT(provider -> provider.minecraftClientSrgJar, - provider -> provider.minecraftClientPatchedSrgJar - ), - SERVER(provider -> provider.minecraftServerSrgJar, - provider -> provider.minecraftServerPatchedSrgJar - ); - - final Function srgJar; - final Function patchedSrgJar; - - Environment(Function srgJar, - Function patchedSrgJar) { - this.srgJar = srgJar; - this.patchedSrgJar = patchedSrgJar; - } - - public String side() { - return name().toLowerCase(Locale.ROOT); - } - } - - private void remapPatchedJar(Logger logger) throws Exception { - getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); - Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); - Path mcOutput = minecraftMergedPatchedJar.toPath(); + private void remapPatchedJar() throws Exception { + logger.lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); + Path mcInput = minecraftPatchedSrgAtJar; + Path mcOutput = minecraftPatchedJar; Path forgeJar = getForgeJar().toPath(); Path forgeUserdevJar = getForgeUserdevJar().toPath(); Files.deleteIfExists(mcOutput); @@ -482,59 +412,44 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { remapper.finish(); } - copyUserdevFiles(forgeUserdevJar.toFile(), minecraftMergedPatchedJar); + copyUserdevFiles(forgeUserdevJar, minecraftPatchedSrgJar); applyLoomPatchVersion(mcOutput); } - private void patchJars(Logger logger) throws IOException { + private void patchJars() throws Exception { Stopwatch stopwatch = Stopwatch.createStarted(); logger.lifecycle(":patching jars"); + patchJars(minecraftSrgJar, minecraftPatchedSrgJar, type.patches.apply(getExtension().getPatchProvider(), getExtension().getForgeUserdevProvider())); - PatchProvider patchProvider = getExtension().getPatchProvider(); - patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); - patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); + copyMissingClasses(minecraftSrgJar, minecraftPatchedSrgJar); + deleteParameterNames(minecraftPatchedSrgJar); - ThreadingUtils.run(MinecraftPatchedProvider.Environment.values(), environment -> { - copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); - deleteParameterNames(environment.patchedSrgJar.apply(this)); - - if (getExtension().isForgeAndNotOfficial()) { - fixParameterAnnotation(environment.patchedSrgJar.apply(this)); - } - }); + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(minecraftPatchedSrgJar); + } logger.lifecycle(":patched jars in " + stopwatch.stop()); } - private void patchJars(File clean, File output, Path patches) throws IOException { - PrintStream previous = System.out; + private void patchJars(Path clean, Path output, Path patches) { + ForgeToolExecutor.exec(project, spec -> { + ForgeUserdevProvider.BinaryPatcherConfig config = getExtension().getForgeUserdevProvider().binaryPatcherConfig; + spec.classpath(DependencyDownloader.download(project, config.dependency())); + spec.getMainClass().set("net.minecraftforge.binarypatcher.ConsoleTool"); - try { - System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - - ConsoleTool.main(new String[] { - "--clean", clean.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString() + for (String arg : config.args()) { + String actual = switch (arg) { + case "{clean}" -> clean.toAbsolutePath().toString(); + case "{output}" -> output.toAbsolutePath().toString(); + case "{patch}" -> patches.toAbsolutePath().toString(); + default -> arg; + }; + spec.args(actual); + } }); - - try { - System.setOut(previous); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } } - private void mergeJars(Logger logger) throws IOException { - // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. - // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); - } - - private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) + private void walkFileSystems(Path source, Path target, Predicate filter, Function> toWalk, FsPathConsumer action) throws IOException { try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { @@ -559,11 +474,11 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { } } - private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { + private void walkFileSystems(Path source, Path target, Predicate filter, FsPathConsumer action) throws IOException { walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); } - private void copyMissingClasses(File source, File target) throws IOException { + private void copyMissingClasses(Path source, Path target) throws IOException { walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { if (Files.exists(targetPath)) return; Path parent = targetPath.getParent(); @@ -576,7 +491,7 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { }); } - private void copyNonClassFiles(File source, File target) throws IOException { + private void copyNonClassFiles(Path source, Path target) throws IOException { Predicate filter = file -> { String s = file.toString(); return !s.endsWith(".class") && !s.startsWith("/META-INF"); @@ -595,7 +510,7 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); } - private void copyUserdevFiles(File source, File target) throws IOException { + private void copyUserdevFiles(Path source, Path target) throws IOException { // Removes the Forge name mapping service definition so that our own is used. // If there are multiple name mapping services with the same "understanding" pair // (source -> target namespace pair), modlauncher throws a fit and will crash. @@ -633,13 +548,27 @@ public class MinecraftPatchedProvider extends MergedMinecraftProvider { } } - @Override - public Path getMergedJar() { - return minecraftMergedPatchedJar.toPath(); + public Path getMinecraftPatchedSrgJar() { + return minecraftPatchedSrgJar; } - @Override - public List getMinecraftJars() { - return List.of(minecraftMergedPatchedJar.toPath()); + public Path getMinecraftPatchedJar() { + return minecraftPatchedJar; + } + + public enum Type { + CLIENT_ONLY("client", "client", (patch, userdev) -> patch.clientPatches), + SERVER_ONLY("server", "server", (patch, userdev) -> patch.serverPatches), + MERGED("merged", "joined", (patch, userdev) -> userdev.joinedPatches); + + private final String id; + private final String mcpId; + private final BiFunction patches; + + Type(String id, String mcpId, BiFunction patches) { + this.id = id; + this.mcpId = mcpId; + this.patches = patches; + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/ConfigValue.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/ConfigValue.java new file mode 100644 index 00000000..ea44b01b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/ConfigValue.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.function.Function; + +/** + * A string or a variable in an MCPConfig step or function. + * + *

The special config value variable {@value #OUTPUT} is treated + * as the current step's output path. + * + *

The suffix {@value #PREVIOUS_OUTPUT_SUFFIX} can be used to suffix step names + * to get their output paths. + */ +public sealed interface ConfigValue { + String OUTPUT = "output"; + String PREVIOUS_OUTPUT_SUFFIX = "Output"; + String SRG_MAPPINGS_NAME = "mappings"; + + R fold(Function constant, Function variable); + + static ConfigValue of(String str) { + if (str.startsWith("{") && str.endsWith("}")) { + return new Variable(str.substring(1, str.length() - 1)); + } + + return new Constant(str); + } + + record Constant(String value) implements ConfigValue { + @Override + public R fold(Function constant, Function variable) { + return constant.apply(this); + } + } + + record Variable(String name) implements ConfigValue { + @Override + public R fold(Function constant, Function variable) { + return variable.apply(this); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigData.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigData.java new file mode 100644 index 00000000..9a0bf0a2 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigData.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * Data extracted from the MCPConfig JSON file. + * + * @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> steps, Map functions) { + public static McpConfigData fromJson(JsonObject json) { + String mappingsPath = json.getAsJsonObject("data").get("mappings").getAsString(); + boolean official = json.has("official") && json.getAsJsonPrimitive("official").getAsBoolean(); + + JsonObject stepsJson = json.getAsJsonObject("steps"); + ImmutableMap.Builder> stepsBuilder = ImmutableMap.builder(); + + for (String key : stepsJson.keySet()) { + ImmutableList.Builder stepListBuilder = ImmutableList.builder(); + + for (JsonElement child : stepsJson.getAsJsonArray(key)) { + stepListBuilder.add(McpConfigStep.fromJson(child.getAsJsonObject())); + } + + stepsBuilder.put(key, stepListBuilder.build()); + } + + JsonObject functionsJson = json.getAsJsonObject("functions"); + ImmutableMap.Builder functionsBuilder = ImmutableMap.builder(); + + for (String key : functionsJson.keySet()) { + functionsBuilder.put(key, McpConfigFunction.fromJson(functionsJson.getAsJsonObject(key))); + } + + return new McpConfigData(mappingsPath, official, stepsBuilder.build(), functionsBuilder.build()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigFunction.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigFunction.java new file mode 100644 index 00000000..beb294da --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigFunction.java @@ -0,0 +1,81 @@ +/* + * 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 java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import net.fabricmc.loom.util.function.CollectionUtil; + +/** + * An executable program for {@linkplain McpConfigStep steps}. + * + * @param version the Gradle-style dependency string of the program + * @param args the command-line arguments + * @param jvmArgs the JVM arguments + * @param repo the Maven repository to download the dependency from + */ +public record McpConfigFunction(String version, List args, List jvmArgs, String repo) { + private static final String VERSION_KEY = "version"; + private static final String ARGS_KEY = "args"; + private static final String JVM_ARGS_KEY = "jvmargs"; + private static final String REPO_KEY = "repo"; + + public String getDownloadUrl() { + String[] parts = version.split(":"); + StringBuilder builder = new StringBuilder(); + builder.append(repo); + // Group: + builder.append(parts[0].replace('.', '/')).append('/'); + // Name: + builder.append(parts[1]).append('/'); + // Version: + builder.append(parts[2]).append('/'); + // Artifact: + builder.append(parts[1]).append('-').append(parts[2]); + + // Classifier: + if (parts.length >= 4) { + builder.append('-').append(parts[3]); + } + + builder.append(".jar"); + return builder.toString(); + } + + public static McpConfigFunction fromJson(JsonObject json) { + String version = json.get(VERSION_KEY).getAsString(); + List args = json.has(ARGS_KEY) ? configValuesFromJson(json.getAsJsonArray(ARGS_KEY)) : List.of(); + List jvmArgs = json.has(JVM_ARGS_KEY) ? configValuesFromJson(json.getAsJsonArray(JVM_ARGS_KEY)) : List.of(); + String repo = json.get(REPO_KEY).getAsString(); + return new McpConfigFunction(version, args, jvmArgs, repo); + } + + private static List configValuesFromJson(JsonArray json) { + return CollectionUtil.map(json, child -> ConfigValue.of(child.getAsString())); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigProvider.java new file mode 100644 index 00000000..eade43c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigProvider.java @@ -0,0 +1,116 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-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 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; +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.ZipUtils; + +public class McpConfigProvider extends DependencyProvider { + private Path mcp; + private Path configJson; + private Path mappings; + private McpConfigData data; + + public McpConfigProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency) throws Exception { + init(dependency.getDependency().getVersion()); + + Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); + + if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) { + Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); + Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + JsonObject json; + + try (Reader reader = Files.newBufferedReader(configJson)) { + json = new Gson().fromJson(reader, JsonObject.class); + } + + data = McpConfigData.fromJson(json); + } + + private void init(String version) throws IOException { + 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 (isRefreshDeps()) { + Files.deleteIfExists(mappings); + } + } + + 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 mappings; + } + + public Path getMcp() { + return mcp; + } + + public boolean isOfficial() { + return data.official(); + } + + public String getMappingsPath() { + return data.mappingsPath(); + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.MCP_CONFIG; + } + + public McpConfigData getData() { + return data; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigStep.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigStep.java new file mode 100644 index 00000000..0cb45f5f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpConfigStep.java @@ -0,0 +1,49 @@ +/* + * 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 java.util.Map; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonObject; + +public record McpConfigStep(String type, String name, Map config) { + private static final String TYPE_KEY = "type"; + private static final String NAME_KEY = "name"; + + public static McpConfigStep fromJson(JsonObject json) { + String type = json.get(TYPE_KEY).getAsString(); + String name = json.has(NAME_KEY) ? json.get(NAME_KEY).getAsString() : type; + ImmutableMap.Builder config = ImmutableMap.builder(); + + for (String key : json.keySet()) { + if (key.equals(TYPE_KEY) || key.equals(NAME_KEY)) continue; + + config.put(key, ConfigValue.of(json.get(key).getAsString())); + } + + return new McpConfigStep(type, name, config.build()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java new file mode 100644 index 00000000..7515e22a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/McpExecutor.java @@ -0,0 +1,230 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.base.Stopwatch; +import com.google.common.hash.Hashing; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.Logger; +import org.gradle.process.JavaExecSpec; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ForgeToolExecutor; +import net.fabricmc.loom.util.function.CollectionUtil; + +public final class McpExecutor { + private static final LogLevel STEP_LOG_LEVEL = LogLevel.LIFECYCLE; + private final Project project; + private final MinecraftProvider minecraftProvider; + private final Path cache; + private final List steps; + private final Map functions; + private final Map extraConfig = new HashMap<>(); + + public McpExecutor(Project project, MinecraftProvider minecraftProvider, Path cache, List steps, Map functions) { + this.project = project; + this.minecraftProvider = minecraftProvider; + this.cache = cache; + this.steps = steps; + this.functions = functions; + } + + private Path getDownloadCache() throws IOException { + Path downloadCache = cache.resolve("downloads"); + Files.createDirectories(downloadCache); + return downloadCache; + } + + private Path getStepCache(String step) { + return cache.resolve(step); + } + + private Path createStepCache(String step) throws IOException { + Path stepCache = getStepCache(step); + Files.createDirectories(stepCache); + return stepCache; + } + + private String resolve(McpConfigStep step, ConfigValue value) { + return value.fold(ConfigValue.Constant::value, variable -> { + String name = variable.name(); + @Nullable ConfigValue valueFromStep = step.config().get(name); + + // If the variable isn't defined in the step's config map, skip it. + // Also skip if it would recurse with the same variable. + if (valueFromStep != null && !valueFromStep.equals(variable)) { + // Otherwise, resolve the nested variable. + return resolve(step, valueFromStep); + } + + if (name.equals(ConfigValue.SRG_MAPPINGS_NAME)) { + return LoomGradleExtension.get(project).getSrgProvider().getSrg().toAbsolutePath().toString(); + } else if (extraConfig.containsKey(name)) { + return extraConfig.get(name); + } + + throw new IllegalArgumentException("Unknown MCP config variable: " + name); + }); + } + + public Path executeUpTo(String step) 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 currentStepIndex = 0; + + project.getLogger().log(STEP_LOG_LEVEL, ":executing {} MCP steps", totalSteps); + + for (McpConfigStep currentStep : steps) { + currentStepIndex++; + StepLogic stepLogic = getStepLogic(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) { + return switch (type) { + case "downloadManifest", "downloadJson" -> new StepLogic.NoOp(); + case "downloadClient" -> new StepLogic.NoOpWithFile(() -> minecraftProvider.getMinecraftClientJar().toPath()); + case "downloadServer" -> new StepLogic.NoOpWithFile(() -> minecraftProvider.getMinecraftServerJar().toPath()); + case "strip" -> new StepLogic.Strip(); + case "listLibraries" -> new StepLogic.ListLibraries(); + case "downloadClientMappings" -> new StepLogic.DownloadManifestFile(minecraftProvider.getVersionInfo().download("client_mappings")); + case "downloadServerMappings" -> new StepLogic.DownloadManifestFile(minecraftProvider.getVersionInfo().download("server_mappings")); + default -> { + if (functions.containsKey(type)) { + yield new StepLogic.OfFunction(functions.get(type)); + } + + throw new UnsupportedOperationException("MCP config step type: " + type); + } + }; + } + + private class ExecutionContextImpl implements StepLogic.ExecutionContext { + private final McpConfigStep step; + + ExecutionContextImpl(McpConfigStep step) { + this.step = step; + } + + @Override + public Logger logger() { + return project.getLogger(); + } + + @Override + public Path setOutput(String fileName) throws IOException { + createStepCache(step.name()); + return setOutput(getStepCache(step.name()).resolve(fileName)); + } + + @Override + public Path setOutput(Path output) { + String absolutePath = output.toAbsolutePath().toString(); + extraConfig.put(ConfigValue.OUTPUT, absolutePath); + extraConfig.put(step.name() + ConfigValue.PREVIOUS_OUTPUT_SUFFIX, absolutePath); + return output; + } + + @Override + public Path mappings() { + return LoomGradleExtension.get(project).getMcpConfigProvider().getMappings(); + } + + @Override + public String resolve(ConfigValue value) { + return McpExecutor.this.resolve(step, value); + } + + @Override + public Path download(String url) throws IOException { + Path path = getDownloadCache().resolve(Hashing.sha256().hashString(url, StandardCharsets.UTF_8).toString().substring(0, 24)); + redirectAwareDownload(url, path); + return path; + } + + // Some of these files linked to the old Forge maven, let's follow the redirects to the new one. + private static void redirectAwareDownload(String urlString, Path path) throws IOException { + URL url = new URL(urlString); + + if (url.getProtocol().equals("http")) { + url = new URL("https", url.getHost(), url.getPort(), url.getFile()); + } + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.connect(); + + if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) { + redirectAwareDownload(connection.getHeaderField("Location"), path); + } else { + try (InputStream in = connection.getInputStream()) { + Files.copy(in, path); + } + } + } + + @Override + public void javaexec(Action configurator) { + ForgeToolExecutor.exec(project, configurator).rethrowFailure().assertNormalExitValue(); + } + + @Override + public Set getMinecraftLibraries() { + return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/StepLogic.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/StepLogic.java new file mode 100644 index 00000000..b3d6b80e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/StepLogic.java @@ -0,0 +1,223 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +import org.gradle.api.Action; +import org.gradle.api.logging.Logger; +import org.gradle.process.JavaExecSpec; + +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.HashedDownloadUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.function.CollectionUtil; + +/** + * The logic for executing a step. This corresponds to the {@code type} key in the step JSON format. + */ +public interface StepLogic { + void execute(ExecutionContext context) throws IOException; + + default String getDisplayName(String stepName) { + return stepName; + } + + interface ExecutionContext { + Logger logger(); + Path setOutput(String fileName) throws IOException; + Path setOutput(Path output); + /** Mappings extracted from {@code data.mappings} in the MCPConfig JSON. */ + Path mappings(); + String resolve(ConfigValue value); + Path download(String url) throws IOException; + void javaexec(Action configurator); + Set getMinecraftLibraries(); + + default List resolve(List configValues) { + return CollectionUtil.map(configValues, this::resolve); + } + } + + /** + * Runs a Forge tool configured by a {@linkplain McpConfigFunction function}. + */ + final class OfFunction implements StepLogic { + private final McpConfigFunction function; + + public OfFunction(McpConfigFunction function) { + this.function = function; + } + + @Override + public void execute(ExecutionContext context) throws IOException { + context.setOutput("output"); + Path jar = context.download(function.getDownloadUrl()); + String mainClass; + + try (JarFile jarFile = new JarFile(jar.toFile())) { + mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); + } catch (IOException e) { + throw new IOException("Could not determine main class for " + jar.toAbsolutePath(), e); + } + + context.javaexec(spec -> { + spec.classpath(jar); + spec.getMainClass().set(mainClass); + spec.args(context.resolve(function.args())); + spec.jvmArgs(context.resolve(function.jvmArgs())); + }); + } + + @Override + public String getDisplayName(String stepName) { + return stepName + " with " + function.version(); + } + } + + /** + * Strips certain classes from the jar. + */ + final class Strip implements StepLogic { + @Override + public void execute(ExecutionContext context) throws IOException { + Set filter = Files.readAllLines(context.mappings(), StandardCharsets.UTF_8).stream() + .filter(s -> !s.startsWith("\t")) + .map(s -> s.split(" ")[0] + ".class") + .collect(Collectors.toSet()); + + Path input = Path.of(context.resolve(new ConfigValue.Variable("input"))); + + try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(context.setOutput("stripped.jar"), true)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input, false)) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path path : (Iterable) Files.walk(fs.get().getPath("/"))::iterator) { + String trimLeadingSlash = trimLeadingSlash(path.toString()); + if (!trimLeadingSlash.endsWith(".class")) continue; + boolean has = filter.contains(trimLeadingSlash); + String s = trimLeadingSlash; + + while (s.contains("$") && !has) { + s = s.substring(0, s.lastIndexOf("$")) + ".class"; + has = filter.contains(s); + } + + if (!has) continue; + Path to = output.get().getPath(trimLeadingSlash); + Path parent = to.getParent(); + if (parent != null) Files.createDirectories(parent); + + completer.add(() -> { + Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES); + }); + } + + completer.complete(); + } + } + } + + private static String trimLeadingSlash(String string) { + if (string.startsWith(File.separator)) { + return string.substring(File.separator.length()); + } else if (string.startsWith("/")) { + return string.substring(1); + } + + return string; + } + } + + /** + * Lists the Minecraft libraries into the output file. + */ + final class ListLibraries implements StepLogic { + @Override + public void execute(ExecutionContext context) throws IOException { + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(context.setOutput("libraries.txt")))) { + for (File lib : context.getMinecraftLibraries()) { + writer.println("-e=" + lib.getAbsolutePath()); + } + } + } + } + + /** + * Downloads a file from the Minecraft version metadata. + */ + final class DownloadManifestFile implements StepLogic { + private final MinecraftVersionMeta.Download download; + + public DownloadManifestFile(MinecraftVersionMeta.Download download) { + this.download = download; + } + + @Override + public void execute(ExecutionContext context) throws IOException { + HashedDownloadUtil.downloadIfInvalid(new URL(download.url()), context.setOutput("output").toFile(), download.sha1(), context.logger(), false); + } + } + + /** + * A no-op step logic that is used for steps automatically executed by Loom earlier. + */ + final class NoOp implements StepLogic { + @Override + public void execute(ExecutionContext context) throws IOException { + } + } + + /** + * A no-op step logic that is used for steps automatically executed by Loom earlier. + * This one returns a file. + */ + final class NoOpWithFile implements StepLogic { + private final Supplier path; + + public NoOpWithFile(Supplier path) { + this.path = path; + } + + @Override + public void execute(ExecutionContext context) throws IOException { + context.setOutput(path.get()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/package-info.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/package-info.java new file mode 100644 index 00000000..f982dc14 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/mcpconfig/package-info.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/** + * 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; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/ForgeMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/ForgeMinecraftProvider.java new file mode 100644 index 00000000..6769dd54 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/ForgeMinecraftProvider.java @@ -0,0 +1,52 @@ +/* + * 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.minecraft; + +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.MergedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider; + +/** + * A {@link net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider} that + * provides a Forge patched Minecraft jar. + */ +public interface ForgeMinecraftProvider { + MinecraftPatchedProvider getPatchedProvider(); + + static MergedMinecraftProvider createMerged(Project project) { + return LoomGradleExtension.get(project).isForge() ? new MergedForgeMinecraftProvider(project) : new MergedMinecraftProvider(project); + } + + static SingleJarMinecraftProvider createServerOnly(Project project) { + return LoomGradleExtension.get(project).isForge() ? SingleJarForgeMinecraftProvider.server(project) : SingleJarMinecraftProvider.server(project); + } + + static SingleJarMinecraftProvider createClientOnly(Project project) { + return LoomGradleExtension.get(project).isForge() ? SingleJarForgeMinecraftProvider.client(project) : SingleJarMinecraftProvider.client(project); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/MergedForgeMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/MergedForgeMinecraftProvider.java new file mode 100644 index 00000000..e6aff590 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/MergedForgeMinecraftProvider.java @@ -0,0 +1,69 @@ +/* + * 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.minecraft; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; + +public final class MergedForgeMinecraftProvider extends MergedMinecraftProvider implements ForgeMinecraftProvider { + private final MinecraftPatchedProvider patchedProvider; + + public MergedForgeMinecraftProvider(Project project) { + super(project); + this.patchedProvider = new MinecraftPatchedProvider(project, this, MinecraftPatchedProvider.Type.MERGED); + } + + @Override + public void provide() throws Exception { + super.provide(); + patchedProvider.provide(); + } + + @Override + protected void mergeJars() throws IOException { + // Don't merge jars in the superclass + } + + @Override + public Path getMergedJar() { + return patchedProvider.getMinecraftPatchedJar(); + } + + @Override + public List getMinecraftJars() { + return List.of(patchedProvider.getMinecraftPatchedJar()); + } + + @Override + public MinecraftPatchedProvider getPatchedProvider() { + return patchedProvider; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/SingleJarForgeMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/SingleJarForgeMinecraftProvider.java new file mode 100644 index 00000000..2ac3bf7e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/minecraft/SingleJarForgeMinecraftProvider.java @@ -0,0 +1,76 @@ +/* + * 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.minecraft; + +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider; + +public final class SingleJarForgeMinecraftProvider extends SingleJarMinecraftProvider implements ForgeMinecraftProvider { + private final MinecraftPatchedProvider patchedProvider; + + private SingleJarForgeMinecraftProvider(Project project, SingleJarMinecraftProvider.Environment environment) { + super(project, environment); + this.patchedProvider = new MinecraftPatchedProvider(project, this, provideServer() ? MinecraftPatchedProvider.Type.SERVER_ONLY : MinecraftPatchedProvider.Type.CLIENT_ONLY); + } + + public static SingleJarForgeMinecraftProvider server(Project project) { + return new SingleJarForgeMinecraftProvider(project, new Server()); + } + + public static SingleJarForgeMinecraftProvider client(Project project) { + return new SingleJarForgeMinecraftProvider(project, new Client()); + } + + @Override + protected boolean provideClient() { + // the client jar is needed for client-extra which the Forge userdev launch thing always checks for + return true; + } + + @Override + protected void processJar() throws Exception { + patchedProvider.provide(); + } + + @Override + public MinecraftPatchedProvider getPatchedProvider() { + return patchedProvider; + } + + @Override + public Path getMinecraftEnvOnlyJar() { + return patchedProvider.getMinecraftPatchedJar(); + } + + @Override + public List getMinecraftJars() { + return List.of(patchedProvider.getMinecraftPatchedJar()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java index b9778488..39a2c982 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java @@ -34,7 +34,7 @@ import net.fabricmc.loom.configuration.decompile.DecompileConfiguration; import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration; import net.fabricmc.loom.configuration.decompile.SplitDecompileConfiguration; import net.fabricmc.loom.configuration.processors.JarProcessorManager; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; @@ -43,7 +43,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftPr public enum MinecraftJarConfiguration { MERGED( - MinecraftPatchedProvider::createMergedMinecraftProvider, + ForgeMinecraftProvider::createMerged, IntermediaryMinecraftProvider.MergedImpl::new, NamedMinecraftProvider.MergedImpl::new, SrgMinecraftProvider.MergedImpl::new, @@ -52,7 +52,7 @@ public enum MinecraftJarConfiguration { List.of("client", "server") ), SERVER_ONLY( - SingleJarMinecraftProvider::server, + ForgeMinecraftProvider::createServerOnly, IntermediaryMinecraftProvider.SingleJarImpl::server, NamedMinecraftProvider.SingleJarImpl::server, SrgMinecraftProvider.SingleJarImpl::server, @@ -61,7 +61,7 @@ public enum MinecraftJarConfiguration { List.of("server") ), CLIENT_ONLY( - SingleJarMinecraftProvider::client, + ForgeMinecraftProvider::createClientOnly, IntermediaryMinecraftProvider.SingleJarImpl::client, NamedMinecraftProvider.SingleJarImpl::client, SrgMinecraftProvider.SingleJarImpl::client, diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java index edd237fd..0f174fd2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java @@ -35,12 +35,12 @@ import org.gradle.api.Project; import net.fabricmc.loom.configuration.providers.BundleMetadata; -public final class SingleJarMinecraftProvider extends MinecraftProvider { +public class SingleJarMinecraftProvider extends MinecraftProvider { private final Environment environment; private Path minecraftEnvOnlyJar; - private SingleJarMinecraftProvider(Project project, Environment environment) { + protected SingleJarMinecraftProvider(Project project, Environment environment) { super(project); this.environment = environment; } @@ -68,7 +68,10 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider { @Override public void provide() throws Exception { super.provide(); + processJar(); + } + protected void processJar() throws Exception { boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftEnvOnlyJar); if (!requiresRefresh) { @@ -114,13 +117,13 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider { return minecraftEnvOnlyJar; } - private interface Environment { + protected interface Environment { String name(); Path getInputJar(SingleJarMinecraftProvider provider) throws Exception; } - private static final class Server implements Environment { + public static final class Server implements Environment { @Override public String name() { return "server"; @@ -139,7 +142,7 @@ public final class SingleJarMinecraftProvider extends MinecraftProvider { } } - private static final class Client implements Environment { + public static final class Client implements Environment { @Override public String name() { return "client"; diff --git a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java index 1678e425..4be09d90 100644 --- a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -154,14 +154,14 @@ public class ForgeSourcesRemapper { PrintStream out = System.out; PrintStream err = System.err; - if (!ForgeToolExecutor.shouldShowVerboseOutput(project)) { + if (!ForgeToolExecutor.shouldShowVerboseStderr(project)) { System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); } remapForgeSourcesInner(project, tmpInput.toPath(), tmpOutput.toPath()); - if (!ForgeToolExecutor.shouldShowVerboseOutput(project)) { + if (!ForgeToolExecutor.shouldShowVerboseStderr(project)) { System.setOut(out); System.setErr(err); } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index b90ba2fb..37917bb7 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -129,6 +129,8 @@ public class Constants { public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:"; public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:"; public static final String UNPROTECT = "io.github.juuxel:unprotect:"; + // Used to upgrade the ASM version for the AT tool. + public static final String ASM = "org.ow2.asm:asm:"; private Dependencies() { } @@ -147,6 +149,7 @@ public class Constants { public static final String ACCESS_TRANSFORMERS = "3.0.1"; public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5"; public static final String UNPROTECT = "1.0.0"; + public static final String ASM = "9.3"; private Versions() { } diff --git a/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java index ede29cc0..d4c43c1d 100644 --- a/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java +++ b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -26,6 +26,7 @@ package net.fabricmc.loom.util; import java.io.File; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,30 +37,58 @@ import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.file.FileCollection; /** - * Simplified dependency downloading. + * Simplified but powerful dependency downloading. * * @author Juuz */ public final class DependencyDownloader { + private final Project project; + private final Set dependencies = new HashSet<>(); + + public DependencyDownloader(Project project) { + this.project = project; + } + + /** + * Adds a dependency to download. + * + * @param dependencyNotation the dependency notation + * @return this downloader + */ + public DependencyDownloader add(String dependencyNotation) { + dependencies.add(dependencyNotation); + return this; + } + + /** + * Resolves the dependencies as well as their transitive dependencies into a {@link FileCollection}. + * + * @return the resolved files + */ + public FileCollection download() { + return download(true, false); + } + /** * Resolves a dependency as well as its transitive dependencies into a {@link FileCollection}. * - * @param project the project needing these files - * @param dependencyNotation the dependency notation + * @param transitive whether to include transitive dependencies + * @param resolve whether to eagerly resolve the file collection * @return the resolved files */ - public static FileCollection download(Project project, String dependencyNotation) { - return download(project, dependencyNotation, true, false); - } + public FileCollection download(boolean transitive, boolean resolve) { + Dependency[] dependencies = this.dependencies.stream() + .map(notation -> { + Dependency dependency = project.getDependencies().create(notation); - public static FileCollection download(Project project, String dependencyNotation, boolean transitive, boolean resolve) { - Dependency dependency = project.getDependencies().create(dependencyNotation); + if (dependency instanceof ModuleDependency md) { + md.setTransitive(transitive); + } - if (dependency instanceof ModuleDependency) { - ((ModuleDependency) dependency).setTransitive(transitive); - } + return dependency; + }).toArray(Dependency[]::new); - Configuration config = project.getConfigurations().detachedConfiguration(dependency); + Configuration config = project.getConfigurations().detachedConfiguration(dependencies); config.setTransitive(transitive); FileCollection files = config.fileCollection(dep -> true); @@ -70,6 +99,21 @@ public final class DependencyDownloader { return files; } + /** + * Resolves a dependency as well as its transitive dependencies into a {@link FileCollection}. + * + * @param project the project needing these files + * @param dependencyNotation the dependency notation + * @return the resolved files + */ + public static FileCollection download(Project project, String dependencyNotation) { + return new DependencyDownloader(project).add(dependencyNotation).download(); + } + + public static FileCollection download(Project project, String dependencyNotation, boolean transitive, boolean resolve) { + return new DependencyDownloader(project).add(dependencyNotation).download(transitive, resolve); + } + private static List collectDependencies(Configuration configuration) { List dependencies = new ArrayList<>(); diff --git a/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java b/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java index fe5dd842..ef630ec3 100644 --- a/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/ForgeToolExecutor.java @@ -37,11 +37,14 @@ import org.gradle.process.JavaExecSpec; * with suppressed output streams to prevent annoying log spam. */ public final class ForgeToolExecutor { - public static boolean shouldShowVerboseOutput(Project project) { - // if running with INFO or DEBUG logging or stacktraces visible - // (stacktrace so errors printed to standard streams show up) - return project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0; + public static boolean shouldShowVerboseStdout(Project project) { + // if running with INFO or DEBUG logging + return project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0; + } + + public static boolean shouldShowVerboseStderr(Project project) { + // if stdout is shown or stacktraces are visible so that errors printed to stderr show up + return shouldShowVerboseStdout(project) || project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS; } /** @@ -55,11 +58,15 @@ public final class ForgeToolExecutor { return project.javaexec(spec -> { configurator.execute(spec); - if (shouldShowVerboseOutput(project)) { + if (shouldShowVerboseStdout(project)) { spec.setStandardOutput(System.out); - spec.setErrorOutput(System.err); } else { spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + + if (shouldShowVerboseStderr(project)) { + spec.setErrorOutput(System.err); + } else { spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); } }); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java deleted file mode 100644 index 06084d57..00000000 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.util.srg; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.common.base.Stopwatch; -import org.gradle.api.Project; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; -import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ForgeToolExecutor; -import net.fabricmc.loom.util.ThreadingUtils; - -public class SpecialSourceExecutor { - private static String trimLeadingSlash(String string) { - if (string.startsWith(File.separator)) { - return string.substring(File.separator.length()); - } else if (string.startsWith("/")) { - return string.substring(1); - } - - return string; - } - - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings) - throws IOException { - Set filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() - .filter(s -> !s.startsWith("\t")) - .map(s -> s.split(" ")[0] + ".class") - .collect(Collectors.toSet()); - LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); - Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); - Files.deleteIfExists(stripped); - - Stopwatch stopwatch = Stopwatch.createStarted(); - - try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) { - try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - - for (Path path : (Iterable) Files.walk(fs.get().getPath("/"))::iterator) { - String trimLeadingSlash = trimLeadingSlash(path.toString()); - if (!trimLeadingSlash.endsWith(".class")) continue; - boolean has = filter.contains(trimLeadingSlash); - String s = trimLeadingSlash; - - while (s.contains("$") && !has) { - s = s.substring(0, s.lastIndexOf("$")) + ".class"; - has = filter.contains(s); - } - - if (!has) continue; - Path to = output.get().getPath(trimLeadingSlash); - Path parent = to.getParent(); - if (parent != null) Files.createDirectories(parent); - - completer.add(() -> { - Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES); - }); - } - - completer.complete(); - } - } finally { - project.getLogger().info("Copied class files in " + stopwatch.stop()); - } - - Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); - Files.deleteIfExists(output); - stopwatch = Stopwatch.createStarted(); - - List args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); - - project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)"); - - Path workingDir = tmpDir(); - - ForgeToolExecutor.exec(project, spec -> { - spec.setArgs(args); - spec.setClasspath(remapAction.getClasspath()); - spec.workingDir(workingDir.toFile()); - spec.getMainClass().set(remapAction.getMainClass()); - }).rethrowFailure().assertNormalExitValue(); - - project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop()); - - Files.deleteIfExists(stripped); - - Path tmp = tmpFile(); - Files.deleteIfExists(tmp); - Files.copy(output, tmp); - - Files.deleteIfExists(output); - return tmp; - } - - private static Path tmpFile() throws IOException { - return Files.createTempFile(null, null); - } - - private static Path tmpDir() throws IOException { - return Files.createTempDirectory(null); - } -}