Support injecting interfaces from the mod source. Add a comment to target classes saying what mod is providing an injected interface. (#581)
parent
e4330a11dc
commit
9662a8b3de
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
public static void process(Path inputMappings, Path outputMappings, List<AccessWidenerFile> accessWideners, Logger logger) {
|
if (accessWideners.isEmpty()) {
|
||||||
MemoryMappingTree mappingTree = new MemoryMappingTree();
|
return false;
|
||||||
|
|
||||||
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())) {
|
if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) {
|
||||||
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappingTree.getSrcNamespace());
|
throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.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);
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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,29 +309,62 @@ 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;
|
|
||||||
|
|
||||||
try {
|
|
||||||
outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to create temp file", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, getProject().getLogger());
|
|
||||||
|
|
||||||
return outputMappings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseMappings;
|
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 {
|
||||||
|
outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to create temp file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MappingsProcessor {
|
||||||
|
boolean transform(MemoryMappingTree mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
private static Constructor<LoomDecompiler> getDecompilerConstructor(String clazz) {
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
public interface OwnInjectedInterface {
|
||||||
|
default void anotherNewMethodThatDidNotExist() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "owndummy",
|
||||||
|
"version": "1",
|
||||||
|
"name": "Own Dummy Mod",
|
||||||
|
"custom": {
|
||||||
|
"loom:injected_interfaces": {
|
||||||
|
"net/minecraft/class_2248": ["OwnInjectedInterface"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue