Mod provided javadoc (#627)

This commit is contained in:
modmuss50
2022-04-19 23:30:28 +01:00
committed by GitHub
parent 29499fd0bd
commit f632dee2df
13 changed files with 396 additions and 17 deletions

View File

@@ -140,6 +140,13 @@ public interface LoomGradleExtensionAPI {
*/
Property<Boolean> getEnableTransitiveAccessWideners();
/**
* When true loom will apply mod provided javadoc from dependencies.
*
* @return the property controlling the mod provided javadoc
*/
Property<Boolean> getEnableModProvidedJavadoc();
@ApiStatus.Experimental
IntermediateMappingsProvider getIntermediateMappingsProvider();

View File

@@ -45,6 +45,7 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.mods.ModJavadocProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration;
@@ -239,6 +240,15 @@ public final class CompileConfiguration {
}
}
if (extension.getEnableModProvidedJavadoc().get()) {
// This doesn't do any processing on the compiled jar, but it does have an effect on the generated sources.
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(project);
if (javadocProcessor != null) {
extension.getGameJarProcessors().add(javadocProcessor);
}
}
JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get());
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();

View File

@@ -61,6 +61,7 @@ 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.ModUtils;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
@@ -270,23 +271,15 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
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 empty if there is none.
*/
public static List<InjectedInterface> fromModJar(Path modJarPath) {
final byte[] modJsonBytes;
final JsonObject jsonObject = ModUtils.getFabricModJson(modJarPath);
try {
modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json");
} catch (IOException e) {
throw new RuntimeException("Failed to extract fabric.mod.json from " + modJarPath);
}
if (modJsonBytes == null) {
if (jsonObject == null) {
return Collections.emptyList();
}
final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
return fromJson(jsonObject);
}
@@ -299,11 +292,11 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has("loom:injected_interfaces")) {
if (!custom.has(Constants.CustomModJsonKeys.INJECTED_INTERFACE)) {
return Collections.emptyList();
}
final JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces");
final JsonObject addedIfaces = custom.getAsJsonObject(Constants.CustomModJsonKeys.INJECTED_INTERFACE);
final List<InjectedInterface> result = new ArrayList<>();

View File

@@ -0,0 +1,218 @@
/*
* 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.configuration.mods;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
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.Constants;
import net.fabricmc.loom.util.ModUtils;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(ModJavadocProcessor.class);
private final List<ModJavadoc> javadocs;
private ModJavadocProcessor(List<ModJavadoc> javadocs) {
this.javadocs = javadocs;
}
@Nullable
public static ModJavadocProcessor create(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final List<ModJavadoc> javadocs = new ArrayList<>();
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
Set<File> artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration())
.get()
.resolve();
for (File artifact : artifacts) {
if (!ModUtils.isMod(artifact.toPath())) {
continue;
}
final ModJavadoc modJavadoc;
try {
modJavadoc = ModJavadoc.fromModJar(artifact.toPath());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mod jar (%s)".formatted(artifact), e);
}
if (modJavadoc != null) {
javadocs.add(modJavadoc);
}
}
}
if (javadocs.isEmpty()) {
return null;
}
return new ModJavadocProcessor(javadocs);
}
@Override
public boolean transform(MemoryMappingTree mappings) {
for (ModJavadoc javadoc : javadocs) {
javadoc.apply(mappings);
}
return true;
}
@Override
public String getId() {
return "loom:interface_injection:" + javadocs.hashCode();
}
@Override
public void setup() {
}
@Override
public void process(File file) {
// No need to actually process anything, we need to be a JarProcessor to ensure that the jar is cached correctly.
}
public record ModJavadoc(String modId, MemoryMappingTree mappingTree) {
@Nullable
public static ModJavadoc fromModJar(Path path) throws IOException {
JsonObject jsonObject = ModUtils.getFabricModJson(path);
if (jsonObject == null || !jsonObject.has("custom")) {
return null;
}
final String modId = jsonObject.get("id").getAsString();
final JsonObject custom = jsonObject.getAsJsonObject("custom");
if (!custom.has(Constants.CustomModJsonKeys.PROVIDED_JAVADOC)) {
return null;
}
final String javaDocPath = custom.getAsJsonPrimitive(Constants.CustomModJsonKeys.PROVIDED_JAVADOC).getAsString();
final byte[] data = ZipUtils.unpack(path, javaDocPath);
final MemoryMappingTree mappings = new MemoryMappingTree();
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(data))) {
MappingReader.read(reader, mappings);
}
if (!mappings.getSrcNamespace().equals(MappingsNamespace.INTERMEDIARY.toString())) {
throw new IllegalStateException("Javadoc provided by mod (%s) must be have an intermediary source namespace".formatted(modId));
}
if (!mappings.getDstNamespaces().isEmpty()) {
throw new IllegalStateException("Javadoc provided by mod (%s) must not contain any dst names".formatted(modId));
}
return new ModJavadoc(modId, mappings);
}
public void apply(MemoryMappingTree target) {
if (!mappingTree.getSrcNamespace().equals(target.getSrcNamespace())) {
throw new IllegalStateException("Cannot apply mappings to differing namespaces. source: %s target: %s".formatted(mappingTree.getSrcNamespace(), target.getSrcNamespace()));
}
for (MappingTree.ClassMapping sourceClass : mappingTree.getClasses()) {
final MappingTree.ClassMapping targetClass = target.getClass(sourceClass.getSrcName());
if (targetClass == null) {
LOGGER.warn("Could not find provided javadoc target class {} from mod {}", sourceClass.getSrcName(), modId);
continue;
}
applyComment(sourceClass, targetClass);
for (MappingTree.FieldMapping sourceField : sourceClass.getFields()) {
final MappingTree.FieldMapping targetField = targetClass.getField(sourceField.getSrcName(), sourceField.getSrcDesc());
if (targetField == null) {
LOGGER.warn("Could not find provided javadoc target field {}{} from mod {}", sourceField.getSrcName(), sourceField.getSrcDesc(), modId);
continue;
}
applyComment(sourceField, targetField);
}
for (MappingTree.MethodMapping sourceMethod : sourceClass.getMethods()) {
final MappingTree.MethodMapping targetMethod = targetClass.getMethod(sourceMethod.getSrcName(), sourceMethod.getSrcDesc());
if (targetMethod == null) {
LOGGER.warn("Could not find provided javadoc target method {}{} from mod {}", sourceMethod.getSrcName(), sourceMethod.getSrcDesc(), modId);
continue;
}
applyComment(sourceMethod, targetMethod);
}
}
}
private <T extends MappingTree.ElementMapping> void applyComment(T source, T target) {
String sourceComment = source.getComment();
if (sourceComment == null) {
LOGGER.warn("Mod {} provided javadoc has mapping for {}, without comment", modId, source);
return;
}
String targetComment = target.getComment();
if (targetComment == null) {
targetComment = "";
} else {
targetComment += "\n";
}
targetComment += sourceComment;
target.setComment(targetComment);
}
}
}

View File

@@ -67,6 +67,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<String> customManifest;
protected final Property<Boolean> setupRemappedVariants;
protected final Property<Boolean> transitiveAccessWideners;
protected final Property<Boolean> modProvidedJavadoc;
protected final Property<String> intermediary;
protected final Property<IntermediateMappingsProvider> intermediateMappingsProvider;
private final Property<Boolean> runtimeOnlyLog4j;
@@ -98,6 +99,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.transitiveAccessWideners = project.getObjects().property(Boolean.class)
.convention(true);
this.transitiveAccessWideners.finalizeValueOnRead();
this.modProvidedJavadoc = project.getObjects().property(Boolean.class)
.convention(true);
this.modProvidedJavadoc.finalizeValueOnRead();
this.intermediary = project.getObjects().property(String.class)
.convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar");
@@ -234,6 +238,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
return transitiveAccessWideners;
}
@Override
public Property<Boolean> getEnableModProvidedJavadoc() {
return modProvidedJavadoc;
}
protected abstract Project getProject();
protected abstract LoomFiles getFiles();

View File

@@ -64,6 +64,7 @@ import net.fabricmc.loom.api.decompilers.LoomDecompiler;
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.configuration.mods.ModJavadocProcessor;
import net.fabricmc.loom.decompilers.LineNumberRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
@@ -329,6 +330,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
mappingsProcessors.add(new InterfaceInjectionProcessor(getProject()));
}
final ModJavadocProcessor javadocProcessor = ModJavadocProcessor.create(getProject());
if (javadocProcessor != null) {
mappingsProcessors.add(javadocProcessor);
}
if (mappingsProcessors.isEmpty()) {
return inputMappings;
}

View File

@@ -142,4 +142,9 @@ public class Constants {
private TaskGroup() {
}
}
public static final class CustomModJsonKeys {
public static final String INJECTED_INTERFACE = "loom:injected_interfaces";
public static final String PROVIDED_JAVADOC = "loom:provided_javadoc";
}
}

View File

@@ -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
@@ -25,12 +25,42 @@
package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradlePlugin;
public final class ModUtils {
private ModUtils() {
}
public static boolean isMod(File input) {
return ZipUtils.contains(input.toPath(), "fabric.mod.json");
public static boolean isMod(File file) {
return isMod(file.toPath());
}
public static boolean isMod(Path input) {
return ZipUtils.contains(input, "fabric.mod.json");
}
@Nullable
public static JsonObject getFabricModJson(Path path) {
final byte[] modJsonBytes;
try {
modJsonBytes = ZipUtils.unpackNullable(path, "fabric.mod.json");
} catch (IOException e) {
throw new UncheckedIOException("Failed to extract fabric.mod.json from " + path, e);
}
if (modJsonBytes == null) {
return null;
}
return LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
}
}