Add support for quilt.mod.json5 (#150)

See:
- RFC: QuiltMC/rfcs#83
- Build tool impl: QuiltMC/quilt-loom#36, QuiltMC/quilt-loom#37
- Required loader PR: QuiltMC/quilt-loader#329
This commit is contained in:
Juuz
2025-08-18 19:55:04 +03:00
committed by GitHub
parent b27bd29bb8
commit df56ef4e8e
6 changed files with 68 additions and 3 deletions

View File

@@ -167,6 +167,9 @@ dependencies {
// Forge mods.toml parsing
implementation libs.night.config.toml
// quilt.mod.json5 parsing
implementation libs.jankson
// Testing
testImplementation(gradleTestKit())
testImplementation(testLibs.spock) {

View File

@@ -20,7 +20,7 @@ test-retry = "1.5.6"
checkstyle = "10.17.0"
codenarc = "3.4.0"
# Forge libraries
# Architectury libraries
forge-installer-tools = "1.2.0"
lorenz = "0.5.3"
mcinjector = "3.8.0"
@@ -29,6 +29,7 @@ forge-diffpatch = "2.0.7"
night-config = "3.6.6"
datafixerupper = "6.0.8"
at = "1.0.1"
jankson = "1.2.3"
[libraries]
# Loom compile libraries
@@ -56,7 +57,7 @@ fabric-unpick-utils = { module = "net.fabricmc.unpick:unpick-format-utils", vers
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-metadata = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" }
# Forge support
# Architectury
forge-installer-tools = { module = "net.minecraftforge:installertools", version.ref = "forge-installer-tools" }
lorenz = { module = "org.cadixdev:lorenz", version.ref = "lorenz" }
mcinjector = { module = "de.oceanlabs.mcp:mcinjector", version.ref = "mcinjector" }
@@ -65,6 +66,7 @@ forge-diffpatch = { module = "net.minecraftforge:DiffPatch", version.ref = "forg
night-config-toml = { module = "com.electronwill.night-config:toml", version.ref = "night-config" }
datafixerupper = { module = "com.mojang:datafixerupper", version.ref = "datafixerupper" }
at = { module = "dev.architectury:at", version.ref = "at" }
jankson = { module = "blue.endless:jankson", version.ref = "jankson" }
[plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View File

@@ -2,12 +2,17 @@ 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.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import blue.endless.jankson.Jankson;
import blue.endless.jankson.JsonElement;
import blue.endless.jankson.JsonGrammar;
import blue.endless.jankson.api.SyntaxError;
import com.google.common.collect.ImmutableMap;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
@@ -15,6 +20,7 @@ import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -25,6 +31,7 @@ public final class ModMetadataFiles {
private static final Logger LOGGER = Logging.getLogger(ModMetadataFiles.class);
private static final Map<String, Function<byte[], ModMetadataFile>> SINGLE_FILE_METADATA_TYPES = ImmutableMap.<String, Function<byte[], ModMetadataFile>>builder()
.put(QuiltModJson.FILE_NAME, QuiltModJson::of)
.put(QuiltModJson.JSON5_FILE_NAME, convertJson5ToJson(QuiltModJson::of))
.put(ArchitecturyCommonJson.FILE_NAME, ArchitecturyCommonJson::of)
.put(ModsToml.FILE_PATH, onError(ModsToml::of, "Could not load mods.toml", () -> new ErroringModMetadataFile("mods.toml")))
.put(ModsToml.NEOFORGE_FILE_PATH, onError(ModsToml::of, "Could not load neoforge.mods.toml", () -> new ErroringModMetadataFile("neoforge.mods.toml")))
@@ -41,6 +48,20 @@ public final class ModMetadataFiles {
};
}
private static <R> Function<byte[], R> convertJson5ToJson(Function<byte[], R> next) {
return bytes -> {
var text = new String(bytes, StandardCharsets.UTF_8);
Jankson jankson = Jankson.builder().build();
String json;
try {
json = jankson.fromJson(text, JsonElement.class).toJson(JsonGrammar.STRICT);
} catch (SyntaxError e) {
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Could not read JSON5 file", e);
}
return next.apply(json.getBytes(StandardCharsets.UTF_8));
};
}
/**
* Reads the mod metadata file from a jar.
*

View File

@@ -25,6 +25,8 @@ import net.fabricmc.loom.util.function.CollectionUtil;
public final class QuiltModJson implements JsonBackedModMetadataFile, SingleIdModMetadataFile {
public static final String FILE_NAME = "quilt.mod.json";
// See RFC 0083
public static final String JSON5_FILE_NAME = "quilt.mod.json5";
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";

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.task;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -34,8 +35,13 @@ import java.util.stream.Stream;
import javax.inject.Inject;
import blue.endless.jankson.Jankson;
import blue.endless.jankson.JsonElement;
import blue.endless.jankson.JsonGrammar;
import blue.endless.jankson.api.SyntaxError;
import com.google.gson.JsonObject;
import dev.architectury.loom.extensions.ModBuildExtensions;
import dev.architectury.loom.metadata.QuiltModJson;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
@@ -72,6 +78,7 @@ import net.fabricmc.loom.task.service.MixinRefmapService;
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.ModPlatform;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.SidedClassVisitor;
@@ -280,6 +287,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
ModBuildExtensions.convertAwToAt(serviceFactory, getParameters().getAtAccessWideners().get(), outputFile, mappingsServiceOptions);
}
if (getParameters().getPlatform().get() == ModPlatform.QUILT) {
convertQmj5();
}
if (!getParameters().getPlatform().get().isForgeLike()) {
modifyJarManifest();
}
@@ -425,6 +436,31 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
ZipUtils.transformJson(JsonObject.class, outputFile, FabricModJsonFactory.FABRIC_MOD_JSON, FabricModJsonUtils::optimizeFmj);
}
private void convertQmj5() throws IOException {
byte[] bytes = ZipUtils.unpackNullable(outputFile, QuiltModJson.JSON5_FILE_NAME);
if (bytes == null) return;
if (ZipUtils.contains(outputFile, QuiltModJson.FILE_NAME)) {
throw new IllegalStateException("Output file contains both quilt.mod.json and quilt.mod.json5");
}
Jankson jankson = Jankson.builder().build();
JsonElement json;
try {
json = jankson.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonElement.class);
} catch (SyntaxError e) {
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Could not read quilt.mod.json5", e);
}
String qmj = json.toJson(JsonGrammar.STRICT);
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(outputFile, false)) {
Files.delete(fs.getPath(QuiltModJson.JSON5_FILE_NAME));
Files.writeString(fs.getPath(QuiltModJson.FILE_NAME), qmj, StandardCharsets.UTF_8);
}
}
}
@Override

View File

@@ -216,7 +216,8 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
if (quilt) {
launchConfig
.argument("client", "--version")
.argument("client", "Architectury Loom");
.argument("client", "Architectury Loom")
.property("loader.enable_quilt_mod_json5_in_dev_env", "true");
}
if (platform.isForgeLike()) {