mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
Add actual mods.toml metadata parsing
This commit is contained in:
@@ -124,6 +124,9 @@ dependencies {
|
||||
implementation ('com.opencsv:opencsv:5.4')
|
||||
implementation ('net.minecraftforge:DiffPatch:2.0.7')
|
||||
|
||||
// Forge mods.toml parsing
|
||||
implementation ('com.electronwill.night-config:toml:3.6.6')
|
||||
|
||||
// Testing
|
||||
testImplementation(gradleTestKit())
|
||||
testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') {
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
|
||||
public final class ArchitecturyCommonJson implements JsonBackedModMetadataFile {
|
||||
public final class ArchitecturyCommonJson implements JsonBackedModMetadataFile, SingleIdModMetadataFile {
|
||||
public static final String FILE_NAME = "architectury.common.json";
|
||||
private static final String ACCESS_WIDENER_KEY = "accessWidener";
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.Set;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
|
||||
/**
|
||||
* The metadata file of a mod, such as {@link ArchitecturyCommonJson architectury.common.json} or
|
||||
@@ -14,11 +15,17 @@ import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
* @see net.fabricmc.loom.util.fmj.FabricModJson
|
||||
*/
|
||||
public interface ModMetadataFile {
|
||||
/**
|
||||
* {@return all mod IDs in this mod metadata file}.
|
||||
*/
|
||||
Set<String> getIds();
|
||||
|
||||
/**
|
||||
* {@return the mod ID in this mod metadata file, or {@code null} if absent}.
|
||||
*/
|
||||
// TODO: When we have mods.toml here, shouldn't it support multiple IDs + maybe a "first ID"?
|
||||
@Nullable String getId();
|
||||
default @Nullable String getId() {
|
||||
return CollectionUtil.first(getIds()).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the paths to the access widener file of this mod, or an empty set if absent}.
|
||||
|
||||
@@ -21,7 +21,7 @@ public final class ModMetadataFiles {
|
||||
private static final Map<String, Function<byte[], ModMetadataFile>> SINGLE_FILE_METADATA_TYPES = ImmutableMap.<String, Function<byte[], ModMetadataFile>>builder()
|
||||
.put(ArchitecturyCommonJson.FILE_NAME, ArchitecturyCommonJson::of)
|
||||
.put(QuiltModJson.FILE_NAME, QuiltModJson::of)
|
||||
.put(ModsToml.FILE_PATH, bytes -> ModsToml.INSTANCE)
|
||||
.put(ModsToml.FILE_PATH, ModsToml::of)
|
||||
.build();
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,23 +1,69 @@
|
||||
package dev.architectury.loom.metadata;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
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.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import com.electronwill.nightconfig.core.io.ParsingException;
|
||||
import com.electronwill.nightconfig.toml.TomlParser;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
|
||||
// A no-op mod metadata file for Forge's mods.toml.
|
||||
public final class ModsToml implements ModMetadataFile {
|
||||
public static final String FILE_PATH = "META-INF/mods.toml";
|
||||
public static final ModsToml INSTANCE = new ModsToml();
|
||||
private final Config config;
|
||||
|
||||
private ModsToml() {
|
||||
private ModsToml(Config config) {
|
||||
this.config = Objects.requireNonNull(config);
|
||||
}
|
||||
|
||||
public static ModsToml of(byte[] utf8) {
|
||||
return of(new String(utf8, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static ModsToml of(String text) {
|
||||
try {
|
||||
return new ModsToml(new TomlParser().parse(text));
|
||||
} catch (ParsingException e) {
|
||||
throw new IllegalArgumentException("Could not parse mods.toml", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ModsToml of(Path path) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
return new ModsToml(new TomlParser().parse(reader));
|
||||
} catch (ParsingException e) {
|
||||
throw new IllegalArgumentException("Could not parse mods.toml", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ModsToml of(File file) throws IOException {
|
||||
return of(file.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getId() {
|
||||
return null;
|
||||
public Set<String> getIds() {
|
||||
final Optional<List<Config>> mods = config.getOptional("mods");
|
||||
if (mods.isEmpty()) return Set.of();
|
||||
|
||||
final ImmutableSet.Builder<String> modIds = ImmutableSet.builder();
|
||||
|
||||
for (final Config mod : mods.get()) {
|
||||
final Optional<String> modId = mod.getOptional("modId");
|
||||
modId.ifPresent(modIds::add);
|
||||
}
|
||||
|
||||
return modIds.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,4 +85,14 @@ public final class ModsToml implements ModMetadataFile {
|
||||
public List<String> getMixinConfigs() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this || obj instanceof ModsToml modsToml && modsToml.config.equals(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return config.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
|
||||
import net.fabricmc.loom.util.function.CollectionUtil;
|
||||
|
||||
public final class QuiltModJson implements JsonBackedModMetadataFile {
|
||||
public final class QuiltModJson implements JsonBackedModMetadataFile, SingleIdModMetadataFile {
|
||||
public static final String FILE_NAME = "quilt.mod.json";
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(QuiltModJson.class);
|
||||
private static final String ACCESS_WIDENER_KEY = "access_widener";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package dev.architectury.loom.metadata;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
interface SingleIdModMetadataFile extends ModMetadataFile {
|
||||
@Override
|
||||
default Set<String> getIds() {
|
||||
final String id = getId();
|
||||
return id != null ? Set.of(id) : Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable String getId();
|
||||
}
|
||||
@@ -144,4 +144,22 @@ public final class CollectionUtil {
|
||||
|
||||
return Optional.of(single);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first element of an iterable.
|
||||
*
|
||||
* @param iterable the iterable
|
||||
* @param <A> the element type
|
||||
* @return the first element, or empty if there are no elements
|
||||
*/
|
||||
public static <A> Optional<A> first(Iterable<? extends A> iterable) {
|
||||
final Iterator<? extends A> iter = iterable.iterator();
|
||||
|
||||
// No elements
|
||||
if (!iter.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(iter.next());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.forge
|
||||
|
||||
import dev.architectury.loom.metadata.ModsToml
|
||||
import spock.lang.Specification
|
||||
import spock.lang.TempDir
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Path
|
||||
|
||||
class ModsTomlTest extends Specification {
|
||||
private static final String OF_TEST_INPUT =
|
||||
'''
|
||||
|[[mods]]
|
||||
|modId="hello"
|
||||
|[[mods]]
|
||||
|modId="world"
|
||||
'''.stripMargin()
|
||||
|
||||
@TempDir
|
||||
Path tempDir
|
||||
|
||||
def "create from byte[]"() {
|
||||
given:
|
||||
def bytes = OF_TEST_INPUT.getBytes(StandardCharsets.UTF_8)
|
||||
when:
|
||||
def modsToml = ModsToml.of(bytes)
|
||||
then:
|
||||
modsToml.ids == ['hello', 'world'] as Set
|
||||
}
|
||||
|
||||
def "create from String"() {
|
||||
when:
|
||||
def modsToml = ModsToml.of(OF_TEST_INPUT)
|
||||
then:
|
||||
modsToml.ids == ['hello', 'world'] as Set
|
||||
}
|
||||
|
||||
def "create from File"() {
|
||||
given:
|
||||
def file = new File(tempDir.toFile(), 'mods.toml')
|
||||
file.text = OF_TEST_INPUT
|
||||
when:
|
||||
def modsToml = ModsToml.of(file)
|
||||
then:
|
||||
modsToml.ids == ['hello', 'world'] as Set
|
||||
}
|
||||
|
||||
def "create from Path"() {
|
||||
given:
|
||||
def path = tempDir.resolve('mods.toml')
|
||||
path.text = OF_TEST_INPUT
|
||||
when:
|
||||
def modsToml = ModsToml.of(path)
|
||||
then:
|
||||
modsToml.ids == ['hello', 'world'] as Set
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user