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
This commit is contained in:
Juuxel
2022-05-22 18:41:27 +03:00
committed by GitHub
parent 8ba98091a0
commit 70def8a31d
27 changed files with 1339 additions and 643 deletions

View File

@@ -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')

View File

@@ -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;

View File

@@ -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

View File

@@ -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<String> args = new ArrayList<>();
args.add("--inJar");
args.add(input.toAbsolutePath().toString());

View File

@@ -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);
}

View File

@@ -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<? extends Path>) 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<? extends Path>) 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);
});
}
}

View File

@@ -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<String, JsonElement> 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<String> args) {
public static BinaryPatcherConfig fromJson(JsonObject json) {
String dependency = json.get("version").getAsString();
List<String> args = List.of(LoomGradlePlugin.GSON.fromJson(json.get("args"), String[].class));
return new BinaryPatcherConfig(dependency, args);
}
}
}

View File

@@ -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<String> 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<String> 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<String> getArgs(Path input, Path output, Path mappings, FileCollection libraries) {
List<String> 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;
}
}
}

View File

@@ -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>) 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<McpConfigStep> steps = data.steps().get(type.mcpId);
McpExecutor executor = new McpExecutor(project, minecraftProvider, Files.createTempDirectory("loom-mcp"), steps, data.functions());
Path output = executor.executeUpTo("rename");
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<File> 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<? extends Path>) 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<File> toDelete = new ArrayList<>();
private void accessTransformForge() throws IOException {
List<Path> 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<MinecraftPatchedProvider, File> srgJar;
final Function<MinecraftPatchedProvider, File> patchedSrgJar;
Environment(Function<MinecraftPatchedProvider, File> srgJar,
Function<MinecraftPatchedProvider, File> 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<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action)
private void walkFileSystems(Path source, Path target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> 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<Path> filter, FsPathConsumer action) throws IOException {
private void walkFileSystems(Path source, Path target, Predicate<Path> 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<Path> 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<Path> 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<PatchProvider, ForgeUserdevProvider, Path> patches;
Type(String id, String mcpId, BiFunction<PatchProvider, ForgeUserdevProvider, Path> patches) {
this.id = id;
this.mcpId = mcpId;
this.patches = patches;
}
}
}

View File

@@ -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.
*
* <p>The special config value variable {@value #OUTPUT} is treated
* as the current step's output path.
*
* <p>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> R fold(Function<? super Constant, ? extends R> constant, Function<? super Variable, ? extends R> 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> R fold(Function<? super Constant, ? extends R> constant, Function<? super Variable, ? extends R> variable) {
return constant.apply(this);
}
}
record Variable(String name) implements ConfigValue {
@Override
public <R> R fold(Function<? super Constant, ? extends R> constant, Function<? super Variable, ? extends R> variable) {
return variable.apply(this);
}
}
}

View File

@@ -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<String, List<McpConfigStep>> steps, Map<String, McpConfigFunction> 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<String, List<McpConfigStep>> stepsBuilder = ImmutableMap.builder();
for (String key : stepsJson.keySet()) {
ImmutableList.Builder<McpConfigStep> 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<String, McpConfigFunction> 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());
}
}

View File

@@ -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<ConfigValue> args, List<ConfigValue> 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<ConfigValue> args = json.has(ARGS_KEY) ? configValuesFromJson(json.getAsJsonArray(ARGS_KEY)) : List.of();
List<ConfigValue> 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<ConfigValue> configValuesFromJson(JsonArray json) {
return CollectionUtil.map(json, child -> ConfigValue.of(child.getAsString()));
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, ConfigValue> 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<String, ConfigValue> 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());
}
}

View File

@@ -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<McpConfigStep> steps;
private final Map<String, McpConfigFunction> functions;
private final Map<String, String> extraConfig = new HashMap<>();
public McpExecutor(Project project, MinecraftProvider minecraftProvider, Path cache, List<McpConfigStep> steps, Map<String, McpConfigFunction> 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<? super JavaExecSpec> configurator) {
ForgeToolExecutor.exec(project, configurator).rethrowFailure().assertNormalExitValue();
}
@Override
public Set<File> getMinecraftLibraries() {
return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve();
}
}
}

View File

@@ -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<? super JavaExecSpec> configurator);
Set<File> getMinecraftLibraries();
default List<String> resolve(List<ConfigValue> 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<String> 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<? extends Path>) 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> path;
public NoOpWithFile(Supplier<Path> path) {
this.path = path;
}
@Override
public void execute(ExecutionContext context) throws IOException {
context.setOutput(path.get());
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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<Path> getMinecraftJars() {
return List.of(patchedProvider.getMinecraftPatchedJar());
}
@Override
public MinecraftPatchedProvider getPatchedProvider() {
return patchedProvider;
}
}

View File

@@ -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<Path> getMinecraftJars() {
return List.of(patchedProvider.getMinecraftPatchedJar());
}
}

View File

@@ -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,

View File

@@ -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";

View File

@@ -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);
}

View File

@@ -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() {
}

View File

@@ -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<String> 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<Dependency> collectDependencies(Configuration configuration) {
List<Dependency> dependencies = new ArrayList<>();

View File

@@ -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);
}
});

View File

@@ -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<File> mcLibs, Path officialJar, Path mappings)
throws IOException {
Set<String> 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<? extends Path>) 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<String> 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);
}
}