Rewrite GenSources including full support for CFR. (#511)

* Rewrite CFR decompiler interface. Support javadoc

* CFR line numbers and fixes.

* Cleanup and fix

* Use WorkerExecutor to fork, massively cleans up the fernflower code, but does remove the fancy multithreaded logging.

* Use IPC to get logging back from the decompilers.

* Cleanup UnpickJarTask, fix leak in IPCServer

* Used published CFR build

* Handle older windows versions that do not support AF_UNIX.

* Fixes and basic unit test

* Improve memory handling of genSources

* Stop decompile worker JVM
dev/0.11
modmuss50 2021-10-11 13:47:16 +01:00 committed by GitHub
parent 5315d3c5b2
commit e2439b7f57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1355 additions and 845 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
try {
//noinspection unchecked
return (Constructor<LoomDecompiler>) Class.forName(clazz).getConstructor();
} catch (NoSuchMethodException | ClassNotFoundException e) {
return null;
}
public GenerateSourcesTask setInputJar(File inputJar) {
this.inputJar = inputJar;
return this;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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