Rework how unpick and linenumber maps are applied (#907)

This should hopefully vastly improve debugging, and more imporantly not work in a consistant manner, making debugging issues a lot easier.

This commit contains an intergration test that uses a real debugger to check that breakpoints are being fired as expected.
This commit is contained in:
modmuss
2023-06-16 21:55:04 +01:00
committed by GitHub
parent fe823ddb30
commit 4e593fc5ae
14 changed files with 478 additions and 264 deletions

View File

@@ -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'

View File

@@ -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) {

View File

@@ -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<T extends MappedMinecraftProvider> {
protected final Project project;
@@ -50,11 +51,13 @@ public abstract class DecompileConfiguration<T extends MappedMinecraftProvider>
public abstract void afterEvaluation();
protected final TaskProvider<UnpickJarTask> 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));
}
}

View File

@@ -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<Mapp
@Override
public final void afterEvaluation() {
List<Path> minecraftJars = minecraftProvider.getMinecraftJarPaths();
final List<MinecraftJar> 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);
}
});
});

View File

@@ -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<MappedMinecraftProvider.Split> {
@@ -44,41 +44,26 @@ public final class SplitDecompileConfiguration extends DecompileConfiguration<Ma
@Override
public void afterEvaluation() {
File commonJarToDecompile = minecraftProvider.getCommonJar().toFile();
File clientOnlyJarToDecompile = minecraftProvider.getClientOnlyJar().toFile();
TaskProvider<UnpickJarTask> unpickCommonJar = null;
TaskProvider<UnpickJarTask> 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<UnpickJarTask> unpickCommonJarTask = unpickCommonJar;
final TaskProvider<UnpickJarTask> unpickClientOnlyJarTask = unpickClientOnlyJar;
final MinecraftJar commonJar = minecraftProvider.getCommonJar();
final MinecraftJar clientOnlyJar = minecraftProvider.getClientOnlyJar();
final TaskProvider<Task> 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<Task> 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.

View File

@@ -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<ConfigContext, MinecraftProvider> minecraftProviderFunction;
private final BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
private final BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
private final List<String> supportedEnvironments;
@@ -82,15 +84,15 @@ public enum MinecraftJarConfiguration {
@SuppressWarnings("unchecked") // Just a bit of a generic mess :)
<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>, Q extends MappedMinecraftProvider> MinecraftJarConfiguration(
Function<ConfigContext, M> minecraftProviderFunction,
BiFunction<ConfigContext, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<ConfigContext, M, P> namedMinecraftProviderBiFunction,
BiFunction<Project, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<Project, M, P> namedMinecraftProviderBiFunction,
BiFunction<P, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
BiFunction<ConfigContext, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
List<String> supportedEnvironments
) {
this.minecraftProviderFunction = (Function<ConfigContext, MinecraftProvider>) minecraftProviderFunction;
this.intermediaryMinecraftProviderBiFunction = (BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.intermediaryMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
this.decompileConfigurationBiFunction = (BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
this.supportedEnvironments = supportedEnvironments;
@@ -100,11 +102,11 @@ public enum MinecraftJarConfiguration {
return minecraftProviderFunction;
}
public BiFunction<ConfigContext, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
public BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> getIntermediaryMinecraftProviderBiFunction() {
return intermediaryMinecraftProviderBiFunction;
}
public BiFunction<ConfigContext, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
public BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> getNamedMinecraftProviderBiFunction() {
return namedMinecraftProviderBiFunction;
}

View File

@@ -53,13 +53,13 @@ import net.fabricmc.tinyremapper.TinyRemapper;
public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvider> 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<M extends MinecraftProvide
return Collections.emptyList();
}
public void provide(boolean applyDependencies) throws Exception {
public List<MinecraftJar> provide(ProvideContext context) throws Exception {
final List<RemappedJars> 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<M extends MinecraftProvide
}
}
if (applyDependencies) {
if (context.applyDependencies()) {
final List<String> 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<M extends MinecraftProvide
return true;
}
private void remapInputs(List<RemappedJars> remappedJars) throws IOException {
private void remapInputs(List<RemappedJars> 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<M extends MinecraftProvide
}
}
public ConfigContext getConfigContext() {
return configContext;
}
public Project getProject() {
return getConfigContext().project();
return project;
}
public M getMinecraftProvider() {

View File

@@ -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 sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> 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<M extends MinecraftPr
}
public static final class MergedImpl extends IntermediaryMinecraftProvider<MergedMinecraftProvider> 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<M extends MinecraftPr
}
public static final class SplitImpl extends IntermediaryMinecraftProvider<SplitMinecraftProvider> 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<M extends MinecraftPr
public static final class SingleJarImpl extends IntermediaryMinecraftProvider<SingleJarMinecraftProvider> 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

View File

@@ -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<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> {
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<M extends MinecraftProvider> extend
}
public static final class MergedImpl extends NamedMinecraftProvider<MergedMinecraftProvider> 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<M extends MinecraftProvider> extend
}
public static final class SplitImpl extends NamedMinecraftProvider<SplitMinecraftProvider> 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<M extends MinecraftProvider> extend
public static final class SingleJarImpl extends NamedMinecraftProvider<SingleJarMinecraftProvider> 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

View File

@@ -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<M extends MinecraftProvide
private final MinecraftJarProcessorManager jarProcessorManager;
public ProcessedNamedMinecraftProvider(P parentMinecraftProvide, MinecraftJarProcessorManager jarProcessorManager) {
super(parentMinecraftProvide.getConfigContext(), parentMinecraftProvide.getMinecraftProvider());
super(parentMinecraftProvide.getProject(), parentMinecraftProvide.getMinecraftProvider());
this.parentMinecraftProvider = parentMinecraftProvide;
this.jarProcessorManager = Objects.requireNonNull(jarProcessorManager);
}
@Override
public void provide(boolean applyDependencies) throws Exception {
parentMinecraftProvider.provide(false);
public List<MinecraftJar> 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<MinecraftJar, MinecraftJar> 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<M extends MinecraftProvide
return MavenScope.LOCAL;
}
private void processJars() throws IOException {
for (MinecraftJar minecraftJar : parentMinecraftProvider.getMinecraftJars()) {
final MinecraftJar outputJar = getProcessedJar(minecraftJar);
private void processJars(Map<MinecraftJar, MinecraftJar> minecraftJarMap, ConfigContext configContext) throws IOException {
for (Map.Entry<MinecraftJar, MinecraftJar> 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));
}
}

View File

@@ -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<String> 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<MinecraftJar> 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<String> 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<String> getUnpickArgs(Path inputJar, Path outputJar) {
var fileArgs = new ArrayList<File>();
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);

View File

@@ -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();
}
}

View File

@@ -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<String, Integer> 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<String, CompletableFuture<BreakpointEvent>> 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<BreakpointEvent> 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 <T> Function<Maybe<T>, CompletionStage<T>> toCompletionStage() {
return { Maybe<T> m ->
CompletableFuture<T> cf = new CompletableFuture<>()
m.subscribe(cf.&complete, cf.&completeExceptionally) { cf.complete(null) }
return cf
}
}
Observable<BreakpointEvent> 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()
}
}
}

View File

@@ -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) {