Support injecting interfaces from the mod source. Add a comment to target classes saying what mod is providing an injected interface. (#581)

dev/0.11
modmuss50 2022-01-24 15:21:00 +00:00 committed by GitHub
parent e4330a11dc
commit 9662a8b3de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 285 additions and 84 deletions

View File

@ -0,0 +1,52 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.api;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.SourceSet;
public interface InterfaceInjectionExtensionAPI {
/**
* When true loom will inject interfaces declared in dependency mod manifests into the minecraft jar file.
* This is used to expose interfaces that are implemented on Minecraft classes by mixins at runtime
* in the dev environment.
*
* @return the property controlling interface injection.
*/
Property<Boolean> getEnableDependencyInterfaceInjection();
/**
* Contains a list of {@link SourceSet} that may contain a fabric.mod.json file with interfaces to inject.
* By default, this list contains only the main {@link SourceSet}.
*
* @return the list property containing the {@link SourceSet}
*/
ListProperty<SourceSet> getInterfaceInjectionSourceSets();
default boolean isEnabled() {
return getEnableDependencyInterfaceInjection().get() || !getInterfaceInjectionSourceSets().get().isEmpty();
}
}

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2021 FabricMC * Copyright (c) 2021-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -86,6 +86,12 @@ public interface LoomGradleExtensionAPI {
// TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed. // TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed.
MixinExtensionAPI getMixin(); MixinExtensionAPI getMixin();
default void interfaceInjection(Action<InterfaceInjectionExtensionAPI> action) {
action.execute(getInterfaceInjection());
}
InterfaceInjectionExtensionAPI getInterfaceInjection();
Property<String> getCustomMinecraftManifest(); Property<String> getCustomMinecraftManifest();
/** /**
@ -131,15 +137,6 @@ public interface LoomGradleExtensionAPI {
*/ */
Property<String> getIntermediaryUrl(); Property<String> getIntermediaryUrl();
/**
* When true loom will inject interfaces declared in mod manifests into the minecraft jar file.
* This is used to expose interfaces that are implemented on Minecraft classes by mixins at runtime
* in the dev environment.
*
* @return the property controlling interface injection.
*/
Property<Boolean> getEnableInterfaceInjection();
@ApiStatus.Experimental @ApiStatus.Experimental
Property<MinecraftJarConfiguration> getMinecraftJarConfiguration(); Property<MinecraftJarConfiguration> getMinecraftJarConfiguration();

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2016-2021 FabricMC * Copyright (c) 2016-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -233,7 +233,7 @@ public final class CompileConfiguration {
} }
} }
if (extension.getEnableInterfaceInjection().get()) { if (extension.getInterfaceInjection().isEnabled()) {
InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project); InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project);
if (!jarProcessor.isEmpty()) { if (!jarProcessor.isEmpty()) {

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2021 FabricMC * Copyright (c) 2021-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -24,58 +24,44 @@
package net.fabricmc.loom.configuration.accesswidener; package net.fabricmc.loom.configuration.accesswidener;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logger;
import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.accesswidener.TransitiveOnlyFilter; import net.fabricmc.accesswidener.TransitiveOnlyFilter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingReader; import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class TransitiveAccessWidenerMappingsProcessor { public record TransitiveAccessWidenerMappingsProcessor(Project project) implements GenerateSourcesTask.MappingsProcessor {
private TransitiveAccessWidenerMappingsProcessor() { @Override
public boolean transform(MemoryMappingTree mappings) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
List<AccessWidenerFile> accessWideners = extension.getTransitiveAccessWideners();
if (accessWideners.isEmpty()) {
return false;
} }
public static void process(Path inputMappings, Path outputMappings, List<AccessWidenerFile> accessWideners, Logger logger) { if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) {
MemoryMappingTree mappingTree = new MemoryMappingTree(); throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace());
try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) {
MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString()));
} catch (IOException e) {
throw new RuntimeException("Failed to read mappings", e);
}
if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappingTree.getSrcNamespace())) {
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappingTree.getSrcNamespace());
} }
for (AccessWidenerFile accessWidener : accessWideners) { for (AccessWidenerFile accessWidener : accessWideners) {
MappingCommentVisitor mappingCommentVisitor = new MappingCommentVisitor(accessWidener.modId(), mappingTree, logger); MappingCommentVisitor mappingCommentVisitor = new MappingCommentVisitor(accessWidener.modId(), mappings, project.getLogger());
AccessWidenerReader accessWidenerReader = new AccessWidenerReader(new TransitiveOnlyFilter(mappingCommentVisitor)); AccessWidenerReader accessWidenerReader = new AccessWidenerReader(new TransitiveOnlyFilter(mappingCommentVisitor));
accessWidenerReader.read(accessWidener.content()); accessWidenerReader.read(accessWidener.content());
} }
try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) { return true;
Tiny2Writer tiny2Writer = new Tiny2Writer(writer, false);
mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString()));
} catch (IOException e) {
throw new RuntimeException("Failed to write mappings", e);
}
} }
private static record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor { private record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor {
@Override @Override
public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) {
MappingTree.ClassMapping classMapping = mappingTree.getClass(name); MappingTree.ClassMapping classMapping = mappingTree.getClass(name);

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2021 FabricMC * Copyright (c) 2021-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -26,7 +26,9 @@ package net.fabricmc.loom.configuration.ifaceinject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -40,40 +42,47 @@ import java.util.stream.Collectors;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.hash.Hasher; import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyRemapper;
public class InterfaceInjectionProcessor implements JarProcessor { public class InterfaceInjectionProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor {
// Filename used to store hash of injected interfaces in processed jar file // Filename used to store hash of injected interfaces in processed jar file
private static final String HASH_FILENAME = "injected_interfaces.sha256"; private static final String HASH_FILENAME = "injected_interfaces.sha256";
private final Map<String, List<InjectedInterface>> injectedInterfaces; private final Map<String, List<InjectedInterface>> injectedInterfaces;
private final Project project; private final Project project;
private final LoomGradleExtension extension; private final LoomGradleExtension extension;
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
private final byte[] inputHash; private final byte[] inputHash;
private Map<String, List<InjectedInterface>> remappedInjectedInterfaces; private Map<String, List<InjectedInterface>> remappedInjectedInterfaces;
public InterfaceInjectionProcessor(Project project) { public InterfaceInjectionProcessor(Project project) {
this.project = project; this.project = project;
this.extension = LoomGradleExtension.get(project); this.extension = LoomGradleExtension.get(project);
this.interfaceInjectionExtension = this.extension.getInterfaceInjection();
this.injectedInterfaces = getInjectedInterfaces().stream() this.injectedInterfaces = getInjectedInterfaces().stream()
.collect(Collectors.groupingBy(InjectedInterface::className)); .collect(Collectors.groupingBy(InjectedInterface::className));
@ -158,6 +167,20 @@ public class InterfaceInjectionProcessor implements JarProcessor {
private List<InjectedInterface> getInjectedInterfaces() { private List<InjectedInterface> getInjectedInterfaces() {
List<InjectedInterface> result = new ArrayList<>(); List<InjectedInterface> result = new ArrayList<>();
if (interfaceInjectionExtension.getEnableDependencyInterfaceInjection().get()) {
result.addAll(getDependencyInjectedInterfaces());
}
for (SourceSet sourceSet : interfaceInjectionExtension.getInterfaceInjectionSourceSets().get()) {
result.addAll(getSourceInjectedInterface(sourceSet));
}
return result;
}
private List<InjectedInterface> getDependencyInjectedInterfaces() {
List<InjectedInterface> result = new ArrayList<>();
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) { for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
// Only apply injected interfaces from mods that are part of the compile classpath // Only apply injected interfaces from mods that are part of the compile classpath
if (!entry.compileClasspath()) { if (!entry.compileClasspath()) {
@ -176,12 +199,81 @@ public class InterfaceInjectionProcessor implements JarProcessor {
return result; return result;
} }
private List<InjectedInterface> getSourceInjectedInterface(SourceSet sourceSet) {
final File fabricModJson;
try {
fabricModJson = sourceSet.getResources()
.matching(patternFilterable -> patternFilterable.include("fabric.mod.json"))
.getSingleFile();
} catch (IllegalStateException e) {
// File not found
return Collections.emptyList();
}
final String jsonString;
try {
jsonString = Files.readString(fabricModJson.toPath(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read fabric.mod.json", e);
}
final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(jsonString, JsonObject.class);
return InjectedInterface.fromJson(jsonObject);
}
@Override
public boolean transform(MemoryMappingTree mappings) {
if (injectedInterfaces.isEmpty()) {
return false;
}
if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) {
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace());
}
for (Map.Entry<String, List<InjectedInterface>> entry : injectedInterfaces.entrySet()) {
final String className = entry.getKey();
final List<InjectedInterface> injectedInterfaces = entry.getValue();
MappingTree.ClassMapping classMapping = mappings.getClass(className);
if (classMapping == null) {
final String modIds = injectedInterfaces.stream().map(InjectedInterface::modId).distinct().collect(Collectors.joining(","));
project.getLogger().warn("Failed to find class ({}) to add injected interfaces from mod(s) ({})", className, modIds);
continue;
}
classMapping.setComment(appendComment(classMapping.getComment(), injectedInterfaces));
}
return true;
}
private static String appendComment(String comment, List<InjectedInterface> injectedInterfaces) {
for (InjectedInterface injectedInterface : injectedInterfaces) {
String iiComment = "Interface {@link %s} injected by mod %s".formatted(injectedInterface.ifaceName.substring(injectedInterface.ifaceName.lastIndexOf("/") + 1), injectedInterface.modId);
if (comment == null || !comment.contains(iiComment)) {
if (comment == null) {
comment = iiComment;
} else {
comment += "\n" + iiComment;
}
}
}
return comment;
}
private record InjectedInterface(String modId, String className, String ifaceName) { private record InjectedInterface(String modId, String className, String ifaceName) {
/** /**
* Reads the injected interfaces contained in a mod jar, or returns null if there is none. * Reads the injected interfaces contained in a mod jar, or returns null if there is none.
*/ */
public static List<InjectedInterface> fromModJar(Path modJarPath) { public static List<InjectedInterface> fromModJar(Path modJarPath) {
byte[] modJsonBytes; final byte[] modJsonBytes;
try { try {
modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json"); modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json");
@ -193,26 +285,30 @@ public class InterfaceInjectionProcessor implements JarProcessor {
return Collections.emptyList(); return Collections.emptyList();
} }
JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
String modId = jsonObject.get("id").getAsString(); return fromJson(jsonObject);
}
public static List<InjectedInterface> fromJson(JsonObject jsonObject) {
final String modId = jsonObject.get("id").getAsString();
if (!jsonObject.has("custom")) { if (!jsonObject.has("custom")) {
return Collections.emptyList(); return Collections.emptyList();
} }
JsonObject custom = jsonObject.getAsJsonObject("custom"); final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has("loom:injected_interfaces")) { if (!custom.has("loom:injected_interfaces")) {
return Collections.emptyList(); return Collections.emptyList();
} }
JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces"); final JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces");
List<InjectedInterface> result = new ArrayList<>(); final List<InjectedInterface> result = new ArrayList<>();
for (String className : addedIfaces.keySet()) { for (String className : addedIfaces.keySet()) {
JsonArray ifaceNames = addedIfaces.getAsJsonArray(className); final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
for (JsonElement ifaceName : ifaceNames) { for (JsonElement ifaceName : ifaceNames) {
result.add(new InjectedInterface(modId, className, ifaceName.getAsString())); result.add(new InjectedInterface(modId, className, ifaceName.getAsString()));

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2021 FabricMC * Copyright (c) 2021-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -30,10 +30,13 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.DecompilerOptions;
@ -62,9 +65,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<Boolean> setupRemappedVariants; protected final Property<Boolean> setupRemappedVariants;
protected final Property<Boolean> transitiveAccessWideners; protected final Property<Boolean> transitiveAccessWideners;
protected final Property<String> intermediary; protected final Property<String> intermediary;
protected final Property<Boolean> enableInterfaceInjection;
private final Property<Boolean> runtimeOnlyLog4j; private final Property<Boolean> runtimeOnlyLog4j;
private final Property<MinecraftJarConfiguration> minecraftJarConfiguration; private final Property<MinecraftJarConfiguration> minecraftJarConfiguration;
private final InterfaceInjectionExtensionAPI interfaceInjectionExtension;
private final ModVersionParser versionParser; private final ModVersionParser versionParser;
@ -88,9 +91,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.transitiveAccessWideners.finalizeValueOnRead(); this.transitiveAccessWideners.finalizeValueOnRead();
this.intermediary = project.getObjects().property(String.class) this.intermediary = project.getObjects().property(String.class)
.convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar"); .convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar");
this.enableInterfaceInjection = project.getObjects().property(Boolean.class)
.convention(true);
this.enableInterfaceInjection.finalizeValueOnRead();
this.versionParser = new ModVersionParser(project); this.versionParser = new ModVersionParser(project);
@ -108,6 +108,18 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.runtimeOnlyLog4j = project.getObjects().property(Boolean.class).convention(false); this.runtimeOnlyLog4j = project.getObjects().property(Boolean.class).convention(false);
this.runtimeOnlyLog4j.finalizeValueOnRead(); this.runtimeOnlyLog4j.finalizeValueOnRead();
this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class);
// Add main source set by default
interfaceInjection(interfaceInjection -> {
final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
final SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
interfaceInjection.getInterfaceInjectionSourceSets().add(main);
interfaceInjection.getInterfaceInjectionSourceSets().finalizeValueOnRead();
interfaceInjection.getEnableDependencyInterfaceInjection().convention(true).finalizeValueOnRead();
});
} }
@Override @Override
@ -202,11 +214,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return intermediary; return intermediary;
} }
@Override
public Property<Boolean> getEnableInterfaceInjection() {
return enableInterfaceInjection;
}
@Override @Override
public void disableDeprecatedPomGeneration(MavenPublication publication) { public void disableDeprecatedPomGeneration(MavenPublication publication) {
net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication);
@ -222,6 +229,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return runtimeOnlyLog4j; return runtimeOnlyLog4j;
} }
@Override
public InterfaceInjectionExtensionAPI getInterfaceInjection() {
return interfaceInjectionExtension;
}
// This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods // This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods
private final class EnsureCompile extends LoomGradleExtensionApiImpl { private final class EnsureCompile extends LoomGradleExtensionApiImpl {
private EnsureCompile() { private EnsureCompile() {

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of fabric-loom, licensed under the MIT License (MIT). * This file is part of fabric-loom, licensed under the MIT License (MIT).
* *
* Copyright (c) 2016-2021 FabricMC * Copyright (c) 2016-2022 FabricMC
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -26,12 +26,16 @@ package net.fabricmc.loom.task;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -57,8 +61,9 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.api.decompilers.LoomDecompiler;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor; import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.FileSystemUtil;
@ -69,6 +74,10 @@ import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger;
import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper;
import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCClient;
import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.ipc.IPCServer;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public abstract class GenerateSourcesTask extends AbstractLoomTask { public abstract class GenerateSourcesTask extends AbstractLoomTask {
private final DecompilerOptions decompilerOptions; private final DecompilerOptions decompilerOptions;
@ -300,16 +309,43 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
} }
private Path getMappings() { private Path getMappings() {
Path baseMappings = getExtension().getMappingsProvider().tinyMappings; Path inputMappings = getExtension().getMappingsProvider().tinyMappings;
if (getExtension().getEnableTransitiveAccessWideners().get()) { MemoryMappingTree mappingTree = new MemoryMappingTree();
List<AccessWidenerFile> accessWideners = getExtension().getTransitiveAccessWideners();
if (accessWideners.isEmpty()) { try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) {
return baseMappings; MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString()));
} catch (IOException e) {
throw new RuntimeException("Failed to read mappings", e);
} }
Path outputMappings; final List<MappingsProcessor> mappingsProcessors = new ArrayList<>();
if (getExtension().getEnableTransitiveAccessWideners().get()) {
mappingsProcessors.add(new TransitiveAccessWidenerMappingsProcessor(getProject()));
}
if (getExtension().getInterfaceInjection().isEnabled()) {
mappingsProcessors.add(new InterfaceInjectionProcessor(getProject()));
}
if (mappingsProcessors.isEmpty()) {
return inputMappings;
}
boolean transformed = false;
for (MappingsProcessor mappingsProcessor : mappingsProcessors) {
if (mappingsProcessor.transform(mappingTree)) {
transformed = true;
}
}
if (!transformed) {
return inputMappings;
}
final Path outputMappings;
try { try {
outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny"); outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny");
@ -317,12 +353,18 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
throw new RuntimeException("Failed to create temp file", e); throw new RuntimeException("Failed to create temp file", e);
} }
TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, getProject().getLogger()); try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) {
Tiny2Writer tiny2Writer = new Tiny2Writer(writer, false);
mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString()));
} catch (IOException e) {
throw new RuntimeException("Failed to write mappings", e);
}
return outputMappings; return outputMappings;
} }
return baseMappings; public interface MappingsProcessor {
boolean transform(MemoryMappingTree mappings);
} }
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) { private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {

View File

@ -4,7 +4,6 @@
"version": "1", "version": "1",
"name": "Dummy Mod", "name": "Dummy Mod",
"custom": { "custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": { "loom:injected_interfaces": {
"net/minecraft/class_2248": ["InjectedInterface"] "net/minecraft/class_2248": ["InjectedInterface"]
} }

View File

@ -6,5 +6,6 @@ public class ExampleMod implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
Blocks.AIR.newMethodThatDidNotExist(); Blocks.AIR.newMethodThatDidNotExist();
Blocks.AIR.anotherNewMethodThatDidNotExist();
} }
} }

View File

@ -0,0 +1,5 @@
public interface OwnInjectedInterface {
default void anotherNewMethodThatDidNotExist() {
}
}

View File

@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "owndummy",
"version": "1",
"name": "Own Dummy Mod",
"custom": {
"loom:injected_interfaces": {
"net/minecraft/class_2248": ["OwnInjectedInterface"]
}
}
}