diff options
Diffstat (limited to 'src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java')
-rw-r--r-- | src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java | 244 |
1 files changed, 208 insertions, 36 deletions
diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index a19746b0..8c6dca24 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -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); + + workQueue.submit(DecompileAction.class, params -> { + params.getDecompilerClass().set(decompiler.getClass().getCanonicalName()); + + params.getInputJar().set(getInputJar()); + params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar()); + params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar")); + params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap")); + params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar")); + params.getMappings().set(getMappings().toFile()); + + if (ipcPath != null) { + params.getIPCPath().set(ipcPath.toFile()); + } + + params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)); + }); + + workQueue.await(); + + if (useProcessIsolation()) { + boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue); + + if (!stopped) { + throw new RuntimeException("Failed to stop decompile worker JVM"); + } + } + } + + private WorkQueue createWorkQueue(String jvmMarkerValue) { + if (!useProcessIsolation()) { + return getWorkerExecutor().noIsolation(); + } + + return getWorkerExecutor().processIsolation(spec -> { + spec.forkOptions(forkOptions -> { + forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get())); + forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue); + }); + }); + } + + private boolean useProcessIsolation() { + // Useful if you want to debug the decompiler, make sure you run gradle with enough memory. + return !Boolean.getBoolean("fabric.loom.genSources.debug"); + } + + public interface DecompileParams extends WorkParameters { + Property<String> getDecompilerClass(); + + RegularFileProperty getInputJar(); + RegularFileProperty getRuntimeJar(); + RegularFileProperty getSourcesDestinationJar(); + RegularFileProperty getLinemap(); + RegularFileProperty getLinemapJar(); + RegularFileProperty getMappings(); + + RegularFileProperty getIPCPath(); + + ConfigurableFileCollection getClassPath(); + } + + public abstract static class DecompileAction implements WorkAction<DecompileParams> { + @Override + public void execute() { + if (!getParameters().getIPCPath().isPresent() || !OperatingSystem.isUnixDomainSocketsSupported()) { + // Does not support unix domain sockets, print to sout. + doDecompile(System.out::println); + return; + } - ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName()); - progressLogger.start("Adjusting line numbers", "linemap"); + final Path ipcPath = getParameters().getIPCPath().get().getAsFile().toPath(); - 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("/")); + try (IPCClient ipcClient = new IPCClient(ipcPath)) { + doDecompile(new ThreadedSimpleProgressLogger(ipcClient)); + } catch (Exception e) { + throw new RuntimeException("Failed to setup IPC Client", e); + } } - progressLogger.completed(); + 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; + } } } |