From 71b7bea8548df06cd4b580fcf77c28d363dd2587 Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 11 Sep 2023 11:29:01 +0100 Subject: [PATCH] Support the Vineflower decompiler (#951) --- build.gradle | 76 ++++++- gradle/runtime.libs.versions.toml | 2 + gradle/test.libs.versions.toml | 2 +- .../cfr/CFRObfuscationMapping.java | 3 +- .../loom/decompilers/cfr/CFRSinkFactory.java | 16 +- .../decompilers/cfr/LoomCFRDecompiler.java | 23 ++- .../decompilers/LoomInternalDecompiler.java} | 43 ++-- .../FabricFernFlowerDecompiler.java | 86 ++++++++ .../fernflower/FernflowerLogger.java | 6 +- .../fernflower/ThreadSafeResultSaver.java | 0 .../fernflower/TinyJavadocProvider.java | 15 +- .../vineflower/ThreadSafeResultSaver.java | 172 ++++++++++++++++ .../vineflower/TinyJavadocProvider.java | 188 ++++++++++++++++++ .../vineflower/VineflowerDecompiler.java} | 26 +-- .../vineflower/VineflowerLogger.java | 87 ++++++++ .../decompilers/DecompilerConfiguration.java | 108 +++++++++- .../test/integration/DecompileTest.groovy | 1 + 17 files changed, 790 insertions(+), 64 deletions(-) rename src/{main/java => decompilers/cfr}/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java (98%) rename src/{main/java => decompilers/cfr}/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java (90%) rename src/{main/java => decompilers/cfr}/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java (87%) rename src/{main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java => decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java} (63%) create mode 100644 src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java rename src/{main/java => decompilers/fernflower}/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java (92%) rename src/{main/java => decompilers/fernflower}/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java (100%) rename src/{main/java => decompilers/fernflower}/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java (93%) create mode 100644 src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java create mode 100644 src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java rename src/{main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java => decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java} (73%) create mode 100644 src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java diff --git a/build.gradle b/build.gradle index 4c6034e4..014f8da9 100644 --- a/build.gradle +++ b/build.gradle @@ -8,11 +8,37 @@ plugins { id 'checkstyle' id 'jacoco' id 'codenarc' - alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first. alias(libs.plugins.spotless) alias(libs.plugins.retry) } +/** + * Haha this is fun :) The Kotlin gradle plugin triggers deprecation warnings for custom configurations (https://youtrack.jetbrains.com/issue/KT-60879) + * We need to make DefaultConfiguration.isSpecialCaseOfChangingUsage think that our configurstion is a special case and not deprecated. + * We do this by setting DefaultConfiguration.roleAtCreation to LEGACY, thus isInLegacyRole will now return true. + * + * Yeah I know we can just ignore the deprecation warning, but doing so wouldn't alert us to issues when testing against pre-release Gradle versions. Also this is more fun :) + */ +def brokenConfigurations = [ + "commonDecompilerRuntimeClasspath", + "fernflowerRuntimeClasspath", + "cfrRuntimeClasspath", + "vineflowerRuntimeClasspath" +] + +configurations.configureEach { + if (brokenConfigurations.contains(it.name)) { + // For some reason Gradle stops us from using Groovy magic to do this, so lets do it the boring way. + def field = org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.class.getDeclaredField("roleAtCreation") + field.setAccessible(true) + field.set(it, ConfigurationRoles.LEGACY) + } +} + +// Ensure we apply the Kotlin plugin after, to allow for the above configuration to take place first +apply plugin: libs.plugins.kotlin.get().pluginId + tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" } @@ -62,6 +88,29 @@ configurations.all { } } +sourceSets { + commonDecompiler { + java { + srcDir("src/decompilers/common") + } + } + fernflower { + java { + srcDir("src/decompilers/fernflower") + } + } + cfr { + java { + srcDir("src/decompilers/cfr") + } + } + vineflower { + java { + srcDir("src/decompilers/vineflower") + } + } +} + dependencies { implementation gradleApi() @@ -89,8 +138,23 @@ dependencies { } // decompilers - compileOnly runtimeLibs.fernflower - compileOnly runtimeLibs.cfr + fernflowerCompileOnly runtimeLibs.fernflower + fernflowerCompileOnly libs.fabric.mapping.io + + cfrCompileOnly runtimeLibs.cfr + cfrCompileOnly libs.fabric.mapping.io + + vineflowerCompileOnly runtimeLibs.vineflower + vineflowerCompileOnly libs.fabric.mapping.io + + fernflowerApi sourceSets.commonDecompiler.output + cfrApi sourceSets.commonDecompiler.output + vineflowerApi sourceSets.commonDecompiler.output + + implementation sourceSets.commonDecompiler.output + implementation sourceSets.fernflower.output + implementation sourceSets.cfr.output + implementation sourceSets.vineflower.output // source code remapping implementation libs.fabric.mercury @@ -130,6 +194,10 @@ jar { } from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) } + from sourceSets.commonDecompiler.output.classesDirs + from sourceSets.cfr.output.classesDirs + from sourceSets.fernflower.output.classesDirs + from sourceSets.vineflower.output.classesDirs } base { @@ -222,6 +290,8 @@ test { } } + +import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles import org.gradle.launcher.cli.KotlinDslVersion import org.gradle.util.GradleVersion import org.w3c.dom.Document diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index d67b9fd1..fc707f6c 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -2,6 +2,7 @@ # Decompilers fernflower = "2.0.0" cfr = "0.2.1" +vineflower = "1.9.3" # Runtime depedencies mixin-compile-extensions = "0.6.0" @@ -14,6 +15,7 @@ native-support = "1.0.1" # Decompilers fernflower = { module = "net.fabricmc:fabric-fernflower", version.ref = "fernflower" } cfr = { module = "net.fabricmc:cfr", version.ref = "cfr" } +vineflower = { module = "org.vineflower:vineflower", version.ref = "vineflower" } # Runtime depedencies mixin-compile-extensions = { module = "net.fabricmc:fabric-mixin-compile-extensions", version.ref = "mixin-compile-extensions" } diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 4c2b6573..1c637f77 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.4.0" java-debug = "0.48.0" mixin = "0.11.4+mixin.0.8.5" -gradle-nightly = "8.4-20230821223421+0000" +gradle-nightly = "8.5-20230908221250+0000" fabric-loader = "0.14.22" fabric-installer = "0.11.1" diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java similarity index 98% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java index aae26019..34236a43 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java @@ -45,7 +45,6 @@ import org.benf.cfr.reader.mapping.NullMapping; import org.benf.cfr.reader.util.output.DelegatingDumper; import org.benf.cfr.reader.util.output.Dumper; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.tree.MappingTree; @@ -66,7 +65,7 @@ public class CFRObfuscationMapping extends NullMapping { private static MappingTree readMappings(Path input) { try (BufferedReader reader = Files.newBufferedReader(input)) { MemoryMappingTree mappingTree = new MemoryMappingTree(); - MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString()); + MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named"); MappingReader.read(reader, nsSwitch); return mappingTree; diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java similarity index 90% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java index bdc5a28a..67a89473 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.decompilers.cfr; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -37,23 +38,18 @@ import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; -import com.google.common.base.Charsets; import org.benf.cfr.reader.api.OutputSinkFactory; import org.benf.cfr.reader.api.SinkReturns; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import net.fabricmc.loom.util.IOStringConsumer; +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; public class CFRSinkFactory implements OutputSinkFactory { - private static final Logger ERROR_LOGGER = LoggerFactory.getLogger(CFRSinkFactory.class); - private final JarOutputStream outputStream; - private final IOStringConsumer logger; + private final LoomInternalDecompiler.Logger logger; private final Set addedDirectories = new HashSet<>(); private final Map> lineMap = new TreeMap<>(); - public CFRSinkFactory(JarOutputStream outputStream, IOStringConsumer logger) { + public CFRSinkFactory(JarOutputStream outputStream, LoomInternalDecompiler.Logger logger) { this.outputStream = outputStream; this.logger = logger; } @@ -72,7 +68,7 @@ public class CFRSinkFactory implements OutputSinkFactory { return switch (sinkType) { case JAVA -> (Sink) decompiledSink(); case LINENUMBER -> (Sink) lineNumberMappingSink(); - case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e); + case EXCEPTION -> (e) -> logger.error((String) e); default -> null; }; } @@ -83,7 +79,7 @@ public class CFRSinkFactory implements OutputSinkFactory { if (!filename.isEmpty()) filename += "/"; filename += sinkable.getClassName() + ".java"; - byte[] data = sinkable.getJava().getBytes(Charsets.UTF_8); + byte[] data = sinkable.getJava().getBytes(StandardCharsets.UTF_8); writeToJar(filename, data); }; diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java similarity index 87% rename from src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java rename to src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java index de0b74d8..72f7b2e5 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java +++ b/src/decompilers/cfr/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java @@ -45,10 +45,9 @@ import org.benf.cfr.reader.util.getopt.Options; import org.benf.cfr.reader.util.getopt.OptionsImpl; import org.benf.cfr.reader.util.output.SinkDumperFactory; -import net.fabricmc.loom.api.decompilers.DecompilationMetadata; -import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; -public final class LoomCFRDecompiler implements LoomDecompiler { +public final class LoomCFRDecompiler implements LoomInternalDecompiler { private static final Map DECOMPILE_OPTIONS = Map.of( "renameillegalidents", "true", "trackbytecodeloc", "true", @@ -56,16 +55,18 @@ public final class LoomCFRDecompiler implements LoomDecompiler { ); @Override - public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { + public void decompile(LoomInternalDecompiler.Context context) { + Path compiledJar = context.compiledJar(); + final String path = compiledJar.toAbsolutePath().toString(); final Map allOptions = new HashMap<>(DECOMPILE_OPTIONS); - allOptions.putAll(metaData.options()); + allOptions.putAll(context.options()); final Options options = OptionsImpl.getFactory().create(allOptions); ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options); - for (Path library : metaData.libraries()) { + for (Path library : context.libraries()) { classFileSource.addJarContent(library.toAbsolutePath().toString(), AnalysisType.JAR); } @@ -73,8 +74,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler { DCCommonState state = new DCCommonState(options, classFileSource); - if (metaData.javaDocs() != null) { - state = new DCCommonState(state, new CFRObfuscationMapping(metaData.javaDocs())); + if (context.javaDocs() != null) { + state = new DCCommonState(state, new CFRObfuscationMapping(context.javaDocs())); } final Manifest manifest = new Manifest(); @@ -82,8 +83,8 @@ public final class LoomCFRDecompiler implements LoomDecompiler { Map> lineMap; - try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(sourcesDestination), manifest)) { - CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, metaData.logger()); + try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(context.sourcesDestination()), manifest)) { + CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, context.logger()); SinkDumperFactory dumperFactory = new SinkDumperFactory(cfrSinkFactory, options); Driver.doJar(state, path, AnalysisType.JAR, dumperFactory); @@ -93,7 +94,7 @@ public final class LoomCFRDecompiler implements LoomDecompiler { throw new UncheckedIOException("Failed to decompile", e); } - writeLineMap(linemapDestination, lineMap); + writeLineMap(context.linemapDestination(), lineMap); } private void writeLineMap(Path output, Map> lineMap) { diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java b/src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java similarity index 63% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java rename to src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java index ee07f045..ad687052 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernFlowerUtils.java +++ b/src/decompilers/common/net/fabricmc/loom/decompilers/LoomInternalDecompiler.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2022 FabricMC + * Copyright (c) 2023 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 @@ -22,23 +22,40 @@ * SOFTWARE. */ -package net.fabricmc.loom.decompilers.fernflower; +package net.fabricmc.loom.decompilers; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; -import org.jetbrains.java.decompiler.util.InterpreterUtil; +// This is an internal interface to loom, DO NOT USE this in your own plugins. +public interface LoomInternalDecompiler { + void decompile(Context context); -import net.fabricmc.loom.util.ZipUtils; + interface Context { + Path compiledJar(); -public class FernFlowerUtils { - public static byte[] getBytecode(String externalPath, String internalPath) throws IOException { - File file = new File(externalPath); + Path sourcesDestination(); - if (internalPath == null) { - return InterpreterUtil.getBytes(file); - } else { - return ZipUtils.unpack(file.toPath(), internalPath); - } + Path linemapDestination(); + + int numberOfThreads(); + + Path javaDocs(); + + Collection libraries(); + + Logger logger(); + + Map options(); + + byte[] unpackZip(Path zip, String path) throws IOException; + } + + interface Logger { + void accept(String data) throws IOException; + + void error(String msg); } } diff --git a/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java new file mode 100644 index 00000000..f0e33ee2 --- /dev/null +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java @@ -0,0 +1,86 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2019-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.decompilers.fernflower; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import net.fabricmc.fernflower.api.IFabricJavadocProvider; +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; + +public final class FabricFernFlowerDecompiler implements LoomInternalDecompiler { + @Override + public void decompile(LoomInternalDecompiler.Context context) { + Path sourcesDestination = context.sourcesDestination(); + Path linemapDestination = context.linemapDestination(); + + final Map options = new HashMap<>( + Map.of( + IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1", + IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", + IFernflowerPreferences.REMOVE_SYNTHETIC, "1", + IFernflowerPreferences.LOG_LEVEL, "trace", + IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()), + IFernflowerPreferences.INDENT_STRING, "\t", + IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile()) + ) + ); + + options.putAll(context.options()); + + IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile); + Fernflower ff = new Fernflower((externalPath, internalPath) -> FabricFernFlowerDecompiler.this.getBytecode(externalPath, internalPath, context), saver, options, new FernflowerLogger(context.logger())); + + for (Path library : context.libraries()) { + ff.addLibrary(library.toFile()); + } + + ff.addSource(context.compiledJar().toFile()); + + try { + ff.decompileContext(); + } finally { + ff.clearContext(); + } + } + + private byte[] getBytecode(String externalPath, String internalPath, LoomInternalDecompiler.Context context) throws IOException { + File file = new File(externalPath); + + if (internalPath == null) { + return InterpreterUtil.getBytes(file); + } else { + return context.unpackZip(file.toPath(), internalPath); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java similarity index 92% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java index 3699e3a6..e43e0ecd 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java @@ -28,12 +28,12 @@ import java.io.IOException; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; -import net.fabricmc.loom.util.IOStringConsumer; +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; public class FernflowerLogger extends IFernflowerLogger { - private final IOStringConsumer logger; + private final LoomInternalDecompiler.Logger logger; - public FernflowerLogger(IOStringConsumer logger) { + public FernflowerLogger(LoomInternalDecompiler.Logger logger) { this.logger = logger; } diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java similarity index 100% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/ThreadSafeResultSaver.java diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java similarity index 93% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java rename to src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java index ced8161d..c8060834 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java +++ b/src/decompilers/fernflower/net/fabricmc/loom/decompilers/fernflower/TinyJavadocProvider.java @@ -35,16 +35,17 @@ import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructField; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.StructRecordComponent; -import org.objectweb.asm.Opcodes; import net.fabricmc.fernflower.api.IFabricJavadocProvider; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; public class TinyJavadocProvider implements IFabricJavadocProvider { + private static final int ACC_STATIC = 0x0008; + private static final int ACC_RECORD = 0x10000; + private final MappingTree mappingTree; public TinyJavadocProvider(File tinyFile) { @@ -93,7 +94,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider { addedParam = true; } - parts.add(String.format("@param %s %s", fieldMapping.getName(MappingsNamespace.NAMED.toString()), comment)); + parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment)); } } @@ -151,7 +152,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider { addedParam = true; } - parts.add(String.format("@param %s %s", argMapping.getName(MappingsNamespace.NAMED.toString()), comment)); + parts.add(String.format("@param %s %s", argMapping.getName("named"), comment)); } } @@ -168,7 +169,7 @@ public class TinyJavadocProvider implements IFabricJavadocProvider { private static MappingTree readMappings(File input) { try (BufferedReader reader = Files.newBufferedReader(input.toPath())) { MemoryMappingTree mappingTree = new MemoryMappingTree(); - MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString()); + MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named"); MappingReader.read(reader, nsSwitch); return mappingTree; @@ -178,10 +179,10 @@ public class TinyJavadocProvider implements IFabricJavadocProvider { } public static boolean isRecord(StructClass structClass) { - return (structClass.getAccessFlags() & Opcodes.ACC_RECORD) != 0; + return (structClass.getAccessFlags() & ACC_RECORD) != 0; } public static boolean isStatic(StructField structField) { - return (structField.getAccessFlags() & Opcodes.ACC_STATIC) != 0; + return (structField.getAccessFlags() & ACC_STATIC) != 0; } } diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java new file mode 100644 index 00000000..bb77aa16 --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/ThreadSafeResultSaver.java @@ -0,0 +1,172 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2019-2023 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.decompilers.vineflower; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +public class ThreadSafeResultSaver implements IResultSaver { + private final Supplier output; + private final Supplier lineMapFile; + + public Map outputStreams = new HashMap<>(); + public Map saveExecutors = new HashMap<>(); + public PrintWriter lineMapWriter; + + public ThreadSafeResultSaver(Supplier output, Supplier lineMapFile) { + this.output = output; + this.lineMapFile = lineMapFile; + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + String key = path + "/" + archiveName; + File file = output.get(); + + try { + FileOutputStream fos = new FileOutputStream(file); + ZipOutputStream zos = manifest == null ? new ZipOutputStream(fos) : new JarOutputStream(fos, manifest); + outputStreams.put(key, zos); + saveExecutors.put(key, Executors.newSingleThreadExecutor()); + } catch (IOException e) { + throw new RuntimeException("Unable to create archive: " + file, e); + } + + if (lineMapFile.get() != null) { + try { + lineMapWriter = new PrintWriter(new FileWriter(lineMapFile.get())); + } catch (IOException e) { + throw new RuntimeException("Unable to create line mapping file: " + lineMapFile.get(), e); + } + } + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) { + this.saveClassEntry(path, archiveName, qualifiedName, entryName, content, null); + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) { + String key = path + "/" + archiveName; + ExecutorService executor = saveExecutors.get(key); + executor.submit(() -> { + ZipOutputStream zos = outputStreams.get(key); + + try { + zos.putNextEntry(new ZipEntry(entryName)); + + if (content != null) { + zos.write(content.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + DecompilerContext.getLogger().writeMessage("Cannot write entry " + entryName, e); + } + + if (mapping != null && lineMapWriter != null) { + int maxLine = 0; + int maxLineDest = 0; + StringBuilder builder = new StringBuilder(); + + for (int i = 0; i < mapping.length; i += 2) { + maxLine = Math.max(maxLine, mapping[i]); + maxLineDest = Math.max(maxLineDest, mapping[i + 1]); + builder.append("\t").append(mapping[i]).append("\t").append(mapping[i + 1]).append("\n"); + } + + lineMapWriter.println(qualifiedName + "\t" + maxLine + "\t" + maxLineDest); + lineMapWriter.println(builder.toString()); + } + }); + } + + @Override + public void closeArchive(String path, String archiveName) { + String key = path + "/" + archiveName; + ExecutorService executor = saveExecutors.get(key); + Future closeFuture = executor.submit(() -> { + ZipOutputStream zos = outputStreams.get(key); + + try { + zos.close(); + } catch (IOException e) { + throw new RuntimeException("Unable to close zip. " + key, e); + } + }); + executor.shutdown(); + + try { + closeFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + outputStreams.remove(key); + saveExecutors.remove(key); + + if (lineMapWriter != null) { + lineMapWriter.flush(); + lineMapWriter.close(); + } + } + + @Override + public void saveFolder(String path) { + } + + @Override + public void copyFile(String source, String path, String entryName) { + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + } + + @Override + public void saveDirEntry(String path, String archiveName, String entryName) { + } + + @Override + public void copyEntry(String source, String path, String archiveName, String entry) { + } +} diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java new file mode 100644 index 00000000..ba8ada9f --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/TinyJavadocProvider.java @@ -0,0 +1,188 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2019-2023 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.decompilers.vineflower; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.StructRecordComponent; + +import net.fabricmc.fernflower.api.IFabricJavadocProvider; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class TinyJavadocProvider implements IFabricJavadocProvider { + private static final int ACC_STATIC = 0x0008; + private static final int ACC_RECORD = 0x10000; + + private final MappingTree mappingTree; + + public TinyJavadocProvider(File tinyFile) { + mappingTree = readMappings(tinyFile); + } + + @Override + public String getClassDoc(StructClass structClass) { + MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName); + + if (classMapping == null) { + return null; + } + + if (!isRecord(structClass)) { + return classMapping.getComment(); + } + + /** + * Handle the record component docs here. + * + * Record components are mapped via the field name, thus take the docs from the fields and display them on then class. + */ + List parts = new ArrayList<>(); + + if (classMapping.getComment() != null) { + parts.add(classMapping.getComment()); + } + + boolean addedParam = false; + + for (StructRecordComponent component : structClass.getRecordComponents()) { + // The component will always match the field name and descriptor + MappingTree.FieldMapping fieldMapping = classMapping.getField(component.getName(), component.getDescriptor()); + + if (fieldMapping == null) { + continue; + } + + String comment = fieldMapping.getComment(); + + if (comment != null) { + if (!addedParam && classMapping.getComment() != null) { + //Add a blank line before components when the class has a comment + parts.add(""); + addedParam = true; + } + + parts.add(String.format("@param %s %s", fieldMapping.getName("named"), comment)); + } + } + + if (parts.isEmpty()) { + return null; + } + + return String.join("\n", parts); + } + + @Override + public String getFieldDoc(StructClass structClass, StructField structField) { + // None static fields in records are handled in the class javadoc. + if (isRecord(structClass) && !isStatic(structField)) { + return null; + } + + MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName); + + if (classMapping == null) { + return null; + } + + MappingTree.FieldMapping fieldMapping = classMapping.getField(structField.getName(), structField.getDescriptor()); + + return fieldMapping != null ? fieldMapping.getComment() : null; + } + + @Override + public String getMethodDoc(StructClass structClass, StructMethod structMethod) { + MappingTree.ClassMapping classMapping = mappingTree.getClass(structClass.qualifiedName); + + if (classMapping == null) { + return null; + } + + MappingTree.MethodMapping methodMapping = classMapping.getMethod(structMethod.getName(), structMethod.getDescriptor()); + + if (methodMapping != null) { + List parts = new ArrayList<>(); + + if (methodMapping.getComment() != null) { + parts.add(methodMapping.getComment()); + } + + boolean addedParam = false; + + for (MappingTree.MethodArgMapping argMapping : methodMapping.getArgs()) { + String comment = argMapping.getComment(); + + if (comment != null) { + if (!addedParam && methodMapping.getComment() != null) { + //Add a blank line before params when the method has a comment + parts.add(""); + addedParam = true; + } + + parts.add(String.format("@param %s %s", argMapping.getName("named"), comment)); + } + } + + if (parts.isEmpty()) { + return null; + } + + return String.join("\n", parts); + } + + return null; + } + + private static MappingTree readMappings(File input) { + try (BufferedReader reader = Files.newBufferedReader(input.toPath())) { + MemoryMappingTree mappingTree = new MemoryMappingTree(); + MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, "named"); + MappingReader.read(reader, nsSwitch); + + return mappingTree; + } catch (IOException e) { + throw new RuntimeException("Failed to read mappings", e); + } + } + + public static boolean isRecord(StructClass structClass) { + return (structClass.getAccessFlags() & ACC_RECORD) != 0; + } + + public static boolean isStatic(StructField structField) { + return (structField.getAccessFlags() & ACC_STATIC) != 0; + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java similarity index 73% rename from src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java rename to src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java index 2d2041b6..ad865d73 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2019-2021 FabricMC + * Copyright (c) 2019-2023 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 @@ -22,7 +22,7 @@ * SOFTWARE. */ -package net.fabricmc.loom.decompilers.fernflower; +package net.fabricmc.loom.decompilers.vineflower; import java.nio.file.Path; import java.util.HashMap; @@ -33,34 +33,36 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IResultSaver; import net.fabricmc.fernflower.api.IFabricJavadocProvider; -import net.fabricmc.loom.api.decompilers.DecompilationMetadata; -import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; -public final class FabricFernFlowerDecompiler implements LoomDecompiler { +public final class VineflowerDecompiler implements LoomInternalDecompiler { @Override - public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { + public void decompile(Context context) { + Path sourcesDestination = context.sourcesDestination(); + Path linemapDestination = context.linemapDestination(); + final Map options = new HashMap<>( Map.of( IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1", IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1", IFernflowerPreferences.REMOVE_SYNTHETIC, "1", IFernflowerPreferences.LOG_LEVEL, "trace", - IFernflowerPreferences.THREADS, String.valueOf(metaData.numberOfThreads()), + IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()), IFernflowerPreferences.INDENT_STRING, "\t", - IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile()) + IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile()) ) ); - options.putAll(metaData.options()); + options.putAll(context.options()); IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile); - Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger())); + Fernflower ff = new Fernflower(saver, options, new VineflowerLogger(context.logger())); - for (Path library : metaData.libraries()) { + for (Path library : context.libraries()) { ff.addLibrary(library.toFile()); } - ff.addSource(compiledJar.toFile()); + ff.addSource(context.compiledJar().toFile()); try { ff.decompileContext(); diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java new file mode 100644 index 00000000..675636f4 --- /dev/null +++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerLogger.java @@ -0,0 +1,87 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021-2023 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.decompilers.vineflower; + +import java.io.IOException; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; + +import net.fabricmc.loom.decompilers.LoomInternalDecompiler; + +public class VineflowerLogger extends IFernflowerLogger { + private final LoomInternalDecompiler.Logger logger; + + public VineflowerLogger(LoomInternalDecompiler.Logger logger) { + this.logger = logger; + } + + @Override + public void writeMessage(String message, Severity severity) { + if (severity.ordinal() < Severity.ERROR.ordinal()) return; + + System.err.println(message); + } + + @Override + public void writeMessage(String message, Severity severity, Throwable t) { + if (severity.ordinal() < Severity.ERROR.ordinal()) return; + + writeMessage(message, severity); + t.printStackTrace(System.err); + } + + private void write(String data) { + try { + logger.accept(data); + } catch (IOException e) { + throw new RuntimeException("Failed to log", e); + } + } + + @Override + public void startReadingClass(String className) { + write("Decompiling " + className); + } + + @Override + public void startClass(String className) { + write("Decompiling " + className); + } + + @Override + public void startWriteClass(String className) { + // Nope + } + + @Override + public void startMethod(String methodName) { + // Nope + } + + @Override + public void endMethod() { + // Nope + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java index 70cecf6b..61862955 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java +++ b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java @@ -24,17 +24,27 @@ package net.fabricmc.loom.decompilers; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; + import javax.inject.Inject; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler; import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler; +import net.fabricmc.loom.decompilers.vineflower.VineflowerDecompiler; import net.fabricmc.loom.util.LoomVersions; +import net.fabricmc.loom.util.ZipUtils; public abstract class DecompilerConfiguration implements Runnable { @Inject @@ -44,9 +54,11 @@ public abstract class DecompilerConfiguration implements Runnable { public void run() { var fernflowerConfiguration = createConfiguration("fernflower", LoomVersions.FERNFLOWER); var cfrConfiguration = createConfiguration("cfr", LoomVersions.CFR); + var vineflowerConfiguration = createConfiguration("vineflower", LoomVersions.VINEFLOWER); - registerDecompiler(getProject(), "fernFlower", FabricFernFlowerDecompiler.class, fernflowerConfiguration); - registerDecompiler(getProject(), "cfr", LoomCFRDecompiler.class, cfrConfiguration); + registerDecompiler(getProject(), "fernFlower", BuiltinFernflower.class, fernflowerConfiguration); + registerDecompiler(getProject(), "cfr", BuiltinCfr.class, cfrConfiguration); + registerDecompiler(getProject(), "vineflower", BuiltinVineflower.class, vineflowerConfiguration); } private NamedDomainObjectProvider createConfiguration(String name, LoomVersions version) { @@ -62,4 +74,96 @@ public abstract class DecompilerConfiguration implements Runnable { options.getClasspath().from(configuration); }); } + + // We need to wrap the internal API with the public API. + // This is needed as the sourceset containing fabric's decompilers do not have access to loom classes. + private abstract static sealed class BuiltinDecompiler implements LoomDecompiler permits BuiltinFernflower, BuiltinCfr, BuiltinVineflower { + private final LoomInternalDecompiler internalDecompiler; + + BuiltinDecompiler(LoomInternalDecompiler internalDecompiler) { + this.internalDecompiler = internalDecompiler; + } + + @Override + public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { + final Logger slf4jLogger = LoggerFactory.getLogger(internalDecompiler.getClass()); + + final var logger = new LoomInternalDecompiler.Logger() { + @Override + public void accept(String data) throws IOException { + metaData.logger().accept(data); + } + + @Override + public void error(String msg) { + slf4jLogger.error(msg); + } + }; + + internalDecompiler.decompile(new LoomInternalDecompiler.Context() { + @Override + public Path compiledJar() { + return compiledJar; + } + + @Override + public Path sourcesDestination() { + return sourcesDestination; + } + + @Override + public Path linemapDestination() { + return linemapDestination; + } + + @Override + public int numberOfThreads() { + return metaData.numberOfThreads(); + } + + @Override + public Path javaDocs() { + return metaData.javaDocs(); + } + + @Override + public Collection libraries() { + return metaData.libraries(); + } + + @Override + public LoomInternalDecompiler.Logger logger() { + return logger; + } + + @Override + public Map options() { + return metaData.options(); + } + + @Override + public byte[] unpackZip(Path zip, String path) throws IOException { + return ZipUtils.unpack(zip, path); + } + }); + } + } + + public static final class BuiltinFernflower extends BuiltinDecompiler { + public BuiltinFernflower() { + super(new FabricFernFlowerDecompiler()); + } + } + + public static final class BuiltinCfr extends BuiltinDecompiler { + public BuiltinCfr() { + super(new LoomCFRDecompiler()); + } + } + + public static final class BuiltinVineflower extends BuiltinDecompiler { + public BuiltinVineflower() { + super(new VineflowerDecompiler()); + } + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy index 5b2f0e10..7431aae0 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy @@ -48,6 +48,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { decompiler | task | version 'fernflower' | "genSourcesWithFernFlower" | PRE_RELEASE_GRADLE 'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE + 'vineflower' | "genSourcesWithVineflower" | PRE_RELEASE_GRADLE } @Unroll