diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java index de9d11b2..dd3a1e98 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java @@ -30,6 +30,7 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.ConfigurationContainer; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; @@ -63,5 +64,6 @@ public abstract class DecompileConfiguration task.getUnpickConstantJar().setFrom(configurations.getByName(Constants.Configurations.MAPPING_CONSTANTS)); task.getUnpickClasspath().setFrom(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); task.getUnpickClasspath().from(configurations.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED)); + extension.getMinecraftJars(MappingsNamespace.NAMED).forEach(task.getUnpickClasspath()::from); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java index a26dfa8d..f93e119f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java @@ -61,7 +61,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration { task.getInputJarName().set(minecraftJar.getName()); - task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", minecraftJar.getPath())); + task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", minecraftJar.getPath())); task.dependsOn(project.getTasks().named("validateAccessWidener")); task.setDescription("Decompile minecraft using %s.".formatted(decompilerName)); diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java index 0501a287..e47f7ddc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java @@ -55,7 +55,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration commonDecompileTask = createDecompileTasks("Common", task -> { task.getInputJarName().set(commonJar.getName()); - task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath())); + task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath())); if (mappingConfiguration.hasUnpickDefinitions()) { File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar"); @@ -65,7 +65,7 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> { task.getInputJarName().set(clientOnlyJar.getName()); - task.getOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath())); + task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath())); if (mappingConfiguration.hasUnpickDefinitions()) { File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar"); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java index ac6f2eb2..3c49e258 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -85,11 +86,18 @@ public abstract class AbstractMappedMinecraftProvider provide(ProvideContext context) throws Exception { final List remappedJars = getRemappedJars(); - assert !remappedJars.isEmpty(); + final List minecraftJars = remappedJars.stream() + .map(RemappedJars::outputJar) + .toList(); - if (!areOutputsValid(remappedJars) || context.refreshOutputs()) { + if (remappedJars.isEmpty()) { + throw new IllegalStateException("No remapped jars provided"); + } + + if (!areOutputsValid(remappedJars) || context.refreshOutputs() || !hasBackupJars(minecraftJars)) { try { remapInputs(remappedJars, context.configContext()); + createBackupJars(minecraftJars); } catch (Throwable t) { cleanOutputs(remappedJars); @@ -108,9 +116,29 @@ public abstract class AbstractMappedMinecraftProvider minecraftJars) { + for (MinecraftJar minecraftJar : minecraftJars) { + if (!Files.exists(getBackupJarPath(minecraftJar))) { + return false; + } + } + + return true; + } + + protected void createBackupJars(List minecraftJars) throws IOException { + for (MinecraftJar minecraftJar : minecraftJars) { + Files.copy(minecraftJar.getPath(), getBackupJarPath(minecraftJar), StandardCopyOption.REPLACE_EXISTING); + } } public record ProvideContext(boolean applyDependencies, boolean refreshOutputs, ConfigContext configContext) { @@ -266,6 +294,7 @@ public abstract class AbstractMappedMinecraftProvider remappedJars) throws IOException { for (RemappedJars remappedJar : remappedJars) { Files.deleteIfExists(remappedJar.outputJarPath()); + Files.deleteIfExists(getBackupJarPath(remappedJar.outputJar())); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java index daacc701..53604c62 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java @@ -58,17 +58,20 @@ public abstract class ProcessedNamedMinecraftProvider provide(ProvideContext context) throws Exception { + final List parentMinecraftJars = parentMinecraftProvider.getMinecraftJars(); + final Map minecraftJarOutputMap = parentMinecraftJars.stream() + .collect(Collectors.toMap(Function.identity(), this::getProcessedJar)); + final List minecraftJars = List.copyOf(minecraftJarOutputMap.values()); + parentMinecraftProvider.provide(context.withApplyDependencies(false)); - boolean requiresProcessing = context.refreshOutputs() || parentMinecraftProvider.getMinecraftJars().stream() + boolean requiresProcessing = context.refreshOutputs() || !hasBackupJars(minecraftJars) || parentMinecraftJars.stream() .map(this::getProcessedPath) .anyMatch(jarProcessorManager::requiresProcessingJar); - final Map minecraftJarOutputMap = parentMinecraftProvider.getMinecraftJars().stream() - .collect(Collectors.toMap(Function.identity(), this::getProcessedJar)); - if (requiresProcessing) { processJars(minecraftJarOutputMap, context.configContext()); + createBackupJars(minecraftJars); } if (context.applyDependencies()) { diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 4dccab02..a577b9c5 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -29,9 +29,7 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.Reader; import java.io.UncheckedIOException; -import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.charset.StandardCharsets; @@ -64,10 +62,12 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; +import org.gradle.internal.logging.progress.ProgressLoggerFactory; import org.gradle.process.ExecOperations; import org.gradle.process.ExecResult; import org.gradle.work.DisableCachingByDefault; @@ -78,17 +78,10 @@ import org.gradle.workers.WorkerExecutor; import org.gradle.workers.internal.WorkerDaemonClientsManager; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.LoomDecompiler; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.configuration.ConfigContextImpl; -import net.fabricmc.loom.configuration.processors.MappingProcessorContextImpl; -import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar; import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider; import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; @@ -97,6 +90,7 @@ import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.decompilers.cache.CachedData; import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl; import net.fabricmc.loom.decompilers.cache.CachedJarProcessor; +import net.fabricmc.loom.task.service.SourceMappingsService; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ExceptionUtil; @@ -110,14 +104,10 @@ import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.service.ScopedServiceFactory; -import net.fabricmc.mappingio.MappingReader; -import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; -import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter; import net.fabricmc.mappingio.tree.MemoryMappingTree; @DisableCachingByDefault public abstract class GenerateSourcesTask extends AbstractLoomTask { - private static final Logger LOGGER = LoggerFactory.getLogger(GenerateSourcesTask.class); private static final String CACHE_VERSION = "v1"; private final DecompilerOptions decompilerOptions; @@ -127,11 +117,21 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Input public abstract Property getInputJarName(); + @InputFiles // Only contains a single file + protected abstract ConfigurableFileCollection getClassesInputJar(); + @InputFiles - public abstract ConfigurableFileCollection getClasspath(); + protected abstract ConfigurableFileCollection getClasspath(); + + @InputFiles + protected abstract ConfigurableFileCollection getMinecraftCompileLibraries(); @OutputFile - public abstract RegularFileProperty getOutputJar(); + public abstract RegularFileProperty getSourcesOutputJar(); + + // Contains the remapped linenumbers + @OutputFile + protected abstract ConfigurableFileCollection getClassesOutputJar(); // Single jar // Unpick @InputFile @@ -155,6 +155,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Optional public abstract RegularFileProperty getUnpickOutputJar(); + @OutputFile + protected abstract RegularFileProperty getUnpickLogConfig(); + @Input @Option(option = "use-cache", description = "Use the decompile cache") @ApiStatus.Experimental @@ -165,6 +168,11 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @ApiStatus.Experimental public abstract Property getResetCache(); + // Internal inputs + @ApiStatus.Internal + @Nested + protected abstract Property getMappings(); + // Internal outputs @ApiStatus.Internal @Internal @@ -172,13 +180,16 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { // Injects @Inject - public abstract WorkerExecutor getWorkerExecutor(); + protected abstract WorkerExecutor getWorkerExecutor(); @Inject - public abstract ExecOperations getExecOperations(); + protected abstract ExecOperations getExecOperations(); @Inject - public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager(); + protected abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager(); + + @Inject + protected abstract ProgressLoggerFactory getProgressLoggerFactory(); // Prevent Gradle from running two gen sources tasks in parallel @ServiceReference(SyncTaskBuildService.NAME) @@ -188,19 +199,48 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { public GenerateSourcesTask(DecompilerOptions decompilerOptions) { this.decompilerOptions = decompilerOptions; + getClassesInputJar().setFrom(getInputJarName().map(minecraftJarName -> { + final List minecraftJars = getExtension().getNamedMinecraftProvider().getMinecraftJars(); + + for (MinecraftJar minecraftJar : minecraftJars) { + if (minecraftJar.getName().equals(minecraftJarName)) { + final Path backupJarPath = AbstractMappedMinecraftProvider.getBackupJarPath(minecraftJar); + + if (Files.notExists(backupJarPath)) { + throw new IllegalStateException("Input minecraft jar not found at: " + backupJarPath); + } + + return backupJarPath.toFile(); + } + } + + throw new IllegalStateException("Input minecraft jar not found: " + getInputJarName().get()); + })); + getClassesOutputJar().setFrom(getInputJarName().map(minecraftJarName -> { + final List minecraftJars = getExtension().getNamedMinecraftProvider().getMinecraftJars(); + + for (MinecraftJar minecraftJar : minecraftJars) { + if (minecraftJar.getName().equals(minecraftJarName)) { + return minecraftJar.toFile(); + } + } + + throw new IllegalStateException("Input minecraft jar not found: " + getInputJarName().get()); + })); + getOutputs().upToDateWhen((o) -> false); getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead(); dependsOn(decompilerOptions.getClasspath().getBuiltBy()); - LoomGradleExtension extension = LoomGradleExtension.get(getProject()); - getDecompileCacheFile().set(extension.getFiles().getDecompileCache(CACHE_VERSION)); + getMinecraftCompileLibraries().from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); + getDecompileCacheFile().set(getExtension().getFiles().getDecompileCache(CACHE_VERSION)); getUnpickRuntimeClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH)); + getUnpickLogConfig().set(getExtension().getFiles().getUnpickLoggingConfigFile()); getUseCache().convention(true); - getResetCache().convention(extension.refreshDeps()); + getResetCache().convention(getExtension().refreshDeps()); - doNotTrackState("Cannot rebuild input jar without project."); - notCompatibleWithConfigurationCache("Cannot rebuild input jar without project."); + getMappings().set(SourceMappingsService.create(getProject())); } @TaskAction @@ -222,13 +262,13 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { return; } - LOGGER.info("Using decompile cache."); + getLogger().info("Using decompile cache."); try (var timer = new Timer("Decompiled sources with cache")) { final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath(); if (getResetCache().get()) { - LOGGER.warn("Resetting decompile cache"); + getLogger().warn("Resetting decompile cache"); Files.deleteIfExists(cacheFile); } @@ -246,38 +286,40 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private void runWithCache(Path cacheRoot) throws IOException { - final MinecraftJar minecraftJar = rebuildInputJar(); + final Path classesInputJar = getClassesInputJar().getSingleFile().toPath(); + final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath(); + final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath(); final var cacheRules = new CachedFileStoreImpl.CacheRules(50_000, Duration.ofDays(90)); final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules); final String cacheKey = getCacheKey(); final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey); final CachedJarProcessor.WorkRequest workRequest; - LOGGER.info("Decompile cache key: {}", cacheKey); + getLogger().info("Decompile cache key: {}", cacheKey); try (var timer = new Timer("Prepare job")) { - workRequest = cachedJarProcessor.prepareJob(minecraftJar.getPath()); + workRequest = cachedJarProcessor.prepareJob(classesInputJar); } final CachedJarProcessor.WorkJob job = workRequest.job(); final CachedJarProcessor.CacheStats cacheStats = workRequest.stats(); - getProject().getLogger().lifecycle("Decompile cache stats: {} hits, {} misses", cacheStats.hits(), cacheStats.misses()); + getLogger().lifecycle("Decompile cache stats: {} hits, {} misses", cacheStats.hits(), cacheStats.misses()); ClassLineNumbers outputLineNumbers = null; if (job instanceof CachedJarProcessor.WorkToDoJob workToDoJob) { - Path inputJar = workToDoJob.incomplete(); + Path workInputJar = workToDoJob.incomplete(); @Nullable Path existingClasses = (job instanceof CachedJarProcessor.PartialWorkJob partialWorkJob) ? partialWorkJob.existingClasses() : null; if (getUnpickDefinitions().isPresent()) { try (var timer = new Timer("Unpick")) { - inputJar = unpickJar(inputJar, existingClasses); + workInputJar = unpickJar(workInputJar, existingClasses); } } try (var timer = new Timer("Decompile")) { - outputLineNumbers = runDecompileJob(inputJar, workToDoJob.output(), existingClasses); + outputLineNumbers = runDecompileJob(workInputJar, workToDoJob.output(), existingClasses); removeForgeInnerClassSources(workToDoJob.output()); outputLineNumbers = filterForgeLineNumbers(outputLineNumbers); } @@ -290,24 +332,20 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } // The final output sources jar - final Path sourcesJar = getOutputJar().get().getAsFile().toPath(); - Files.deleteIfExists(sourcesJar); + Files.deleteIfExists(sourcesOutputJar); try (var timer = new Timer("Complete job")) { - cachedJarProcessor.completeJob(sourcesJar, job, outputLineNumbers); + cachedJarProcessor.completeJob(sourcesOutputJar, job, outputLineNumbers); } - LOGGER.info("Decompiled sources written to {}", sourcesJar); - - // This is the minecraft jar used at runtime. - final Path classesJar = minecraftJar.getPath(); + getLogger().info("Decompiled sources written to {}", sourcesOutputJar); // Remap the line numbers with the new and existing numbers final ClassLineNumbers existingLinenumbers = workRequest.lineNumbers(); final ClassLineNumbers lineNumbers = ClassLineNumbers.merge(existingLinenumbers, outputLineNumbers); if (lineNumbers == null) { - LOGGER.info("No line numbers to remap, skipping remapping"); + getLogger().info("No line numbers to remap, skipping remapping"); return; } @@ -315,10 +353,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { Files.delete(tempJar); try (var timer = new Timer("Remap line numbers")) { - remapLineNumbers(lineNumbers, classesJar, tempJar); + remapLineNumbers(lineNumbers, classesInputJar, tempJar); } - Files.move(tempJar, classesJar, StandardCopyOption.REPLACE_EXISTING); + Files.move(tempJar, classesOutputJar, StandardCopyOption.REPLACE_EXISTING); try (var timer = new Timer("Prune cache")) { decompileCache.prune(); @@ -326,11 +364,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private void runWithoutCache() throws IOException { - final MinecraftJar minecraftJar = rebuildInputJar(); + Path inputJar = getClassesInputJar().getSingleFile().toPath(); + final Path outputJar = getSourcesOutputJar().get().getAsFile().toPath(); - Path inputJar = minecraftJar.getPath(); // The final output sources jar - final Path sourcesJar = getOutputJar().get().getAsFile().toPath(); if (getUnpickDefinitions().isPresent()) { try (var timer = new Timer("Unpick")) { @@ -341,32 +378,30 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { ClassLineNumbers lineNumbers; try (var timer = new Timer("Decompile")) { - lineNumbers = runDecompileJob(inputJar, sourcesJar, null); + lineNumbers = runDecompileJob(inputJar, outputJar, null); removeForgeInnerClassSources(sourcesJar); lineNumbers = filterForgeLineNumbers(lineNumbers); } - if (Files.notExists(sourcesJar)) { + if (Files.notExists(outputJar)) { throw new RuntimeException("Failed to decompile sources"); } - LOGGER.info("Decompiled sources written to {}", sourcesJar); + getLogger().info("Decompiled sources written to {}", outputJar); if (lineNumbers == null) { - LOGGER.info("No line numbers to remap, skipping remapping"); + getLogger().info("No line numbers to remap, skipping remapping"); return; } - // This is the minecraft jar used at runtime. - final Path classesJar = minecraftJar.getPath(); final Path tempJar = Files.createTempFile("loom", "linenumber-remap.jar"); Files.delete(tempJar); try (var timer = new Timer("Remap line numbers")) { - remapLineNumbers(lineNumbers, classesJar, tempJar); + remapLineNumbers(lineNumbers, inputJar, tempJar); } - Files.move(tempJar, classesJar, StandardCopyOption.REPLACE_EXISTING); + Files.move(tempJar, outputJar, StandardCopyOption.REPLACE_EXISTING); } private String getCacheKey() { @@ -374,7 +409,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { sj.add(getDecompilerCheckKey()); sj.add(getUnpickCacheKey()); - LOGGER.info("Decompile cache data: {}", sj); + getLogger().info("Decompile cache data: {}", sj); try { return Checksum.sha256Hex(sj.toString().getBytes(StandardCharsets.UTF_8)); @@ -415,7 +450,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { Files.delete(lineMapFile); if (!platform.supportsUnixDomainSockets()) { - getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system."); + getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system."); doWork(null, inputJar, outputJar, lineMapFile, existingJar); @@ -433,7 +468,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { final Path ipcPath = Files.createTempFile("loom", "ipc"); Files.deleteIfExists(ipcPath); - try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources"); + try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getLogger(), getProgressLoggerFactory(), decompilerOptions.getName(), "Decompiling minecraft sources"); IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) { doWork(logReceiver, inputJar, outputJar, lineMapFile, existingJar); } catch (InterruptedException e) { @@ -507,31 +542,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - // Re-run the named minecraft provider to give us a fresh jar to decompile. - // This prevents re-applying line maps on an existing jar. - private MinecraftJar rebuildInputJar() { - final List minecraftJars; - - try (var serviceFactory = new ScopedServiceFactory()) { - final var configContext = new ConfigContextImpl(getProject(), serviceFactory, getExtension()); - final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(false, true, configContext); - minecraftJars = getExtension().getNamedMinecraftProvider().provide(provideContext); - } catch (Exception e) { - throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to rebuild input jars", e); - } - - for (MinecraftJar minecraftJar : minecraftJars) { - if (minecraftJar.getName().equals(getInputJarName().get())) { - return minecraftJar; - } - } - - throw new IllegalStateException("Could not find minecraft jar (%s) but got (%s)".formatted( - getInputJarName().get(), - minecraftJars.stream().map(MinecraftJar::getName).collect(Collectors.joining(", "))) - ); - } - private Path unpickJar(Path inputJar, @Nullable Path existingClasses) { final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath(); final List args = getUnpickArgs(inputJar, outputJar, existingClasses); @@ -556,11 +566,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { fileArgs.add(getUnpickDefinitions().get().getAsFile()); fileArgs.add(getUnpickConstantJar().getSingleFile()); - // Classpath - for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.NAMED)) { - fileArgs.add(minecraftJar.toFile()); - } - for (File file : getUnpickClasspath()) { fileArgs.add(file); } @@ -575,13 +580,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private File writeUnpickLogConfig() { - final File unpickLoggingConfigFile = getExtension().getFiles().getUnpickLoggingConfigFile(); + final File unpickLoggingConfigFile = getUnpickLogConfig().getAsFile().get(); try (InputStream is = GenerateSourcesTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { - Files.deleteIfExists(unpickLoggingConfigFile.toPath()); - Files.copy(Objects.requireNonNull(is), unpickLoggingConfigFile.toPath()); + Files.copy(Objects.requireNonNull(is), unpickLoggingConfigFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - throw new org.gradle.api.UncheckedIOException("Failed to copy unpick logging config", e); + throw new UncheckedIOException("Failed to copy unpick logging config", e); } return unpickLoggingConfigFile; @@ -598,33 +602,30 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { lineNumbers.write(writer); } - LOGGER.info("Wrote linemap to {}", lineMap); + getLogger().info("Wrote linemap to {}", lineMap); } private void doWork(@Nullable IPCServer ipcServer, Path inputJar, Path outputJar, Path linemapFile, @Nullable Path existingClasses) { final String jvmMarkerValue = UUID.randomUUID().toString(); final WorkQueue workQueue = createWorkQueue(jvmMarkerValue); - ConfigurableFileCollection classpath = getProject().files(); - classpath.from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); - - if (existingClasses != null) { - classpath.from(existingClasses); - } - workQueue.submit(DecompileAction.class, params -> { params.getDecompilerOptions().set(decompilerOptions.toDto()); params.getInputJar().set(inputJar.toFile()); params.getOutputJar().set(outputJar.toFile()); params.getLinemapFile().set(linemapFile.toFile()); - params.getMappings().set(getMappings().toFile()); + params.getMappings().set(getMappings()); if (ipcServer != null) { params.getIPCPath().set(ipcServer.getPath().toFile()); } - params.getClassPath().setFrom(classpath); + params.getClassPath().setFrom(getMinecraftCompileLibraries()); + + if (existingClasses != null) { + params.getClassPath().from(existingClasses); + } // Architectury params.getForge().set(getExtension().isForgeLike()); @@ -637,7 +638,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue); if (!stopped && ipcServer.hasReceivedMessage()) { - LOGGER.info("Failed to stop decompile worker JVM, it may have already been stopped?"); + getLogger().info("Failed to stop decompile worker JVM, it may have already been stopped?"); } } } @@ -671,7 +672,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { RegularFileProperty getInputJar(); RegularFileProperty getOutputJar(); RegularFileProperty getLinemapFile(); - RegularFileProperty getMappings(); + Property getMappings(); RegularFileProperty getIPCPath(); @@ -718,26 +719,32 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { throw new RuntimeException("Failed to create decompiler", e); } - final var metadata = new DecompilationMetadata( - decompilerOptions.maxThreads(), - getParameters().getMappings().get().getAsFile().toPath(), - getLibraries(), - logger, - decompilerOptions.options() - ); + try (var serviceFactory = new ScopedServiceFactory()) { + final SourceMappingsService mappingsService = serviceFactory.get(getParameters().getMappings()); - decompiler.decompile( - inputJar, - outputJar, - linemap, - metadata - ); + final var metadata = new DecompilationMetadata( + decompilerOptions.maxThreads(), + mappingsService.getMappingsFile(), + getLibraries(), + logger, + decompilerOptions.options() + ); - // Close the decompile loggers - try { - metadata.logger().accept(ThreadedProgressLoggerConsumer.CLOSE_LOGGERS); + decompiler.decompile( + inputJar, + outputJar, + linemap, + metadata + ); + + // Close the decompile loggers + try { + metadata.logger().accept(ThreadedProgressLoggerConsumer.CLOSE_LOGGERS); + } catch (IOException e) { + throw new UncheckedIOException("Failed to close loggers", e); + } } catch (IOException e) { - throw new UncheckedIOException("Failed to close loggers", e); + throw new UncheckedIOException(e); } } @@ -750,66 +757,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private Path getMappings() { - Path inputMappings = getExtension().getPlatformMappingFile(); - - MemoryMappingTree mappingTree = new MemoryMappingTree(); - - try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) { - MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString())); - } catch (IOException e) { - throw new RuntimeException("Failed to read mappings", e); - } - - final List mappingsProcessors = new ArrayList<>(); - - MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(getProject()); - - if (minecraftJarProcessorManager != null) { - mappingsProcessors.add(mappings -> { - try (var serviceFactory = new ScopedServiceFactory()) { - final var configContext = new ConfigContextImpl(getProject(), serviceFactory, getExtension()); - return minecraftJarProcessorManager.processMappings(mappings, new MappingProcessorContextImpl(configContext)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } - - if (mappingsProcessors.isEmpty()) { - return inputMappings; - } - - boolean transformed = false; - - for (MappingsProcessor mappingsProcessor : mappingsProcessors) { - if (mappingsProcessor.transform(mappingTree)) { - transformed = true; - } - } - - if (!transformed) { - return inputMappings; - } - - final Path outputMappings; - - try { - outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny"); - } catch (IOException e) { - throw new RuntimeException("Failed to create temp file", e); - } - - try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) { - var tiny2Writer = new Tiny2FileWriter(writer, false); - mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString())); - } catch (IOException e) { - throw new RuntimeException("Failed to write mappings", e); - } - - return outputMappings; - } - public static File getJarFileWithSuffix(String suffix, Path runtimeJar) { final String path = runtimeJar.toFile().getAbsolutePath(); @@ -881,7 +828,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Override public void close() { - getProject().getLogger().info("{} took {}ms", name, System.currentTimeMillis() - start); + getLogger().info("{} took {}ms", name, System.currentTimeMillis() - start); } } } diff --git a/src/main/java/net/fabricmc/loom/task/service/SourceMappingsService.java b/src/main/java/net/fabricmc/loom/task/service/SourceMappingsService.java new file mode 100644 index 00000000..9c178dd3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/SourceMappingsService.java @@ -0,0 +1,141 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.task.service; + +import java.io.IOException; +import java.io.Reader; +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.util.ArrayList; +import java.util.List; + +import org.gradle.api.Project; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.InputFiles; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.ConfigContextImpl; +import net.fabricmc.loom.configuration.processors.MappingProcessorContextImpl; +import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.service.ScopedServiceFactory; +import net.fabricmc.loom.util.service.Service; +import net.fabricmc.loom.util.service.ServiceFactory; +import net.fabricmc.loom.util.service.ServiceType; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; +import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public class SourceMappingsService extends Service { + public static final ServiceType TYPE = new ServiceType<>(Options.class, SourceMappingsService.class); + + public interface Options extends Service.Options { + @InputFiles + ConfigurableFileCollection getMappings(); // Only a single file + } + + public static Provider create(Project project) { + final Path mappings = getMappings(project); + + return TYPE.create(project, options -> { + options.getMappings().from(project.file(mappings)); + }); + } + + private static Path getMappings(Project project) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + Path inputMappings = extension.getMappingConfiguration().tinyMappings; + + MemoryMappingTree mappingTree = new MemoryMappingTree(); + + try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) { + MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString())); + } catch (IOException e) { + throw new RuntimeException("Failed to read mappings", e); + } + + final List mappingsProcessors = new ArrayList<>(); + + MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(project); + + if (minecraftJarProcessorManager != null) { + mappingsProcessors.add(mappings -> { + try (var serviceFactory = new ScopedServiceFactory()) { + final var configContext = new ConfigContextImpl(project, serviceFactory, extension); + return minecraftJarProcessorManager.processMappings(mappings, new MappingProcessorContextImpl(configContext)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + if (mappingsProcessors.isEmpty()) { + return inputMappings; + } + + boolean transformed = false; + + for (GenerateSourcesTask.MappingsProcessor mappingsProcessor : mappingsProcessors) { + if (mappingsProcessor.transform(mappingTree)) { + transformed = true; + } + } + + if (!transformed) { + return inputMappings; + } + + final Path outputMappings; + + try { + outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny"); + } catch (IOException e) { + throw new RuntimeException("Failed to create temp file", e); + } + + try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) { + var tiny2Writer = new Tiny2FileWriter(writer, false); + mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString())); + } catch (IOException e) { + throw new RuntimeException("Failed to write mappings", e); + } + + return outputMappings; + } + + public SourceMappingsService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + public Path getMappingsFile() { + return getOptions().getMappings().getSingleFile().toPath(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java index 94f49eed..1d7381fa 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/ThreadedProgressLoggerConsumer.java @@ -31,13 +31,14 @@ import java.util.function.Consumer; import org.gradle.api.Project; import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.logging.Logger; import org.gradle.internal.logging.progress.ProgressLogger; import org.gradle.internal.logging.progress.ProgressLoggerFactory; public class ThreadedProgressLoggerConsumer implements Consumer, AutoCloseable { public static final String CLOSE_LOGGERS = "LOOM_CLOSE_LOGGERS"; - private final Project project; + private final Logger logger; private final String name; private final String desc; @@ -46,7 +47,7 @@ public class ThreadedProgressLoggerConsumer implements Consumer, AutoClo private final Map loggers = Collections.synchronizedMap(new HashMap<>()); public ThreadedProgressLoggerConsumer(Project project, String name, String desc) { - this.project = project; + this.logger = project.getLogger(); this.name = name; this.desc = desc; @@ -55,10 +56,19 @@ public class ThreadedProgressLoggerConsumer implements Consumer, AutoClo progressGroup.started(); } + public ThreadedProgressLoggerConsumer(Logger logger, ProgressLoggerFactory progressLoggerFactory, String name, String desc) { + this.logger = logger; + this.name = name; + this.desc = desc; + this.progressLoggerFactory = progressLoggerFactory; + 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); + logger.debug("Malformed threaded IPC log message: " + line); return; } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy index ee791785..52824211 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy @@ -142,6 +142,11 @@ class DebugLineNumbersTest extends Specification implements GradleProjectTestTra private static String getClassSource(GradleProject gradle, String classname, String mappings = MAPPINGS) { File sourcesJar = gradle.getGeneratedSources(mappings, MinecraftJar.Type.SERVER.toString()) + + if (!sourcesJar.exists()) { + throw new IllegalStateException("Sources jar not found: $sourcesJar") + } + return new String(ZipUtils.unpack(sourcesJar.toPath(), classname), StandardCharsets.UTF_8) } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy index 39bf1a1e..120d9e73 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy @@ -39,7 +39,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { def gradle = gradleProject(project: "decompile", version: version) when: - def result = gradle.run(task: task, configurationCache: false) + def result = gradle.run(task: task) then: result.task(":${task}").outcome == SUCCESS @@ -63,7 +63,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { } ''' when: - def result = gradle.run(task: "genSourcesWithCustom", configurationCache: false) + def result = gradle.run(task: "genSourcesWithCustom") then: result.task(":genSourcesWithCustom").outcome == SUCCESS @@ -87,7 +87,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { ''' when: - def result = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"], configurationCache: false) + def result = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"]) // Add fabric API to the project, this introduces some transitive access wideners gradle.buildGradle << ''' @@ -96,10 +96,10 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { } ''' - def result2 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"], configurationCache: false) + def result2 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"]) // And run again, with no changes - def result3 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"], configurationCache: false) + def result3 = gradle.run(tasks: ["genSourcesWithVineflower"], args: ["--use-cache", "--info"]) then: result.task(":genSourcesWithVineflower").outcome == SUCCESS diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy index 5ebb0d86..fa09aee9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/UnpickTest.groovy @@ -42,7 +42,7 @@ class UnpickTest extends Specification implements GradleProjectTestTrait { def gradle = gradleProject(project: "unpick", version: version) when: - def result = gradle.run(task: "genSources", configurationCache: false) + def result = gradle.run(task: "genSources") then: result.task(":genSources").outcome == SUCCESS getClassSource(gradle, "net/minecraft/block/CakeBlock.java").contains("Block.DEFAULT_SET_BLOCK_STATE_FLAG")