mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Split Architectury extensions to Fabric classes into new classes (#116)
* Split custom RemapJarTask logic into a new class * Split a lot of logic for Arch and Quilt mod metadata into new classes * ArchitecturyCommonJson: Fix outdated error message * Add minimal unit tests for ACJ and QMJ * QuiltModJson: Fix error when there are no injected interfaces * QuiltModJsonTest: Add test for mixin configs * QuiltModJsonTest: Move to correct package * Add tests for creating ACJ and QMJ instances
This commit is contained in:
@@ -166,6 +166,7 @@ spotless {
|
||||
java {
|
||||
licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-")
|
||||
targetExclude("**/loom/util/DownloadUtil.java", "**/loom/util/FileSystemUtil.java")
|
||||
targetExclude("**/dev/architectury/**")
|
||||
}
|
||||
|
||||
groovy {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package dev.architectury.loom.extensions;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.cadixdev.at.AccessTransformSet;
|
||||
import org.cadixdev.at.io.AccessTransformFormats;
|
||||
import org.cadixdev.lorenz.MappingSet;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.task.service.MappingsService;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.LfWriter;
|
||||
import net.fabricmc.loom.util.aw2at.Aw2At;
|
||||
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
|
||||
import net.fabricmc.lorenztiny.TinyMappingsReader;
|
||||
|
||||
public final class ModBuildExtensions {
|
||||
public static Set<String> readMixinConfigsFromManifest(File jarFile) {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
@Nullable Manifest manifest = jar.getManifest();
|
||||
|
||||
if (manifest != null) {
|
||||
Attributes attributes = manifest.getMainAttributes();
|
||||
String mixinConfigs = attributes.getValue(Constants.Forge.MIXIN_CONFIGS_MANIFEST_KEY);
|
||||
|
||||
if (mixinConfigs != null) {
|
||||
return Set.of(mixinConfigs.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
return Set.of();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read mixin configs from jar " + jarFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void convertAwToAt(SetProperty<String> atAccessWidenersProperty, Path outputFile, Property<String> mappingBuildServiceUuid) throws IOException {
|
||||
if (!atAccessWidenersProperty.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> atAccessWideners = atAccessWidenersProperty.get();
|
||||
|
||||
if (atAccessWideners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AccessTransformSet at = AccessTransformSet.create();
|
||||
|
||||
try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(outputFile, false)) {
|
||||
FileSystem fs = fileSystem.get();
|
||||
Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
|
||||
|
||||
if (Files.exists(atPath)) {
|
||||
throw new FileAlreadyExistsException("Jar " + outputFile + " already contains an access transformer - cannot convert AWs!");
|
||||
}
|
||||
|
||||
for (String aw : atAccessWideners) {
|
||||
Path awPath = fs.getPath(aw);
|
||||
|
||||
if (Files.notExists(awPath)) {
|
||||
throw new NoSuchFileException("Could not find AW '" + aw + "' to convert into AT!");
|
||||
}
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(awPath, StandardCharsets.UTF_8)) {
|
||||
at.merge(Aw2At.toAccessTransformSet(reader));
|
||||
}
|
||||
|
||||
Files.delete(awPath);
|
||||
}
|
||||
|
||||
MappingsService service = UnsafeWorkQueueHelper.get(mappingBuildServiceUuid, MappingsService.class);
|
||||
|
||||
try (TinyMappingsReader reader = new TinyMappingsReader(service.getMemoryMappingTree(), service.getFromNamespace(), service.getToNamespace())) {
|
||||
MappingSet mappingSet = reader.read();
|
||||
at = at.remap(mappingSet);
|
||||
}
|
||||
|
||||
try (Writer writer = new LfWriter(Files.newBufferedWriter(atPath))) {
|
||||
AccessTransformFormats.FML.write(writer, at);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package dev.architectury.loom.metadata;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
|
||||
public final class ArchitecturyCommonJson implements ModMetadataFile {
|
||||
private static final String ACCESS_WIDENER_KEY = "accessWidener";
|
||||
|
||||
private final JsonObject json;
|
||||
|
||||
private ArchitecturyCommonJson(JsonObject json) {
|
||||
this.json = Objects.requireNonNull(json, "json");
|
||||
}
|
||||
|
||||
public static ArchitecturyCommonJson of(byte[] utf8) {
|
||||
return of(new String(utf8, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static ArchitecturyCommonJson of(String text) {
|
||||
return of(LoomGradlePlugin.GSON.fromJson(text, JsonObject.class));
|
||||
}
|
||||
|
||||
public static ArchitecturyCommonJson of(Path path) throws IOException {
|
||||
return of(Files.readString(path, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static ArchitecturyCommonJson of(File file) throws IOException {
|
||||
return of(file.toPath());
|
||||
}
|
||||
|
||||
public static ArchitecturyCommonJson of(JsonObject json) {
|
||||
return new ArchitecturyCommonJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getAccessWidener() {
|
||||
if (json.has(ACCESS_WIDENER_KEY)) {
|
||||
return json.get(ACCESS_WIDENER_KEY).getAsString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InterfaceInjectionProcessor.InjectedInterface> getInjectedInterfaces(@Nullable String modId) {
|
||||
if (modId == null) {
|
||||
throw new IllegalArgumentException("getInjectedInterfaces: mod ID has to be provided for architectury.common.json");
|
||||
}
|
||||
|
||||
return getInjectedInterfaces(json, modId);
|
||||
}
|
||||
|
||||
static List<InterfaceInjectionProcessor.InjectedInterface> getInjectedInterfaces(JsonObject json, String modId) {
|
||||
Objects.requireNonNull(modId, "mod ID");
|
||||
|
||||
if (json.has("injected_interfaces")) {
|
||||
JsonObject addedIfaces = json.getAsJsonObject("injected_interfaces");
|
||||
|
||||
final List<InterfaceInjectionProcessor.InjectedInterface> result = new ArrayList<>();
|
||||
|
||||
for (String className : addedIfaces.keySet()) {
|
||||
final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
|
||||
|
||||
for (JsonElement ifaceName : ifaceNames) {
|
||||
result.add(new InterfaceInjectionProcessor.InjectedInterface(modId, className, ifaceName.getAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package dev.architectury.loom.metadata;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
|
||||
public interface ModMetadataFile {
|
||||
@Nullable String getAccessWidener();
|
||||
List<InterfaceInjectionProcessor.InjectedInterface> getInjectedInterfaces(@Nullable String modId);
|
||||
}
|
||||
120
src/main/java/dev/architectury/loom/metadata/QuiltModJson.java
Normal file
120
src/main/java/dev/architectury/loom/metadata/QuiltModJson.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package dev.architectury.loom.metadata;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
|
||||
public final class QuiltModJson implements ModMetadataFile {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QuiltModJson.class);
|
||||
private static final String ACCESS_WIDENER_KEY = "access_widener";
|
||||
private static final String MIXIN_KEY = "mixin";
|
||||
|
||||
private final JsonObject json;
|
||||
|
||||
private QuiltModJson(JsonObject json) {
|
||||
this.json = Objects.requireNonNull(json, "json");
|
||||
}
|
||||
|
||||
public static QuiltModJson of(byte[] utf8) {
|
||||
return of(new String(utf8, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static QuiltModJson of(String text) {
|
||||
return of(LoomGradlePlugin.GSON.fromJson(text, JsonObject.class));
|
||||
}
|
||||
|
||||
public static QuiltModJson of(Path path) throws IOException {
|
||||
return of(Files.readString(path, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static QuiltModJson of(File file) throws IOException {
|
||||
return of(file.toPath());
|
||||
}
|
||||
|
||||
public static QuiltModJson of(JsonObject json) {
|
||||
return new QuiltModJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getAccessWidener() {
|
||||
if (json.has(ACCESS_WIDENER_KEY)) {
|
||||
if (json.get(ACCESS_WIDENER_KEY).isJsonArray()) {
|
||||
JsonArray array = json.get(ACCESS_WIDENER_KEY).getAsJsonArray();
|
||||
|
||||
// TODO (1.1): Support multiple access wideners in Quilt mods
|
||||
if (array.size() != 1) {
|
||||
throw new UnsupportedOperationException("Loom does not support multiple access wideners in one mod!");
|
||||
}
|
||||
|
||||
return array.get(0).getAsString();
|
||||
} else {
|
||||
return json.get(ACCESS_WIDENER_KEY).getAsString();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InterfaceInjectionProcessor.InjectedInterface> getInjectedInterfaces(@Nullable String modId) {
|
||||
try {
|
||||
modId = Objects.requireNonNullElseGet(modId, () -> json.getAsJsonObject("quilt_loader").get("id").getAsString());
|
||||
} catch (NullPointerException e) {
|
||||
throw new IllegalArgumentException("Could not determine mod ID for Quilt mod and no fallback provided");
|
||||
}
|
||||
|
||||
// Quilt injected interfaces have the same format as architectury.common.json
|
||||
if (json.has("quilt_loom")) {
|
||||
JsonElement quiltLoom = json.get("quilt_loom");
|
||||
|
||||
if (quiltLoom.isJsonObject()) {
|
||||
return ArchitecturyCommonJson.getInjectedInterfaces(json.getAsJsonObject("quilt_loom"), modId);
|
||||
} else {
|
||||
LOGGER.warn("Unexpected type for 'quilt_loom' in quilt.mod.json: {}", quiltLoom.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public List<String> getMixinConfigs() {
|
||||
// RFC 0002: The `mixin` field:
|
||||
// Type: Array/String
|
||||
// Required: False
|
||||
|
||||
if (json.has(MIXIN_KEY)) {
|
||||
JsonElement mixin = json.get(MIXIN_KEY);
|
||||
|
||||
if (mixin.isJsonPrimitive()) {
|
||||
return List.of(mixin.getAsString());
|
||||
} else if (mixin.isJsonArray()) {
|
||||
List<String> mixinConfigs = new ArrayList<>();
|
||||
|
||||
for (JsonElement child : mixin.getAsJsonArray()) {
|
||||
mixinConfigs.add(child.getAsString());
|
||||
}
|
||||
|
||||
return mixinConfigs;
|
||||
} else {
|
||||
LOGGER.warn("'mixin' key in quilt.mod.json is of unexpected type {}", mixin.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,9 @@ import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.architectury.loom.metadata.ArchitecturyCommonJson;
|
||||
import dev.architectury.loom.metadata.QuiltModJson;
|
||||
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
|
||||
@@ -56,7 +57,7 @@ public record AccessWidenerFile(
|
||||
|
||||
if (modJsonBytes == null) {
|
||||
if (ZipUtils.contains(modJarPath, "architectury.common.json")) {
|
||||
String awPath = null;
|
||||
String awPath;
|
||||
byte[] commonJsonBytes;
|
||||
|
||||
try {
|
||||
@@ -66,13 +67,8 @@ public record AccessWidenerFile(
|
||||
}
|
||||
|
||||
if (commonJsonBytes != null) {
|
||||
JsonObject jsonObject = new Gson().fromJson(new String(commonJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
|
||||
if (jsonObject.has("accessWidener")) {
|
||||
awPath = jsonObject.get("accessWidener").getAsString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
awPath = ArchitecturyCommonJson.of(commonJsonBytes).getAccessWidener();
|
||||
if (awPath == null) return null;
|
||||
} else {
|
||||
// ???????????
|
||||
throw new IllegalArgumentException("The architectury.common.json file does not exist.");
|
||||
@@ -94,7 +90,7 @@ public record AccessWidenerFile(
|
||||
}
|
||||
|
||||
if (ZipUtils.contains(modJarPath, "quilt.mod.json")) {
|
||||
String awPath = null;
|
||||
String awPath;
|
||||
byte[] quiltModBytes;
|
||||
|
||||
try {
|
||||
@@ -104,23 +100,8 @@ public record AccessWidenerFile(
|
||||
}
|
||||
|
||||
if (quiltModBytes != null) {
|
||||
JsonObject jsonObject = new Gson().fromJson(new String(quiltModBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
|
||||
if (jsonObject.has("access_widener")) {
|
||||
if (jsonObject.get("access_widener").isJsonArray()) {
|
||||
JsonArray array = jsonObject.get("access_widener").getAsJsonArray();
|
||||
|
||||
if (array.size() != 1) {
|
||||
throw new UnsupportedOperationException("Loom does not support multiple access wideners in one mod!");
|
||||
}
|
||||
|
||||
awPath = array.get(0).getAsString();
|
||||
} else {
|
||||
awPath = jsonObject.get("access_widener").getAsString();
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
awPath = QuiltModJson.of(quiltModBytes).getAccessWidener();
|
||||
if (awPath == null) return null;
|
||||
} else {
|
||||
// ???????????
|
||||
throw new IllegalArgumentException("The quilt.mod.json file does not exist.");
|
||||
|
||||
@@ -44,10 +44,11 @@ import java.util.stream.Stream;
|
||||
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 dev.architectury.loom.metadata.ArchitecturyCommonJson;
|
||||
import dev.architectury.loom.metadata.QuiltModJson;
|
||||
import dev.architectury.tinyremapper.TinyRemapper;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
@@ -213,16 +214,11 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
.matching(patternFilterable -> patternFilterable.include("architectury.common.json"))
|
||||
.getSingleFile();
|
||||
|
||||
final String jsonString;
|
||||
|
||||
try {
|
||||
jsonString = Files.readString(archCommonJson.toPath(), StandardCharsets.UTF_8);
|
||||
return ArchitecturyCommonJson.of(archCommonJson).getInjectedInterfaces(archCommonJson.getAbsolutePath());
|
||||
} catch (IOException e2) {
|
||||
throw new UncheckedIOException("Failed to read architectury.common.json", e2);
|
||||
}
|
||||
|
||||
JsonObject jsonObject = new Gson().fromJson(jsonString, JsonObject.class);
|
||||
return InjectedInterface.fromJsonArch(jsonObject, archCommonJson.getAbsolutePath());
|
||||
} catch (IllegalStateException e2) {
|
||||
File quiltModJson;
|
||||
|
||||
@@ -231,20 +227,11 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
.matching(patternFilterable -> patternFilterable.include("quilt.mod.json"))
|
||||
.getSingleFile();
|
||||
|
||||
final String jsonString;
|
||||
|
||||
try {
|
||||
jsonString = Files.readString(quiltModJson.toPath(), StandardCharsets.UTF_8);
|
||||
return QuiltModJson.of(quiltModJson).getInjectedInterfaces(quiltModJson.getAbsolutePath());
|
||||
} catch (IOException e3) {
|
||||
throw new UncheckedIOException("Failed to read quilt.mod.json", e3);
|
||||
}
|
||||
|
||||
JsonObject jsonObject = new Gson().fromJson(jsonString, JsonObject.class);
|
||||
|
||||
if (jsonObject.has("quilt_loom")) {
|
||||
// quilt injected interfaces has the same format as architectury.common.json
|
||||
return InjectedInterface.fromJsonArch(jsonObject.getAsJsonObject("quilt_loom"), quiltModJson.getAbsolutePath());
|
||||
}
|
||||
} catch (IllegalStateException e3) {
|
||||
// File not found
|
||||
}
|
||||
@@ -310,7 +297,7 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
return comment;
|
||||
}
|
||||
|
||||
private record InjectedInterface(String modId, String className, String ifaceName) {
|
||||
public record InjectedInterface(String modId, String className, String ifaceName) {
|
||||
/**
|
||||
* Reads the injected interfaces contained in a mod jar, or returns empty if there is none.
|
||||
*/
|
||||
@@ -327,8 +314,7 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
}
|
||||
|
||||
if (commonJsonBytes != null) {
|
||||
JsonObject commonJsonObject = new Gson().fromJson(new String(commonJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
return fromJsonArch(commonJsonObject, modJarPath.toString());
|
||||
return ArchitecturyCommonJson.of(commonJsonBytes).getInjectedInterfaces(modJarPath.toString());
|
||||
} else {
|
||||
try {
|
||||
commonJsonBytes = ZipUtils.unpackNullable(modJarPath, "quilt.mod.json");
|
||||
@@ -337,12 +323,7 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
}
|
||||
|
||||
if (commonJsonBytes != null) {
|
||||
JsonObject commonJsonObject = new Gson().fromJson(new String(commonJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
|
||||
if (commonJsonObject.has("quilt_loom")) {
|
||||
// quilt injected interfaces has the same format as architectury.common.json
|
||||
return fromJsonArch(commonJsonObject.getAsJsonObject("quilt_loom"), modJarPath.toString());
|
||||
}
|
||||
return QuiltModJson.of(commonJsonBytes).getInjectedInterfaces(modJarPath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,26 +364,6 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<InjectedInterface> fromJsonArch(JsonObject jsonObject, String modId) {
|
||||
if (jsonObject.has("injected_interfaces")) {
|
||||
JsonObject addedIfaces = jsonObject.getAsJsonObject("injected_interfaces");
|
||||
|
||||
final List<InjectedInterface> result = new ArrayList<>();
|
||||
|
||||
for (String className : addedIfaces.keySet()) {
|
||||
final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className);
|
||||
|
||||
for (JsonElement ifaceName : ifaceNames) {
|
||||
result.add(new InjectedInterface(modId, className, ifaceName.getAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static class InjectingClassVisitor extends ClassVisitor {
|
||||
|
||||
@@ -24,46 +24,30 @@
|
||||
|
||||
package net.fabricmc.loom.task;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.architectury.loom.extensions.ModBuildExtensions;
|
||||
import dev.architectury.loom.metadata.QuiltModJson;
|
||||
import dev.architectury.tinyremapper.OutputConsumerPath;
|
||||
import dev.architectury.tinyremapper.TinyRemapper;
|
||||
import org.cadixdev.at.AccessTransformSet;
|
||||
import org.cadixdev.at.io.AccessTransformFormats;
|
||||
import org.cadixdev.lorenz.MappingSet;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
@@ -79,7 +63,6 @@ import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.TaskDependency;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -87,7 +70,6 @@ import net.fabricmc.accesswidener.AccessWidenerReader;
|
||||
import net.fabricmc.accesswidener.AccessWidenerRemapper;
|
||||
import net.fabricmc.accesswidener.AccessWidenerWriter;
|
||||
import net.fabricmc.loom.LoomGradleExtension;
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.build.MixinRefmapHelper;
|
||||
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
|
||||
import net.fabricmc.loom.build.nesting.IncludedJarFactory.LazyNestedFile;
|
||||
@@ -100,16 +82,12 @@ import net.fabricmc.loom.task.service.MappingsService;
|
||||
import net.fabricmc.loom.task.service.TinyRemapperService;
|
||||
import net.fabricmc.loom.util.Constants;
|
||||
import net.fabricmc.loom.util.ExceptionUtil;
|
||||
import net.fabricmc.loom.util.FileSystemUtil;
|
||||
import net.fabricmc.loom.util.LfWriter;
|
||||
import net.fabricmc.loom.util.ModPlatform;
|
||||
import net.fabricmc.loom.util.ModUtils;
|
||||
import net.fabricmc.loom.util.Pair;
|
||||
import net.fabricmc.loom.util.SidedClassVisitor;
|
||||
import net.fabricmc.loom.util.ZipUtils;
|
||||
import net.fabricmc.loom.util.aw2at.Aw2At;
|
||||
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
|
||||
import net.fabricmc.lorenztiny.TinyMappingsReader;
|
||||
|
||||
public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
@InputFiles
|
||||
@@ -250,22 +228,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
byte[] bytes = ZipUtils.unpackNullable(getInputFile().getAsFile().get().toPath(), "quilt.mod.json");
|
||||
|
||||
if (bytes != null) {
|
||||
JsonObject json = LoomGradlePlugin.GSON.fromJson(new InputStreamReader(new ByteArrayInputStream(bytes)), JsonObject.class);
|
||||
JsonElement mixins = json.has("mixin") ? json.get("mixin") : json.get("mixins");
|
||||
|
||||
if (mixins != null) {
|
||||
if (mixins.isJsonPrimitive()) {
|
||||
allMixinConfigs = Collections.singletonList(mixins.getAsString());
|
||||
} else if (mixins.isJsonArray()) {
|
||||
allMixinConfigs = StreamSupport.stream(mixins.getAsJsonArray().spliterator(), false)
|
||||
.map(JsonElement::getAsString)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
throw new RuntimeException("Unknown mixin type: " + mixins.getClass().getName());
|
||||
}
|
||||
} else {
|
||||
allMixinConfigs = Collections.emptyList();
|
||||
}
|
||||
allMixinConfigs = QuiltModJson.of(bytes).getMixinConfigs();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read file quilt.mod.json in the jar.", e);
|
||||
@@ -273,7 +236,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
}
|
||||
|
||||
if (allMixinConfigs == null && getReadMixinConfigsFromManifest().get()) {
|
||||
allMixinConfigs = readMixinConfigsFromManifest();
|
||||
allMixinConfigs = ModBuildExtensions.readMixinConfigsFromManifest(getInputFile().get().getAsFile());
|
||||
}
|
||||
|
||||
if (allMixinConfigs == null) {
|
||||
@@ -309,27 +272,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<String> readMixinConfigsFromManifest() {
|
||||
File inputJar = getInputFile().get().getAsFile();
|
||||
|
||||
try (JarFile jar = new JarFile(inputJar)) {
|
||||
@Nullable Manifest manifest = jar.getManifest();
|
||||
|
||||
if (manifest != null) {
|
||||
Attributes attributes = manifest.getMainAttributes();
|
||||
String mixinConfigs = attributes.getValue(Constants.Forge.MIXIN_CONFIGS_MANIFEST_KEY);
|
||||
|
||||
if (mixinConfigs != null) {
|
||||
return Set.of(mixinConfigs.split(","));
|
||||
}
|
||||
}
|
||||
|
||||
return Set.of();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read mixin configs from input jar", e);
|
||||
}
|
||||
}
|
||||
|
||||
public interface RemapParams extends AbstractRemapParams {
|
||||
ConfigurableFileCollection getNestedJars();
|
||||
|
||||
@@ -381,7 +323,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
|
||||
addRefmaps();
|
||||
addNestedJars();
|
||||
convertAwToAt();
|
||||
ModBuildExtensions.convertAwToAt(getParameters().getAtAccessWideners(), outputFile, getParameters().getMappingBuildServiceUuid());
|
||||
|
||||
if (getParameters().getPlatform().get() != ModPlatform.FORGE) {
|
||||
modifyJarManifest();
|
||||
@@ -455,55 +397,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
|
||||
ZipUtils.replace(outputFile, accessWidenerFile.path(), remapped);
|
||||
}
|
||||
|
||||
private void convertAwToAt() throws IOException {
|
||||
if (!this.getParameters().getAtAccessWideners().isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> atAccessWideners = this.getParameters().getAtAccessWideners().get();
|
||||
|
||||
if (atAccessWideners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AccessTransformSet at = AccessTransformSet.create();
|
||||
File jar = outputFile.toFile();
|
||||
|
||||
try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(jar, false)) {
|
||||
FileSystem fs = fileSystem.get();
|
||||
Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
|
||||
|
||||
if (Files.exists(atPath)) {
|
||||
throw new FileAlreadyExistsException("Jar " + jar + " already contains an access transformer - cannot convert AWs!");
|
||||
}
|
||||
|
||||
for (String aw : atAccessWideners) {
|
||||
Path awPath = fs.getPath(aw);
|
||||
|
||||
if (Files.notExists(awPath)) {
|
||||
throw new NoSuchFileException("Could not find AW '" + aw + "' to convert into AT!");
|
||||
}
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(awPath, StandardCharsets.UTF_8)) {
|
||||
at.merge(Aw2At.toAccessTransformSet(reader));
|
||||
}
|
||||
|
||||
Files.delete(awPath);
|
||||
}
|
||||
|
||||
MappingsService service = UnsafeWorkQueueHelper.get(getParameters().getMappingBuildServiceUuid(), MappingsService.class);
|
||||
|
||||
try (TinyMappingsReader reader = new TinyMappingsReader(service.getMemoryMappingTree(), service.getFromNamespace(), service.getToNamespace())) {
|
||||
MappingSet mappingSet = reader.read();
|
||||
at = at.remap(mappingSet);
|
||||
}
|
||||
|
||||
try (Writer writer = new LfWriter(Files.newBufferedWriter(atPath))) {
|
||||
AccessTransformFormats.FML.write(writer, at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] remapAccessWidener(byte[] input) {
|
||||
int version = AccessWidenerReader.readVersion(input);
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2023 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.test.unit.architectury
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import dev.architectury.loom.metadata.ArchitecturyCommonJson
|
||||
import spock.lang.Specification
|
||||
import spock.lang.TempDir
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
|
||||
class ArchitecturyCommonJsonTest extends Specification {
|
||||
private static final String OF_TEST_INPUT = '{"accessWidener":"foo.accesswidener"}'
|
||||
|
||||
@TempDir
|
||||
Path tempDir
|
||||
|
||||
def "create from byte[]"() {
|
||||
given:
|
||||
def bytes = OF_TEST_INPUT.getBytes(StandardCharsets.UTF_8)
|
||||
when:
|
||||
def acj = ArchitecturyCommonJson.of(bytes)
|
||||
then:
|
||||
acj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from String"() {
|
||||
when:
|
||||
def acj = ArchitecturyCommonJson.of(OF_TEST_INPUT)
|
||||
then:
|
||||
acj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from File"() {
|
||||
given:
|
||||
def file = new File(tempDir.toFile(), 'architectury.common.json')
|
||||
file.text = OF_TEST_INPUT
|
||||
when:
|
||||
def acj = ArchitecturyCommonJson.of(file)
|
||||
then:
|
||||
acj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from Path"() {
|
||||
given:
|
||||
def path = tempDir.resolve('architectury.common.json')
|
||||
path.text = OF_TEST_INPUT
|
||||
when:
|
||||
def acj = ArchitecturyCommonJson.of(path)
|
||||
then:
|
||||
acj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from JsonObject"() {
|
||||
given:
|
||||
def json = new JsonObject()
|
||||
json.addProperty('accessWidener', 'foo.accesswidener')
|
||||
when:
|
||||
def acj = ArchitecturyCommonJson.of(json)
|
||||
then:
|
||||
acj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "read access widener"() {
|
||||
given:
|
||||
def acj = ArchitecturyCommonJson.of(jsonText)
|
||||
when:
|
||||
def accessWidenerName = acj.accessWidener
|
||||
then:
|
||||
accessWidenerName == expectedAw
|
||||
where:
|
||||
jsonText | expectedAw
|
||||
'{}' | null
|
||||
'{"accessWidener":"foo.accesswidener"}' | 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "read injected interfaces"() {
|
||||
given:
|
||||
def acj = ArchitecturyCommonJson.of(jsonText)
|
||||
when:
|
||||
def injectedInterfaces = acj.getInjectedInterfaces('foo')
|
||||
Map<String, List<String>> itfMap = [:]
|
||||
for (def entry : injectedInterfaces) {
|
||||
itfMap.computeIfAbsent(entry.className()) { [] }.add(entry.ifaceName())
|
||||
}
|
||||
then:
|
||||
itfMap == expected
|
||||
where:
|
||||
jsonText | expected
|
||||
'{}' | [:]
|
||||
'{"injected_interfaces":{"target/class/Here":["my/Interface","another/Itf"]}}' | ['target/class/Here': ['my/Interface', 'another/Itf']]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2023 FabricMC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.fabricmc.loom.test.unit.quilt
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import dev.architectury.loom.metadata.QuiltModJson
|
||||
import spock.lang.Specification
|
||||
import spock.lang.TempDir
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
|
||||
class QuiltModJsonTest extends Specification {
|
||||
private static final String OF_TEST_INPUT = '{"access_widener":"foo.accesswidener"}'
|
||||
|
||||
@TempDir
|
||||
Path tempDir
|
||||
|
||||
def "create from byte[]"() {
|
||||
given:
|
||||
def bytes = OF_TEST_INPUT.getBytes(StandardCharsets.UTF_8)
|
||||
when:
|
||||
def qmj = QuiltModJson.of(bytes)
|
||||
then:
|
||||
qmj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from String"() {
|
||||
when:
|
||||
def qmj = QuiltModJson.of(OF_TEST_INPUT)
|
||||
then:
|
||||
qmj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from File"() {
|
||||
given:
|
||||
def file = new File(tempDir.toFile(), 'quilt.mod.json')
|
||||
file.text = OF_TEST_INPUT
|
||||
when:
|
||||
def qmj = QuiltModJson.of(file)
|
||||
then:
|
||||
qmj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from Path"() {
|
||||
given:
|
||||
def path = tempDir.resolve('quilt.mod.json')
|
||||
path.text = OF_TEST_INPUT
|
||||
when:
|
||||
def qmj = QuiltModJson.of(path)
|
||||
then:
|
||||
qmj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "create from JsonObject"() {
|
||||
given:
|
||||
def json = new JsonObject()
|
||||
json.addProperty('access_widener', 'foo.accesswidener')
|
||||
when:
|
||||
def qmj = QuiltModJson.of(json)
|
||||
then:
|
||||
qmj.accessWidener == 'foo.accesswidener'
|
||||
}
|
||||
|
||||
def "read access widener"() {
|
||||
given:
|
||||
def qmj = QuiltModJson.of(jsonText)
|
||||
when:
|
||||
def accessWidenerName = qmj.accessWidener
|
||||
then:
|
||||
accessWidenerName == expectedAw
|
||||
where:
|
||||
jsonText | expectedAw
|
||||
'{}' | null
|
||||
'{"access_widener":"foo.accesswidener"}' | 'foo.accesswidener'
|
||||
'{"access_widener":["bar.accesswidener"]}' | 'bar.accesswidener'
|
||||
}
|
||||
|
||||
def "read injected interfaces"() {
|
||||
given:
|
||||
def qmj = QuiltModJson.of(jsonText)
|
||||
when:
|
||||
def injectedInterfaces = qmj.getInjectedInterfaces('foo')
|
||||
Map<String, List<String>> itfMap = [:]
|
||||
for (def entry : injectedInterfaces) {
|
||||
itfMap.computeIfAbsent(entry.className()) { [] }.add(entry.ifaceName())
|
||||
}
|
||||
then:
|
||||
itfMap == expected
|
||||
where:
|
||||
jsonText | expected
|
||||
'{}' | [:]
|
||||
'{"quilt_loom":{"injected_interfaces":{"target/class/Here":["my/Interface","another/Itf"]}}}' | ['target/class/Here': ['my/Interface', 'another/Itf']]
|
||||
}
|
||||
|
||||
def "read mixin configs"() {
|
||||
given:
|
||||
def qmj = QuiltModJson.of(jsonText)
|
||||
when:
|
||||
def mixinConfigs = qmj.mixinConfigs
|
||||
then:
|
||||
mixinConfigs == expected
|
||||
where:
|
||||
jsonText | expected
|
||||
'{}' | []
|
||||
'{"mixin":"foo.mixins.json"}' | ['foo.mixins.json']
|
||||
'{"mixin":["foo.mixins.json","bar.mixins.json"]}' | ['foo.mixins.json', 'bar.mixins.json']
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user