From 13b09c3d48b7eb9dfaa5aae3a695297dc6f6253e Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 8 Nov 2022 22:30:37 +0000 Subject: [PATCH 1/4] First class tweaker work --- build.gradle | 2 + .../classtweaker/ClassTweakerEntry.java | 75 ++++++++++ .../classtweaker/ClassTweakerFactory.java | 41 ++++++ .../classtweaker/ClassTweakerFactoryImpl.java | 66 +++++++++ .../ClassTweakerJarProcessor.java | 102 ++++++++++++++ .../fabricmc/loom/util/fmj/FabricModJson.java | 29 +++- .../loom/util/fmj/FabricModJsonV0.java | 9 +- .../loom/util/fmj/FabricModJsonV1.java | 7 +- .../loom/util/fmj/FabricModJsonV2.java | 9 +- .../classtweaker/ClassTweakerEntryTest.groovy | 130 ++++++++++++++++++ .../ClassTweakerJarProcessorTest.groovy | 101 ++++++++++++++ .../test/unit/fmj/FabricModJsonV0Test.groovy | 13 +- .../test/unit/fmj/FabricModJsonV1Test.groovy | 13 +- .../test/unit/fmj/FabricModJsonV2Test.groovy | 13 +- 14 files changed, 597 insertions(+), 13 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerEntry.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerEntryTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy diff --git a/build.gradle b/build.gradle index ea7033700..f3fb5728c 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ repositories { url = 'https://maven.fabricmc.net/' } mavenCentral() + mavenLocal() } configurations { @@ -81,6 +82,7 @@ dependencies { // tinyfile management implementation ('net.fabricmc:tiny-remapper:0.8.5') implementation 'net.fabricmc:access-widener:2.1.0' + implementation 'net.fabricmc:class-tweaker:0.0.0' implementation 'net.fabricmc:mapping-io:0.2.1' implementation ('net.fabricmc:lorenz-tiny:4.0.2') { diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerEntry.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerEntry.java new file mode 100644 index 000000000..e540a5a73 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerEntry.java @@ -0,0 +1,75 @@ +/* + * 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.configuration.classtweaker; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.VisibleForTesting; + +import net.fabricmc.classtweaker.api.ClassTweaker; +import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.ModEnvironment; + +public record ClassTweakerEntry(FabricModJson modJson, String path, ModEnvironment environment, boolean local) { + public static List readAll(FabricModJson modJson, boolean local) { + var entries = new ArrayList(); + + for (Map.Entry entry : modJson.getClassTweakers().entrySet()) { + entries.add(new ClassTweakerEntry(modJson, entry.getKey(), entry.getValue(), local)); + } + + return Collections.unmodifiableList(entries); + } + + @VisibleForTesting + public int maxSupportedVersion() { + if (modJson.getMetadataVersion() >= 2) { + // FMJ 2.0 is required for class tweaker support. Otherwise limited to access wideners. + return ClassTweaker.CT_V1; + } + + return ClassTweaker.AW_V2; + } + + public void read(ClassTweakerFactory factory, ClassTweakerVisitor classTweakerVisitor) throws IOException { + final byte[] data = readRaw(); + final int ctVersion = factory.readVersion(data); + + if (ctVersion > maxSupportedVersion()) { + throw new UnsupportedOperationException("Class tweaker '%s' with version '%d' from mod '%s' is not supported with by this metadata version '%d'.".formatted(path, ctVersion, modJson.getId(), modJson().getMetadataVersion())); + } + + factory.read(classTweakerVisitor, data, modJson.getId()); + } + + private byte[] readRaw() throws IOException { + return modJson.getSource().read(path); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java new file mode 100644 index 000000000..70abc7b5d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java @@ -0,0 +1,41 @@ +/* + * 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.configuration.classtweaker; + +import java.io.IOException; +import java.util.List; + +import net.fabricmc.classtweaker.api.ClassTweaker; +import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor; + +public interface ClassTweakerFactory { + ClassTweakerFactory DEFAULT = new ClassTweakerFactoryImpl(); + + int readVersion(byte[] data); + + void read(ClassTweakerVisitor visitor, byte[] data, String modId); + + ClassTweaker readEntries(List entries) throws IOException; +} diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java new file mode 100644 index 000000000..84984e7ee --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java @@ -0,0 +1,66 @@ +/* + * 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.configuration.classtweaker; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +import net.fabricmc.classtweaker.api.ClassTweaker; +import net.fabricmc.classtweaker.api.ClassTweakerReader; +import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor; + +final class ClassTweakerFactoryImpl implements ClassTweakerFactory { + @Override + public int readVersion(byte[] data) { + return ClassTweakerReader.readVersion(data); + } + + @Override + public void read(ClassTweakerVisitor visitor, byte[] data, String modId) { + Objects.requireNonNull(visitor); + Objects.requireNonNull(data); + Objects.requireNonNull(modId); + + ClassTweakerReader.create(visitor).read(data, modId); + } + + @Override + public ClassTweaker readEntries(List entries) throws IOException { + final ClassTweaker classTweaker = ClassTweaker.newInstance(); + + for (ClassTweakerEntry entry : entries) { + ClassTweakerVisitor visitor = classTweaker; + + if (entry.local()) { + visitor = ClassTweakerVisitor.transitiveOnly(visitor); + } + + entry.read(this, visitor); + } + + return classTweaker; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java new file mode 100644 index 000000000..ffc7dfda9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java @@ -0,0 +1,102 @@ +/* + * 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.configuration.classtweaker; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.classtweaker.api.ClassTweaker; +import net.fabricmc.loom.api.processor.MinecraftJarProcessor; +import net.fabricmc.loom.api.processor.ProcessorContext; +import net.fabricmc.loom.api.processor.SpecContext; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.ModEnvironment; + +public abstract class ClassTweakerJarProcessor implements MinecraftJarProcessor { + private final ClassTweakerFactory classTweakerFactory; + + public ClassTweakerJarProcessor(ClassTweakerFactory classTweakerFactory) { + this.classTweakerFactory = classTweakerFactory; + } + + @Override + public @Nullable ClassTweakerJarProcessor.Spec buildSpec(SpecContext context) { + var classTweakers = new ArrayList(); + + for (FabricModJson localMod : context.localMods()) { + classTweakers.addAll(ClassTweakerEntry.readAll(localMod, true)); + } + + for (FabricModJson dependentMod : context.modDependencies()) { + // TODO check and cache if it has any transitive CTs + classTweakers.addAll(ClassTweakerEntry.readAll(dependentMod, false)); + } + + if (classTweakers.isEmpty()) { + return null; + } + + return new Spec(Collections.unmodifiableList(classTweakers)); + } + + public record Spec(List classTweakers) implements MinecraftJarProcessor.Spec { + List getSupportedClassTweakers(ProcessorContext context) { + return classTweakers.stream() + .filter(entry -> isSupported(entry.environment(), context)) + .toList(); + } + + private static boolean isSupported(ModEnvironment modEnvironment, ProcessorContext context) { + if (context.isMerged()) { + // All envs are supported wth a merged jar + return true; + } + + if (context.includesClient() && modEnvironment.isClient()) { + return true; + } + + if (context.includesServer() && modEnvironment.isServer()) { + return true; + } + + // Universal supports all jars + return modEnvironment == ModEnvironment.UNIVERSAL; + } + } + + @Override + public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException { + final List classTweakers = spec.getSupportedClassTweakers(context); + final ClassTweaker classTweaker = classTweakerFactory.readEntries(classTweakers); + + // TODO apply to jar + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java index 592ae3f81..bf07c8aa3 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java @@ -34,16 +34,18 @@ import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; -public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2 { +public abstract class FabricModJson { protected final JsonObject jsonObject; private final FabricModJsonSource source; - protected FabricModJson(JsonObject jsonObject, FabricModJsonSource source) { + FabricModJson(JsonObject jsonObject, FabricModJsonSource source) { this.jsonObject = Objects.requireNonNull(jsonObject); this.source = Objects.requireNonNull(source); } - public abstract int getVersion(); + public abstract int getMetadataVersion(); + + public abstract String getModVersion(); public String getId() { return readString(jsonObject, "id"); @@ -56,7 +58,26 @@ public String getId() { public abstract Map getClassTweakers(); - public final FabricModJsonSource getSource() { + public FabricModJsonSource getSource() { return source; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + FabricModJson that = (FabricModJson) o; + return getModVersion().equals(that.getModVersion()) && getId().equals(that.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getModVersion()); + } } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java index 935b0cea8..e860b6418 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV0.java @@ -24,6 +24,8 @@ package net.fabricmc.loom.util.fmj; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -42,10 +44,15 @@ public final class FabricModJsonV0 extends FabricModJson { } @Override - public int getVersion() { + public int getMetadataVersion() { return 0; } + @Override + public String getModVersion() { + return readString(jsonObject, "version"); + } + @Override @Nullable public JsonElement getCustom(String key) { diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java index 16239e0bc..5780cab47 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV1.java @@ -44,10 +44,15 @@ public final class FabricModJsonV1 extends FabricModJson { } @Override - public int getVersion() { + public int getMetadataVersion() { return 1; } + @Override + public String getModVersion() { + return readString(jsonObject, "version"); + } + @Override @Nullable public JsonElement getCustom(String key) { diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java index ed52bef46..ca4a802c4 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonV2.java @@ -24,6 +24,8 @@ package net.fabricmc.loom.util.fmj; +import static net.fabricmc.loom.util.fmj.FabricModJsonUtils.readString; + import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -45,10 +47,15 @@ public final class FabricModJsonV2 extends FabricModJson { } @Override - public int getVersion() { + public int getMetadataVersion() { return 2; } + @Override + public String getModVersion() { + return readString(jsonObject, "version"); + } + @Override @Nullable public JsonElement getCustom(String key) { diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerEntryTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerEntryTest.groovy new file mode 100644 index 000000000..63297d6ba --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerEntryTest.groovy @@ -0,0 +1,130 @@ +/* + * 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.classtweaker + +import net.fabricmc.classtweaker.api.ClassTweaker +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerEntry +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerFactory +import net.fabricmc.loom.util.fmj.FabricModJson +import net.fabricmc.loom.util.fmj.FabricModJsonSource +import net.fabricmc.loom.util.fmj.ModEnvironment +import spock.lang.Specification + +class ClassTweakerEntryTest extends Specification { + def "read multiple from local mod"() { + given: + def mod = Mock(FabricModJson) + mod.getClassTweakers() >> ["test.classtweaker": ModEnvironment.UNIVERSAL, "test.client.classtweaker": ModEnvironment.CLIENT] + when: + def entries = ClassTweakerEntry.readAll(mod, true) + then: + entries.size() == 2 + entries[0].path() == "test.classtweaker" + entries[0].environment() == ModEnvironment.UNIVERSAL + entries[0].local() == true + + entries[1].path() == "test.client.classtweaker" + entries[1].environment() == ModEnvironment.CLIENT + entries[1].local() == true + } + + def "read multiple from remote mod"() { + given: + def mod = Mock(FabricModJson) + mod.getClassTweakers() >> ["test.classtweaker": ModEnvironment.UNIVERSAL, "test.client.classtweaker": ModEnvironment.SERVER] + when: + def entries = ClassTweakerEntry.readAll(mod, false) + then: + entries.size() == 2 + entries[0].path() == "test.classtweaker" + entries[0].environment() == ModEnvironment.UNIVERSAL + entries[0].local() == false + + entries[1].path() == "test.client.classtweaker" + entries[1].environment() == ModEnvironment.SERVER + entries[1].local() == false + } + + def "max supported version"() { + given: + def mod = Mock(FabricModJson) + mod.metadataVersion >> metadata + when: + def entry = new ClassTweakerEntry(mod, null, null, false) + then: + entry.maxSupportedVersion() == ctVersion + where: + metadata | ctVersion + 0 | ClassTweaker.AW_V2 + 1 | ClassTweaker.AW_V2 + 2 | ClassTweaker.CT_V1 + } + + def "read"() { + given: + def data = "Hello World".bytes + + def modSource = Mock(FabricModJsonSource) + modSource.read("test.classtweaker") >> data + + def mod = Mock(FabricModJson) + mod.metadataVersion >> 2 + mod.id >> "modid" + mod.source >> modSource + + def factory = Mock(ClassTweakerFactory) + factory.readVersion(data) >> ClassTweaker.CT_V1 + + when: + def entry = new ClassTweakerEntry(mod, "test.classtweaker", null, false) + entry.read(factory, null) + + then: + 1 * factory.read(null, data, "modid") + } + + def "read unsupported version"() { + given: + def data = "Hello World".bytes + + def modSource = Mock(FabricModJsonSource) + modSource.read("test.classtweaker") >> data + + def mod = Mock(FabricModJson) + mod.metadataVersion >> 1 // V1 FMJ trying to read a CT_V1 + mod.id >> "modid" + mod.source >> modSource + + def factory = Mock(ClassTweakerFactory) + factory.readVersion(data) >> ClassTweaker.CT_V1 + + when: + def entry = new ClassTweakerEntry(mod, "test.classtweaker", null, false) + entry.read(factory, null) + then: + def e = thrown(UnsupportedOperationException) + e.message == "Class tweaker 'test.classtweaker' with version '3' from mod 'modid' is not supported with by this metadata version '1'." + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy new file mode 100644 index 000000000..6f29f23c6 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy @@ -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.test.unit.classtweaker + +import net.fabricmc.loom.api.processor.SpecContext +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerFactory +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerJarProcessor +import net.fabricmc.loom.util.fmj.FabricModJson +import net.fabricmc.loom.util.fmj.ModEnvironment +import spock.lang.Specification + +class ClassTweakerJarProcessorTest extends Specification { + ClassTweakerFactory factory = Mock(ClassTweakerFactory) + SpecContext context = Mock(SpecContext) + ClassTweakerJarProcessor processor = new ClassTweakerJarProcessor(factory) { + String name = "Test class tweaker" + } + + def "spec: no mods"() { + given: + context.localMods() >> [] + context.modDependencies() >> [] + when: + def spec = processor.buildSpec(context) + then: + spec == null + } + + def "spec: local mod no class tweaker"() { + given: + def mod = Mock(FabricModJson) + mod.getClassTweakers() >> [:] + + context.localMods() >> [mod] + context.modDependencies() >> [] + when: + def spec = processor.buildSpec(context) + then: + spec == null + } + + def "spec: local mod"() { + given: + def mod = Mock(FabricModJson) + mod.getClassTweakers() >> ["test.classtweaker": ModEnvironment.UNIVERSAL] + + context.localMods() >> [mod] + context.modDependencies() >> [] + when: + def spec = processor.buildSpec(context) + then: + spec != null + spec.classTweakers().size() == 1 + spec.classTweakers()[0].path() == "test.classtweaker" + spec.classTweakers()[0].environment() == ModEnvironment.UNIVERSAL + } + + def "spec: local mod and remote mod"() { + given: + def mod = Mock(FabricModJson) + mod.getClassTweakers() >> ["test.classtweaker": ModEnvironment.UNIVERSAL] + + def remoteMod = Mock(FabricModJson) + remoteMod.getClassTweakers() >> ["remote.classtweaker": ModEnvironment.CLIENT] + + context.localMods() >> [mod] + context.modDependencies() >> [remoteMod] + when: + def spec = processor.buildSpec(context) + then: + spec != null + spec.classTweakers().size() == 2 + spec.classTweakers()[0].path() == "test.classtweaker" + spec.classTweakers()[0].environment() == ModEnvironment.UNIVERSAL + + spec.classTweakers()[1].path() == "remote.classtweaker" + spec.classTweakers()[1].environment() == ModEnvironment.CLIENT + } +} 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 index c8585ab3d..106a85fcf 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy @@ -54,13 +54,22 @@ class FabricModJsonV0Test extends Specification { static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) - def "version"() { + def "metadata version"() { given: def mockSource = Mock(FabricModJsonSource) when: def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) then: - fmj.version == 0 + fmj.metadataVersion == 0 + } + + def "mod version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.modVersion == "1.0.0" } def "id"() { 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 index b11da5bcc..c212417c9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy @@ -61,13 +61,22 @@ class FabricModJsonV1Test extends Specification { static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) - def "version"() { + def "metadata version"() { given: def mockSource = Mock(FabricModJsonSource) when: def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) then: - fmj.version == 1 + fmj.metadataVersion == 1 + } + + def "mod version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.modVersion == "1.0.0" } def "id"() { 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 index dba71c698..7e416a14c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy @@ -75,13 +75,22 @@ class FabricModJsonV2Test extends Specification { static JsonObject JSON_OBJECT = new Gson().fromJson(JSON, JsonObject.class) - def "version"() { + def "metadata version"() { given: def mockSource = Mock(FabricModJsonSource) when: def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) then: - fmj.version == 2 + fmj.metadataVersion == 2 + } + + def "mod version"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.modVersion == "1.0.0" } def "id"() { From 7d657ab23da8d95dda0e89757d66dfe59313cf0a Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 9 Nov 2022 13:25:34 +0000 Subject: [PATCH 2/4] Start on jar processing --- .../classtweaker/ClassTweakerFactory.java | 3 + .../classtweaker/ClassTweakerFactoryImpl.java | 6 ++ .../ClassTweakerJarProcessor.java | 5 +- .../ClassTweakerJarTransformer.java | 79 +++++++++++++++++++ .../ClassTweakerJarProcessorTest.groovy | 65 +++++++++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java index 70abc7b5d..56014e3ed 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactory.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.configuration.classtweaker; import java.io.IOException; +import java.nio.file.Path; import java.util.List; import net.fabricmc.classtweaker.api.ClassTweaker; @@ -38,4 +39,6 @@ public interface ClassTweakerFactory { void read(ClassTweakerVisitor visitor, byte[] data, String modId); ClassTweaker readEntries(List entries) throws IOException; + + void transformJar(Path jar, ClassTweaker classTweaker) throws IOException; } diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java index 84984e7ee..d5db767ec 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.configuration.classtweaker; import java.io.IOException; +import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -63,4 +64,9 @@ public ClassTweaker readEntries(List entries) throws IOExcept return classTweaker; } + + @Override + public void transformJar(Path jar, ClassTweaker classTweaker) throws IOException { + new ClassTweakerJarTransformer(classTweaker).transform(jar); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java index ffc7dfda9..6a920e3a2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarProcessor.java @@ -94,9 +94,10 @@ private static boolean isSupported(ModEnvironment modEnvironment, ProcessorConte @Override public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException { + assert !spec.classTweakers.isEmpty(); + final List classTweakers = spec.getSupportedClassTweakers(context); final ClassTweaker classTweaker = classTweakerFactory.readEntries(classTweakers); - - // TODO apply to jar + classTweakerFactory.transformJar(jar, classTweaker); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java new file mode 100644 index 000000000..fc0de4630 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java @@ -0,0 +1,79 @@ +/* + * 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.configuration.classtweaker; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.BiConsumer; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; + +import net.fabricmc.classtweaker.api.ClassTweaker; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.FileSystemUtil; + +public record ClassTweakerJarTransformer(ClassTweaker classTweaker) { + public void transform(Path jar) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false)) { + for (String target : classTweaker.getTargets()) { + transformClass(fs, target); + } + } + } + + private void transformClass(FileSystemUtil.Delegate fs, String name) throws IOException { + final Path path = fs.getPath(nameToPath(name)); + byte[] inputBytes = Files.readAllBytes(path); + + final ClassReader reader = new ClassReader(inputBytes); + final ClassWriter writer = new ClassWriter(0); + + final ClassVisitor classVisitor = classTweaker.createClassVisitor(Constants.ASM_VERSION, writer, consumeGeneratedClass(fs)); + reader.accept(classVisitor, 0); + + byte[] outputBytes = writer.toByteArray(); + Files.write(path, outputBytes); + } + + private BiConsumer consumeGeneratedClass(FileSystemUtil.Delegate fs) { + return (name, bytes) -> { + final Path path = fs.getPath(nameToPath(name)); + + try { + Files.write(path, bytes); + } catch (IOException e) { + throw new UncheckedIOException("Failed to write generated class: %s".formatted(name), e); + } + }; + } + + private static String nameToPath(String name) { + return name.replaceAll("\\.", "/") + ".class"; + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy index 6f29f23c6..8089cb149 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarProcessorTest.groovy @@ -24,7 +24,9 @@ package net.fabricmc.loom.test.unit.classtweaker +import net.fabricmc.loom.api.processor.ProcessorContext import net.fabricmc.loom.api.processor.SpecContext +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerEntry import net.fabricmc.loom.configuration.classtweaker.ClassTweakerFactory import net.fabricmc.loom.configuration.classtweaker.ClassTweakerJarProcessor import net.fabricmc.loom.util.fmj.FabricModJson @@ -98,4 +100,67 @@ class ClassTweakerJarProcessorTest extends Specification { spec.classTweakers()[1].path() == "remote.classtweaker" spec.classTweakers()[1].environment() == ModEnvironment.CLIENT } + + def "process: merged jar"() { + given: + def context = Mock(ProcessorContext) + context.isMerged() >> true + + def mod = Mock(FabricModJson) + + def spec = new ClassTweakerJarProcessor.Spec([ + new ClassTweakerEntry(mod, "mod.classtweaker", ModEnvironment.UNIVERSAL, false) + ]) + + when: + processor.processJar(null, spec, context) + + then: + 1 * factory.readEntries { it.size() == 1 && it[0].path == "mod.classtweaker" } + 1 * factory.transformJar(_, _) + } + + def "process: client jar"() { + given: + def context = Mock(ProcessorContext) + context.isMerged() >> false + context.includesServer() >> false + context.includesClient() >> true + + def mod = Mock(FabricModJson) + + def spec = new ClassTweakerJarProcessor.Spec([ + new ClassTweakerEntry(mod, "client.classtweaker", ModEnvironment.CLIENT, false), + new ClassTweakerEntry(mod, "server.classtweaker", ModEnvironment.SERVER, false) + ]) + + when: + processor.processJar(null, spec, context) + + then: + 1 * factory.readEntries { it.size() == 1 && it[0].path == "client.classtweaker" } + 1 * factory.transformJar(_, _) + } + + def "process: server jar"() { + given: + def context = Mock(ProcessorContext) + context.isMerged() >> false + context.includesServer() >> true + context.includesClient() >> false + + def mod = Mock(FabricModJson) + + def spec = new ClassTweakerJarProcessor.Spec([ + new ClassTweakerEntry(mod, "modid.classtweaker", ModEnvironment.UNIVERSAL, false), + new ClassTweakerEntry(mod, "server.classtweaker", ModEnvironment.SERVER, false) + ]) + + when: + processor.processJar(null, spec, context) + + then: + 1 * factory.readEntries { it.size() == 2 && it[0].path == "modid.classtweaker" && it[1].path == "server.classtweaker" } + 1 * factory.transformJar(_, _) + } } From 4477cb4226f571bc21bf177b0549400900286bde Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Thu, 17 Nov 2022 19:50:42 +0000 Subject: [PATCH 3/4] More tests and a few fixes --- build.gradle | 1 + .../classtweaker/ClassTweakerFactoryImpl.java | 3 +- .../ClassTweakerJarTransformer.java | 12 ++- .../ClassTweakerFactoryImplTest.groovy | 92 +++++++++++++++++++ .../ClassTweakerJarTransformerTest.groovy | 72 +++++++++++++++ .../unit/classtweaker/classes/TestClass.java | 30 ++++++ .../unit/classtweaker/classes/TestEnum.java | 38 ++++++++ .../loom/test/util/FileSystemTestTrait.groovy | 86 +++++++++++++++++ 8 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerFactoryImplTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestClass.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestEnum.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/util/FileSystemTestTrait.groovy diff --git a/build.gradle b/build.gradle index f3fb5728c..e0b8aca72 100644 --- a/build.gradle +++ b/build.gradle @@ -115,6 +115,7 @@ dependencies { } testImplementation 'net.fabricmc:fabric-installer:0.9.0' testImplementation 'org.mockito:mockito-core:4.8.0' + testImplementation 'com.google.jimfs:jimfs:1.2' compileOnly 'org.jetbrains:annotations:23.0.0' testCompileOnly 'org.jetbrains:annotations:23.0.0' diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java index d5db767ec..b77392be7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerFactoryImpl.java @@ -36,6 +36,7 @@ final class ClassTweakerFactoryImpl implements ClassTweakerFactory { @Override public int readVersion(byte[] data) { + Objects.requireNonNull(data); return ClassTweakerReader.readVersion(data); } @@ -55,7 +56,7 @@ public ClassTweaker readEntries(List entries) throws IOExcept for (ClassTweakerEntry entry : entries) { ClassTweakerVisitor visitor = classTweaker; - if (entry.local()) { + if (!entry.local()) { visitor = ClassTweakerVisitor.transitiveOnly(visitor); } diff --git a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java index fc0de4630..1bc54a8a5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java +++ b/src/main/java/net/fabricmc/loom/configuration/classtweaker/ClassTweakerJarTransformer.java @@ -30,6 +30,7 @@ import java.nio.file.Path; import java.util.function.BiConsumer; +import org.jetbrains.annotations.VisibleForTesting; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -41,9 +42,14 @@ public record ClassTweakerJarTransformer(ClassTweaker classTweaker) { public void transform(Path jar) throws IOException { try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false)) { - for (String target : classTweaker.getTargets()) { - transformClass(fs, target); - } + transform(fs); + } + } + + @VisibleForTesting + public void transform(FileSystemUtil.Delegate fs) throws IOException { + for (String target : classTweaker.getTargets()) { + transformClass(fs, target); } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerFactoryImplTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerFactoryImplTest.groovy new file mode 100644 index 000000000..ccc52ea90 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerFactoryImplTest.groovy @@ -0,0 +1,92 @@ +/* + * 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.classtweaker + +import net.fabricmc.classtweaker.api.ClassTweaker +import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerEntry +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerFactory +import net.fabricmc.loom.util.fmj.FabricModJson +import net.fabricmc.loom.util.fmj.FabricModJsonSource +import net.fabricmc.loom.util.fmj.ModEnvironment +import spock.lang.Specification + +class ClassTweakerFactoryImplTest extends Specification { + def "read version"() { + given: + def input = "classTweaker\tv1\tsomenamespace" + + when: + def version = ClassTweakerFactory.DEFAULT.readVersion(input.bytes) + + then: + version == ClassTweaker.CT_V1 + } + + def "read"() { + given: + def input = "classTweaker\tv1\tsomenamespace" + def classTweakerVisitor = Mock(ClassTweakerVisitor) + + when: + ClassTweakerFactory.DEFAULT.read(classTweakerVisitor, input.bytes, "test") + + then: + 1 * classTweakerVisitor.visitHeader("somenamespace") + } + + def "read entries"() { + given: + def localInput = "classTweaker\tv1\tsomenamespace\n" + + "inject-interface\ttest/LocalClass\ttest/InterfaceTests" + def remoteInput = "classTweaker\tv1\tsomenamespace\n" + + "transitive-inject-interface\ttest/RemoteTClass\ttest/InterfaceTests\n" + + "inject-interface\ttest/RemoteClass\ttest/InterfaceTests\n" + + def localMod = Mock(FabricModJson) + def localModSource = Mock(FabricModJsonSource) + localModSource.read("local.classtweaker") >> localInput.bytes + localMod.getSource() >> localModSource + localMod.getMetadataVersion() >> 2 + localMod.getId() >> "localMod" + + def remoteMod = Mock(FabricModJson) + def remoteModSource = Mock(FabricModJsonSource) + remoteModSource.read("remote.classtweaker") >> remoteInput.bytes + remoteMod.getSource() >> remoteModSource + remoteMod.getMetadataVersion() >> 2 + remoteMod.getId() >> "remotemod" + + def localEntry = new ClassTweakerEntry(localMod, "local.classtweaker", ModEnvironment.UNIVERSAL, true) + def remoteEntry = new ClassTweakerEntry(remoteMod, "remote.classtweaker", ModEnvironment.UNIVERSAL, false) + + when: + def classTweaker = ClassTweakerFactory.DEFAULT.readEntries([localEntry, remoteEntry]) + + then: + // Test to make sure that only the remote transitive class is included + classTweaker.getTargets().asList() == ["test.LocalClass", "test.RemoteTClass"] + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy new file mode 100644 index 000000000..21e77300f --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy @@ -0,0 +1,72 @@ +/* + * 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.classtweaker + +import net.fabricmc.classtweaker.api.ClassTweaker +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerFactory +import net.fabricmc.loom.configuration.classtweaker.ClassTweakerJarTransformer +import net.fabricmc.loom.test.unit.classtweaker.classes.TestClass +import net.fabricmc.loom.test.unit.classtweaker.classes.TestEnum +import net.fabricmc.loom.test.util.FileSystemTestTrait +import spock.lang.Specification + +import java.lang.reflect.Modifier + +class ClassTweakerJarTransformerTest extends Specification implements FileSystemTestTrait { + def "transform"() { + given: + def testClass = copyClass(TestClass.class) + def testEnum = copyClass(TestEnum.class) + + def input = """ +classTweaker\tv1\tsomenamespace +accessible\tmethod\t$testClass\thello\t()V +extend-enum\t$testEnum\tADDED\t(Ljava/lang/String;ILjava/lang/String;)V +\toverride\thello\tnet/fabricmc/classtweaker/StaticClass\thello\t(I)V +""".trim() + def classTweaker = createClassTweaker(input) + + when: + def inputClass = readClass(testClass) + def inputEnum = readClass(testEnum) + + new ClassTweakerJarTransformer(classTweaker).transform(delegate) + + def outputClass = readClass(testClass) + def outputEnum = readClass(testEnum) + then: + !Modifier.isPublic(inputClass.methods[1].access) + Modifier.isPublic(outputClass.methods[1].access) + + inputEnum.fields.size() == 2 + outputEnum.fields.size() == 3 + } + + private ClassTweaker createClassTweaker(String input) { + def instance = ClassTweaker.newInstance() + ClassTweakerFactory.DEFAULT.read(instance, input.bytes, "test") + return instance + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestClass.java b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestClass.java new file mode 100644 index 000000000..759c1f6dc --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestClass.java @@ -0,0 +1,30 @@ +/* + * 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.classtweaker.classes; + +public class TestClass { + private void hello() { + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestEnum.java b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestEnum.java new file mode 100644 index 000000000..f4cdc3fce --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/classes/TestEnum.java @@ -0,0 +1,38 @@ +/* + * 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.classtweaker.classes; + +public enum TestEnum { + HELLO("hello") { + @Override + void hello(int b) { + } + }; + + TestEnum(String hello) { + } + + abstract void hello(int b); +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/FileSystemTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/FileSystemTestTrait.groovy new file mode 100644 index 000000000..9b815c257 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/util/FileSystemTestTrait.groovy @@ -0,0 +1,86 @@ +/* + * 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.util + +import com.google.common.jimfs.Configuration +import com.google.common.jimfs.Jimfs +import net.fabricmc.loom.util.FileSystemUtil +import net.fabricmc.tinyremapper.FileSystemReference +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode + +import java.nio.file.FileSystem +import java.nio.file.Files + +trait FileSystemTestTrait { + FileSystem fileSystem = Jimfs.newFileSystem(Configuration.forCurrentPlatform()) + FileSystemUtil.Delegate delegate + + private FileSystemReference reference + + @SuppressWarnings('GroovyAccessibility') + def setup() { + synchronized (FileSystemReference.openFsMap) { + reference = new FileSystemReference(fileSystem) + FileSystemReference.openFsMap.put(fileSystem, Collections.emptySet()) + } + + delegate = new FileSystemUtil.Delegate(reference) + } + + def cleanup() { + fileSystem.close() + } + + // Copy a class to the fs and return the path + String copyClass(Class clazz) { + def classname = clazz.name.replace('.', '/') + def filename = "${classname}.class" + def input = clazz.classLoader.getResourceAsStream(filename) + + if (input == null) { + throw new NullPointerException("Unable to find $filename") + } + + def path = delegate.getPath(filename) + + Files.createDirectories(path.getParent()) + Files.copy(input, path) + + return classname + } + + ClassNode readClass(String classname) { + def filename = "${classname}.class" + def path = delegate.getPath(filename) + def bytes = Files.readAllBytes(path) + + def classNode = new ClassNode() + def classReader = new ClassReader(bytes) + classReader.accept(classNode, 0) + + return classNode + } +} \ No newline at end of file From 0200fa79a825947bc7c58f8ae64ef9dc39152f45 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 18 Nov 2022 09:46:49 +0000 Subject: [PATCH 4/4] Fix test --- .../test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy index 21e77300f..dcb8760b0 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/classtweaker/ClassTweakerJarTransformerTest.groovy @@ -45,6 +45,7 @@ classTweaker\tv1\tsomenamespace accessible\tmethod\t$testClass\thello\t()V extend-enum\t$testEnum\tADDED\t(Ljava/lang/String;ILjava/lang/String;)V \toverride\thello\tnet/fabricmc/classtweaker/StaticClass\thello\t(I)V +\tparams\t"Hello world!" """.trim() def classTweaker = createClassTweaker(input)