From 674572f1df41ebab29f6bc780b32eff10eb295d7 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Sun, 19 Feb 2023 02:36:11 +0200 Subject: [PATCH] Add actual mods.toml metadata parsing --- build.gradle | 3 + .../loom/metadata/ArchitecturyCommonJson.java | 2 +- .../loom/metadata/ModMetadataFile.java | 11 ++- .../loom/metadata/ModMetadataFiles.java | 2 +- .../architectury/loom/metadata/ModsToml.java | 66 +++++++++++++-- .../loom/metadata/QuiltModJson.java | 2 +- .../metadata/SingleIdModMetadataFile.java | 16 ++++ .../loom/util/function/CollectionUtil.java | 18 +++++ .../loom/test/unit/forge/ModsTomlTest.groovy | 81 +++++++++++++++++++ 9 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 src/main/java/dev/architectury/loom/metadata/SingleIdModMetadataFile.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/forge/ModsTomlTest.groovy diff --git a/build.gradle b/build.gradle index f098843d..0ce4a010 100644 --- a/build.gradle +++ b/build.gradle @@ -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') { diff --git a/src/main/java/dev/architectury/loom/metadata/ArchitecturyCommonJson.java b/src/main/java/dev/architectury/loom/metadata/ArchitecturyCommonJson.java index df489bc1..c0e44898 100644 --- a/src/main/java/dev/architectury/loom/metadata/ArchitecturyCommonJson.java +++ b/src/main/java/dev/architectury/loom/metadata/ArchitecturyCommonJson.java @@ -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"; diff --git a/src/main/java/dev/architectury/loom/metadata/ModMetadataFile.java b/src/main/java/dev/architectury/loom/metadata/ModMetadataFile.java index 5b95e10c..ded523d4 100644 --- a/src/main/java/dev/architectury/loom/metadata/ModMetadataFile.java +++ b/src/main/java/dev/architectury/loom/metadata/ModMetadataFile.java @@ -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 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}. diff --git a/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java b/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java index 814925a5..8f175a8d 100644 --- a/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java +++ b/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java @@ -21,7 +21,7 @@ public final class ModMetadataFiles { private static final Map> SINGLE_FILE_METADATA_TYPES = ImmutableMap.>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(); /** diff --git a/src/main/java/dev/architectury/loom/metadata/ModsToml.java b/src/main/java/dev/architectury/loom/metadata/ModsToml.java index e08e592e..cb94e485 100644 --- a/src/main/java/dev/architectury/loom/metadata/ModsToml.java +++ b/src/main/java/dev/architectury/loom/metadata/ModsToml.java @@ -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 getIds() { + final Optional> mods = config.getOptional("mods"); + if (mods.isEmpty()) return Set.of(); + + final ImmutableSet.Builder modIds = ImmutableSet.builder(); + + for (final Config mod : mods.get()) { + final Optional modId = mod.getOptional("modId"); + modId.ifPresent(modIds::add); + } + + return modIds.build(); } @Override @@ -39,4 +85,14 @@ public final class ModsToml implements ModMetadataFile { public List 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(); + } } diff --git a/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java b/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java index 7f7d096e..05044f99 100644 --- a/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java +++ b/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java @@ -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"; diff --git a/src/main/java/dev/architectury/loom/metadata/SingleIdModMetadataFile.java b/src/main/java/dev/architectury/loom/metadata/SingleIdModMetadataFile.java new file mode 100644 index 00000000..32f3be1c --- /dev/null +++ b/src/main/java/dev/architectury/loom/metadata/SingleIdModMetadataFile.java @@ -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 getIds() { + final String id = getId(); + return id != null ? Set.of(id) : Set.of(); + } + + @Override + @Nullable String getId(); +} diff --git a/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java index 0e1c1661..5bd2513f 100644 --- a/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java +++ b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java @@ -144,4 +144,22 @@ public final class CollectionUtil { return Optional.of(single); } + + /** + * Gets the first element of an iterable. + * + * @param iterable the iterable + * @param the element type + * @return the first element, or empty if there are no elements + */ + public static Optional first(Iterable iterable) { + final Iterator iter = iterable.iterator(); + + // No elements + if (!iter.hasNext()) { + return Optional.empty(); + } + + return Optional.of(iter.next()); + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/forge/ModsTomlTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/forge/ModsTomlTest.groovy new file mode 100644 index 00000000..c5c7faf2 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/forge/ModsTomlTest.groovy @@ -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 + } +}