Support injecting interfaces from the mod source. Add a comment to target classes saying what mod is providing an injected interface. (#581)
This commit is contained in:
		
							parent
							
								
									e4330a11dc
								
							
						
					
					
						commit
						9662a8b3de
					
				
					 11 changed files with 285 additions and 84 deletions
				
			
		|  | @ -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). | ||||
|  * | ||||
|  * Copyright (c) 2021 FabricMC | ||||
|  * Copyright (c) 2021-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 | ||||
|  | @ -86,6 +86,12 @@ public interface LoomGradleExtensionAPI { | |||
| 	// TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed. | ||||
| 	MixinExtensionAPI getMixin(); | ||||
| 
 | ||||
| 	default void interfaceInjection(Action<InterfaceInjectionExtensionAPI> action) { | ||||
| 		action.execute(getInterfaceInjection()); | ||||
| 	} | ||||
| 
 | ||||
| 	InterfaceInjectionExtensionAPI getInterfaceInjection(); | ||||
| 
 | ||||
| 	Property<String> getCustomMinecraftManifest(); | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -131,15 +137,6 @@ public interface LoomGradleExtensionAPI { | |||
| 	 */ | ||||
| 	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 | ||||
| 	Property<MinecraftJarConfiguration> getMinecraftJarConfiguration(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /* | ||||
|  * 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 | ||||
|  * 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); | ||||
| 
 | ||||
| 			if (!jarProcessor.isEmpty()) { | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /* | ||||
|  * 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 | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  | @ -24,58 +24,44 @@ | |||
| 
 | ||||
| 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 org.gradle.api.Project; | ||||
| import org.gradle.api.logging.Logger; | ||||
| 
 | ||||
| import net.fabricmc.accesswidener.AccessWidenerReader; | ||||
| import net.fabricmc.accesswidener.AccessWidenerVisitor; | ||||
| import net.fabricmc.accesswidener.TransitiveOnlyFilter; | ||||
| import net.fabricmc.loom.LoomGradleExtension; | ||||
| import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; | ||||
| import net.fabricmc.mappingio.MappingReader; | ||||
| import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; | ||||
| import net.fabricmc.mappingio.format.Tiny2Writer; | ||||
| import net.fabricmc.loom.task.GenerateSourcesTask; | ||||
| import net.fabricmc.mappingio.tree.MappingTree; | ||||
| import net.fabricmc.mappingio.tree.MemoryMappingTree; | ||||
| 
 | ||||
| public final class TransitiveAccessWidenerMappingsProcessor { | ||||
| 	private TransitiveAccessWidenerMappingsProcessor() { | ||||
| 	} | ||||
| public record TransitiveAccessWidenerMappingsProcessor(Project project) implements GenerateSourcesTask.MappingsProcessor { | ||||
| 	@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) { | ||||
| 		MemoryMappingTree mappingTree = new MemoryMappingTree(); | ||||
| 
 | ||||
| 		try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) { | ||||
| 			MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString())); | ||||
| 		} catch (IOException e) { | ||||
| 			throw new RuntimeException("Failed to read mappings", e); | ||||
| 		if (accessWideners.isEmpty()) { | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappingTree.getSrcNamespace())) { | ||||
| 			throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappingTree.getSrcNamespace()); | ||||
| 		if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) { | ||||
| 			throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace()); | ||||
| 		} | ||||
| 
 | ||||
| 		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.read(accessWidener.content()); | ||||
| 		} | ||||
| 
 | ||||
| 		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 true; | ||||
| 	} | ||||
| 
 | ||||
| 	private static record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor { | ||||
| 	private record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor { | ||||
| 		@Override | ||||
| 		public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { | ||||
| 			MappingTree.ClassMapping classMapping = mappingTree.getClass(name); | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /* | ||||
|  * 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 | ||||
|  * 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.IOException; | ||||
| import java.io.UncheckedIOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
|  | @ -40,40 +42,47 @@ import java.util.stream.Collectors; | |||
| import com.google.common.base.Preconditions; | ||||
| import com.google.common.hash.Hasher; | ||||
| import com.google.common.hash.Hashing; | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.JsonArray; | ||||
| import com.google.gson.JsonElement; | ||||
| import com.google.gson.JsonObject; | ||||
| import org.gradle.api.Project; | ||||
| import org.gradle.api.tasks.SourceSet; | ||||
| import org.objectweb.asm.ClassReader; | ||||
| import org.objectweb.asm.ClassVisitor; | ||||
| import org.objectweb.asm.ClassWriter; | ||||
| import org.objectweb.asm.commons.Remapper; | ||||
| 
 | ||||
| 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.configuration.RemappedConfigurationEntry; | ||||
| import net.fabricmc.loom.configuration.processors.JarProcessor; | ||||
| import net.fabricmc.loom.task.GenerateSourcesTask; | ||||
| import net.fabricmc.loom.util.Checksum; | ||||
| import net.fabricmc.loom.util.Constants; | ||||
| import net.fabricmc.loom.util.Pair; | ||||
| import net.fabricmc.loom.util.TinyRemapperHelper; | ||||
| import net.fabricmc.loom.util.ZipUtils; | ||||
| import net.fabricmc.mappingio.tree.MappingTree; | ||||
| import net.fabricmc.mappingio.tree.MemoryMappingTree; | ||||
| 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 | ||||
| 	private static final String HASH_FILENAME = "injected_interfaces.sha256"; | ||||
| 
 | ||||
| 	private final Map<String, List<InjectedInterface>> injectedInterfaces; | ||||
| 	private final Project project; | ||||
| 	private final LoomGradleExtension extension; | ||||
| 	private final InterfaceInjectionExtensionAPI interfaceInjectionExtension; | ||||
| 	private final byte[] inputHash; | ||||
| 	private Map<String, List<InjectedInterface>> remappedInjectedInterfaces; | ||||
| 
 | ||||
| 	public InterfaceInjectionProcessor(Project project) { | ||||
| 		this.project = project; | ||||
| 		this.extension = LoomGradleExtension.get(project); | ||||
| 		this.interfaceInjectionExtension = this.extension.getInterfaceInjection(); | ||||
| 		this.injectedInterfaces = getInjectedInterfaces().stream() | ||||
| 				.collect(Collectors.groupingBy(InjectedInterface::className)); | ||||
| 
 | ||||
|  | @ -158,6 +167,20 @@ public class InterfaceInjectionProcessor implements JarProcessor { | |||
| 	private List<InjectedInterface> getInjectedInterfaces() { | ||||
| 		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) { | ||||
| 			// Only apply injected interfaces from mods that are part of the compile classpath | ||||
| 			if (!entry.compileClasspath()) { | ||||
|  | @ -176,12 +199,81 @@ public class InterfaceInjectionProcessor implements JarProcessor { | |||
| 		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) { | ||||
| 		/** | ||||
| 		 * Reads the injected interfaces contained in a mod jar, or returns null if there is none. | ||||
| 		 */ | ||||
| 		public static List<InjectedInterface> fromModJar(Path modJarPath) { | ||||
| 			byte[] modJsonBytes; | ||||
| 			final byte[] modJsonBytes; | ||||
| 
 | ||||
| 			try { | ||||
| 				modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json"); | ||||
|  | @ -193,26 +285,30 @@ public class InterfaceInjectionProcessor implements JarProcessor { | |||
| 				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")) { | ||||
| 				return Collections.emptyList(); | ||||
| 			} | ||||
| 
 | ||||
| 			JsonObject custom = jsonObject.getAsJsonObject("custom"); | ||||
| 			final JsonObject custom = jsonObject.getAsJsonObject("custom"); | ||||
| 
 | ||||
| 			if (!custom.has("loom:injected_interfaces")) { | ||||
| 				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()) { | ||||
| 				JsonArray ifaceNames = addedIfaces.getAsJsonArray(className); | ||||
| 				final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className); | ||||
| 
 | ||||
| 				for (JsonElement ifaceName : ifaceNames) { | ||||
| 					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). | ||||
|  * | ||||
|  * Copyright (c) 2021 FabricMC | ||||
|  * Copyright (c) 2021-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 | ||||
|  | @ -30,10 +30,13 @@ import org.gradle.api.Project; | |||
| import org.gradle.api.artifacts.Dependency; | ||||
| import org.gradle.api.file.ConfigurableFileCollection; | ||||
| import org.gradle.api.file.RegularFileProperty; | ||||
| import org.gradle.api.plugins.JavaPluginExtension; | ||||
| import org.gradle.api.provider.ListProperty; | ||||
| import org.gradle.api.provider.Property; | ||||
| 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.MixinExtensionAPI; | ||||
| 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> transitiveAccessWideners; | ||||
| 	protected final Property<String> intermediary; | ||||
| 	protected final Property<Boolean> enableInterfaceInjection; | ||||
| 	private final Property<Boolean> runtimeOnlyLog4j; | ||||
| 	private final Property<MinecraftJarConfiguration> minecraftJarConfiguration; | ||||
| 	private final InterfaceInjectionExtensionAPI interfaceInjectionExtension; | ||||
| 
 | ||||
| 	private final ModVersionParser versionParser; | ||||
| 
 | ||||
|  | @ -88,9 +91,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA | |||
| 		this.transitiveAccessWideners.finalizeValueOnRead(); | ||||
| 		this.intermediary = project.getObjects().property(String.class) | ||||
| 				.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); | ||||
| 
 | ||||
|  | @ -108,6 +108,18 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA | |||
| 
 | ||||
| 		this.runtimeOnlyLog4j = project.getObjects().property(Boolean.class).convention(false); | ||||
| 		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 | ||||
|  | @ -202,11 +214,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA | |||
| 		return intermediary; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public Property<Boolean> getEnableInterfaceInjection() { | ||||
| 		return enableInterfaceInjection; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void disableDeprecatedPomGeneration(MavenPublication publication) { | ||||
| 		net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); | ||||
|  | @ -222,6 +229,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA | |||
| 		return runtimeOnlyLog4j; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public InterfaceInjectionExtensionAPI getInterfaceInjection() { | ||||
| 		return interfaceInjectionExtension; | ||||
| 	} | ||||
| 
 | ||||
| 	// This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods | ||||
| 	private final class EnsureCompile extends LoomGradleExtensionApiImpl { | ||||
| 		private EnsureCompile() { | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| /* | ||||
|  * 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 | ||||
|  * 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.IOException; | ||||
| import java.io.Reader; | ||||
| import java.io.UncheckedIOException; | ||||
| import java.io.Writer; | ||||
| import java.lang.reflect.Constructor; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.StandardCopyOption; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 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.DecompilerOptions; | ||||
| 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.ifaceinject.InterfaceInjectionProcessor; | ||||
| import net.fabricmc.loom.decompilers.LineNumberRemapper; | ||||
| import net.fabricmc.loom.util.Constants; | ||||
| 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.ipc.IPCClient; | ||||
| 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 { | ||||
| 	private final DecompilerOptions decompilerOptions; | ||||
|  | @ -300,29 +309,62 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { | |||
| 	} | ||||
| 
 | ||||
| 	private Path getMappings() { | ||||
| 		Path baseMappings = getExtension().getMappingsProvider().tinyMappings; | ||||
| 		Path inputMappings = getExtension().getMappingsProvider().tinyMappings; | ||||
| 
 | ||||
| 		if (getExtension().getEnableTransitiveAccessWideners().get()) { | ||||
| 			List<AccessWidenerFile> accessWideners = getExtension().getTransitiveAccessWideners(); | ||||
| 		MemoryMappingTree mappingTree = new MemoryMappingTree(); | ||||
| 
 | ||||
| 			if (accessWideners.isEmpty()) { | ||||
| 				return baseMappings; | ||||
| 			} | ||||
| 
 | ||||
| 			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; | ||||
| 		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); | ||||
| 		} | ||||
| 
 | ||||
| 		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) { | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ | |||
|   "version": "1", | ||||
|   "name": "Dummy Mod", | ||||
|   "custom": { | ||||
|     "fabric-api:module-lifecycle": "stable", | ||||
|     "loom:injected_interfaces": { | ||||
|       "net/minecraft/class_2248": ["InjectedInterface"] | ||||
|     } | ||||
|  |  | |||
|  | @ -6,5 +6,6 @@ public class ExampleMod implements ModInitializer { | |||
| 	@Override | ||||
| 	public void onInitialize() { | ||||
| 		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 a new issue