diff --git a/build.gradle b/build.gradle index 0850aae9..2e411eeb 100644 --- a/build.gradle +++ b/build.gradle @@ -95,7 +95,7 @@ dependencies { // decompilers implementation ('net.fabricmc:fabric-fernflower:2.0.0') - implementation ('net.fabricmc:cfr:0.2.0') + implementation ('net.fabricmc:cfr:0.2.1') // source code remapping implementation ('net.fabricmc:mercury:0.3.0') @@ -118,6 +118,7 @@ dependencies { exclude group: 'org.jetbrains.kotlin' } testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'com.microsoft.java:com.microsoft.java.debug.core:0.46.0' compileOnly 'org.jetbrains:annotations:24.0.1' testCompileOnly 'org.jetbrains:annotations:24.0.1' diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index a37f3635..8930e596 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -57,6 +57,7 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.extension.MixinExtension; @@ -153,8 +154,8 @@ public abstract class CompileConfiguration implements Runnable { mappingConfiguration.applyToProject(getProject(), mappingsDep); // Provide the remapped mc jars - final IntermediaryMinecraftProvider intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(configContext, minecraftProvider); - NamedMinecraftProvider namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(configContext, minecraftProvider); + final IntermediaryMinecraftProvider intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(project, minecraftProvider); + NamedMinecraftProvider namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(project, minecraftProvider); registerGameProcessors(configContext); MinecraftJarProcessorManager minecraftJarProcessorManager = MinecraftJarProcessorManager.create(getProject()); @@ -164,11 +165,13 @@ public abstract class CompileConfiguration implements Runnable { namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, minecraftJarProcessorManager); } + final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(true, extension.refreshDeps(), configContext); + extension.setIntermediaryMinecraftProvider(intermediaryMinecraftProvider); - intermediaryMinecraftProvider.provide(true); + intermediaryMinecraftProvider.provide(provideContext); extension.setNamedMinecraftProvider(namedMinecraftProvider); - namedMinecraftProvider.provide(true); + namedMinecraftProvider.provide(provideContext); } private void registerGameProcessors(ConfigContext configContext) { 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 f535d093..0a6a5948 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java @@ -27,13 +27,14 @@ package net.fabricmc.loom.configuration.decompile; import java.io.File; import org.gradle.api.Project; -import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.artifacts.ConfigurationContainer; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; -import net.fabricmc.loom.task.UnpickJarTask; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.Constants; public abstract class DecompileConfiguration { protected final Project project; @@ -50,11 +51,13 @@ public abstract class DecompileConfiguration public abstract void afterEvaluation(); - protected final TaskProvider createUnpickJarTask(String name, File inputJar, File outputJar) { - return project.getTasks().register(name, UnpickJarTask.class, unpickJarTask -> { - unpickJarTask.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile()); - unpickJarTask.getInputJar().set(inputJar); - unpickJarTask.getOutputJar().set(outputJar); - }); + protected final void configureUnpick(GenerateSourcesTask task, File unpickOutputJar) { + final ConfigurationContainer configurations = task.getProject().getConfigurations(); + + task.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile()); + task.getUnpickOutputJar().set(unpickOutputJar); + 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)); } } 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 6fa25b5c..538c3018 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java @@ -25,11 +25,11 @@ package net.fabricmc.loom.configuration.decompile; import java.io.File; -import java.nio.file.Path; import java.util.List; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ConfigContext; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.loom.util.Constants; @@ -41,36 +41,25 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration minecraftJars = minecraftProvider.getMinecraftJarPaths(); + final List minecraftJars = minecraftProvider.getMinecraftJars(); assert minecraftJars.size() == 1; - - final File namedJar = minecraftJars.get(0).toFile(); - - File mappedJar = namedJar; - - if (mappingConfiguration.hasUnpickDefinitions()) { - File outputJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar"); - createUnpickJarTask("unpickJar", namedJar, outputJar); - - mappedJar = outputJar; - } - - final File inputJar = mappedJar; + final MinecraftJar minecraftJar = minecraftJars.get(0); LoomGradleExtension.get(project).getDecompilerOptions().forEach(options -> { final String decompilerName = options.getFormattedName(); String taskName = "genSourcesWith" + decompilerName; // Decompiler will be passed to the constructor of GenerateSourcesTask project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> { - task.getInputJar().set(inputJar); - task.getRuntimeJar().set(namedJar); + task.getInputJarName().set(minecraftJar.getName()); + task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", minecraftJar.getPath())); task.dependsOn(project.getTasks().named("validateAccessWidener")); task.setDescription("Decompile minecraft using %s.".formatted(decompilerName)); task.setGroup(Constants.TaskGroup.FABRIC); if (mappingConfiguration.hasUnpickDefinitions()) { - task.dependsOn(project.getTasks().named("unpickJar")); + final File outputJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar"); + configureUnpick(task, outputJar); } }); }); 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 0dd69ff2..0cf8fbe3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java @@ -32,9 +32,9 @@ import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.configuration.ConfigContext; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; import net.fabricmc.loom.task.GenerateSourcesTask; -import net.fabricmc.loom.task.UnpickJarTask; import net.fabricmc.loom.util.Constants; public final class SplitDecompileConfiguration extends DecompileConfiguration { @@ -44,41 +44,26 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration unpickCommonJar = null; - TaskProvider unpickClientOnlyJar = null; - - if (mappingConfiguration.hasUnpickDefinitions()) { - commonJarToDecompile = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar"); - clientOnlyJarToDecompile = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar"); - - unpickCommonJar = createUnpickJarTask("unpickCommonJar", minecraftProvider.getCommonJar().toFile(), commonJarToDecompile); - unpickClientOnlyJar = createUnpickJarTask("unpickClientOnlyJar", minecraftProvider.getClientOnlyJar().toFile(), clientOnlyJarToDecompile); - } - - // Need to re-declare them as final to access them from the lambada - final File commonJar = commonJarToDecompile; - final File clientOnlyJar = clientOnlyJarToDecompile; - final TaskProvider unpickCommonJarTask = unpickCommonJar; - final TaskProvider unpickClientOnlyJarTask = unpickClientOnlyJar; + final MinecraftJar commonJar = minecraftProvider.getCommonJar(); + final MinecraftJar clientOnlyJar = minecraftProvider.getClientOnlyJar(); final TaskProvider commonDecompileTask = createDecompileTasks("Common", task -> { - task.getInputJar().set(commonJar); - task.getRuntimeJar().set(minecraftProvider.getCommonJar().toFile()); + task.getInputJarName().set(commonJar.getName()); + task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", commonJar.getPath())); - if (unpickCommonJarTask != null) { - task.dependsOn(unpickCommonJarTask); + if (mappingConfiguration.hasUnpickDefinitions()) { + File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar"); + configureUnpick(task, unpickJar); } }); final TaskProvider clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> { - task.getInputJar().set(clientOnlyJar); - task.getRuntimeJar().set(minecraftProvider.getClientOnlyJar().toFile()); + task.getInputJarName().set(clientOnlyJar.getName()); + task.getOutputJar().fileValue(GenerateSourcesTask.getMappedJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath())); - if (unpickCommonJarTask != null) { - task.dependsOn(unpickClientOnlyJarTask); + if (mappingConfiguration.hasUnpickDefinitions()) { + File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar"); + configureUnpick(task, unpickJar); } // Don't allow them to run at the same time. diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java index 04a7468a..10aa592e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; +import org.gradle.api.Project; + import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.decompile.DecompileConfiguration; import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration; @@ -73,8 +75,8 @@ public enum MinecraftJarConfiguration { ); private final Function minecraftProviderFunction; - private final BiFunction> intermediaryMinecraftProviderBiFunction; - private final BiFunction> namedMinecraftProviderBiFunction; + private final BiFunction> intermediaryMinecraftProviderBiFunction; + private final BiFunction> namedMinecraftProviderBiFunction; private final BiFunction, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider> processedNamedMinecraftProviderBiFunction; private final BiFunction> decompileConfigurationBiFunction; private final List supportedEnvironments; @@ -82,15 +84,15 @@ public enum MinecraftJarConfiguration { @SuppressWarnings("unchecked") // Just a bit of a generic mess :) , Q extends MappedMinecraftProvider> MinecraftJarConfiguration( Function minecraftProviderFunction, - BiFunction> intermediaryMinecraftProviderBiFunction, - BiFunction namedMinecraftProviderBiFunction, + BiFunction> intermediaryMinecraftProviderBiFunction, + BiFunction namedMinecraftProviderBiFunction, BiFunction> processedNamedMinecraftProviderBiFunction, BiFunction> decompileConfigurationBiFunction, List supportedEnvironments ) { this.minecraftProviderFunction = (Function) minecraftProviderFunction; - this.intermediaryMinecraftProviderBiFunction = (BiFunction>) (Object) intermediaryMinecraftProviderBiFunction; - this.namedMinecraftProviderBiFunction = (BiFunction>) namedMinecraftProviderBiFunction; + this.intermediaryMinecraftProviderBiFunction = (BiFunction>) (Object) intermediaryMinecraftProviderBiFunction; + this.namedMinecraftProviderBiFunction = (BiFunction>) namedMinecraftProviderBiFunction; this.processedNamedMinecraftProviderBiFunction = (BiFunction, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider>) (Object) processedNamedMinecraftProviderBiFunction; this.decompileConfigurationBiFunction = (BiFunction>) decompileConfigurationBiFunction; this.supportedEnvironments = supportedEnvironments; @@ -100,11 +102,11 @@ public enum MinecraftJarConfiguration { return minecraftProviderFunction; } - public BiFunction> getIntermediaryMinecraftProviderBiFunction() { + public BiFunction> getIntermediaryMinecraftProviderBiFunction() { return intermediaryMinecraftProviderBiFunction; } - public BiFunction> getNamedMinecraftProviderBiFunction() { + public BiFunction> getNamedMinecraftProviderBiFunction() { return namedMinecraftProviderBiFunction; } 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 97c8acf1..61cba122 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 @@ -53,13 +53,13 @@ import net.fabricmc.tinyremapper.TinyRemapper; public abstract class AbstractMappedMinecraftProvider implements MappedMinecraftProvider.ProviderImpl { protected final M minecraftProvider; - protected final ConfigContext configContext; + private final Project project; protected final LoomGradleExtension extension; - public AbstractMappedMinecraftProvider(ConfigContext configContext, M minecraftProvider) { - this.configContext = configContext; + public AbstractMappedMinecraftProvider(Project project, M minecraftProvider) { this.minecraftProvider = minecraftProvider; - this.extension = configContext.extension(); + this.project = project; + this.extension = LoomGradleExtension.get(project); } public abstract MappingsNamespace getTargetNamespace(); @@ -70,13 +70,13 @@ public abstract class AbstractMappedMinecraftProvider provide(ProvideContext context) throws Exception { final List remappedJars = getRemappedJars(); assert !remappedJars.isEmpty(); - if (!areOutputsValid(remappedJars) || extension.refreshDeps()) { + if (!areOutputsValid(remappedJars) || context.refreshOutputs()) { try { - remapInputs(remappedJars); + remapInputs(remappedJars, context.configContext()); } catch (Throwable t) { cleanOutputs(remappedJars); @@ -84,17 +84,25 @@ public abstract class AbstractMappedMinecraftProvider dependencyTargets = getDependencyTargets(); - if (dependencyTargets.isEmpty()) { - return; + if (!dependencyTargets.isEmpty()) { + MinecraftSourceSets.get(getProject()).applyDependencies( + (configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)), + dependencyTargets + ); } + } - MinecraftSourceSets.get(getProject()).applyDependencies( - (configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name)), - dependencyTargets - ); + return remappedJars.stream() + .map(RemappedJars::outputJar) + .toList(); + } + + public record ProvideContext(boolean applyDependencies, boolean refreshOutputs, ConfigContext configContext) { + ProvideContext withApplyDependencies(boolean applyDependencies) { + return new ProvideContext(applyDependencies, refreshOutputs(), configContext()); } } @@ -154,15 +162,15 @@ public abstract class AbstractMappedMinecraftProvider remappedJars) throws IOException { + private void remapInputs(List remappedJars, ConfigContext configContext) throws IOException { cleanOutputs(remappedJars); for (RemappedJars remappedJar : remappedJars) { - remapJar(remappedJar); + remapJar(remappedJar, configContext); } } - private void remapJar(RemappedJars remappedJars) throws IOException { + private void remapJar(RemappedJars remappedJars, ConfigContext configContext) throws IOException { final MappingConfiguration mappingConfiguration = extension.getMappingConfiguration(); final String fromM = remappedJars.sourceNamespace().toString(); final String toM = getTargetNamespace().toString(); @@ -214,12 +222,8 @@ public abstract class AbstractMappedMinecraftProvider extends AbstractMappedMinecraftProvider permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl { - public IntermediaryMinecraftProvider(ConfigContext configContext, M minecraftProvider) { - super(configContext, minecraftProvider); + public IntermediaryMinecraftProvider(Project project, M minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -51,8 +52,8 @@ public abstract sealed class IntermediaryMinecraftProvider implements Merged { - public MergedImpl(ConfigContext configContext, MergedMinecraftProvider minecraftProvider) { - super(configContext, minecraftProvider); + public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -64,8 +65,8 @@ public abstract sealed class IntermediaryMinecraftProvider implements Split { - public SplitImpl(ConfigContext configContext, SplitMinecraftProvider minecraftProvider) { - super(configContext, minecraftProvider); + public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -85,17 +86,17 @@ public abstract sealed class IntermediaryMinecraftProvider implements SingleJar { private final SingleJarEnvType env; - private SingleJarImpl(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) { - super(configContext, minecraftProvider); + private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) { + super(project, minecraftProvider); this.env = env; } - public static SingleJarImpl server(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) { - return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.SERVER); + public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.SERVER); } - public static SingleJarImpl client(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) { - return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.CLIENT); + public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.CLIENT); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java index 8f07eda6..7534fdb6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java @@ -26,8 +26,9 @@ package net.fabricmc.loom.configuration.providers.minecraft.mapped; import java.util.List; +import org.gradle.api.Project; + import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType; @@ -36,8 +37,8 @@ import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvide import net.fabricmc.tinyremapper.TinyRemapper; public abstract class NamedMinecraftProvider extends AbstractMappedMinecraftProvider { - public NamedMinecraftProvider(ConfigContext configContext, M minecraftProvider) { - super(configContext, minecraftProvider); + public NamedMinecraftProvider(Project project, M minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -51,8 +52,8 @@ public abstract class NamedMinecraftProvider extend } public static final class MergedImpl extends NamedMinecraftProvider implements Merged { - public MergedImpl(ConfigContext configContext, MergedMinecraftProvider minecraftProvider) { - super(configContext, minecraftProvider); + public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -69,8 +70,8 @@ public abstract class NamedMinecraftProvider extend } public static final class SplitImpl extends NamedMinecraftProvider implements Split { - public SplitImpl(ConfigContext configContext, SplitMinecraftProvider minecraftProvider) { - super(configContext, minecraftProvider); + public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); } @Override @@ -95,17 +96,17 @@ public abstract class NamedMinecraftProvider extend public static final class SingleJarImpl extends NamedMinecraftProvider implements SingleJar { private final SingleJarEnvType env; - private SingleJarImpl(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) { - super(configContext, minecraftProvider); + private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) { + super(project, minecraftProvider); this.env = env; } - public static SingleJarImpl server(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) { - return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.SERVER); + public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.SERVER); } - public static SingleJarImpl client(ConfigContext configContext, SingleJarMinecraftProvider minecraftProvider) { - return new SingleJarImpl(configContext, minecraftProvider, SingleJarEnvType.CLIENT); + public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) { + return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.CLIENT); } @Override 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 005fbdf1..d956079f 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 @@ -29,10 +29,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; import org.gradle.api.Project; +import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper; import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager; import net.fabricmc.loom.configuration.processors.ProcessorContextImpl; @@ -49,26 +53,31 @@ public abstract class ProcessedNamedMinecraftProvider provide(ProvideContext context) throws Exception { + parentMinecraftProvider.provide(context.withApplyDependencies(false)); - boolean requiresProcessing = parentMinecraftProvider.getMinecraftJars().stream() + boolean requiresProcessing = context.refreshOutputs() || parentMinecraftProvider.getMinecraftJars().stream() .map(this::getProcessedPath) .anyMatch(jarProcessorManager::requiresProcessingJar); + final Map minecraftJarOutputMap = parentMinecraftProvider.getMinecraftJars().stream() + .collect(Collectors.toMap(Function.identity(), this::getProcessedJar)); + if (requiresProcessing) { - processJars(); + processJars(minecraftJarOutputMap, context.configContext()); } - if (applyDependencies) { + if (context.applyDependencies()) { applyDependencies(); } + + return List.copyOf(minecraftJarOutputMap.values()); } @Override @@ -76,14 +85,17 @@ public abstract class ProcessedNamedMinecraftProvider minecraftJarMap, ConfigContext configContext) throws IOException { + for (Map.Entry entry : minecraftJarMap.entrySet()) { + final MinecraftJar minecraftJar = entry.getKey(); + final MinecraftJar outputJar = entry.getValue(); deleteSimilarJars(outputJar.getPath()); final LocalMavenHelper mavenHelper = getMavenHelper(minecraftJar.getName()); final Path outputPath = mavenHelper.copyToMaven(minecraftJar.getPath(), null); + assert outputJar.getPath().equals(outputPath); + jarProcessorManager.processJar(outputPath, new ProcessorContextImpl(configContext, minecraftJar)); } } diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 7f28716c..a87176e2 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.task; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.Reader; import java.io.UncheckedIOException; import java.io.Writer; @@ -49,10 +50,13 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.services.ServiceReference; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; import org.gradle.work.DisableCachingByDefault; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; @@ -68,6 +72,8 @@ 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.decompilers.LineNumberRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; @@ -90,16 +96,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { private final DecompilerOptions decompilerOptions; /** - * The jar to decompile, can be the unpick jar. + * The jar name to decompile, {@link MinecraftJar#getName()}. */ - @InputFile - public abstract RegularFileProperty getInputJar(); - - /** - * The jar used at runtime. - */ - @InputFile - public abstract RegularFileProperty getRuntimeJar(); + @Input + public abstract Property getInputJarName(); @InputFiles public abstract ConfigurableFileCollection getClasspath(); @@ -107,9 +107,26 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @OutputFile public abstract RegularFileProperty getOutputJar(); + // Unpick + @InputFile + public abstract RegularFileProperty getUnpickDefinitions(); + + @InputFiles + public abstract ConfigurableFileCollection getUnpickConstantJar(); + + @InputFiles + public abstract ConfigurableFileCollection getUnpickClasspath(); + + @OutputFile + public abstract RegularFileProperty getUnpickOutputJar(); + + // Injects @Inject public abstract WorkerExecutor getWorkerExecutor(); + @Inject + public abstract ExecOperations getExecOperations(); + @Inject public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager(); @@ -124,8 +141,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getOutputs().upToDateWhen((o) -> false); getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead(); dependsOn(decompilerOptions.getClasspath().getBuiltBy()); - - getOutputJar().fileProvider(getProject().provider(() -> getMappedJarFileWithSuffix("-sources.jar"))); } @TaskAction @@ -136,10 +151,20 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements."); } + final MinecraftJar minecraftJar = rebuildInputJar(); + // Input jar is the jar to decompile, this may be unpicked. + Path inputJar = minecraftJar.getPath(); + // Runtime jar is the jar used to run the game + final Path runtimeJar = inputJar; + + if (getUnpickDefinitions().isPresent()) { + inputJar = unpickJar(inputJar); + } + if (!platform.supportsUnixDomainSockets()) { getProject().getLogger().warn("Decompile worker logging disabled as Unix Domain Sockets is not supported on your operating system."); - doWork(null); + doWork(null, inputJar, runtimeJar); return; } @@ -149,7 +174,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources"); IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) { - doWork(logReceiver); + doWork(logReceiver, inputJar, runtimeJar); } catch (InterruptedException e) { throw new RuntimeException("Failed to shutdown log receiver", e); } finally { @@ -157,18 +182,94 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private void doWork(@Nullable IPCServer ipcServer) { + // 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 serviceManager = new ScopedSharedServiceManager()) { + final var configContext = new ConfigContextImpl(getProject(), serviceManager, getExtension()); + final var provideContext = new AbstractMappedMinecraftProvider.ProvideContext(false, true, configContext); + minecraftJars = getExtension().getNamedMinecraftProvider().provide(provideContext); + } catch (Exception e) { + throw new RuntimeException("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) { + final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath(); + final List args = getUnpickArgs(inputJar, outputJar); + + ExecResult result = getExecOperations().javaexec(spec -> { + spec.getMainClass().set("daomephsta.unpick.cli.Main"); + spec.classpath(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH)); + spec.args(args); + spec.systemProperty("java.util.logging.config.file", writeUnpickLogConfig().getAbsolutePath()); + }); + + result.rethrowFailure(); + + return outputJar; + } + + private List getUnpickArgs(Path inputJar, Path outputJar) { + var fileArgs = new ArrayList(); + + fileArgs.add(inputJar.toFile()); + fileArgs.add(outputJar.toFile()); + 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); + } + + return fileArgs.stream() + .map(File::getAbsolutePath) + .toList(); + } + + private File writeUnpickLogConfig() { + final File unpickLoggingConfigFile = getExtension().getFiles().getUnpickLoggingConfigFile(); + + try (InputStream is = GenerateSourcesTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { + Files.deleteIfExists(unpickLoggingConfigFile.toPath()); + Files.copy(Objects.requireNonNull(is), unpickLoggingConfigFile.toPath()); + } catch (IOException e) { + throw new org.gradle.api.UncheckedIOException("Failed to copy unpick logging config", e); + } + + return unpickLoggingConfigFile; + } + + private void doWork(@Nullable IPCServer ipcServer, Path inputJar, Path runtimeJar) { final String jvmMarkerValue = UUID.randomUUID().toString(); final WorkQueue workQueue = createWorkQueue(jvmMarkerValue); workQueue.submit(DecompileAction.class, params -> { params.getDecompilerOptions().set(decompilerOptions.toDto()); - params.getInputJar().set(getInputJar()); - params.getRuntimeJar().set(getRuntimeJar()); + params.getInputJar().set(inputJar.toFile()); + params.getRuntimeJar().set(runtimeJar.toFile()); params.getSourcesDestinationJar().set(getOutputJar()); - params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap")); - params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar")); + params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap", runtimeJar)); + params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar", runtimeJar)); params.getMappings().set(getMappings().toFile()); if (ipcServer != null) { @@ -317,8 +418,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private File getMappedJarFileWithSuffix(String suffix) { - String path = getRuntimeJar().get().getAsFile().getAbsolutePath(); + public static File getMappedJarFileWithSuffix(String suffix, Path runtimeJar) { + final String path = runtimeJar.toFile().getAbsolutePath(); if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { throw new RuntimeException("Invalid mapped JAR path: " + path); diff --git a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java b/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java deleted file mode 100644 index d3c52e72..00000000 --- a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.task; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -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; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.extension.LoomFiles; -import net.fabricmc.loom.util.Constants; - -public abstract class UnpickJarTask extends JavaExec { - @InputFile - public abstract RegularFileProperty getInputJar(); - - @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() { - 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_COMPILE_LIBRARIES)); - getUnpickClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED)); - } - - @Override - public void exec() { - fileArg(getInputJar().get().getAsFile(), getOutputJar().get().getAsFile(), getUnpickDefinitions().get().getAsFile()); - fileArg(getConstantJar().getSingleFile()); - - // Classpath - for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.NAMED)) { - fileArg(minecraftJar.toFile()); - } - - for (File file : getUnpickClasspath()) { - fileArg(file); - } - - writeUnpickLogConfig(); - systemProperty("java.util.logging.config.file", getDirectories().getUnpickLoggingConfigFile().getAbsolutePath()); - - super.exec(); - } - - private void writeUnpickLogConfig() { - try (InputStream is = UnpickJarTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { - Files.deleteIfExists(getDirectories().getUnpickLoggingConfigFile().toPath()); - Files.copy(is, getDirectories().getUnpickLoggingConfigFile().toPath()); - } catch (IOException e) { - throw new RuntimeException("Failed to copy unpick logging config", e); - } - } - - private void fileArg(File... files) { - for (File file : files) { - args(file.getAbsolutePath()); - } - } - - @Internal - protected LoomGradleExtension getExtension() { - return LoomGradleExtension.get(getProject()); - } - - private LoomFiles getDirectories() { - return getExtension().getFiles(); - } -} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy new file mode 100644 index 00000000..23a099af --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DebugLineNumbersTest.groovy @@ -0,0 +1,230 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 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.integration + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionStage +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +import com.microsoft.java.debug.core.DebugUtility +import com.microsoft.java.debug.core.IDebugSession +import com.sun.jdi.Bootstrap +import com.sun.jdi.event.BreakpointEvent +import groovy.transform.CompileStatic +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.functions.Function +import spock.lang.Specification +import spock.lang.Timeout + +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.util.ZipUtils + +import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +@Timeout(value = 30, unit = TimeUnit.MINUTES) +class DebugLineNumbersTest extends Specification implements GradleProjectTestTrait { + static final String MAPPINGS = "1.20.1-net.fabricmc.yarn.1_20_1.1.20.1+build.1-v2" + static final Map BREAKPOINTS = [ + "net.minecraft.server.dedicated.ServerPropertiesLoader": 16, + "net.minecraft.server.dedicated.MinecraftDedicatedServer": 107, + "net.minecraft.registry.RegistryOps": 67 + ] + + def "Debug test"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << ''' + loom { + // Just test with the server, no need to also decompile the client + serverOnlyMinecraftJar() + } + + dependencies { + minecraft "com.mojang:minecraft:1.20.1" + mappings "net.fabricmc:yarn:1.20.1+build.1:v2" + modImplementation 'net.fabricmc:fabric-loader:0.14.21' + } + + runServer { + debugOptions { + enabled = true + port = 8050 + host = "*" + server = true + suspend = true + } + } + ''' + when: + // First generate sources + def genSources = gradle.run(task: "genSources") + genSources.task(":genSources").outcome == SUCCESS + + // Print out the source of the file + def lines = getClassSource(gradle, "net/minecraft/server/dedicated/ServerPropertiesLoader.java").lines().toList() + int l = 1 + for (final def line in lines) { + //println(l++ + ": " + line) + } + + // I agree + def runDir = new File(gradle.projectDir, "run") + runDir.mkdirs() + new File(runDir, "eula.txt") << "eula=true" + new File(runDir, "server.properties") << "" + + // Run the gradle task off thread + def executor = Executors.newSingleThreadExecutor() + def resultCF = CompletableFuture.supplyAsync({ + gradle.run(task: "runServer") + }, executor) + + Map> futures + def debugger = new Debugger(openDebugSession()) + + try { + futures = BREAKPOINTS.collectEntries { className, line -> + [(className): debugger.addBreakpoint(className, line)] + } + + // Start running the game, the process has been suspended until this point. + debugger.start() + + // Wait for all of the breakpoints + futures.values().forEach { + def result = it.get() + println("Breakpoint triggered: ${result.location()}") + } + } finally { + // Close the debugger and target process + debugger.close() + } + + def result = resultCF.get() + executor.shutdown() + + then: + result.task(":runServer").outcome == SUCCESS + + BREAKPOINTS.forEach { className, line -> + futures[className].get().location().lineNumber() == line + } + } + + private static String getClassSource(GradleProject gradle, String classname, String mappings = MAPPINGS) { + File sourcesJar = gradle.getGeneratedSources(mappings, "serveronly") + return new String(ZipUtils.unpack(sourcesJar.toPath(), classname), StandardCharsets.UTF_8) + } + + private static IDebugSession openDebugSession() { + int timeout = 5 + int maxTimeout = 120 / timeout + + for (i in 0..maxTimeout) { + try { + return DebugUtility.attach( + Bootstrap.virtualMachineManager(), + "127.0.0.1", + 8050, + timeout + ) + } catch (ConnectException e) { + Thread.sleep(timeout * 1000) + if (i == maxTimeout) { + throw e + } + } + } + + throw new IllegalStateException() + } + + @CompileStatic // Makes RxJava somewhat usable in Groovy + class Debugger implements AutoCloseable { + final IDebugSession debugSession + + Debugger(IDebugSession debugSession) { + this.debugSession = debugSession + + debugSession.eventHub.events().subscribe({ }) { + // Manually bail out, as it seems this can be called after close() + it.printStackTrace() + System.exit(-1) + } + } + + CompletableFuture addBreakpoint(String className, int lineNumber) { + def breakpoint = debugSession.createBreakpoint( + className, + lineNumber, + 0, + null, + null + ) + + // Wait for the breakpoint to be installed + return breakpoint.install().thenCompose { + // Then compose with the first result + return breakpointEvents() + .filter { event -> + event.location().sourcePath().replaceAll("[\\\\/]", ".") == className + ".java" && + event.location().lineNumber() == lineNumber + } + .firstElement() + .to(toCompletionStage()) + } + } + + private static Function, CompletionStage> toCompletionStage() { + return { Maybe m -> + CompletableFuture cf = new CompletableFuture<>() + m.subscribe(cf.&complete, cf.&completeExceptionally) { cf.complete(null) } + return cf + } + } + + Observable breakpointEvents() { + return debugSession.getEventHub().breakpointEvents() + .map { + it.shouldResume = true + it.event as BreakpointEvent + } + } + + void start() { + debugSession.start() + } + + @Override + void close() throws Exception { + debugSession.terminate() + debugSession.eventHub.close() + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index 591893a0..a741f095 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -236,8 +236,8 @@ trait GradleProjectTestTrait { return ZipUtils.unpackNullable(file.toPath(), entryName) != null } - File getGeneratedSources(String mappings) { - return new File(getGradleHomeDir(), "caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-merged/${mappings}/minecraft-merged-${mappings}-sources.jar") + File getGeneratedSources(String mappings, String jarType = "merged") { + return new File(getGradleHomeDir(), "caches/fabric-loom/minecraftMaven/net/minecraft/minecraft-${jarType}/${mappings}/minecraft-${jarType}-${mappings}-sources.jar") } File getGeneratedLocalSources(String mappings) {