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