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 JVMdev/0.11
parent
5315d3c5b2
commit
e2439b7f57
|
@ -88,7 +88,7 @@ dependencies {
|
||||||
|
|
||||||
// decompilers
|
// decompilers
|
||||||
implementation ('net.fabricmc:fabric-fernflower:1.4.1')
|
implementation ('net.fabricmc:fabric-fernflower:1.4.1')
|
||||||
implementation ('org.benf:cfr:0.151')
|
implementation ('net.fabricmc:cfr:0.0.8')
|
||||||
|
|
||||||
// source code remapping
|
// source code remapping
|
||||||
implementation ('net.fabricmc:mercury:0.2.4')
|
implementation ('net.fabricmc:mercury:0.2.4')
|
||||||
|
|
|
@ -27,5 +27,7 @@ package net.fabricmc.loom.api.decompilers;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
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) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
|
||||||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||||
import net.fabricmc.loom.util.MirrorUtil;
|
import net.fabricmc.loom.util.MirrorUtil;
|
||||||
import net.fabricmc.loom.util.HashedDownloadUtil;
|
import net.fabricmc.loom.util.HashedDownloadUtil;
|
||||||
import net.fabricmc.loom.util.gradle.ProgressLogger;
|
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
|
||||||
|
|
||||||
public class MinecraftAssetsProvider {
|
public class MinecraftAssetsProvider {
|
||||||
public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException {
|
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);
|
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)));
|
ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1)));
|
||||||
|
|
||||||
AssetIndex index;
|
AssetIndex index;
|
||||||
|
@ -114,15 +114,15 @@ public class MinecraftAssetsProvider {
|
||||||
|
|
||||||
project.getLogger().debug("validating asset " + assetName[0]);
|
project.getLogger().debug("validating asset " + assetName[0]);
|
||||||
|
|
||||||
final ProgressLogger[] progressLogger = new ProgressLogger[1];
|
final ProgressLoggerHelper[] progressLogger = new ProgressLoggerHelper[1];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
|
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) {
|
if (logger == null) {
|
||||||
//Create a new logger if we need one
|
//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");
|
progressLogger[0].start("Downloading assets...", "assets");
|
||||||
} else {
|
} else {
|
||||||
// use a free logger if we can
|
// use a free logger if we can
|
||||||
|
@ -157,6 +157,6 @@ public class MinecraftAssetsProvider {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
loggers.forEach(ProgressLogger::completed);
|
loggers.forEach(ProgressLoggerHelper::completed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ package net.fabricmc.loom.decompilers;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
|
||||||
import net.fabricmc.loom.LoomGradleExtension;
|
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;
|
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
|
||||||
|
|
||||||
public final class DecompilerConfiguration {
|
public final class DecompilerConfiguration {
|
||||||
|
@ -36,7 +36,7 @@ public final class DecompilerConfiguration {
|
||||||
|
|
||||||
public static void setup(Project project) {
|
public static void setup(Project project) {
|
||||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||||
extension.getGameDecompilers().add(new FabricFernFlowerDecompiler(project));
|
extension.getGameDecompilers().add(new FabricFernFlowerDecompiler());
|
||||||
extension.getGameDecompilers().add(new FabricCFRDecompiler(project));
|
extension.getGameDecompilers().add(new LoomCFRDecompiler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,10 +47,9 @@ import org.objectweb.asm.Label;
|
||||||
import org.objectweb.asm.MethodVisitor;
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
|
||||||
import net.fabricmc.loom.util.Constants;
|
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.
|
* Created by covers1624 on 18/02/19.
|
||||||
*/
|
*/
|
||||||
public class LineNumberRemapper {
|
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<>() {
|
Files.walkFileTree(input, new SimpleFileVisitor<>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
@ -110,7 +109,7 @@ public class LineNumberRemapper {
|
||||||
String idx = rel.substring(0, rel.length() - 6);
|
String idx = rel.substring(0, rel.length() - 6);
|
||||||
|
|
||||||
if (logger != null) {
|
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.
|
int dollarPos = idx.indexOf('$'); //This makes the assumption that only Java classes are to be remapped.
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* 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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -24,20 +24,43 @@
|
||||||
|
|
||||||
package net.fabricmc.loom.decompilers.fernflower;
|
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 {
|
import org.jetbrains.java.decompiler.main.Fernflower;
|
||||||
public FabricFernFlowerDecompiler(Project project) {
|
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
|
||||||
super(project);
|
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
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
return "FabricFlower"; // Or something else?
|
return "FabricFlower"; // Or something else?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends AbstractForkedFFExecutor> fernFlowerExecutor() {
|
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||||
return FabricForkedFFExecutor.class;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
* 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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -24,34 +24,60 @@
|
||||||
|
|
||||||
package net.fabricmc.loom.decompilers.fernflower;
|
package net.fabricmc.loom.decompilers.fernflower;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.jetbrains.java.decompiler.main.Fernflower;
|
|
||||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
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 class FernflowerLogger extends IFernflowerLogger {
|
||||||
public static void main(String[] args) {
|
private final IOStringConsumer logger;
|
||||||
AbstractForkedFFExecutor.decompile(args, new FabricForkedFFExecutor());
|
|
||||||
|
public FernflowerLogger(IOStringConsumer logger) {
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runFF(Map<String, Object> options, List<File> libraries, File input, File output, File lineMap, File mappings) {
|
public void writeMessage(String message, Severity severity) {
|
||||||
options.put(IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(mappings));
|
if (message.contains("Inconsistent inner class entries for")) return;
|
||||||
|
System.err.println(message);
|
||||||
|
}
|
||||||
|
|
||||||
IResultSaver saver = new ThreadSafeResultSaver(() -> output, () -> lineMap);
|
@Override
|
||||||
IFernflowerLogger logger = new ThreadIDFFLogger();
|
public void writeMessage(String message, Severity severity, Throwable t) {
|
||||||
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, logger);
|
writeMessage(message, severity);
|
||||||
|
}
|
||||||
|
|
||||||
for (File library : libraries) {
|
private void write(String data) {
|
||||||
ff.addLibrary(library);
|
try {
|
||||||
|
logger.accept(data);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to log", e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ff.addSource(input);
|
@Override
|
||||||
ff.decompileContext();
|
public void startReadingClass(String className) {
|
||||||
|
write("Decompiling " + className);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startClass(String className) {
|
||||||
|
write("Decompiling " + className);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startWriteClass(String className) {
|
||||||
|
// Nope
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startMethod(String methodName) {
|
||||||
|
// Nope
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endMethod() {
|
||||||
|
// Nope
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,18 +26,33 @@ package net.fabricmc.loom.task;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
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.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
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.InputFile;
|
||||||
import org.gradle.api.tasks.TaskAction;
|
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.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
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.configuration.providers.mappings.MappingsProviderImpl;
|
||||||
import net.fabricmc.loom.decompilers.LineNumberRemapper;
|
import net.fabricmc.loom.decompilers.LineNumberRemapper;
|
||||||
import net.fabricmc.loom.util.Constants;
|
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;
|
import net.fabricmc.stitch.util.StitchUtil;
|
||||||
|
|
||||||
public class GenerateSourcesTask extends AbstractLoomTask {
|
public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||||
public final LoomDecompiler decompiler;
|
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
|
@Inject
|
||||||
public GenerateSourcesTask(LoomDecompiler decompiler) {
|
public GenerateSourcesTask(LoomDecompiler decompiler) {
|
||||||
this.decompiler = 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);
|
getOutputs().upToDateWhen((o) -> false);
|
||||||
|
getMaxMemory().convention(4096L).finalizeValueOnRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
public void doTask() throws Throwable {
|
public void run() throws IOException {
|
||||||
int threads = Runtime.getRuntime().availableProcessors();
|
if (!OperatingSystem.is64Bit()) {
|
||||||
Collection<Path> libraries = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
|
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
||||||
.stream().map(File::toPath).collect(Collectors.toSet());
|
}
|
||||||
|
|
||||||
DecompilationMetadata metadata = new DecompilationMetadata(threads, getMappings(), libraries);
|
if (!OperatingSystem.isUnixDomainSocketsSupported()) {
|
||||||
Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath();
|
getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system.");
|
||||||
Path sourcesDestination = getMappedJarFileWithSuffix("-sources.jar").toPath();
|
|
||||||
Path linemap = getMappedJarFileWithSuffix("-sources.lmap").toPath();
|
|
||||||
decompiler.decompile(inputJar.toPath(), sourcesDestination, linemap, metadata);
|
|
||||||
|
|
||||||
if (Files.exists(linemap)) {
|
doWork(null);
|
||||||
Path linemappedJarDestination = getMappedJarFileWithSuffix("-linemapped.jar").toPath();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Line map the actually jar used to run the game, not the one used to decompile
|
// Set up the IPC path to get the log output back from the forked JVM
|
||||||
remapLineNumbers(runtimeJar, linemap, linemappedJarDestination);
|
final Path ipcPath = Files.createTempFile("loom", "ipc");
|
||||||
|
Files.deleteIfExists(ipcPath);
|
||||||
|
|
||||||
Files.copy(linemappedJarDestination, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
|
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompiler.name(), "Decompiling minecraft sources");
|
||||||
Files.delete(linemappedJarDestination);
|
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 {
|
private void doWork(@Nullable Path ipcPath) {
|
||||||
getProject().getLogger().info(":adjusting line numbers");
|
final String jvmMarkerValue = UUID.randomUUID().toString();
|
||||||
LineNumberRemapper remapper = new LineNumberRemapper();
|
final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
|
||||||
remapper.readMappings(linemap.toFile());
|
|
||||||
|
|
||||||
ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName());
|
workQueue.submit(DecompileAction.class, params -> {
|
||||||
progressLogger.start("Adjusting line numbers", "linemap");
|
params.getDecompilerClass().set(decompiler.getClass().getCanonicalName());
|
||||||
|
|
||||||
try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true);
|
params.getInputJar().set(getInputJar());
|
||||||
StitchUtil.FileSystemDelegate outFs = StitchUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) {
|
params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar());
|
||||||
remapper.process(progressLogger, inFs.get().getPath("/"), outFs.get().getPath("/"));
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
progressLogger.completed();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Path ipcPath = getParameters().getIPCPath().get().getAsFile().toPath();
|
||||||
|
|
||||||
|
try (IPCClient ipcClient = new IPCClient(ipcPath)) {
|
||||||
|
doDecompile(new ThreadedSimpleProgressLogger(ipcClient));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to setup IPC Client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
private File getMappedJarFileWithSuffix(String suffix) {
|
||||||
|
@ -140,13 +313,12 @@ public class GenerateSourcesTask extends AbstractLoomTask {
|
||||||
return baseMappings;
|
return baseMappings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@InputFile
|
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
||||||
public File getInputJar() {
|
try {
|
||||||
return inputJar;
|
//noinspection unchecked
|
||||||
}
|
return (Constructor<LoomDecompiler>) Class.forName(clazz).getConstructor();
|
||||||
|
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
||||||
public GenerateSourcesTask setInputJar(File inputJar) {
|
return null;
|
||||||
this.inputJar = inputJar;
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,9 +128,9 @@ public final class LoomTasks {
|
||||||
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
|
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
|
||||||
|
|
||||||
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
|
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
|
||||||
unpickJarTask.setUnpickDefinition(mappingsProvider.getUnpickDefinitionsFile());
|
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
|
||||||
unpickJarTask.setInputJar(mappingsProvider.mappedProvider.getMappedJar());
|
unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
|
||||||
unpickJarTask.setOutputJar(outputJar);
|
unpickJarTask.getOutputJar().set(outputJar);
|
||||||
});
|
});
|
||||||
|
|
||||||
inputJar = outputJar;
|
inputJar = outputJar;
|
||||||
|
@ -142,7 +142,7 @@ public final class LoomTasks {
|
||||||
String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name();
|
String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name();
|
||||||
// decompiler will be passed to the constructor of GenerateSourcesTask
|
// decompiler will be passed to the constructor of GenerateSourcesTask
|
||||||
GenerateSourcesTask generateSourcesTask = tasks.register(taskName, GenerateSourcesTask.class, decompiler).get();
|
GenerateSourcesTask generateSourcesTask = tasks.register(taskName, GenerateSourcesTask.class, decompiler).get();
|
||||||
generateSourcesTask.setInputJar(inputJar);
|
generateSourcesTask.getInputJar().set(inputJar);
|
||||||
|
|
||||||
if (mappingsProvider.hasUnpickDefinitions()) {
|
if (mappingsProvider.hasUnpickDefinitions()) {
|
||||||
generateSourcesTask.dependsOn(tasks.getByName("unpickJar"));
|
generateSourcesTask.dependsOn(tasks.getByName("unpickJar"));
|
||||||
|
|
|
@ -29,7 +29,12 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
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.InputFile;
|
||||||
|
import org.gradle.api.tasks.InputFiles;
|
||||||
import org.gradle.api.tasks.Internal;
|
import org.gradle.api.tasks.Internal;
|
||||||
import org.gradle.api.tasks.JavaExec;
|
import org.gradle.api.tasks.JavaExec;
|
||||||
import org.gradle.api.tasks.OutputFile;
|
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.extension.LoomFiles;
|
||||||
import net.fabricmc.loom.util.Constants;
|
import net.fabricmc.loom.util.Constants;
|
||||||
|
|
||||||
public class UnpickJarTask extends JavaExec {
|
public abstract class UnpickJarTask extends JavaExec {
|
||||||
File inputJar;
|
@InputFile
|
||||||
File unpickDefinition;
|
public abstract RegularFileProperty getInputJar();
|
||||||
|
|
||||||
File outputJar;
|
@InputFile
|
||||||
|
public abstract RegularFileProperty getUnpickDefinitions();
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
// Only 1 file, but it comes from a configuration
|
||||||
|
public abstract ConfigurableFileCollection getConstantJar();
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
public abstract ConfigurableFileCollection getUnpickClasspath();
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
public abstract RegularFileProperty getOutputJar();
|
||||||
|
|
||||||
|
@Inject
|
||||||
public UnpickJarTask() {
|
public UnpickJarTask() {
|
||||||
getOutputs().upToDateWhen(e -> false);
|
|
||||||
classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
||||||
getMainClass().set("daomephsta.unpick.cli.Main");
|
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
|
@Override
|
||||||
public void exec() {
|
public void exec() {
|
||||||
fileArg(getInputJar(), getOutputJar(), getUnpickDefinition());
|
fileArg(getInputJar().get().getAsFile(), getOutputJar().get().getAsFile(), getUnpickDefinitions().get().getAsFile());
|
||||||
fileArg(getConstantJar());
|
fileArg(getConstantJar().getSingleFile());
|
||||||
|
|
||||||
// Classpath
|
// Classpath
|
||||||
fileArg(getExtension().getMinecraftMappedProvider().getMappedJar());
|
fileArg(getExtension().getMinecraftMappedProvider().getMappedJar());
|
||||||
fileArg(getMinecraftDependencies());
|
|
||||||
|
for (File file : getUnpickClasspath()) {
|
||||||
|
fileArg(file);
|
||||||
|
}
|
||||||
|
|
||||||
writeUnpickLogConfig();
|
writeUnpickLogConfig();
|
||||||
systemProperty("java.util.logging.config.file", getDirectories().getUnpickLoggingConfigFile().getAbsolutePath());
|
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) {
|
private void fileArg(File... files) {
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
args(file.getAbsolutePath());
|
args(file.getAbsolutePath());
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -24,6 +24,11 @@
|
||||||
|
|
||||||
package net.fabricmc.loom.util;
|
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 class OperatingSystem {
|
||||||
public static String getOS() {
|
public static String getOS() {
|
||||||
String osName = System.getProperty("os.name").toLowerCase();
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
@ -63,4 +68,15 @@ public class OperatingSystem {
|
||||||
// CI seems to be set by most popular CI services
|
// CI seems to be set by most popular CI services
|
||||||
return System.getenv("CI") != null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension;
|
||||||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||||
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
|
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
|
||||||
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
|
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.lorenztiny.TinyMappingsReader;
|
||||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||||
import net.fabricmc.stitch.util.StitchUtil;
|
import net.fabricmc.stitch.util.StitchUtil;
|
||||||
|
@ -51,7 +51,7 @@ import net.fabricmc.stitch.util.StitchUtil;
|
||||||
public class SourceRemapper {
|
public class SourceRemapper {
|
||||||
private final Project project;
|
private final Project project;
|
||||||
private final boolean toNamed;
|
private final boolean toNamed;
|
||||||
private final List<Consumer<ProgressLogger>> remapTasks = new ArrayList<>();
|
private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>();
|
||||||
|
|
||||||
private Mercury mercury;
|
private Mercury mercury;
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ public class SourceRemapper {
|
||||||
|
|
||||||
project.getLogger().lifecycle(":remapping sources");
|
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");
|
progressLogger.start("Remapping dependency sources", "sources");
|
||||||
|
|
||||||
remapTasks.forEach(consumer -> consumer.accept(progressLogger));
|
remapTasks.forEach(consumer -> consumer.accept(progressLogger));
|
||||||
|
|
|
@ -32,11 +32,11 @@ import org.gradle.api.Project;
|
||||||
/**
|
/**
|
||||||
* Wrapper to ProgressLogger internal API.
|
* Wrapper to ProgressLogger internal API.
|
||||||
*/
|
*/
|
||||||
public class ProgressLogger {
|
public class ProgressLoggerHelper {
|
||||||
private final Object logger;
|
private final Object logger;
|
||||||
private final Method getDescription, setDescription, getShortDescription, setShortDescription, getLoggingHeader, setLoggingHeader, start, started, startedArg, progress, completed, completedArg;
|
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.logger = logger;
|
||||||
this.getDescription = getMethod("getDescription");
|
this.getDescription = getMethod("getDescription");
|
||||||
this.setDescription = getMethod("setDescription", String.class);
|
this.setDescription = getMethod("setDescription", String.class);
|
||||||
|
@ -102,17 +102,17 @@ public class ProgressLogger {
|
||||||
* @param category The logger category
|
* @param category The logger category
|
||||||
* @return In any case a progress logger
|
* @return In any case a progress logger
|
||||||
*/
|
*/
|
||||||
public static ProgressLogger getProgressFactory(Project project, String category) {
|
public static ProgressLoggerHelper getProgressFactory(Project project, String category) {
|
||||||
try {
|
try {
|
||||||
Method getServices = project.getClass().getMethod("getServices");
|
Method getServices = project.getClass().getMethod("getServices");
|
||||||
Object serviceFactory = getServices.invoke(project);
|
Object serviceFactory = getServices.invoke(project);
|
||||||
Method get = serviceFactory.getClass().getMethod("get", Class.class);
|
Method get = serviceFactory.getClass().getMethod("get", Class.class);
|
||||||
Object progressLoggerFactory = get.invoke(serviceFactory, getFactoryClass());
|
Object progressLoggerFactory = get.invoke(serviceFactory, getFactoryClass());
|
||||||
Method newOperation = progressLoggerFactory.getClass().getMethod("newOperation", String.class);
|
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) {
|
} catch (Exception e) {
|
||||||
project.getLogger().error("Unable to get progress logger. Download progress will not be displayed.");
|
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.
|
* @param description The description.
|
||||||
*/
|
*/
|
||||||
public ProgressLogger setDescription(String description) {
|
public ProgressLoggerHelper setDescription(String description) {
|
||||||
invoke(setDescription, description);
|
invoke(setDescription, description);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ public class ProgressLogger {
|
||||||
*
|
*
|
||||||
* @param description The short description.
|
* @param description The short description.
|
||||||
*/
|
*/
|
||||||
public ProgressLogger setShortDescription(String description) {
|
public ProgressLoggerHelper setShortDescription(String description) {
|
||||||
invoke(setShortDescription, description);
|
invoke(setShortDescription, description);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ public class ProgressLogger {
|
||||||
*
|
*
|
||||||
* @param header The header. May be empty or null.
|
* @param header The header. May be empty or null.
|
||||||
*/
|
*/
|
||||||
public ProgressLogger setLoggingHeader(String header) {
|
public ProgressLoggerHelper setLoggingHeader(String header) {
|
||||||
invoke(setLoggingHeader, header);
|
invoke(setLoggingHeader, header);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ public class ProgressLogger {
|
||||||
*
|
*
|
||||||
* @return this logger instance
|
* @return this logger instance
|
||||||
*/
|
*/
|
||||||
public ProgressLogger start(String description, String shortDescription) {
|
public ProgressLoggerHelper start(String description, String shortDescription) {
|
||||||
invoke(start, description, shortDescription);
|
invoke(start, description, shortDescription);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait {
|
||||||
decompiler | task | version
|
decompiler | task | version
|
||||||
'fernflower' | "genSources" | DEFAULT_GRADLE
|
'fernflower' | "genSources" | DEFAULT_GRADLE
|
||||||
'fernflower' | "genSources" | PRE_RELEASE_GRADLE
|
'fernflower' | "genSources" | PRE_RELEASE_GRADLE
|
||||||
'cfr' | "genSourcesWithExperimentalCfr" | DEFAULT_GRADLE
|
'cfr' | "genSourcesWithCfr" | DEFAULT_GRADLE
|
||||||
|
'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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.test.unit
|
||||||
|
|
||||||
|
import net.fabricmc.loom.util.ipc.IPCClient
|
||||||
|
import net.fabricmc.loom.util.ipc.IPCServer
|
||||||
|
import spock.lang.Specification
|
||||||
|
import spock.lang.Timeout
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Timeout(20)
|
||||||
|
class IPCTest extends Specification {
|
||||||
|
def "ipc test"() {
|
||||||
|
given:
|
||||||
|
def path = Files.createTempFile("loom", "ipc")
|
||||||
|
Files.deleteIfExists(path)
|
||||||
|
|
||||||
|
def received = []
|
||||||
|
Consumer<String> consumer = { str ->
|
||||||
|
println str
|
||||||
|
received << str
|
||||||
|
}
|
||||||
|
|
||||||
|
when:
|
||||||
|
def ipcServer = new IPCServer(path, consumer)
|
||||||
|
|
||||||
|
new IPCClient(path).withCloseable { client ->
|
||||||
|
client.accept("Test")
|
||||||
|
client.accept("Hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow ipcServer to finish reading, before closing.
|
||||||
|
while (received.size() != 2) { }
|
||||||
|
ipcServer.close()
|
||||||
|
|
||||||
|
then:
|
||||||
|
received.size() == 2
|
||||||
|
received[0] == "Test"
|
||||||
|
received[1] == "Hello"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
minecraft "com.mojang:minecraft:1.16.5"
|
minecraft "com.mojang:minecraft:21w38a"
|
||||||
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
|
mappings "net.fabricmc:yarn:21w38a+build.11:v2"
|
||||||
modImplementation "net.fabricmc:fabric-loader:0.11.2"
|
modImplementation "net.fabricmc:fabric-loader:0.11.7"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue