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