diff --git a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java b/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java index 1a833a76..288f1d0b 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java @@ -53,8 +53,8 @@ import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.task.RemapTaskConfiguration; -import net.fabricmc.loom.util.ModUtils; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; public final class IncludedJarFactory { private final Project project; @@ -143,7 +143,7 @@ public final class IncludedJarFactory { } private File getNestableJar(final File input, final Metadata metadata) { - if (ModUtils.isMod(input)) { + if (FabricModJsonFactory.isModJar(input)) { // Input is a mod, nothing needs to be done. return input; } diff --git a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java index 06bf4b57..e495d6a8 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java @@ -38,9 +38,9 @@ import com.google.gson.JsonObject; import org.gradle.api.UncheckedIOException; import org.slf4j.Logger; -import net.fabricmc.loom.util.ModUtils; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; public class JarNester { public static void nestJars(Collection jars, File modJar, Logger logger) { @@ -49,7 +49,7 @@ public class JarNester { return; } - Preconditions.checkArgument(ModUtils.isMod(modJar), "Cannot nest jars into none mod jar " + modJar.getName()); + Preconditions.checkArgument(FabricModJsonFactory.isModJar(modJar), "Cannot nest jars into none mod jar " + modJar.getName()); try { ZipUtils.add(modJar.toPath(), jars.stream().map(file -> { @@ -69,7 +69,7 @@ public class JarNester { for (File file : jars) { String nestedJarPath = "META-INF/jars/" + file.getName(); - Preconditions.checkArgument(ModUtils.isMod(file), "Cannot nest none mod jar: " + file.getName()); + Preconditions.checkArgument(FabricModJsonFactory.isModJar(file), "Cannot nest none mod jar: " + file.getName()); for (JsonElement nestedJar : nestedJars) { JsonObject jsonObject = nestedJar.getAsJsonObject(); diff --git a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java index 03321139..3ef529ab 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -28,7 +28,6 @@ import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -55,7 +54,6 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.Remapper; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; import net.fabricmc.loom.api.RemapConfigurationSettings; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -63,10 +61,11 @@ 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; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.tinyremapper.TinyRemapper; @@ -198,28 +197,15 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource } private List getSourceInjectedInterface(SourceSet sourceSet) { - final File fabricModJson; + final FabricModJson fabricModJson; try { - fabricModJson = sourceSet.getResources() - .matching(patternFilterable -> patternFilterable.include("fabric.mod.json")) - .getSingleFile(); - } catch (IllegalStateException e) { - // File not found - return Collections.emptyList(); - } - - final String jsonString; - - try { - jsonString = Files.readString(fabricModJson.toPath(), StandardCharsets.UTF_8); + fabricModJson = FabricModJsonFactory.createFromSourceSetNullable(sourceSet); } catch (IOException e) { - throw new UncheckedIOException("Failed to read fabric.mod.json", e); + throw new UncheckedIOException(e); } - final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(jsonString, JsonObject.class); - - return InjectedInterface.fromJson(jsonObject); + return InjectedInterface.fromFabricModJson(fabricModJson); } @Override @@ -271,29 +257,18 @@ public class InterfaceInjectionProcessor implements JarProcessor, GenerateSource * Reads the injected interfaces contained in a mod jar, or returns empty if there is none. */ public static List fromModJar(Path modJarPath) { - final JsonObject jsonObject = ModUtils.getFabricModJson(modJarPath); - - if (jsonObject == null) { - return Collections.emptyList(); - } - - return fromJson(jsonObject); + return fromFabricModJson(FabricModJsonFactory.createFromZip(modJarPath)); } - public static List fromJson(JsonObject jsonObject) { - final String modId = jsonObject.get("id").getAsString(); + public static List fromFabricModJson(FabricModJson fabricModJson) { + final String modId = fabricModJson.getId(); + final JsonElement jsonElement = fabricModJson.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE); - if (!jsonObject.has("custom")) { + if (jsonElement == null) { return Collections.emptyList(); } - final JsonObject custom = jsonObject.getAsJsonObject("custom"); - - if (!custom.has(Constants.CustomModJsonKeys.INJECTED_INTERFACE)) { - return Collections.emptyList(); - } - - final JsonObject addedIfaces = custom.getAsJsonObject(Constants.CustomModJsonKeys.INJECTED_INTERFACE); + final JsonObject addedIfaces = jsonElement.getAsJsonObject(); final List result = new ArrayList<>(); diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/AccessWidenerUtils.java b/src/main/java/net/fabricmc/loom/configuration/mods/AccessWidenerUtils.java index 3f463919..c2a7e917 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/AccessWidenerUtils.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/AccessWidenerUtils.java @@ -25,18 +25,18 @@ package net.fabricmc.loom.configuration.mods; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.List; -import com.google.gson.JsonObject; import org.objectweb.asm.commons.Remapper; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerWriter; -import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; +import net.fabricmc.loom.util.fmj.ModEnvironment; public class AccessWidenerUtils { /** @@ -58,16 +58,20 @@ public class AccessWidenerUtils { } public static AccessWidenerData readAccessWidenerData(Path inputJar) throws IOException { - byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json"); - JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromZip(inputJar); + final List classTweakers = fabricModJson.getClassTweakers(ModEnvironment.UNIVERSAL); - if (!jsonObject.has("accessWidener")) { + if (classTweakers.isEmpty()) { return null; } - String accessWidenerPath = jsonObject.get("accessWidener").getAsString(); - byte[] accessWidener = ZipUtils.unpack(inputJar, accessWidenerPath); - AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener); + if (classTweakers.size() != 1) { + throw new UnsupportedOperationException("TODO: support multiple class tweakers"); + } + + final String accessWidenerPath = classTweakers.get(0); + final byte[] accessWidener = fabricModJson.getSource().read(accessWidenerPath); + final AccessWidenerReader.Header header = AccessWidenerReader.readHeader(accessWidener); return new AccessWidenerData(accessWidenerPath, header, accessWidener); } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index 3f9f8bce..d29c4c66 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -55,9 +55,9 @@ import net.fabricmc.loom.configuration.mods.dependency.ModDependency; import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.ModUtils; import net.fabricmc.loom.util.OperatingSystem; import net.fabricmc.loom.util.SourceRemapper; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; @SuppressWarnings("UnstableApiUsage") public class ModConfigurationRemapper { @@ -88,7 +88,7 @@ public class ModConfigurationRemapper { final List modDependencies = new ArrayList<>(); for (ArtifactRef artifact : resolveArtifacts(project, sourceConfig)) { - if (!ModUtils.isMod(artifact.path())) { + if (!FabricModJsonFactory.isModJar(artifact.path())) { artifact.applyToConfiguration(project, targetConfig); continue; } diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java b/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java index b06447fe..e7fc7ece 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java @@ -35,7 +35,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import com.google.gson.JsonObject; +import com.google.gson.JsonElement; import org.gradle.api.Project; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -46,8 +46,9 @@ import net.fabricmc.loom.api.RemapConfigurationSettings; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; 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.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; @@ -70,7 +71,7 @@ public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesT final Set artifacts = entry.getSourceConfiguration().get().resolve(); for (File artifact : artifacts) { - if (!ModUtils.isMod(artifact.toPath())) { + if (!FabricModJsonFactory.isModJar(artifact.toPath())) { continue; } @@ -121,20 +122,15 @@ public final class ModJavadocProcessor implements JarProcessor, GenerateSourcesT public record ModJavadoc(String modId, MemoryMappingTree mappingTree) { @Nullable public static ModJavadoc fromModJar(Path path) throws IOException { - JsonObject jsonObject = ModUtils.getFabricModJson(path); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromZip(path); + final String modId = fabricModJson.getId(); + final JsonElement customElement = fabricModJson.getCustom(Constants.CustomModJsonKeys.PROVIDED_JAVADOC); - if (jsonObject == null || !jsonObject.has("custom")) { + if (customElement == null) { 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 String javaDocPath = customElement.getAsString(); final byte[] data = ZipUtils.unpack(path, javaDocPath); final MemoryMappingTree mappings = new MemoryMappingTree(); diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 5fc7a01f..8938f832 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -58,7 +58,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.build.MixinRefmapHelper; import net.fabricmc.loom.build.nesting.IncludedJarFactory; import net.fabricmc.loom.build.nesting.JarNester; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; @@ -67,10 +66,11 @@ import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.task.service.TinyRemapperService; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ExceptionUtil; -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.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; @@ -138,14 +138,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); final MixinExtension mixinExtension = extension.getMixin(); - final JsonObject fabricModJson = ModUtils.getFabricModJson(getInputFile().getAsFile().get().toPath()); - - if (fabricModJson == null) { - getProject().getLogger().warn("Could not find fabric.mod.json file in: " + getInputFile().getAsFile().get().getName()); - return; - } - - final Collection allMixinConfigs = MixinRefmapHelper.getMixinConfigurationFiles(fabricModJson); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromZip(getInputFile().getAsFile().get().toPath()); + final Collection allMixinConfigs = fabricModJson.getMixinConfigurations(); for (SourceSet sourceSet : mixinExtension.getMixinSourceSets()) { MixinExtension.MixinInformationContainer container = Objects.requireNonNull( diff --git a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java similarity index 53% rename from src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java rename to src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java index 69a24625..50635a52 100644 --- a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2022 FabricMC + * 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 @@ -22,40 +22,40 @@ * SOFTWARE. */ -package net.fabricmc.loom.build; +package net.fabricmc.loom.util.fmj; -import java.util.Collection; -import java.util.Collections; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString; -import com.google.gson.JsonArray; +import java.util.List; +import java.util.Objects; + +import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public final class MixinRefmapHelper { - private MixinRefmapHelper() { } +public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2 { + protected final JsonObject jsonObject; + private final FabricModJsonSource source; - private static final String FABRIC_MOD_JSON = "fabric.mod.json"; + protected FabricModJson(JsonObject jsonObject, FabricModJsonSource source) { + this.jsonObject = Objects.requireNonNull(jsonObject); + this.source = Objects.requireNonNull(source); + } - @NotNull - public static Collection getMixinConfigurationFiles(JsonObject fabricModJson) { - JsonArray mixins = fabricModJson.getAsJsonArray("mixins"); + public abstract int getVersion(); - if (mixins == null) { - return Collections.emptyList(); - } + public String getId() { + return readString(jsonObject, "id"); + } - return StreamSupport.stream(mixins.spliterator(), false) - .map(e -> { - if (e instanceof JsonPrimitive str) { - return str.getAsString(); - } else if (e instanceof JsonObject obj) { - return obj.get("config").getAsString(); - } else { - throw new RuntimeException("Incorrect fabric.mod.json format"); - } - }).collect(Collectors.toSet()); + @Nullable + public abstract JsonElement getCustom(String key); + + public abstract List getMixinConfigurations(); + + public abstract List getClassTweakers(ModEnvironment modEnvironment); + + public final FabricModJsonSource getSource() { + return source; } } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java new file mode 100644 index 00000000..39053754 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java @@ -0,0 +1,116 @@ +/* + * 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.util.fmj; + +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readInt; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.gson.JsonObject; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.gradle.SourceSetHelper; + +public final class FabricModJsonFactory { + private static final String FABRIC_MOD_JSON = "fabric.mod.json"; + + private FabricModJsonFactory() { + } + + @VisibleForTesting + public static FabricModJson create(JsonObject jsonObject, FabricModJsonSource source) { + int schemaVersion = 0; + + if (jsonObject.has("schemaVersion")) { + // V0 had no schemaVersion key. + schemaVersion = readInt(jsonObject, "schemaVersion"); + } + + return switch (schemaVersion) { + case 0 -> new FabricModJsonV0(jsonObject, source); + case 1 -> new FabricModJsonV1(jsonObject, source); + case 2 -> new FabricModJsonV2(jsonObject, source); + default -> throw new UnsupportedOperationException(String.format("This version of fabric-loom doesn't support the newer fabric.mod.json schema version of (%s) Please update fabric-loom to be able to read this.", schemaVersion)); + }; + } + + public static FabricModJson createFromZip(Path zipPath) { + try { + return create(ZipUtils.unpackGson(zipPath, FABRIC_MOD_JSON, JsonObject.class), new FabricModJsonSource.ZipSource(zipPath)); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read fabric.mod.json file in zip: " + zipPath, e); + } + } + + @Nullable + public static FabricModJson createFromZipNullable(Path zipPath) throws IOException { + JsonObject jsonObject = ZipUtils.unpackGsonNullable(zipPath, FABRIC_MOD_JSON, JsonObject.class); + + if (jsonObject == null) { + return null; + } + + return create(jsonObject, new FabricModJsonSource.ZipSource(zipPath)); + } + + public static FabricModJson createFromDirectory(Path directory) throws IOException { + final Path path = directory.resolve(FABRIC_MOD_JSON); + + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.DirectorySource(directory)); + } + } + + @Nullable + public static FabricModJson createFromSourceSetNullable(SourceSet sourceSet) throws IOException { + final File file = SourceSetHelper.findFileInResource(sourceSet, FABRIC_MOD_JSON); + + if (file == null) { + return null; + } + + try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSet)); + } + } + + public static boolean isModJar(File file) { + return isModJar(file.toPath()); + } + + public static boolean isModJar(Path input) { + return ZipUtils.contains(input, FABRIC_MOD_JSON); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java new file mode 100644 index 00000000..b803add0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java @@ -0,0 +1,71 @@ +/* + * 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.util.fmj; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.gradle.api.tasks.SourceSet; + +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.gradle.SourceSetHelper; + +/** + * A mod may be a zip, directory or Gradle {@link SourceSet} + * This abstraction allows easily reading a contained file from the mod. + */ +public interface FabricModJsonSource { + byte[] read(String path) throws IOException; + + record ZipSource(Path zipPath) implements FabricModJsonSource { + @Override + public byte[] read(String path) throws IOException { + return ZipUtils.unpack(zipPath, path); + } + } + + record DirectorySource(Path directoryPath) implements FabricModJsonSource { + @Override + public byte[] read(String path) throws IOException { + return Files.readAllBytes(directoryPath.resolve(path)); + } + } + + record SourceSetSource(SourceSet sourceSet) implements FabricModJsonSource { + @Override + public byte[] read(String path) throws IOException { + final File file = SourceSetHelper.findFileInResource(sourceSet, path); + + if (file == null) { + throw new FileNotFoundException("Could not find: " + path); + } + + return Files.readAllBytes(file.toPath()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java new file mode 100644 index 00000000..78b94032 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonUtils.java @@ -0,0 +1,73 @@ +/* + * 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.util.fmj; + +import java.util.Locale; +import java.util.function.Predicate; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +final class FabricModJsonUtils { + private FabricModJsonUtils() { + } + + public static String readString(JsonObject jsonObject, String key) { + final JsonElement element = getElement(jsonObject, key); + ensurePrimitive(element, JsonPrimitive::isString, key); + + return element.getAsString(); + } + + public static int readInt(JsonObject jsonObject, String key) { + final JsonElement element = getElement(jsonObject, key); + ensurePrimitive(element, JsonPrimitive::isNumber, key); + + return element.getAsInt(); + } + + private static JsonElement getElement(JsonObject jsonObject, String key) { + final JsonElement element = jsonObject.get(key); + + if (element == null) { + throw new ParseException("Unable to find json element for key (%s)", key); + } + + return element; + } + + private static void ensurePrimitive(JsonElement jsonElement, Predicate predicate, String key) { + if (!jsonElement.isJsonPrimitive() || !predicate.test(jsonElement.getAsJsonPrimitive())) { + throw new ParseException("Unexpected primitive type for key (%s)", key); + } + } + + static class ParseException extends RuntimeException { + ParseException(String message, Object... args) { + super(String.format(Locale.ROOT, message, args)); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java new file mode 100644 index 00000000..ff3adaea --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java @@ -0,0 +1,89 @@ +/* + * 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.util.fmj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.Nullable; + +@Deprecated +public final class FabricModJsonV0 extends FabricModJson { + FabricModJsonV0(JsonObject jsonObject, FabricModJsonSource source) { + super(jsonObject, source); + } + + @Override + public int getVersion() { + return 0; + } + + @Override + @Nullable + public JsonElement getCustom(String key) { + return null; + } + + @Override + public List getMixinConfigurations() { + final JsonObject mixinsObject = jsonObject.getAsJsonObject("mixins"); + + if (mixinsObject == null) { + return Collections.emptyList(); + } + + final List mixins = new ArrayList<>(); + + for (String key : mixinsObject.keySet()) { + final JsonElement jsonElement = mixinsObject.get(key); + + if (jsonElement instanceof JsonArray jsonArray) { + for (JsonElement arrayElement : jsonArray) { + if (arrayElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) { + mixins.add(jsonPrimitive.getAsString()); + } else { + throw new FabricModJsonUtils.ParseException("Expected entries in mixin %s to be an array of strings", key); + } + } + } else if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) { + mixins.add(jsonPrimitive.getAsString()); + } else { + throw new FabricModJsonUtils.ParseException("Expected mixin %s to be a string or an array of strings", key); + } + } + + return Collections.unmodifiableList(mixins); + } + + @Override + public List getClassTweakers(ModEnvironment modEnvironment) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java new file mode 100644 index 00000000..78ea2e6b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java @@ -0,0 +1,101 @@ +/* + * 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.util.fmj; + +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.Nullable; + +public final class FabricModJsonV1 extends FabricModJson { + FabricModJsonV1(JsonObject jsonObject, FabricModJsonSource source) { + super(jsonObject, source); + } + + @Override + public int getVersion() { + return 1; + } + + @Override + @Nullable + public JsonElement getCustom(String key) { + return getCustom(jsonObject, key); + } + + static JsonElement getCustom(JsonObject jsonObject, String key) { + if (!jsonObject.has("custom")) { + return null; + } + + final JsonObject custom = jsonObject.getAsJsonObject("custom"); + + if (!custom.has(key)) { + return null; + } + + return custom.get(key); + } + + @Override + public List getMixinConfigurations() { + final JsonArray mixinArray = jsonObject.getAsJsonArray("mixins"); + + if (mixinArray == null) { + return Collections.emptyList(); + } + + return StreamSupport.stream(mixinArray.spliterator(), false) + .map(FabricModJsonV1::readMixinElement) + .collect(Collectors.toList()); + } + + private static String readMixinElement(JsonElement jsonElement) { + if (jsonElement instanceof JsonPrimitive str) { + return str.getAsString(); + } else if (jsonElement instanceof JsonObject obj) { + return obj.get("config").getAsString(); + } else { + throw new FabricModJsonUtils.ParseException("Expected mixin element to be an object or string"); + } + } + + @Override + public List getClassTweakers(ModEnvironment modEnvironment) { + if (!jsonObject.has("accessWidener")) { + return Collections.emptyList(); + } + + return List.of(readString(jsonObject, "accessWidener")); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java new file mode 100644 index 00000000..029269b1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java @@ -0,0 +1,133 @@ +/* + * 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.util.fmj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Experimental +public final class FabricModJsonV2 extends FabricModJson { + FabricModJsonV2(JsonObject jsonObject, FabricModJsonSource source) { + super(jsonObject, source); + } + + @Override + public int getVersion() { + return 2; + } + + @Override + @Nullable + public JsonElement getCustom(String key) { + return FabricModJsonV1.getCustom(jsonObject, key); + } + + @Override + public List getMixinConfigurations() { + if (!jsonObject.has("mixins")) { + return Collections.emptyList(); + } + + return getConditionalConfigs(jsonObject.get("mixins"), ModEnvironment.UNIVERSAL); + } + + @Override + public List getClassTweakers(ModEnvironment modEnvironment) { + if (!jsonObject.has("classTweakers")) { + return Collections.emptyList(); + } + + return getConditionalConfigs(jsonObject.get("classTweakers"), modEnvironment); + } + + private List getConditionalConfigs(JsonElement jsonElement, ModEnvironment modEnvironment) { + final List values = new ArrayList<>(); + + if (jsonElement instanceof JsonArray jsonArray) { + for (JsonElement arrayElement : jsonArray) { + final String value = readConditionalConfig(arrayElement, modEnvironment); + + if (value != null) { + values.add(value); + } + } + } else if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) { + final String value = readConditionalConfig(jsonPrimitive, modEnvironment); + + if (value != null) { + values.add(value); + } + } else { + throw new FabricModJsonUtils.ParseException("Must be a string or array of strings"); + } + + return values; + } + + @Nullable + private String readConditionalConfig(JsonElement jsonElement, ModEnvironment modEnvironment) { + if (jsonElement instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isString()) { + return jsonElement.getAsString(); + } else if (jsonElement instanceof JsonObject jsonObject) { + final String config = FabricModJsonUtils.readString(jsonObject, "config"); + + if (!validForEnvironment(jsonObject, modEnvironment)) { + return null; + } + + return config; + } else { + throw new FabricModJsonUtils.ParseException("Must be a string or an object"); + } + } + + private boolean validForEnvironment(JsonObject jsonObject, ModEnvironment modEnvironment) { + if (!jsonObject.has("environment")) { + // Default enabled for all envs. + return true; + } + + if (!(jsonObject.get("environment") instanceof JsonPrimitive jsonPrimitive) || !jsonPrimitive.isString()) { + throw new FabricModJsonUtils.ParseException("Environment must be a string"); + } + + final String environment = jsonPrimitive.getAsString(); + + return switch (environment) { + case "*" -> true; + case "client" -> modEnvironment.isClient(); + case "server" -> modEnvironment.isServer(); + default -> throw new FabricModJsonUtils.ParseException("Invalid environment type: " + environment); + }; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/ModUtils.java b/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java similarity index 60% rename from src/main/java/net/fabricmc/loom/util/ModUtils.java rename to src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java index 31298d95..b96687ad 100644 --- a/src/main/java/net/fabricmc/loom/util/ModUtils.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2022 FabricMC + * 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 @@ -22,34 +22,26 @@ * SOFTWARE. */ -package net.fabricmc.loom.util; +package net.fabricmc.loom.util.fmj; -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; +public enum ModEnvironment { + UNIVERSAL(true, true), + CLIENT(true, false), + SERVER(false, true); -import com.google.gson.JsonObject; -import org.jetbrains.annotations.Nullable; + private final boolean client; + private final boolean server; -public final class ModUtils { - private ModUtils() { + ModEnvironment(boolean client, boolean server) { + this.client = client; + this.server = server; } - public static boolean isMod(File file) { - return isMod(file.toPath()); + public boolean isClient() { + return client; } - public static boolean isMod(Path input) { - return ZipUtils.contains(input, "fabric.mod.json"); - } - - @Nullable - public static JsonObject getFabricModJson(Path path) { - try { - return ZipUtils.unpackGsonNullable(path, "fabric.mod.json", JsonObject.class); - } catch (IOException e) { - throw new UncheckedIOException("Failed to extract fabric.mod.json from " + path, e); - } + public boolean isServer() { + return server; } } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java index aec6bc63..ff108c38 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java @@ -272,4 +272,16 @@ public final class SourceSetHelper { return Collections.singletonList(new File(binDir, reference.sourceSet().getName())); } + + @Nullable + public static File findFileInResource(SourceSet sourceSet, String path) { + try { + return sourceSet.getResources() + .matching(patternFilterable -> patternFilterable.include(path)) + .getSingleFile(); + } catch (IllegalStateException e) { + // File not found + return null; + } + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy new file mode 100644 index 00000000..e8555d5f --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy @@ -0,0 +1,105 @@ +/* + * 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.test.unit.fmj + +import com.google.gson.Gson +import com.google.gson.JsonObject +import net.fabricmc.loom.util.Constants +import net.fabricmc.loom.util.fmj.FabricModJsonFactory +import net.fabricmc.loom.util.fmj.FabricModJsonSource +import net.fabricmc.loom.util.fmj.ModEnvironment +import org.intellij.lang.annotations.Language +import spock.lang.Specification + +class FabricModJsonV0Test extends Specification { + // I think this is the old v0 format ¯\_(ツ)_/¯ + @Language("json") + static String JSON = """ +{ + "id": "example-mod-id", + "name": "Example mod name for testing", + "version": "1.0.0", + "side": "universal", + "initializers": [ + ], + "mixins": { + "client": "mixins.client.json", + "common": [ + "mixins.common.json" + ] + } +} +""" + + static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) + + def "version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.version == 0 + } + + def "id"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.id == "example-mod-id" + } + + def "mixins"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.mixinConfigurations == ["mixins.client.json", "mixins.common.json"] + } + + // Not supported in this version + def "injected interfaces"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE) + then: + jsonObject == null + } + + // Not supported in this version + def "class tweaker"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.getClassTweakers(ModEnvironment.UNIVERSAL) == [] + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy new file mode 100644 index 00000000..25fa1209 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy @@ -0,0 +1,110 @@ +/* + * 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.test.unit.fmj + +import com.google.gson.Gson +import com.google.gson.JsonObject +import net.fabricmc.loom.util.Constants +import net.fabricmc.loom.util.fmj.FabricModJsonFactory +import net.fabricmc.loom.util.fmj.FabricModJsonSource +import net.fabricmc.loom.util.fmj.ModEnvironment +import org.intellij.lang.annotations.Language +import spock.lang.Specification + +class FabricModJsonV1Test extends Specification { + @Language("json") + static String JSON = """ +{ + "schemaVersion": 1, + "id": "example-mod-id", + "name": "Example mod name for testing", + "version": "1.0.0", + "environment": "client", + "license": "Apache-2.0", + "mixins": [ + { + "config": "test.client.mixins.json", + "environment": "client" + }, + "test.mixins.json" + ], + "accessWidener" : "modid.accesswidener", + "custom": { + "loom:injected_interfaces": { + "net/minecraft/class_123": ["net/test/TestClass"] + } + } +} +""" + + static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) + + def "version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.version == 1 + } + + def "id"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.id == "example-mod-id" + } + + def "mixins"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.mixinConfigurations == ["test.client.mixins.json", "test.mixins.json"] + } + + def "injected interfaces"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE) + then: + jsonObject instanceof JsonObject + jsonObject.has("net/minecraft/class_123") + } + + def "access widener"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.getClassTweakers(ModEnvironment.SERVER) == ["modid.accesswidener"] + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy new file mode 100644 index 00000000..63009e85 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy @@ -0,0 +1,142 @@ +/* + * 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.test.unit.fmj + +import com.google.gson.Gson +import com.google.gson.JsonObject +import net.fabricmc.loom.util.Constants +import net.fabricmc.loom.util.fmj.FabricModJsonFactory +import net.fabricmc.loom.util.fmj.FabricModJsonSource +import net.fabricmc.loom.util.fmj.ModEnvironment +import org.intellij.lang.annotations.Language +import spock.lang.Specification + +class FabricModJsonV2Test extends Specification { + @Language("json") + static String JSON = """ +{ + "schemaVersion": 2, + "id": "example-mod-id", + "name": "Example mod name for testing", + "version": "1.0.0", + "environment": "client", + "license": "Apache-2.0", + "mixins": [ + { + "config": "test.client.mixins.json", + "environment": "client" + }, + { + "config": "test.server.mixins.json", + "environment": "server" + }, + "test.mixins.json" + ], + "classTweakers": [ + { + "config": "client.ct", + "environment": "client" + }, + { + "config": "server.ct", + "environment": "server" + }, + "universal.ct" + ], + "custom": { + "loom:injected_interfaces": { + "net/minecraft/class_123": ["net/test/TestClass"] + } + } +} +""" + + static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) + + def "version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.version == 2 + } + + def "id"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.id == "example-mod-id" + } + + def "mixins"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.mixinConfigurations == ["test.client.mixins.json", "test.server.mixins.json", "test.mixins.json"] + } + + def "injected interfaces"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + def jsonObject = fmj.getCustom(Constants.CustomModJsonKeys.INJECTED_INTERFACE) + then: + jsonObject instanceof JsonObject + jsonObject.has("net/minecraft/class_123") + } + + def "universal class tweakers"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.getClassTweakers(ModEnvironment.UNIVERSAL) == ["client.ct", "server.ct", "universal.ct"] + } + + def "client class tweakers"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.getClassTweakers(ModEnvironment.CLIENT) == ["client.ct", "universal.ct"] + } + + def "server class tweakers"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.getClassTweakers(ModEnvironment.SERVER) == ["server.ct", "universal.ct"] + } +}