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
|
||||
implementation ('net.fabricmc:fabric-fernflower:1.4.1')
|
||||
implementation ('org.benf:cfr:0.151')
|
||||
implementation ('net.fabricmc:cfr:0.0.8')
|
||||
|
||||
// source code remapping
|
||||
implementation ('net.fabricmc:mercury:0.2.4')
|
||||
|
|
|
@ -27,5 +27,7 @@ package net.fabricmc.loom.api.decompilers;
|
|||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public record DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection<Path> libraries) {
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
|
||||
public record DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection<Path> libraries, IOStringConsumer logger) {
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
|
|||
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
|
||||
import net.fabricmc.loom.util.MirrorUtil;
|
||||
import net.fabricmc.loom.util.HashedDownloadUtil;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLogger;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
|
||||
|
||||
public class MinecraftAssetsProvider {
|
||||
public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException {
|
||||
|
@ -78,7 +78,7 @@ public class MinecraftAssetsProvider {
|
|||
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false);
|
||||
}
|
||||
|
||||
Deque<ProgressLogger> loggers = new ConcurrentLinkedDeque<>();
|
||||
Deque<ProgressLoggerHelper> loggers = new ConcurrentLinkedDeque<>();
|
||||
ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1)));
|
||||
|
||||
AssetIndex index;
|
||||
|
@ -114,15 +114,15 @@ public class MinecraftAssetsProvider {
|
|||
|
||||
project.getLogger().debug("validating asset " + assetName[0]);
|
||||
|
||||
final ProgressLogger[] progressLogger = new ProgressLogger[1];
|
||||
final ProgressLoggerHelper[] progressLogger = new ProgressLoggerHelper[1];
|
||||
|
||||
try {
|
||||
HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
|
||||
ProgressLogger logger = loggers.pollFirst();
|
||||
ProgressLoggerHelper logger = loggers.pollFirst();
|
||||
|
||||
if (logger == null) {
|
||||
//Create a new logger if we need one
|
||||
progressLogger[0] = ProgressLogger.getProgressFactory(project, MinecraftAssetsProvider.class.getName());
|
||||
progressLogger[0] = ProgressLoggerHelper.getProgressFactory(project, MinecraftAssetsProvider.class.getName());
|
||||
progressLogger[0].start("Downloading assets...", "assets");
|
||||
} else {
|
||||
// use a free logger if we can
|
||||
|
@ -157,6 +157,6 @@ public class MinecraftAssetsProvider {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
loggers.forEach(ProgressLogger::completed);
|
||||
loggers.forEach(ProgressLoggerHelper::completed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ package net.fabricmc.loom.decompilers;
|
|||
import org.gradle.api.Project;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.decompilers.cfr.FabricCFRDecompiler;
|
||||
import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler;
|
||||
import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler;
|
||||
|
||||
public final class DecompilerConfiguration {
|
||||
|
@ -36,7 +36,7 @@ public final class DecompilerConfiguration {
|
|||
|
||||
public static void setup(Project project) {
|
||||
LoomGradleExtension extension = LoomGradleExtension.get(project);
|
||||
extension.getGameDecompilers().add(new FabricFernFlowerDecompiler(project));
|
||||
extension.getGameDecompilers().add(new FabricCFRDecompiler(project));
|
||||
extension.getGameDecompilers().add(new FabricFernFlowerDecompiler());
|
||||
extension.getGameDecompilers().add(new LoomCFRDecompiler());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,9 @@ import org.objectweb.asm.Label;
|
|||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLogger;
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
|
||||
/**
|
||||
* TODO, Move to stitch.
|
||||
* Created by covers1624 on 18/02/19.
|
||||
*/
|
||||
public class LineNumberRemapper {
|
||||
|
@ -88,7 +87,7 @@ public class LineNumberRemapper {
|
|||
}
|
||||
}
|
||||
|
||||
public void process(ProgressLogger logger, Path input, Path output) throws IOException {
|
||||
public void process(IOStringConsumer logger, Path input, Path output) throws IOException {
|
||||
Files.walkFileTree(input, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
|
@ -110,7 +109,7 @@ public class LineNumberRemapper {
|
|||
String idx = rel.substring(0, rel.length() - 6);
|
||||
|
||||
if (logger != null) {
|
||||
logger.progress("Remapping " + idx);
|
||||
logger.accept("Remapping " + idx);
|
||||
}
|
||||
|
||||
int dollarPos = idx.indexOf('$'); //This makes the assumption that only Java classes are to be remapped.
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
* Copyright (c) 2018-2020 FabricMC
|
||||
* Copyright (c) 2019-2021 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -24,20 +24,43 @@
|
|||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class FabricFernFlowerDecompiler extends AbstractFernFlowerDecompiler {
|
||||
public FabricFernFlowerDecompiler(Project project) {
|
||||
super(project);
|
||||
}
|
||||
import org.jetbrains.java.decompiler.main.Fernflower;
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
|
||||
|
||||
public final class FabricFernFlowerDecompiler implements LoomDecompiler {
|
||||
@Override
|
||||
public String name() {
|
||||
return "FabricFlower"; // Or something else?
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends AbstractForkedFFExecutor> fernFlowerExecutor() {
|
||||
return FabricForkedFFExecutor.class;
|
||||
public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) {
|
||||
Map<String, Object> options = Map.of(
|
||||
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1",
|
||||
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
|
||||
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
|
||||
IFernflowerPreferences.LOG_LEVEL, "trace",
|
||||
IFernflowerPreferences.THREADS, String.valueOf(metaData.numberOfThreads()),
|
||||
IFernflowerPreferences.INDENT_STRING, "\t",
|
||||
IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(metaData.javaDocs().toFile())
|
||||
);
|
||||
|
||||
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
|
||||
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, new FernflowerLogger(metaData.logger()));
|
||||
|
||||
for (Path library : metaData.libraries()) {
|
||||
ff.addLibrary(library.toFile());
|
||||
}
|
||||
|
||||
ff.addSource(compiledJar.toFile());
|
||||
ff.decompileContext();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2018-2020 FabricMC
|
||||
* Copyright (c) 2021 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -24,34 +24,60 @@
|
|||
|
||||
package net.fabricmc.loom.decompilers.fernflower;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.java.decompiler.main.Fernflower;
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
|
||||
|
||||
import net.fabricmc.fernflower.api.IFabricJavadocProvider;
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
|
||||
public class FabricForkedFFExecutor extends AbstractForkedFFExecutor {
|
||||
public static void main(String[] args) {
|
||||
AbstractForkedFFExecutor.decompile(args, new FabricForkedFFExecutor());
|
||||
public class FernflowerLogger extends IFernflowerLogger {
|
||||
private final IOStringConsumer logger;
|
||||
|
||||
public FernflowerLogger(IOStringConsumer logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runFF(Map<String, Object> options, List<File> libraries, File input, File output, File lineMap, File mappings) {
|
||||
options.put(IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(mappings));
|
||||
|
||||
IResultSaver saver = new ThreadSafeResultSaver(() -> output, () -> lineMap);
|
||||
IFernflowerLogger logger = new ThreadIDFFLogger();
|
||||
Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, logger);
|
||||
|
||||
for (File library : libraries) {
|
||||
ff.addLibrary(library);
|
||||
public void writeMessage(String message, Severity severity) {
|
||||
if (message.contains("Inconsistent inner class entries for")) return;
|
||||
System.err.println(message);
|
||||
}
|
||||
|
||||
ff.addSource(input);
|
||||
ff.decompileContext();
|
||||
@Override
|
||||
public void writeMessage(String message, Severity severity, Throwable t) {
|
||||
writeMessage(message, severity);
|
||||
}
|
||||
|
||||
private void write(String data) {
|
||||
try {
|
||||
logger.accept(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to log", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startReadingClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startClass(String className) {
|
||||
write("Decompiling " + className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startWriteClass(String className) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMethod(String methodName) {
|
||||
// Nope
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endMethod() {
|
||||
// Nope
|
||||
}
|
||||
}
|
|
@ -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.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.workers.WorkAction;
|
||||
import org.gradle.workers.WorkParameters;
|
||||
import org.gradle.workers.WorkQueue;
|
||||
import org.gradle.workers.WorkerExecutor;
|
||||
import org.gradle.workers.internal.WorkerDaemonClientsManager;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
|
||||
|
@ -47,58 +62,216 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMapp
|
|||
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
|
||||
import net.fabricmc.loom.decompilers.LineNumberRemapper;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLogger;
|
||||
import net.fabricmc.loom.util.IOStringConsumer;
|
||||
import net.fabricmc.loom.util.OperatingSystem;
|
||||
import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer;
|
||||
import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger;
|
||||
import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper;
|
||||
import net.fabricmc.loom.util.ipc.IPCClient;
|
||||
import net.fabricmc.loom.util.ipc.IPCServer;
|
||||
import net.fabricmc.stitch.util.StitchUtil;
|
||||
|
||||
public class GenerateSourcesTask extends AbstractLoomTask {
|
||||
public abstract class GenerateSourcesTask extends AbstractLoomTask {
|
||||
public final LoomDecompiler decompiler;
|
||||
|
||||
private File inputJar;
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getInputJar();
|
||||
|
||||
/**
|
||||
* Max memory for forked JVM in megabytes.
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<Long> getMaxMemory();
|
||||
|
||||
@Inject
|
||||
public abstract WorkerExecutor getWorkerExecutor();
|
||||
|
||||
@Inject
|
||||
public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager();
|
||||
|
||||
@Inject
|
||||
public GenerateSourcesTask(LoomDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
|
||||
Objects.requireNonNull(getDecompilerConstructor(this.decompiler.getClass().getCanonicalName()),
|
||||
"%s must have a no args constructor".formatted(this.decompiler.getClass().getCanonicalName()));
|
||||
|
||||
getOutputs().upToDateWhen((o) -> false);
|
||||
getMaxMemory().convention(4096L).finalizeValueOnRead();
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void doTask() throws Throwable {
|
||||
int threads = Runtime.getRuntime().availableProcessors();
|
||||
Collection<Path> libraries = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
|
||||
.stream().map(File::toPath).collect(Collectors.toSet());
|
||||
public void run() throws IOException {
|
||||
if (!OperatingSystem.is64Bit()) {
|
||||
throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
|
||||
}
|
||||
|
||||
DecompilationMetadata metadata = new DecompilationMetadata(threads, getMappings(), libraries);
|
||||
Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath();
|
||||
Path sourcesDestination = getMappedJarFileWithSuffix("-sources.jar").toPath();
|
||||
Path linemap = getMappedJarFileWithSuffix("-sources.lmap").toPath();
|
||||
decompiler.decompile(inputJar.toPath(), sourcesDestination, linemap, metadata);
|
||||
if (!OperatingSystem.isUnixDomainSocketsSupported()) {
|
||||
getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system.");
|
||||
|
||||
doWork(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the IPC path to get the log output back from the forked JVM
|
||||
final Path ipcPath = Files.createTempFile("loom", "ipc");
|
||||
Files.deleteIfExists(ipcPath);
|
||||
|
||||
try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompiler.name(), "Decompiling minecraft sources");
|
||||
IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) {
|
||||
doWork(ipcPath);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Failed to shutdown log receiver", e);
|
||||
} finally {
|
||||
Files.deleteIfExists(ipcPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void doWork(@Nullable Path ipcPath) {
|
||||
final String jvmMarkerValue = UUID.randomUUID().toString();
|
||||
final WorkQueue workQueue = createWorkQueue(jvmMarkerValue);
|
||||
|
||||
workQueue.submit(DecompileAction.class, params -> {
|
||||
params.getDecompilerClass().set(decompiler.getClass().getCanonicalName());
|
||||
|
||||
params.getInputJar().set(getInputJar());
|
||||
params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar());
|
||||
params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar"));
|
||||
params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap"));
|
||||
params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar"));
|
||||
params.getMappings().set(getMappings().toFile());
|
||||
|
||||
if (ipcPath != null) {
|
||||
params.getIPCPath().set(ipcPath.toFile());
|
||||
}
|
||||
|
||||
params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES));
|
||||
});
|
||||
|
||||
workQueue.await();
|
||||
|
||||
if (useProcessIsolation()) {
|
||||
boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue);
|
||||
|
||||
if (!stopped) {
|
||||
throw new RuntimeException("Failed to stop decompile worker JVM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WorkQueue createWorkQueue(String jvmMarkerValue) {
|
||||
if (!useProcessIsolation()) {
|
||||
return getWorkerExecutor().noIsolation();
|
||||
}
|
||||
|
||||
return getWorkerExecutor().processIsolation(spec -> {
|
||||
spec.forkOptions(forkOptions -> {
|
||||
forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get()));
|
||||
forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private boolean useProcessIsolation() {
|
||||
// Useful if you want to debug the decompiler, make sure you run gradle with enough memory.
|
||||
return !Boolean.getBoolean("fabric.loom.genSources.debug");
|
||||
}
|
||||
|
||||
public interface DecompileParams extends WorkParameters {
|
||||
Property<String> getDecompilerClass();
|
||||
|
||||
RegularFileProperty getInputJar();
|
||||
RegularFileProperty getRuntimeJar();
|
||||
RegularFileProperty getSourcesDestinationJar();
|
||||
RegularFileProperty getLinemap();
|
||||
RegularFileProperty getLinemapJar();
|
||||
RegularFileProperty getMappings();
|
||||
|
||||
RegularFileProperty getIPCPath();
|
||||
|
||||
ConfigurableFileCollection getClassPath();
|
||||
}
|
||||
|
||||
public abstract static class DecompileAction implements WorkAction<DecompileParams> {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (!getParameters().getIPCPath().isPresent() || !OperatingSystem.isUnixDomainSocketsSupported()) {
|
||||
// Does not support unix domain sockets, print to sout.
|
||||
doDecompile(System.out::println);
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
Path linemappedJarDestination = getMappedJarFileWithSuffix("-linemapped.jar").toPath();
|
||||
|
||||
try {
|
||||
// Line map the actually jar used to run the game, not the one used to decompile
|
||||
remapLineNumbers(runtimeJar, linemap, linemappedJarDestination);
|
||||
remapLineNumbers(metadata.logger(), runtimeJar, linemap, linemapJar);
|
||||
|
||||
Files.copy(linemappedJarDestination, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.delete(linemappedJarDestination);
|
||||
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(Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
|
||||
getProject().getLogger().info(":adjusting line numbers");
|
||||
private void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
|
||||
LineNumberRemapper remapper = new LineNumberRemapper();
|
||||
remapper.readMappings(linemap.toFile());
|
||||
|
||||
ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName());
|
||||
progressLogger.start("Adjusting line numbers", "linemap");
|
||||
|
||||
try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true);
|
||||
StitchUtil.FileSystemDelegate outFs = StitchUtil.getJarFileSystem(linemappedJarDestination.toFile(), true)) {
|
||||
remapper.process(progressLogger, inFs.get().getPath("/"), outFs.get().getPath("/"));
|
||||
remapper.process(logger, inFs.get().getPath("/"), outFs.get().getPath("/"));
|
||||
}
|
||||
}
|
||||
|
||||
progressLogger.completed();
|
||||
private Collection<Path> getLibraries() {
|
||||
return getParameters().getClassPath().getFiles().stream().map(File::toPath).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
private File getMappedJarFileWithSuffix(String suffix) {
|
||||
|
@ -140,13 +313,12 @@ public class GenerateSourcesTask extends AbstractLoomTask {
|
|||
return baseMappings;
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public File getInputJar() {
|
||||
return inputJar;
|
||||
}
|
||||
|
||||
public GenerateSourcesTask setInputJar(File inputJar) {
|
||||
this.inputJar = inputJar;
|
||||
return this;
|
||||
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
||||
try {
|
||||
//noinspection unchecked
|
||||
return (Constructor<LoomDecompiler>) Class.forName(clazz).getConstructor();
|
||||
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,9 +128,9 @@ public final class LoomTasks {
|
|||
File outputJar = mappingsProvider.mappedProvider.getUnpickedJar();
|
||||
|
||||
tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> {
|
||||
unpickJarTask.setUnpickDefinition(mappingsProvider.getUnpickDefinitionsFile());
|
||||
unpickJarTask.setInputJar(mappingsProvider.mappedProvider.getMappedJar());
|
||||
unpickJarTask.setOutputJar(outputJar);
|
||||
unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile());
|
||||
unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar());
|
||||
unpickJarTask.getOutputJar().set(outputJar);
|
||||
});
|
||||
|
||||
inputJar = outputJar;
|
||||
|
@ -142,7 +142,7 @@ public final class LoomTasks {
|
|||
String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name();
|
||||
// decompiler will be passed to the constructor of GenerateSourcesTask
|
||||
GenerateSourcesTask generateSourcesTask = tasks.register(taskName, GenerateSourcesTask.class, decompiler).get();
|
||||
generateSourcesTask.setInputJar(inputJar);
|
||||
generateSourcesTask.getInputJar().set(inputJar);
|
||||
|
||||
if (mappingsProvider.hasUnpickDefinitions()) {
|
||||
generateSourcesTask.dependsOn(tasks.getByName("unpickJar"));
|
||||
|
|
|
@ -29,7 +29,12 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
|
@ -39,26 +44,43 @@ import net.fabricmc.loom.configuration.providers.LaunchProvider;
|
|||
import net.fabricmc.loom.extension.LoomFiles;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
|
||||
public class UnpickJarTask extends JavaExec {
|
||||
File inputJar;
|
||||
File unpickDefinition;
|
||||
public abstract class UnpickJarTask extends JavaExec {
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getInputJar();
|
||||
|
||||
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() {
|
||||
getOutputs().upToDateWhen(e -> false);
|
||||
classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH));
|
||||
getMainClass().set("daomephsta.unpick.cli.Main");
|
||||
|
||||
getConstantJar().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MAPPING_CONSTANTS));
|
||||
getUnpickClasspath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exec() {
|
||||
fileArg(getInputJar(), getOutputJar(), getUnpickDefinition());
|
||||
fileArg(getConstantJar());
|
||||
fileArg(getInputJar().get().getAsFile(), getOutputJar().get().getAsFile(), getUnpickDefinitions().get().getAsFile());
|
||||
fileArg(getConstantJar().getSingleFile());
|
||||
|
||||
// Classpath
|
||||
fileArg(getExtension().getMinecraftMappedProvider().getMappedJar());
|
||||
fileArg(getMinecraftDependencies());
|
||||
|
||||
for (File file : getUnpickClasspath()) {
|
||||
fileArg(file);
|
||||
}
|
||||
|
||||
writeUnpickLogConfig();
|
||||
systemProperty("java.util.logging.config.file", getDirectories().getUnpickLoggingConfigFile().getAbsolutePath());
|
||||
|
@ -75,45 +97,6 @@ public class UnpickJarTask extends JavaExec {
|
|||
}
|
||||
}
|
||||
|
||||
private File[] getMinecraftDependencies() {
|
||||
return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)
|
||||
.resolve().toArray(new File[0]);
|
||||
}
|
||||
|
||||
private File getConstantJar() {
|
||||
return getProject().getConfigurations().getByName(Constants.Configurations.MAPPING_CONSTANTS).getSingleFile();
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public File getInputJar() {
|
||||
return inputJar;
|
||||
}
|
||||
|
||||
public UnpickJarTask setInputJar(File inputJar) {
|
||||
this.inputJar = inputJar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public File getUnpickDefinition() {
|
||||
return unpickDefinition;
|
||||
}
|
||||
|
||||
public UnpickJarTask setUnpickDefinition(File unpickDefinition) {
|
||||
this.unpickDefinition = unpickDefinition;
|
||||
return this;
|
||||
}
|
||||
|
||||
@OutputFile
|
||||
public File getOutputJar() {
|
||||
return outputJar;
|
||||
}
|
||||
|
||||
public UnpickJarTask setOutputJar(File outputJar) {
|
||||
this.outputJar = outputJar;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void fileArg(File... files) {
|
||||
for (File file : files) {
|
||||
args(file.getAbsolutePath());
|
||||
|
|
|
@ -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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
|
||||
public class OperatingSystem {
|
||||
public static String getOS() {
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
|
@ -63,4 +68,15 @@ public class OperatingSystem {
|
|||
// CI seems to be set by most popular CI services
|
||||
return System.getenv("CI") != null;
|
||||
}
|
||||
|
||||
// Requires Unix, or Windows 10 17063 or later. See: https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
|
||||
public static boolean isUnixDomainSocketsSupported() {
|
||||
try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
|
||||
return true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension;
|
|||
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
|
||||
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
|
||||
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLogger;
|
||||
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
|
||||
import net.fabricmc.lorenztiny.TinyMappingsReader;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
import net.fabricmc.stitch.util.StitchUtil;
|
||||
|
@ -51,7 +51,7 @@ import net.fabricmc.stitch.util.StitchUtil;
|
|||
public class SourceRemapper {
|
||||
private final Project project;
|
||||
private final boolean toNamed;
|
||||
private final List<Consumer<ProgressLogger>> remapTasks = new ArrayList<>();
|
||||
private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>();
|
||||
|
||||
private Mercury mercury;
|
||||
|
||||
|
@ -90,7 +90,7 @@ public class SourceRemapper {
|
|||
|
||||
project.getLogger().lifecycle(":remapping sources");
|
||||
|
||||
ProgressLogger progressLogger = ProgressLogger.getProgressFactory(project, SourceRemapper.class.getName());
|
||||
ProgressLoggerHelper progressLogger = ProgressLoggerHelper.getProgressFactory(project, SourceRemapper.class.getName());
|
||||
progressLogger.start("Remapping dependency sources", "sources");
|
||||
|
||||
remapTasks.forEach(consumer -> consumer.accept(progressLogger));
|
||||
|
|
|
@ -32,11 +32,11 @@ import org.gradle.api.Project;
|
|||
/**
|
||||
* Wrapper to ProgressLogger internal API.
|
||||
*/
|
||||
public class ProgressLogger {
|
||||
public class ProgressLoggerHelper {
|
||||
private final Object logger;
|
||||
private final Method getDescription, setDescription, getShortDescription, setShortDescription, getLoggingHeader, setLoggingHeader, start, started, startedArg, progress, completed, completedArg;
|
||||
|
||||
private ProgressLogger(Object logger) {
|
||||
private ProgressLoggerHelper(Object logger) {
|
||||
this.logger = logger;
|
||||
this.getDescription = getMethod("getDescription");
|
||||
this.setDescription = getMethod("setDescription", String.class);
|
||||
|
@ -102,17 +102,17 @@ public class ProgressLogger {
|
|||
* @param category The logger category
|
||||
* @return In any case a progress logger
|
||||
*/
|
||||
public static ProgressLogger getProgressFactory(Project project, String category) {
|
||||
public static ProgressLoggerHelper getProgressFactory(Project project, String category) {
|
||||
try {
|
||||
Method getServices = project.getClass().getMethod("getServices");
|
||||
Object serviceFactory = getServices.invoke(project);
|
||||
Method get = serviceFactory.getClass().getMethod("get", Class.class);
|
||||
Object progressLoggerFactory = get.invoke(serviceFactory, getFactoryClass());
|
||||
Method newOperation = progressLoggerFactory.getClass().getMethod("newOperation", String.class);
|
||||
return new ProgressLogger(newOperation.invoke(progressLoggerFactory, category));
|
||||
return new ProgressLoggerHelper(newOperation.invoke(progressLoggerFactory, category));
|
||||
} catch (Exception e) {
|
||||
project.getLogger().error("Unable to get progress logger. Download progress will not be displayed.");
|
||||
return new ProgressLogger(null);
|
||||
return new ProgressLoggerHelper(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ public class ProgressLogger {
|
|||
*
|
||||
* @param description The description.
|
||||
*/
|
||||
public ProgressLogger setDescription(String description) {
|
||||
public ProgressLoggerHelper setDescription(String description) {
|
||||
invoke(setDescription, description);
|
||||
return this;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ public class ProgressLogger {
|
|||
*
|
||||
* @param description The short description.
|
||||
*/
|
||||
public ProgressLogger setShortDescription(String description) {
|
||||
public ProgressLoggerHelper setShortDescription(String description) {
|
||||
invoke(setShortDescription, description);
|
||||
return this;
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ public class ProgressLogger {
|
|||
*
|
||||
* @param header The header. May be empty or null.
|
||||
*/
|
||||
public ProgressLogger setLoggingHeader(String header) {
|
||||
public ProgressLoggerHelper setLoggingHeader(String header) {
|
||||
invoke(setLoggingHeader, header);
|
||||
return this;
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ public class ProgressLogger {
|
|||
*
|
||||
* @return this logger instance
|
||||
*/
|
||||
public ProgressLogger start(String description, String shortDescription) {
|
||||
public ProgressLoggerHelper start(String description, String shortDescription) {
|
||||
invoke(start, description, shortDescription);
|
||||
return this;
|
||||
}
|
|
@ -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
|
||||
'fernflower' | "genSources" | DEFAULT_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 {
|
||||
minecraft "com.mojang:minecraft:1.16.5"
|
||||
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.11.2"
|
||||
minecraft "com.mojang:minecraft:21w38a"
|
||||
mappings "net.fabricmc:yarn:21w38a+build.11:v2"
|
||||
modImplementation "net.fabricmc:fabric-loader:0.11.7"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue