aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authormodmuss50 <modmuss50@gmail.com>2021-10-11 13:47:16 +0100
committerGitHub <noreply@github.com>2021-10-11 13:47:16 +0100
commite2439b7f57a82c365d4726d068b68ea2eb606f78 (patch)
tree5cc78058c70e46e22d1ada1a4411c9356cbdf906 /src/main/java
parent5315d3c5b24bcce47268ef8962c07aeb934b014b (diff)
downloadarchitectury-loom-e2439b7f57a82c365d4726d068b68ea2eb606f78.tar.gz
architectury-loom-e2439b7f57a82c365d4726d068b68ea2eb606f78.tar.bz2
architectury-loom-e2439b7f57a82c365d4726d068b68ea2eb606f78.zip
Rewrite GenSources including full support for CFR. (#511)
* Rewrite CFR decompiler interface. Support javadoc * CFR line numbers and fixes. * Cleanup and fix * Use WorkerExecutor to fork, massively cleans up the fernflower code, but does remove the fancy multithreaded logging. * Use IPC to get logging back from the decompilers. * Cleanup UnpickJarTask, fix leak in IPCServer * Used published CFR build * Handle older windows versions that do not support AF_UNIX. * Fixes and basic unit test * Improve memory handling of genSources * Stop decompile worker JVM
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java4
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java12
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java6
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java7
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java230
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java151
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java233
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java148
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java163
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractForkedFFExecutor.java107
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java39
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java (renamed from src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricForkedFFExecutor.java)64
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java70
-rw-r--r--src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadIDFFLogger.java127
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java244
-rw-r--r--src/main/java/net/fabricmc/loom/task/LoomTasks.java8
-rw-r--r--src/main/java/net/fabricmc/loom/task/UnpickJarTask.java77
-rw-r--r--src/main/java/net/fabricmc/loom/util/IOStringConsumer.java31
-rw-r--r--src/main/java/net/fabricmc/loom/util/OperatingSystem.java16
-rw-r--r--src/main/java/net/fabricmc/loom/util/SourceRemapper.java6
-rw-r--r--src/main/java/net/fabricmc/loom/util/gradle/ProgressLoggerHelper.java (renamed from src/main/java/net/fabricmc/loom/util/gradle/ProgressLogger.java)18
-rw-r--r--src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java95
-rw-r--r--src/main/java/net/fabricmc/loom/util/gradle/ThreadedSimpleProgressLogger.java36
-rw-r--r--src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java77
-rw-r--r--src/main/java/net/fabricmc/loom/util/ipc/IPCClient.java67
-rw-r--r--src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java88
26 files changed, 1284 insertions, 840 deletions
diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java
index 472254e2..a4f6dcfa 100644
--- a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java
+++ b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java
@@ -27,5 +27,7 @@ package net.fabricmc.loom.api.decompilers;
import java.nio.file.Path;
import java.util.Collection;
-public record DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection<Path> libraries) {
+import net.fabricmc.loom.util.IOStringConsumer;
+
+public record DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection<Path> libraries, IOStringConsumer logger) {
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
index ec916272..87f41e0d 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
@@ -45,7 +45,7 @@ import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
+import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
public class MinecraftAssetsProvider {
public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException {
@@ -78,7 +78,7 @@ public class MinecraftAssetsProvider {
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false);
}
- Deque<ProgressLogger> loggers = new ConcurrentLinkedDeque<>();
+ Deque<ProgressLoggerHelper> loggers = new ConcurrentLinkedDeque<>();
ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1)));
AssetIndex index;
@@ -114,15 +114,15 @@ public class MinecraftAssetsProvider {
project.getLogger().debug("validating asset " + assetName[0]);
- final ProgressLogger[] progressLogger = new ProgressLogger[1];
+ final ProgressLoggerHelper[] progressLogger = new ProgressLoggerHelper[1];
try {
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
- ProgressLogger logger = loggers.pollFirst();
+ ProgressLoggerHelper logger = loggers.pollFirst();
if (logger == null) {
//Create a new logger if we need one
- progressLogger[0] = ProgressLogger.getProgressFactory(project, MinecraftAssetsProvider.class.getName());
+ progressLogger[0] = ProgressLoggerHelper.getProgressFactory(project, MinecraftAssetsProvider.class.getName());
progressLogger[0].start("Downloading assets...", "assets");
} else {
// use a free logger if we can
@@ -157,6 +157,6 @@ public class MinecraftAssetsProvider {
throw new RuntimeException(e);
}
- loggers.forEach(ProgressLogger::completed);
+ loggers.forEach(ProgressLoggerHelper::completed);
}
}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java
index bd51c4e8..0c58e5c8 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java
@@ -27,7 +27,7 @@ package net.fabricmc.loom.decompilers;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.loom.decompilers.cfr.FabricCFRDecompiler;
+import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
public final class DecompilerConfiguration {
@@ -36,7 +36,7 @@ public final class DecompilerConfiguration {
public static void setup(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
- extension.getGameDecompilers().add(new FabricFernFlowerDecompiler(project));
- extension.getGameDecompilers().add(new FabricCFRDecompiler(project));
+ extension.getGameDecompilers().add(new FabricFernFlowerDecompiler());
+ extension.getGameDecompilers().add(new LoomCFRDecompiler());
}
}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java b/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java
index 7e500301..307a340e 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java
@@ -47,10 +47,9 @@ import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import net.fabricmc.loom.util.Constants;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
+import net.fabricmc.loom.util.IOStringConsumer;
/**
- * TODO, Move to stitch.
* Created by covers1624 on 18/02/19.
*/
public class LineNumberRemapper {
@@ -88,7 +87,7 @@ public class LineNumberRemapper {
}
}
- public void process(ProgressLogger logger, Path input, Path output) throws IOException {
+ public void process(IOStringConsumer logger, Path input, Path output) throws IOException {
Files.walkFileTree(input, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
@@ -110,7 +109,7 @@ public class LineNumberRemapper {
String idx = rel.substring(0, rel.length() - 6);
if (logger != null) {
- logger.progress("Remapping " + idx);
+ logger.accept("Remapping " + idx);
}
int dollarPos = idx.indexOf('$'); //This makes the assumption that only Java classes are to be remapped.
diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java
new file mode 100644
index 00000000..83afc21b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRObfuscationMapping.java
@@ -0,0 +1,230 @@
+/*
+ * 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.decompilers.cfr;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.benf.cfr.reader.bytecode.analysis.types.JavaRefTypeInstance;
+import org.benf.cfr.reader.bytecode.analysis.types.JavaTypeInstance;
+import org.benf.cfr.reader.bytecode.analysis.types.MethodPrototype;
+import org.benf.cfr.reader.entities.AccessFlag;
+import org.benf.cfr.reader.entities.ClassFile;
+import org.benf.cfr.reader.entities.ClassFileField;
+import org.benf.cfr.reader.entities.Field;
+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;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class CFRObfuscationMapping extends NullMapping {
+ private final MappingTree mappingTree;
+
+ public CFRObfuscationMapping(Path mappings) {
+ mappingTree = readMappings(mappings);
+ }
+
+ @Override
+ public Dumper wrap(Dumper d) {
+ return new JavadocProvidingDumper(d);
+ }
+
+ private static MappingTree readMappings(Path input) {
+ try (BufferedReader reader = Files.newBufferedReader(input)) {
+ MemoryMappingTree mappingTree = new MemoryMappingTree();
+ MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingTree, MappingsNamespace.NAMED.toString());
+ MappingReader.read(reader, nsSwitch);
+
+ return mappingTree;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read mappings", e);
+ }
+ }
+
+ private class JavadocProvidingDumper extends DelegatingDumper {
+ JavadocProvidingDumper(Dumper delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public Dumper dumpClassDoc(JavaTypeInstance owner) {
+ MappingTree.ClassMapping mapping = getClassMapping(owner);
+
+ if (mapping == null) {
+ return this;
+ }
+
+ List<String> recordComponentDocs = new LinkedList<>();
+
+ if (isRecord(owner)) {
+ ClassFile classFile = ((JavaRefTypeInstance) owner).getClassFile();
+
+ for (ClassFileField field : classFile.getFields()) {
+ if (field.getField().testAccessFlag(AccessFlag.ACC_STATIC)) {
+ continue;
+ }
+
+ MappingTree.FieldMapping fieldMapping = mapping.getField(field.getFieldName(), field.getField().getDescriptor());
+
+ if (fieldMapping == null) {
+ continue;
+ }
+
+ String comment = fieldMapping.getComment();
+
+ if (comment != null) {
+ recordComponentDocs.add(String.format("@param %s %s", fieldMapping.getSrcName(), comment));
+ }
+ }
+ }
+
+ String comment = mapping.getComment();
+
+ if (comment != null || !recordComponentDocs.isEmpty()) {
+ print("/**").newln();
+
+ if (comment != null) {
+ for (String line : comment.split("\\R")) {
+ print(" * ").print(line).newln();
+ }
+
+ if (!recordComponentDocs.isEmpty()) {
+ print(" * ").newln();
+ }
+ }
+
+ for (String componentDoc : recordComponentDocs) {
+ print(" * ").print(componentDoc).newln();
+ }
+
+ print(" */").newln();
+ }
+
+ return this;
+ }
+
+ @Override
+ public Dumper dumpMethodDoc(MethodPrototype method) {
+ MappingTree.ClassMapping classMapping = getClassMapping(method.getOwner());
+
+ if (classMapping == null) {
+ return this;
+ }
+
+ List<String> lines = new ArrayList<>();
+ MappingTree.MethodMapping mapping = classMapping.getMethod(method.getName(), method.getOriginalDescriptor());
+
+ if (mapping != null) {
+ String comment = mapping.getComment();
+
+ if (comment != null) {
+ lines.addAll(Arrays.asList(comment.split("\\R")));
+ }
+
+ for (MappingTree.MethodArgMapping arg : mapping.getArgs()) {
+ String argComment = arg.getComment();
+
+ if (argComment != null) {
+ lines.addAll(Arrays.asList(("@param " + arg.getSrcName() + " " + argComment).split("\\R")));
+ }
+ }
+ }
+
+ if (!lines.isEmpty()) {
+ print("/**").newln();
+
+ for (String line : lines) {
+ print(" * ").print(line).newln();
+ }
+
+ print(" */").newln();
+ }
+
+ return this;
+ }
+
+ @Override
+ public Dumper dumpFieldDoc(Field field, JavaTypeInstance owner) {
+ // None static fields in records are handled in the class javadoc.
+ if (isRecord(owner) && !isStatic(field)) {
+ return null;
+ }
+
+ MappingTree.ClassMapping classMapping = getClassMapping(owner);
+
+ if (classMapping == null) {
+ return null;
+ }
+
+ MappingTree.FieldMapping fieldMapping = classMapping.getField(field.getFieldName(), field.getDescriptor());
+ dumpComment(fieldMapping.getComment());
+
+ return this;
+ }
+
+ private MappingTree.ClassMapping getClassMapping(JavaTypeInstance type) {
+ String qualifiedName = type.getRawName().replace('.', '/');
+ return mappingTree.getClass(qualifiedName);
+ }
+
+ private boolean isRecord(JavaTypeInstance javaTypeInstance) {
+ if (javaTypeInstance instanceof JavaRefTypeInstance) {
+ ClassFile classFile = ((JavaRefTypeInstance) javaTypeInstance).getClassFile();
+ return classFile.getClassSignature().getSuperClass().getRawName().equals("java.lang.Record");
+ }
+
+ return false;
+ }
+
+ private boolean isStatic(Field field) {
+ return field.testAccessFlag(AccessFlag.ACC_STATIC);
+ }
+
+ private void dumpComment(String comment) {
+ if (comment == null || comment.isBlank()) {
+ return;
+ }
+
+ print("/**").newln();
+
+ for (String line : comment.split("\n")) {
+ print(" * ").print(line).newln();
+ }
+
+ print(" */").newln();
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java
new file mode 100644
index 00000000..bdc5a28a
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/CFRSinkFactory.java
@@ -0,0 +1,151 @@
+/*
+ * 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.decompilers.cfr;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+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;
+
+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 Set<String> addedDirectories = new HashSet<>();
+ private final Map<String, Map<Integer, Integer>> lineMap = new TreeMap<>();
+
+ public CFRSinkFactory(JarOutputStream outputStream, IOStringConsumer logger) {
+ this.outputStream = outputStream;
+ this.logger = logger;
+ }
+
+ @Override
+ public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> available) {
+ return switch (sinkType) {
+ case JAVA -> Collections.singletonList(SinkClass.DECOMPILED);
+ case LINENUMBER -> Collections.singletonList(SinkClass.LINE_NUMBER_MAPPING);
+ default -> Collections.emptyList();
+ };
+ }
+
+ @Override
+ public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
+ return switch (sinkType) {
+ case JAVA -> (Sink<T>) decompiledSink();
+ case LINENUMBER -> (Sink<T>) lineNumberMappingSink();
+ case EXCEPTION -> (e) -> ERROR_LOGGER.error((String) e);
+ default -> null;
+ };
+ }
+
+ private Sink<SinkReturns.Decompiled> decompiledSink() {
+ return sinkable -> {
+ String filename = sinkable.getPackageName().replace('.', '/');
+ if (!filename.isEmpty()) filename += "/";
+ filename += sinkable.getClassName() + ".java";
+
+ byte[] data = sinkable.getJava().getBytes(Charsets.UTF_8);
+
+ writeToJar(filename, data);
+ };
+ }
+
+ private Sink<SinkReturns.LineNumberMapping> lineNumberMappingSink() {
+ return sinkable -> {
+ final String className = sinkable.getClassName();
+ final NavigableMap<Integer, Integer> classFileMappings = sinkable.getClassFileMappings();
+ final NavigableMap<Integer, Integer> mappings = sinkable.getMappings();
+
+ if (classFileMappings == null || mappings == null) return;
+
+ for (Map.Entry<Integer, Integer> entry : mappings.entrySet()) {
+ // New line number
+ Integer dstLineNumber = entry.getValue();
+
+ // Line mapping in the original jar
+ Integer srcLineNumber = classFileMappings.get(entry.getKey());
+
+ if (srcLineNumber == null || dstLineNumber == null) continue;
+
+ lineMap.computeIfAbsent(className, (c) -> new TreeMap<>()).put(srcLineNumber, dstLineNumber);
+ }
+ };
+ }
+
+ private synchronized void writeToJar(String filename, byte[] data) {
+ String[] path = filename.split("/");
+ String pathPart = "";
+
+ for (int i = 0; i < path.length - 1; i++) {
+ pathPart += path[i] + "/";
+
+ if (addedDirectories.add(pathPart)) {
+ JarEntry entry = new JarEntry(pathPart);
+ entry.setTime(new Date().getTime());
+
+ try {
+ outputStream.putNextEntry(entry);
+ outputStream.closeEntry();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ JarEntry entry = new JarEntry(filename);
+ entry.setTime(new Date().getTime());
+ entry.setSize(data.length);
+
+ try {
+ logger.accept("Writing: " + filename);
+ outputStream.putNextEntry(entry);
+ outputStream.write(data);
+ outputStream.closeEntry();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Map<String, Map<Integer, Integer>> getLineMap() {
+ return Collections.unmodifiableMap(lineMap);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java
deleted file mode 100644
index 7237a499..00000000
--- a/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java
+++ /dev/null
@@ -1,233 +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.decompilers.cfr;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.function.Function;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableMap;
-import org.benf.cfr.reader.api.CfrDriver;
-import org.benf.cfr.reader.api.ClassFileSource;
-import org.benf.cfr.reader.api.OutputSinkFactory;
-import org.benf.cfr.reader.api.SinkReturns;
-import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
-import org.gradle.api.Project;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.internal.logging.progress.ProgressLogger;
-import org.gradle.internal.logging.progress.ProgressLoggerFactory;
-import org.gradle.internal.service.ServiceRegistry;
-
-import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
-import net.fabricmc.loom.api.decompilers.LoomDecompiler;
-
-public class FabricCFRDecompiler implements LoomDecompiler {
- private final Project project;
-
- public FabricCFRDecompiler(Project project) {
- this.project = project;
- }
-
- @Override
- public String name() {
- return "ExperimentalCfr";
- }
-
- @Override
- public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
- project.getLogger().warn("!!!! The CFR decompiler support is currently incomplete, line numbers will not match up and there will be no javadocs in the generated source.");
-
- // Setups the multi threaded logger, the thread id is used as the key to the ProgressLogger's
- ServiceRegistry registry = ((ProjectInternal) project).getServices();
- ProgressLoggerFactory factory = registry.get(ProgressLoggerFactory.class);
- ProgressLogger progressGroup = factory.newOperation(getClass()).setDescription("Decompile");
-
- Map<Long, ProgressLogger> loggerMap = new ConcurrentHashMap<>();
- Function<Long, ProgressLogger> createLogger = (threadId) -> {
- ProgressLogger pl = factory.newOperation(getClass(), progressGroup);
- pl.setDescription("decompile worker");
- pl.started();
- return pl;
- };
-
- progressGroup.started();
-
- Manifest manifest = new Manifest();
- manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
- Set<String> addedDirectories = new HashSet<>();
-
- try (OutputStream fos = Files.newOutputStream(sourcesDestination); JarOutputStream jos = new JarOutputStream(fos, manifest); ZipFile inputZip = new ZipFile(compiledJar.toFile())) {
- CfrDriver driver = new CfrDriver.Builder()
- .withOptions(ImmutableMap.of(
- "renameillegalidents", "true",
- "trackbytecodeloc", "true"
- ))
- .withClassFileSource(new ClassFileSource() {
- @Override
- public void informAnalysisRelativePathDetail(String usePath, String classFilePath) {
- }
-
- @Override
- public Collection<String> addJar(String jarPath) {
- return null;
- }
-
- @Override
- public String getPossiblyRenamedPath(String path) {
- return path;
- }
-
- @Override
- public Pair<byte[], String> getClassFileContent(String path) throws IOException {
- ZipEntry zipEntry = inputZip.getEntry(path);
-
- if (zipEntry == null) {
- throw new FileNotFoundException(path);
- }
-
- try (InputStream inputStream = inputZip.getInputStream(zipEntry)) {
- return Pair.make(inputStream.readAllBytes(), path);
- }
- }
- })
- .withOutputSink(new OutputSinkFactory() {
- @Override
- public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> available) {
- return switch (sinkType) {
- case PROGRESS -> Collections.singletonList(SinkClass.STRING);
- case JAVA -> Collections.singletonList(SinkClass.DECOMPILED);
- default -> Collections.emptyList();
- };
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {
- return switch (sinkType) {
- case PROGRESS -> (p) -> project.getLogger().debug((String) p);
- case JAVA -> (Sink<T>) decompiledSink(jos, addedDirectories);
- case EXCEPTION -> (e) -> project.getLogger().error((String) e);
- default -> null;
- };
- }
- })
- .build();
-
- List<String> classes = Collections.list(inputZip.entries()).stream()
- .map(ZipEntry::getName)
- .filter(input -> input.endsWith(".class"))
- .collect(Collectors.toList());
-
- ExecutorService executorService = Executors.newFixedThreadPool(metaData.numberOfThreads());
- List<Future<?>> futures = new LinkedList<>();
-
- for (String clazz : classes) {
- futures.add(executorService.submit(() -> {
- loggerMap.computeIfAbsent(Thread.currentThread().getId(), createLogger).progress(clazz);
- driver.analyse(Collections.singletonList(clazz));
- }));
- }
-
- for (Future<?> future : futures) {
- future.get();
- }
- } catch (IOException | InterruptedException | ExecutionException e) {
- throw new RuntimeException("Failed to decompile", e);
- } finally {
- loggerMap.forEach((threadId, progressLogger) -> progressLogger.completed());
- }
- }
-
- private static OutputSinkFactory.Sink<SinkReturns.Decompiled> decompiledSink(JarOutputStream jos, Set<String> addedDirectories) {
- return decompiled -> {
- String filename = decompiled.getPackageName().replace('.', '/');
- if (!filename.isEmpty()) filename += "/";
- filename += decompiled.getClassName() + ".java";
-
- byte[] data = decompiled.getJava().getBytes(Charsets.UTF_8);
-
- writeToJar(filename, data, jos, addedDirectories);
- };
- }
-
- // TODO move to task queue?
- private static synchronized void writeToJar(String filename, byte[] data, JarOutputStream jos, Set<String> addedDirectories) {
- String[] path = filename.split("/");
- String pathPart = "";
-
- for (int i = 0; i < path.length - 1; i++) {
- pathPart += path[i] + "/";
-
- if (addedDirectories.add(pathPart)) {
- JarEntry entry = new JarEntry(pathPart);
- entry.setTime(new Date().getTime());
-
- try {
- jos.putNextEntry(entry);
- jos.closeEntry();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- JarEntry entry = new JarEntry(filename);
- entry.setTime(new Date().getTime());
- entry.setSize(data.length);
-
- try {
- jos.putNextEntry(entry);
- jos.write(data);
- jos.closeEntry();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java
new file mode 100644
index 00000000..bbfb0be2
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java
@@ -0,0 +1,148 @@
+/*
+ * 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.decompilers.cfr;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.benf.cfr.reader.Driver;
+import org.benf.cfr.reader.state.ClassFileSourceImpl;
+import org.benf.cfr.reader.state.DCCommonState;
+import org.benf.cfr.reader.util.AnalysisType;
+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.LineNumberRemapper;
+
+public class LoomCFRDecompiler implements LoomDecompiler {
+ private static final Map<String, String> DECOMPILE_OPTIONS = Map.of(
+ "renameillegalidents", "true",
+ "trackbytecodeloc", "true",
+ "comments", "false"
+ );
+
+ @Override
+ public String name() {
+ return "Cfr";
+ }
+
+ @Override
+ public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
+ final String path = compiledJar.toAbsolutePath().toString();
+ final Options options = OptionsImpl.getFactory().create(DECOMPILE_OPTIONS);
+
+ ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options);
+
+ for (Path library : metaData.libraries()) {
+ classFileSource.addJarContent(library.toAbsolutePath().toString(), AnalysisType.JAR);
+ }
+
+ classFileSource.informAnalysisRelativePathDetail(null, null);
+
+ DCCommonState state = new DCCommonState(options, classFileSource);
+
+ if (metaData.javaDocs() != null) {
+ state = new DCCommonState(state, new CFRObfuscationMapping(metaData.javaDocs()));
+ }
+
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+
+ Map<String, Map<Integer, Integer>> lineMap;
+
+ try (JarOutputStream outputStream = new JarOutputStream(Files.newOutputStream(sourcesDestination), manifest)) {
+ CFRSinkFactory cfrSinkFactory = new CFRSinkFactory(outputStream, metaData.logger());
+ SinkDumperFactory dumperFactory = new SinkDumperFactory(cfrSinkFactory, options);
+
+ Driver.doJar(state, path, AnalysisType.JAR, dumperFactory);
+
+ lineMap = cfrSinkFactory.getLineMap();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to decompile", e);
+ }
+
+ writeLineMap(linemapDestination, lineMap);
+ }
+
+ private void writeLineMap(Path output, Map<String, Map<Integer, Integer>> lineMap) {
+ try (Writer writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {
+ for (Map.Entry<String, Map<Integer, Integer>> classEntry : lineMap.entrySet()) {
+ final String name = classEntry.getKey().replace(".", "/");
+
+ final Map<Integer, Integer> mapping = classEntry.getValue();
+
+ int maxLine = 0;
+ int maxLineDest = 0;
+ StringBuilder builder = new StringBuilder();
+
+ for (Map.Entry<Integer, Integer> mappingEntry : mapping.entrySet()) {
+ final int src = mappingEntry.getKey();
+ final int dst = mappingEntry.getValue();
+
+ maxLine = Math.max(maxLine, src);
+ maxLineDest = Math.max(maxLineDest, dst);
+
+ builder.append("\t").append(src).append("\t").append(dst).append("\n");
+ }
+
+ writer.write("%s\t%d\t%d\n".formatted(name, maxLine, maxLineDest));
+ writer.write(builder.toString());
+ writer.write("\n");
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to write line map", e);
+ }
+ }
+
+ // A test main class to make it quicker/easier to debug with minimal jars
+ public static void main(String[] args) throws IOException {
+ LoomCFRDecompiler decompiler = new LoomCFRDecompiler();
+
+ Path lineMap = Paths.get("linemap.txt");
+
+ decompiler.decompile(Paths.get("input.jar"),
+ Paths.get("output-sources.jar"),
+ lineMap,
+ new DecompilationMetadata(4, null, Collections.emptyList(), null)
+ );
+
+ LineNumberRemapper lineNumberRemapper = new LineNumberRemapper();
+ lineNumberRemapper.readMappings(lineMap.toFile());
+ lineNumberRemapper.process(null, Paths.get("input.jar"), Paths.get("output.jar"));
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java
deleted file mode 100644
index f059ed36..00000000
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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 static java.text.MessageFormat.format;
-
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Stack;
-import java.util.function.Supplier;
-
-import org.gradle.api.Project;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.internal.logging.progress.ProgressLogger;
-import org.gradle.internal.logging.progress.ProgressLoggerFactory;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.process.ExecResult;
-import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
-
-import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
-import net.fabricmc.loom.api.decompilers.LoomDecompiler;
-import net.fabricmc.loom.util.ConsumingOutputStream;
-import net.fabricmc.loom.util.OperatingSystem;
-
-public abstract class AbstractFernFlowerDecompiler implements LoomDecompiler {
- private final Project project;
-
- protected AbstractFernFlowerDecompiler(Project project) {
- this.project = project;
- }
-
- public abstract Class<? extends AbstractForkedFFExecutor> fernFlowerExecutor();
-
- @Override
- public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
- if (!OperatingSystem.is64Bit()) {
- throw new UnsupportedOperationException("FernFlower decompiler requires a 64bit JVM to run due to the memory requirements");
- }
-
- project.getLogging().captureStandardOutput(LogLevel.LIFECYCLE);
-
- Map<String, Object> options = new HashMap<>() {{
- put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1");
- put(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1");
- put(IFernflowerPreferences.REMOVE_SYNTHETIC, "1");
- put(IFernflowerPreferences.LOG_LEVEL, "trace");
- put(IFernflowerPreferences.THREADS, metaData.numberOfThreads());
- put(IFernflowerPreferences.INDENT_STRING, "\t");
- }};
-
- List<String> args = new ArrayList<>();
-
- options.forEach((k, v) -> args.add(format("-{0}={1}", k, v)));
- args.add(absolutePathOf(compiledJar));
- args.add("-o=" + absolutePathOf(sourcesDestination));
- args.add("-l=" + absolutePathOf(linemapDestination));
- args.add("-m=" + absolutePathOf(metaData.javaDocs()));
-
- // TODO, Decompiler breaks on jemalloc, J9 module-info.class?
- for (Path library : metaData.libraries()) {
- args.add("-e=" + absolutePathOf(library));
- }
-
- ServiceRegistry registry = ((ProjectInternal) project).getServices();
- ProgressLoggerFactory factory = registry.get(ProgressLoggerFactory.class);
- ProgressLogger progressGroup = factory.newOperation(getClass()).setDescription("Decompile");
- Supplier<ProgressLogger> loggerFactory = () -> {
- ProgressLogger pl = factory.newOperation(getClass(), progressGroup);
- pl.setDescription("decompile worker");
- pl.started();
- return pl;
- };
- Stack<ProgressLogger> freeLoggers = new Stack<>();
- Map<String, ProgressLogger> inUseLoggers = new HashMap<>();
-
- progressGroup.started();
- ExecResult result = ForkingJavaExec.javaexec(
- project,
- spec -> {
- spec.getMainClass().set(fernFlowerExecutor().getName());
- spec.jvmArgs("-Xms200m", "-Xmx3G");
- spec.setArgs(args);
- spec.setErrorOutput(new ConsumingOutputStream(line -> {
- if (line.startsWith("Inconsistent inner class entries")) {
- // Suppress this
- return;
- }
-
- System.err.println(line);
- }));
- spec.setStandardOutput(new ConsumingOutputStream(line -> {
- if (line.startsWith("Listening for transport") || !line.contains("::")) {
- System.out.println(line);
- return;
- }
-
- int sepIdx = line.indexOf("::");
- String id = line.substring(0, sepIdx).trim();
- String data = line.substring(sepIdx + 2).trim();
-
- ProgressLogger logger = inUseLoggers.get(id);
-
- String[] segs = data.split(" ");
-
- if (segs[0].equals("waiting")) {
- if (logger != null) {
- logger.progress("Idle..");
- inUseLoggers.remove(id);
- freeLoggers.push(logger);
- }
- } else {
- if (logger == null) {
- if (!freeLoggers.isEmpty()) {
- logger = freeLoggers.pop();
- } else {
- logger = loggerFactory.get();
- }
-
- inUseLoggers.put(id, logger);
- }
-
- logger.progress(data);
- }
- }));
- });
- inUseLoggers.values().forEach(ProgressLogger::completed);
- freeLoggers.forEach(ProgressLogger::completed);
- progressGroup.completed();
-
- result.rethrowFailure();
- result.assertNormalExitValue();
- }
-
- private static String absolutePathOf(Path path) {
- return path.toAbsolutePath().toString();
- }
-}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractForkedFFExecutor.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractForkedFFExecutor.java
deleted file mode 100644
index 2435233e..00000000
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractForkedFFExecutor.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * This file is part of fabric-loom, licensed under the MIT License (MIT).
- *
- * Copyright (c) 2019-2020 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.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * Entry point for Forked FernFlower task.
- * Takes one parameter, a single file, each line is treated as command line input.
- * Forces one input file.
- * Forces one output file using '-o=/path/to/output'
- * Created by covers1624 on 11/02/19.
- * <p>Extending classes MUST have a standard "public static void main(args)".
- * They may then call AbstractForkedFFExecutor#decompile for it to use the overridden AbstractForkedFFExecutor#runFF
- * </p>
- */
-public abstract class AbstractForkedFFExecutor {
- public static void decompile(String[] args, AbstractForkedFFExecutor ffExecutor) {
- Map<String, Object> options = new HashMap<>();
- File input = null;
- File output = null;
- File lineMap = null;
- File mappings = null;
- List<File> libraries = new ArrayList<>();
-
- boolean isOption = true;
-
- for (String arg : args) {
- if (isOption && arg.length() > 5 && arg.charAt(0) == '-' && arg.charAt(4) == '=') {
- String value = arg.substring(5);
-
- if ("true".equalsIgnoreCase(value)) {
- value = "1";
- } else if ("false".equalsIgnoreCase(value)) {
- value = "0";
- }
-
- options.put(arg.substring(1, 4), value);
- } else {
- isOption = false;
-
- if (arg.startsWith("-e=")) {
- libraries.add(new File(arg.substring(3)));
- } else if (arg.startsWith("-o=")) {
- if (output != null) {
- throw new RuntimeException("Unable to set more than one output.");
- }
-
- output = new File(arg.substring(3));
- } else if (arg.startsWith("-l=")) {
- if (lineMap != null) {
- throw new RuntimeException("Unable to set more than one lineMap file.");
- }
-
- lineMap = new File(arg.substring(3));
- } else if (arg.startsWith("-m=")) {
- if (mappings != null) {
- throw new RuntimeException("Unable to use more than one mappings file.");
- }
-
- mappings = new File(arg.substring(3));
- } else {
- if (input != null) {
- throw new RuntimeException("Unable to set more than one input.");
- }
-
- input = new File(arg);
- }
- }
- }
-
- Objects.requireNonNull(input, "Input not set.");
- Objects.requireNonNull(output, "Output not set.");
- Objects.requireNonNull(mappings, "Mappings not set.");
-
- ffExecutor.runFF(options, libraries, input, output, lineMap, mappings);
- }
-
- public abstract void runFF(Map<String, Object> options, List<File> libraries, File input, File output, File lineMap, File mappings);
-}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java
index b3663773..c3009448 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java
@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
- * Copyright (c) 2018-2020 FabricMC
+ * 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
@@ -24,20 +24,43 @@
package net.fabricmc.loom.decompilers.fernflower;
-import org.gradle.api.Project;
+import java.nio.file.Path;
+import java.util.Map;
-public class FabricFernFlowerDecompiler extends AbstractFernFlowerDecompiler {
- public FabricFernFlowerDecompiler(Project project) {
- super(project);
- }
+import org.jetbrains.java.decompiler.main.Fernflower;
+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;
+public final class FabricFernFlowerDecompiler implements LoomDecompiler {
@Override
public String name() {
return "FabricFlower"; // Or something else?
}
@Override
- public Class<? extends AbstractForkedFFExecutor> fernFlowerExecutor() {
- return FabricForkedFFExecutor.class;
+ public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
+ Map<String, Object> options = 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.INDENT_STRING, "\t",
+ IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile())
+ );
+
+ IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
+ Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger()));
+
+ for (Path library : metaData.libraries()) {
+ ff.addLibrary(library.toFile());
+ }
+
+ ff.addSource(compiledJar.toFile());
+ ff.decompileContext();
}
}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricForkedFFExecutor.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java
index e88c8086..a98060e9 100644
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricForkedFFExecutor.java
+++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FernflowerLogger.java
@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
- * Copyright (c) 2018-2020 FabricMC
+ * 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
@@ -24,34 +24,60 @@
package net.fabricmc.loom.decompilers.fernflower;
-import java.io.File;
-import java.util.List;
-import java.util.Map;
+import java.io.IOException;
-import org.jetbrains.java.decompiler.main.Fernflower;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
-import org.jetbrains.java.decompiler.main.extern.IResultSaver;
-import net.fabricmc.fernflower.api.IFabricJavadocProvider;
+import net.fabricmc.loom.util.IOStringConsumer;
-public class FabricForkedFFExecutor extends AbstractForkedFFExecutor {
- public static void main(String[] args) {
- AbstractForkedFFExecutor.decompile(args, new FabricForkedFFExecutor());
+public class FernflowerLogger extends IFernflowerLogger {
+ private final IOStringConsumer logger;
+
+ public FernflowerLogger(IOStringConsumer logger) {
+ this.logger = logger;
}
@Override
- public void runFF(Map<String, Object> options, List<File> libraries, File input, File output, File lineMap, File mappings) {
- options.put(IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(mappings));
+ public void writeMessage(String message, Severity severity) {
+ if (message.contains("Inconsistent inner class entries for")) return;
+ System.err.println(message);
+ }
- IResultSaver saver = new ThreadSafeResultSaver(() -> output, () -> lineMap);
- IFernflowerLogger logger = new ThreadIDFFLogger();
- Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, logger);
+ @Override
+ public void writeMessage(String message, Severity severity, Throwable t) {
+ writeMessage(message, severity);
+ }
- for (File library : libraries) {
- ff.addLibrary(library);
+ 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
+ }
- ff.addSource(input);
- ff.decompileContext();
+ @Override
+ public void startMethod(String methodName) {
+ // Nope
+ }
+
+ @Override
+ public void endMethod() {
+ // Nope
}
}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java
deleted file mode 100644
index bbcd80de..00000000
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * This file is part of fabric-loom, licensed under the MIT License (MIT).
- *
- * Copyright (c) 2016-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.net.URL;
-import java.net.URLClassLoader;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.dsl.DependencyHandler;
-import org.gradle.api.file.FileCollection;
-import org.gradle.process.ExecResult;
-import org.gradle.process.JavaExecSpec;
-
-/**
- * Simple utility class for a Task that wishes to execute a java process
- * with the classpath of the gradle plugin plus groovy.
- *
- * <p>Created by covers1624 on 11/02/19.
- */
-public class ForkingJavaExec {
- public static ExecResult javaexec(Project project, Action<? super JavaExecSpec> action) {
- return project.javaexec(spec -> {
- spec.classpath(getClasspath(project));
- action.execute(spec);
- });
- }
-
- private static Object getClasspath(Project project) {
- if (System.getProperty("fabric.loom.test") != null) {
- return getTestClasspath();
- }
-
- return getRuntimeClasspath(project.getRootProject().getPlugins().hasPlugin("fabric-loom") ? project.getRootProject() : project);
- }
-
- private static FileCollection getRuntimeClasspath(Project project) {
- ConfigurationContainer configurations = project.getBuildscript().getConfigurations();
- DependencyHandler handler = project.getDependencies();
- return configurations.getByName("classpath")
- .plus(configurations.detachedConfiguration(handler.localGroovy()));
- }
-
- private static URL[] getTestClasspath() {
- return ((URLClassLoader) ForkingJavaExec.class.getClassLoader()).getURLs();
- }
-}
diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadIDFFLogger.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadIDFFLogger.java
deleted file mode 100644
index a7b617bc..00000000
--- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ThreadIDFFLogger.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * This file is part of fabric-loom, licensed under the MIT License (MIT).
- *
- * Copyright (c) 2019-2020 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.PrintStream;
-import java.text.MessageFormat;
-import java.util.Stack;
-
-import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
-
-/**
- * This logger simply prints what each thread is doing
- * to the console in a machine parsable way.
- *
- * <p>Created by covers1624 on 11/02/19.
- */
-public class ThreadIDFFLogger extends IFernflowerLogger {
- public final PrintStream stdOut;
- public final PrintStream stdErr;
-
- private final ThreadLocal<Stack<String>> workingClass = ThreadLocal.withInitial(Stack::new);
- private final ThreadLocal<Stack<String>> line = ThreadLocal.withInitial(Stack::new);
-
- public ThreadIDFFLogger() {
- this(System.err, System.out);
- }
-
- public ThreadIDFFLogger(PrintStream stdOut, PrintStream stdErr) {
- this.stdOut = stdOut;
- this.stdErr = stdErr;
- }
-
- @Override
- public void writeMessage(String message, Severity severity) {
- System.err.println(message);
- }
-
- @Override
- public void writeMessage(String message, Severity severity, Throwable t) {
- System.err.println(message);
- t.printStackTrace(System.err);
- }
-
- private void print() {
- Thread thread = Thread.currentThread();
- long id = thread.getId();
-
- if (line.get().isEmpty()) {
- System.out.println(MessageFormat.format("{0} :: waiting", id));
- return;
- }
-
- String line = this.line.get().peek();
- System.out.println(MessageFormat.format("{0} :: {1}", id, line).trim());
- }
-
- @Override
- public void startReadingClass(String className) {
- workingClass.get().push(className);
- line.get().push("Decompiling " + className);
- print();
- }
-
- @Override
- public void startClass(String className) {
- workingClass.get().push(className);
- line.get().push("Decompiling " + className);
- print();
- }
-
- @Override
- public void startMethod(String methodName) {
- // No need to print out methods
- }
-
- @Override
- public void endMethod() {
- }
-
- @Override
- public void endClass() {
- line.get().pop();
- workingClass.get().pop();
- print();
- }
-
- @Override
- public void startWriteClass(String className) {
- line.get().push("Writing " + className);
- print();
- }
-
- @Override
- public void endWriteClass() {
- line.get().pop();
- print();
- }
-
- @Override
- public void endReadingClass() {
- line.get().pop();
- workingClass.get().pop();
- print();
- }
-}
diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
index a19746b0..8c6dca24 100644
--- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
@@ -26,18 +26,33 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
import java.util.stream.Collectors;
import javax.inject.Inject;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
+import org.gradle.workers.WorkQueue;
+import org.gradle.workers.WorkerExecutor;
+import org.gradle.workers.internal.WorkerDaemonClientsManager;
+import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
@@ -47,58 +62,216 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMapp
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
+import net.fabricmc.loom.util.IOStringConsumer;
+import net.fabricmc.loom.util.OperatingSystem;
+import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer;
+import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger;
+import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper;
+import net.fabricmc.loom.util.ipc.IPCClient;
+import net.fabricmc.loom.util.ipc.IPCServer;
import net.fabricmc.stitch.util.StitchUtil;
-public class GenerateSourcesTask extends AbstractLoomTask {
+public abstract class GenerateSourcesTask extends AbstractLoomTask {
public final LoomDecompiler decompiler;
- private File inputJar;
+ @InputFile
+ public abstract RegularFileProperty getInputJar();
+
+ /**
+ * Max memory for forked JVM in megabytes.
+ */
+ @Input
+ public abstract Property<Long> getMaxMemory();
+
+ @Inject
+ public abstract WorkerExecutor getWorkerExecutor();
+
+ @Inject
+ public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager();
@Inject
public GenerateSourcesTask(LoomDecompiler decompiler) {
this.decompiler = decompiler;
+ Objects.requireNonNull(getDecompilerConstructor(this.decompiler.getClass().getCanonicalName()),
+ "%s must have a no args constructor".formatted(this.decompiler.getClass().getCanonicalName()));
+
getOutputs().upToDateWhen((o) -> false);
+ getMaxMemory().convention(4096L).finalizeValueOnRead();
}
@TaskAction
- public void doTask() throws Throwable {
- int threads = Runtime.getRuntime().availableProcessors();
- Collection<Path> libraries = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
- .stream().map(File::toPath).collect(Collectors.toSet());
+ public void run() throws IOException {
+ if (!OperatingSystem.is64Bit()) {
+ throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
+ }
- DecompilationMetadata metadata = new DecompilationMetadata(threads, getMappings(), libraries);
- Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath();
- Path sourcesDestination = getMappedJarFileWithSuffix("-sources.jar").toPath();
- Path linemap = getMappedJarFileWithSuffix("-sources.lmap").toPath();
- decompiler.decompile(inputJar.toPath(), sourcesDestination, linemap, metadata);
+ if (!OperatingSystem.isUnixDomainSocketsSupported()) {
+ getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system.");
- if (Files.exists(linemap)) {
- Path linemappedJarDestination = getMappedJarFileWithSuffix("-linemapped.jar").toPath();
+ doWork(null);
+ return;
+ }
- // Line map the actually jar used to run the game, not the one used to decompile
- remapLineNumbers(runtimeJar, linemap, linemappedJarDestination);
+ // Set up the IPC path to get the log output back from the forked JVM
+ final Path ipcPath = Files.createTempFile("loom", "ipc");
+ Files.deleteIfExists(ipcPath);
- Files.copy(linemappedJarDestination, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
- Files.delete(linemappedJarDestination);
+ try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompiler.name(), "Decompiling minecraft sources");
+ IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) {
+ doWork(ipcPath);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to shutdown log receiver", e);
+ } finally {
+ Files.deleteIfExists(ipcPath);
}
}
- private void remapLineNumbers(Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
- getProject().getLogger().info(":adjusting line numbers");
- LineNumberRemapper remapper = new LineNumberRemapper();
- remapper.readMappings(linemap.toFile());
+ private void doWork(@Nullable Path ipcPath) {
+ final String jvmMarkerValue = UUID.randomUUID().toString();
+ final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
+
+ workQueue.submit(DecompileAction.class, params -> {
+ params.getDecompilerClass().set(decompiler.getClass().getCanonicalName());
+
+ params.getInputJar().set(getInputJar());
+ params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar());
+ params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar"));
+ params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap"));
+ params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar"));
+ params.getMappings().set(getMappings().toFile());
+
+ if (ipcPath != null) {
+ params.getIPCPath().set(ipcPath.toFile());
+ }
+
+ params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES));
+ });
+
+ workQueue.await();
+
+ if (useProcessIsolation()) {
+ boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue);
+
+ if (!stopped) {
+ throw new RuntimeException("Failed to stop decompile worker JVM");
+ }
+ }
+ }
+
+ private WorkQueue createWorkQueue(String jvmMarkerValue) {
+ if (!useProcessIsolation()) {
+ return getWorkerExecutor().noIsolation();
+ }
+
+ return getWorkerExecutor().processIsolation(spec -> {
+ spec.forkOptions(forkOptions -> {
+ forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get()));
+ forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue);
+ });
+ });
+ }
+
+ private boolean useProcessIsolation() {
+ // Useful if you want to debug the decompiler, make sure you run gradle with enough memory.
+ return !Boolean.getBoolean("fabric.loom.genSources.debug");
+ }
+
+ public interface DecompileParams extends WorkParameters {
+ Property<String> getDecompilerClass();
+
+ RegularFileProperty getInputJar();
+ RegularFileProperty getRuntimeJar();
+ RegularFileProperty getSourcesDestinationJar();
+ RegularFileProperty getLinemap();
+ RegularFileProperty getLinemapJar();
+ RegularFileProperty getMappings();
+
+ RegularFileProperty getIPCPath();
+
+ ConfigurableFileCollection getClassPath();
+ }
+
+ public abstract static class DecompileAction implements WorkAction<DecompileParams> {
+ @Override
+ public void execute() {
+ if (!getParameters().getIPCPath().isPresent() || !OperatingSystem.isUnixDomainSocketsSupported()) {
+ // Does not support unix domain sockets, print to sout.
+ doDecompile(System.out::println);
+ return;
+ }
- ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName());
- progressLogger.start("Adjusting line numbers", "linemap");
+ final Path ipcPath = getParameters().getIPCPath().get().getAsFile().toPath();
- try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true);
- StitchUtil.FileSystemDelegate outFs = StitchUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) {
- remapper.process(progressLogger, inFs.get().getPath("/"), outFs.get().getPath("/"));
+ try (IPCClient ipcClient = new IPCClient(ipcPath)) {
+ doDecompile(new ThreadedSimpleProgressLogger(ipcClient));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to setup IPC Client", e);
+ }
}
- progressLogger.completed();
+ private void doDecompile(IOStringConsumer logger) {
+ final Path inputJar = getParameters().getInputJar().get().getAsFile().toPath();
+ final Path sourcesDestinationJar = getParameters().getSourcesDestinationJar().get().getAsFile().toPath();
+ final Path linemap = getParameters().getLinemap().get().getAsFile().toPath();
+ final Path linemapJar = getParameters().getLinemapJar().get().getAsFile().toPath();
+ final Path runtimeJar = getParameters().getRuntimeJar().get().getAsFile().toPath();
+
+ final LoomDecompiler decompiler;
+
+ try {
+ decompiler = getDecompilerConstructor(getParameters().getDecompilerClass().get()).newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException("Failed to create decompiler", e);
+ }
+
+ DecompilationMetadata metadata = new DecompilationMetadata(
+ Runtime.getRuntime().availableProcessors(),
+ getParameters().getMappings().get().getAsFile().toPath(),
+ getLibraries(),
+ logger
+ );
+
+ decompiler.decompile(
+ inputJar,
+ sourcesDestinationJar,
+ linemap,
+ metadata
+ );
+
+ // Close the decompile loggers
+ try {
+ metadata.logger().accept(ThreadedProgressLoggerConsumer.CLOSE_LOGGERS);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to close loggers", e);
+ }
+
+ if (Files.exists(linemap)) {
+ try {
+ // Line map the actually jar used to run the game, not the one used to decompile
+ remapLineNumbers(metadata.logger(), runtimeJar, linemap, linemapJar);
+
+ Files.copy(linemapJar, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
+ Files.delete(linemapJar);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to remap line numbers", e);
+ }
+ }
+ }
+
+ private void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
+ LineNumberRemapper remapper = new LineNumberRemapper();
+ remapper.readMappings(linemap.toFile());
+
+ try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true);
+ StitchUtil.FileSystemDelegate outFs = StitchUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) {
+ remapper.process(logger, inFs.get().getPath("/"), outFs.get().getPath("/"));
+ }
+ }
+
+ private Collection<Path> getLibraries() {
+ return getParameters().getClassPath().getFiles().stream().map(File::toPath).collect(Collectors.toSet());
+ }
}
private File getMappedJarFileWithSuffix(String suffix) {
@@ -140,13 +313,12 @@ public class GenerateSourcesTask extends AbstractLoomTask {
return baseMappings;
}
- @InputFile
- public File getInputJar() {
- return inputJar;
- }
-
- public GenerateSourcesTask setInputJar(File inputJar) {
- this.inputJar = inputJar;
- return this;
+ private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
+ try {
+ //noinspection unchecked
+ return (Constructor<LoomDecompiler>) Class.forName(clazz).getConstructor();
+ } catch (NoSuchMethodException | ClassNotFoundException e) {
+ return null;
+ }
}
}
diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
index c37fcbb0..5561adb2 100644
--- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java
+++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
@@ -128,9 +128,9 @@ public final class LoomTasks {
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
- unpickJarTask.setUnpickDefinition(mappingsProvider.getUnpickDefinitionsFile());
- unpickJarTask.setInputJar(mappingsProvider.mappedProvider.getMappedJar());
- unpickJarTask.setOutputJar(outputJar);
+ unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
+ unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
+ unpickJarTask.getOutputJar().set(outputJar);
});
inputJar = outputJar;
@@ -142,7 +142,7 @@ public final class LoomTasks {
String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name();
// decompiler will be passed to the constructor of GenerateSourcesTask
GenerateSourcesTask generateSourcesTask = tasks.register(taskName, GenerateSourcesTask.class, decompiler).get();
- generateSourcesTask.setInputJar(inputJar);
+ generateSourcesTask.getInputJar().set(inputJar);
if (mappingsProvider.hasUnpickDefinitions()) {
generateSourcesTask.dependsOn(tasks.getByName("unpickJar"));
diff --git a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java b/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java
index 93c71747..e750cb10 100644
--- a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java
+++ b/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java
@@ -29,7 +29,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
+import javax.inject.Inject;
+
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.OutputFile;
@@ -39,26 +44,43 @@ import net.fabricmc.loom.configuration.providers.LaunchProvider;
import net.fabricmc.loom.extension.LoomFiles;
import net.fabricmc.loom.util.Constants;
-public class UnpickJarTask extends JavaExec {
- File inputJar;
- File unpickDefinition;
+public abstract class UnpickJarTask extends JavaExec {
+ @InputFile
+ public abstract RegularFileProperty getInputJar();
+
+ @InputFile
+ public abstract RegularFileProperty getUnpickDefinitions();
+
+ @InputFiles
+ // Only 1 file, but it comes from a configuration
+ public abstract ConfigurableFileCollection getConstantJar();
+
+ @InputFiles
+ public abstract ConfigurableFileCollection getUnpickClasspath();
- File outputJar;
+ @OutputFile
+ public abstract RegularFileProperty getOutputJar();
+ @Inject
public UnpickJarTask() {
- getOutputs().upToDateWhen(e -> false);
classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
getMainClass().set("daomephsta.unpick.cli.Main");
+
+ getConstantJar().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MAPPING_CONSTANTS));
+ getUnpickClasspath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES));
}
@Override
public void exec() {
- fileArg(getInputJar(), getOutputJar(), getUnpickDefinition());
- fileArg(getConstantJar());
+ fileArg(getInputJar().get().getAsFile(), getOutputJar().get().getAsFile(), getUnpickDefinitions().get().getAsFile());
+ fileArg(getConstantJar().getSingleFile());
// Classpath
fileArg(getExtension().getMinecraftMappedProvider().getMappedJar());
- fileArg(getMinecraftDependencies());
+
+ for (File file : getUnpickClasspath()) {
+ fileArg(file);
+ }
writeUnpickLogConfig();
systemProperty("java.util.logging.config.file", getDirectories().getUnpickLoggingConfigFile().getAbsolutePath());
@@ -75,45 +97,6 @@ public class UnpickJarTask extends JavaExec {
}
}
- private File[] getMinecraftDependencies() {
- return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)
- .resolve().toArray(new File[0]);
- }
-
- private File getConstantJar() {
- return getProject().getConfigurations().getByName(Constants.Configurations.MAPPING_CONSTANTS).getSingleFile();
- }
-
- @InputFile
- public File getInputJar() {
- return inputJar;
- }
-
- public UnpickJarTask setInputJar(File inputJar) {
- this.inputJar = inputJar;
- return this;
- }
-
- @InputFile
- public File getUnpickDefinition() {
- return unpickDefinition;
- }
-
- public UnpickJarTask setUnpickDefinition(File unpickDefinition) {
- this.unpickDefinition = unpickDefinition;
- return this;
- }
-
- @OutputFile
- public File getOutputJar() {
- return outputJar;
- }
-
- public UnpickJarTask setOutputJar(File outputJar) {
- this.outputJar = outputJar;
- return this;
- }
-
private void fileArg(File... files) {
for (File file : files) {
args(file.getAbsolutePath());
diff --git a/src/main/java/net/fabricmc/loom/util/IOStringConsumer.java b/src/main/java/net/fabricmc/loom/util/IOStringConsumer.java
new file mode 100644
index 00000000..2459b3a9
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/IOStringConsumer.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+public interface IOStringConsumer {
+ void accept(String data) throws IOException;
+}
diff --git a/src/main/java/net/fabricmc/loom/util/OperatingSystem.java b/src/main/java/net/fabricmc/loom/util/OperatingSystem.java
index 38e2eb4a..9df41edb 100644
--- a/src/main/java/net/fabricmc/loom/util/OperatingSystem.java
+++ b/src/main/java/net/fabricmc/loom/util/OperatingSystem.java
@@ -24,6 +24,11 @@
package net.fabricmc.loom.util;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.StandardProtocolFamily;
+import java.nio.channels.ServerSocketChannel;
+
public class OperatingSystem {
public static String getOS() {
String osName = System.getProperty("os.name").toLowerCase();
@@ -63,4 +68,15 @@ public class OperatingSystem {
// CI seems to be set by most popular CI services
return System.getenv("CI") != null;
}
+
+ // Requires Unix, or Windows 10 17063 or later. See: https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
+ public static boolean isUnixDomainSocketsSupported() {
+ try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
+ return true;
+ } catch (UnsupportedOperationException e) {
+ return false;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
index ec1ce8a3..776fa6e3 100644
--- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
+++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
@@ -43,7 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
+import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.util.StitchUtil;
@@ -51,7 +51,7 @@ import net.fabricmc.stitch.util.StitchUtil;
public class SourceRemapper {
private final Project project;
private final boolean toNamed;
- private final List<Consumer<ProgressLogger>> remapTasks = new ArrayList<>();
+ private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>();
private Mercury mercury;
@@ -90,7 +90,7 @@ public class SourceRemapper {
project.getLogger().lifecycle(":remapping sources");
- ProgressLogger progressLogger = ProgressLogger.getProgressFactory(project, SourceRemapper.class.getName());
+ ProgressLoggerHelper progressLogger = ProgressLoggerHelper.getProgressFactory(project, SourceRemapper.class.getName());
progressLogger.start("Remapping dependency sources", "sources");
remapTasks.forEach(consumer -> consumer.accept(progressLogger));
diff --git a/src/main/java/net/fabricmc/loom/util/gradle/ProgressLogger.java b/src/main/java/net/fabricmc/loom/util/gradle/ProgressLoggerHelper.java
index 2b3d180e..d2fb1a4a 100644
--- a/src/main/java/net/fabricmc/loom/util/gradle/ProgressLogger.java
+++ b/src/main/java/net/fabricmc/loom/util/gradle/ProgressLoggerHelper.java
@@ -32,11 +32,11 @@ import org.gradle.api.Project;
/**
* Wrapper to ProgressLogger internal API.
*/
-public class ProgressLogger {
+public class ProgressLoggerHelper {
private final Object logger;
private final Method getDescription, setDescription, getShortDescription, setShortDescription, getLoggingHeader, setLoggingHeader, start, started, startedArg, progress, completed, completedArg;
- private ProgressLogger(Object logger) {
+ private ProgressLoggerHelper(Object logger) {
this.logger = logger;
this.getDescription = getMethod("getDescription");
this.setDescription = getMethod("setDescription", String.class);
@@ -102,17 +102,17 @@ public class ProgressLogger {
* @param category The logger category
* @return In any case a progress logger
*/
- public static ProgressLogger getProgressFactory(Project project, String category) {
+ public static ProgressLoggerHelper getProgressFactory(Project project, String category) {
try {
Method getServices = project.getClass().getMethod("getServices");
Object serviceFactory = getServices.invoke(project);
Method get = serviceFactory.getClass().getMethod("get", Class.class);
Object progressLoggerFactory = get.invoke(serviceFactory, getFactoryClass());
Method newOperation = progressLoggerFactory.getClass().getMethod("newOperation", String.class);
- return new ProgressLogger(newOperation.invoke(progressLoggerFactory, category));
+ return new ProgressLoggerHelper(newOperation.invoke(progressLoggerFactory, category));
} catch (Exception e) {
project.getLogger().error("Unable to get progress logger. Download progress will not be displayed.");
- return new ProgressLogger(null);
+ return new ProgressLoggerHelper(null);
}
}
@@ -132,7 +132,7 @@ public class ProgressLogger {
*
* @param description The description.
*/
- public ProgressLogger setDescription(String description) {
+ public ProgressLoggerHelper setDescription(String description) {
invoke(setDescription, description);
return this;
}
@@ -153,7 +153,7 @@ public class ProgressLogger {
*
* @param description The short description.
*/
- public ProgressLogger setShortDescription(String description) {
+ public ProgressLoggerHelper setShortDescription(String description) {
invoke(setShortDescription, description);
return this;
}
@@ -176,7 +176,7 @@ public class ProgressLogger {
*
* @param header The header. May be empty or null.
*/
- public ProgressLogger setLoggingHeader(String header) {
+ public ProgressLoggerHelper setLoggingHeader(String header) {
invoke(setLoggingHeader, header);
return this;
}
@@ -186,7 +186,7 @@ public class ProgressLogger {
*
* @return this logger instance
*/
- public ProgressLogger start(String description, String shortDescription) {
+ public ProgressLoggerHelper start(String description, String shortDescription) {
invoke(start, description, shortDescription);
return this;
}
diff --git a/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java
new file mode 100644
index 00000000..94f49eed
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java
@@ -0,0 +1,95 @@
+/*
+ * 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.gradle;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.internal.logging.progress.ProgressLogger;
+import org.gradle.internal.logging.progress.ProgressLoggerFactory;
+
+public class ThreadedProgressLoggerConsumer implements Consumer<String>, AutoCloseable {
+ public static final String CLOSE_LOGGERS = "LOOM_CLOSE_LOGGERS";
+
+ private final Project project;
+ private final String name;
+ private final String desc;
+
+ private final ProgressLoggerFactory progressLoggerFactory;
+ private final ProgressLogger progressGroup;
+ private final Map<String, ProgressLogger> loggers = Collections.synchronizedMap(new HashMap<>());
+
+ public ThreadedProgressLoggerConsumer(Project project, String name, String desc) {
+ this.project = project;
+ this.name = name;
+ this.desc = desc;
+
+ this.progressLoggerFactory = ((ProjectInternal) project).getServices().get(ProgressLoggerFactory.class);
+ this.progressGroup = this.progressLoggerFactory.newOperation(name).setDescription(desc);
+ progressGroup.started();
+ }
+
+ @Override
+ public void accept(String line) {
+ if (!line.contains("::")) {
+ project.getLogger().debug("Malformed threaded IPC log message: " + line);
+ return;
+ }
+
+ int idx = line.indexOf("::");
+ String id = line.substring(0, idx).trim();
+ String data = line.substring(idx + 2).trim();
+
+ if (data.equals(CLOSE_LOGGERS)) {
+ resetLoggers();
+ return;
+ }
+
+ loggers.computeIfAbsent(id, this::createLogger).progress(data);
+ }
+
+ private ProgressLogger createLogger(String id) {
+ ProgressLogger progressLogger = progressLoggerFactory.newOperation(getClass(), progressGroup);
+ progressLogger.setDescription(desc);
+ progressLogger.started();
+ return progressLogger;
+ }
+
+ private void resetLoggers() {
+ loggers.values().forEach(ProgressLogger::completed);
+ loggers.clear();
+ }
+
+ @Override
+ public void close() {
+ resetLoggers();
+
+ progressGroup.completed();
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/gradle/ThreadedSimpleProgressLogger.java b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedSimpleProgressLogger.java
new file mode 100644
index 00000000..605568a9
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedSimpleProgressLogger.java
@@ -0,0 +1,36 @@
+/*
+ * 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.gradle;
+
+import java.io.IOException;
+
+import net.fabricmc.loom.util.IOStringConsumer;
+
+public record ThreadedSimpleProgressLogger(IOStringConsumer parent) implements IOStringConsumer {
+ @Override
+ public void accept(String data) throws IOException {
+ parent.accept("%d::%s".formatted(Thread.currentThread().getId(), data));
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java
new file mode 100644
index 00000000..b5d1f7f1
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.gradle;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.gradle.api.Transformer;
+import org.gradle.workers.internal.DaemonForkOptions;
+import org.gradle.workers.internal.WorkerDaemonClientsManager;
+
+public class WorkerDaemonClientsManagerHelper {
+ public static final String MARKER_PROP = "fabric.loom.decompile.worker";
+
+ public static boolean stopIdleJVM(WorkerDaemonClientsManager manager, String jvmMarkerValue) {
+ AtomicBoolean stopped = new AtomicBoolean(false);
+
+ /* Transformer<List<WorkerDaemonClient>, List<WorkerDaemonClient>> */
+ Transformer<List<Object>, List<Object>> transformer = workerDaemonClients -> {
+ for (Object /* WorkerDaemonClient */ client : workerDaemonClients) {
+ DaemonForkOptions forkOptions = getForkOptions(client);
+ Map<String, Object> systemProperties = forkOptions.getJavaForkOptions().getSystemProperties();
+
+ if (systemProperties == null || !jvmMarkerValue.equals(systemProperties.get(MARKER_PROP))) {
+ // Not the JVM we are looking for
+ continue;
+ }
+
+ stopped.set(true);
+ return Collections.singletonList(client);
+ }
+
+ return Collections.emptyList();
+ };
+
+ //noinspection unchecked
+ manager.selectIdleClientsToStop((Transformer) transformer);
+
+ return stopped.get();
+ }
+
+ private static DaemonForkOptions getForkOptions(Object /* WorkerDaemonClient */ client) {
+ try {
+ Method getForkOptionsMethod = client.getClass().getDeclaredMethod("getForkOptions");
+ getForkOptionsMethod.setAccessible(true);
+ return (DaemonForkOptions) getForkOptionsMethod.invoke(client);
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/ipc/IPCClient.java b/src/main/java/net/fabricmc/loom/util/ipc/IPCClient.java
new file mode 100644
index 00000000..3e5563d0
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ipc/IPCClient.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ipc;
+
+import java.io.IOException;
+import java.net.UnixDomainSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+import net.fabricmc.loom.util.IOStringConsumer;
+
+public final class IPCClient implements IOStringConsumer, AutoCloseable {
+ private final Path path;
+ private final SocketChannel socketChannel;
+
+ public IPCClient(Path path) throws IOException {
+ this.path = path;
+ socketChannel = setupChannel();
+ }
+
+ private SocketChannel setupChannel() throws IOException {
+ final UnixDomainSocketAddress address = UnixDomainSocketAddress.of(path);
+ return SocketChannel.open(address);
+ }
+
+ @Override
+ public void accept(String s) throws IOException {
+ synchronized (socketChannel) {
+ ByteBuffer buf = ByteBuffer.wrap((s + "\n").getBytes(StandardCharsets.UTF_8));
+
+ while (buf.hasRemaining()) {
+ socketChannel.write(buf);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ synchronized (socketChannel) {
+ socketChannel.close();
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java b/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java
new file mode 100644
index 00000000..7c8158a8
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ipc;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.StandardProtocolFamily;
+import java.net.UnixDomainSocketAddress;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+public class IPCServer implements AutoCloseable {
+ private final ExecutorService loggerReceiverService = Executors.newSingleThreadExecutor();
+ private final Path path;
+ private final Consumer<String> consumer;
+
+ private final CountDownLatch startupLock = new CountDownLatch(1);
+
+ public IPCServer(Path path, Consumer<String> consumer) {
+ this.path = path;
+ this.consumer = consumer;
+
+ loggerReceiverService.submit(this::run);
+
+ try {
+ startupLock.await(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Timed out waiting for IPC server thread to start", e);
+ }
+ }
+
+ public void run() {
+ UnixDomainSocketAddress address = UnixDomainSocketAddress.of(path);
+
+ try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
+ serverChannel.bind(address);
+
+ startupLock.countDown();
+
+ try (SocketChannel clientChannel = serverChannel.accept();
+ Scanner scanner = new Scanner(clientChannel, StandardCharsets.UTF_8)) {
+ while (!Thread.currentThread().isInterrupted()) {
+ if (scanner.hasNextLine()) {
+ this.consumer.accept(scanner.nextLine());
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to listen for IPC messages", e);
+ }
+ }
+
+ @Override
+ public void close() throws InterruptedException {
+ loggerReceiverService.shutdownNow();
+ loggerReceiverService.awaitTermination(10, TimeUnit.SECONDS);
+ }
+}