diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java index ae32f7f..4e3a628 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-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 @@ -26,6 +26,8 @@ package net.fabricmc.loom.api.mappings.layered; import java.util.Locale; +import org.jetbrains.annotations.Nullable; + /** * The standard namespaces used by loom. */ @@ -49,6 +51,21 @@ public enum MappingsNamespace { */ NAMED; + /** + * Gets a {@code MappingsNamespace} from a namespace string. + * + * @param namespace the name of the namespace as a lowercase string + * @return the {@code MappingsNamespace}, or null if not found + */ + public static @Nullable MappingsNamespace of(String namespace) { + return switch (namespace) { + case "official" -> OFFICIAL; + case "intermediary" -> INTERMEDIARY; + case "named" -> NAMED; + default -> null; + }; + } + @Override public String toString() { return name().toLowerCase(Locale.ROOT); diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java new file mode 100644 index 0000000..1dc71f6 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileMappingsSpecBuilder.java @@ -0,0 +1,101 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021-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.api.mappings.layered.spec; + +import org.jetbrains.annotations.ApiStatus; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; + +/** + * A builder for a file mappings layer. + * This layer type supports any mapping type that mapping-io can read. + */ +@ApiStatus.Experimental +public interface FileMappingsSpecBuilder { + /** + * Sets the mapping path inside a zip or jar. + * This will have no effect if the file of this mapping spec + * is not a zip. + * + *

Path components within the path should be separated with {@code /}. + * + *

The default mapping path is {@code mappings/mappings.tiny}, matching regular mapping dependency jars + * such as Yarn's. + * + * @param mappingPath the mapping path, or null if a bare file + * @return this builder + */ + FileMappingsSpecBuilder mappingPath(String mappingPath); + + /** + * Sets the fallback namespaces. They will be used + * if the mapping format itself doesn't provide namespaces with names + * (e.g. Enigma mappings). + * + *

The default fallback namespaces are {@code intermediary} as the source namespace + * and {@code named} as the target namespace as in Yarn. + * + * @param sourceNamespace the fallback source namespace + * @param targetNamespace the fallback target namespace + * @return this builder + */ + FileMappingsSpecBuilder fallbackNamespaces(String sourceNamespace, String targetNamespace); + + /** + * Marks that the file contains Enigma mappings. + * Because they are stored in a directory, the format cannot be auto-detected. + * + * @return this builder + */ + FileMappingsSpecBuilder enigmaMappings(); + + /** + * Sets the merge namespace of this mappings spec. + * + *

The merge namespace is the namespace that is used to match up this layer's + * names to the rest of the mappings. For example, Yarn mappings should be merged through + * the intermediary names. + * + *

The default merge namespace is {@link MappingsNamespace#INTERMEDIARY}. + * + * @param namespace the new merge namespace + * @return this builder + */ + FileMappingsSpecBuilder mergeNamespace(MappingsNamespace namespace); + + /** + * Sets the merge namespace of this mappings spec. + * + *

The merge namespace is the namespace that is used to match up this layer's + * names to the rest of the mappings. For example, Yarn mappings should be merged through + * the intermediary names. + * + *

The default merge namespace is {@code intermediary}. + * + * @param namespace the new merge namespace + * @return this builder + */ + FileMappingsSpecBuilder mergeNamespace(String namespace); +} diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileSpec.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileSpec.java index 5af12f7..2f9bd59 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileSpec.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/FileSpec.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -50,9 +50,10 @@ public interface FileSpec { *

The parameter will be evaluated like this: *

* * @param o the file notation @@ -73,6 +74,8 @@ public interface FileSpec { return createFromFile(p); } else if (o instanceof FileSystemLocation l) { return createFromFile(l); + } else if (o instanceof FileSpec s) { + return s; } throw new UnsupportedOperationException("Cannot create FileSpec from object of type:" + o.getClass().getCanonicalName()); diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java index e3faef1..faaaba2 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -77,4 +77,40 @@ public interface LayeredMappingSpecBuilder { */ @ApiStatus.Experimental LayeredMappingSpecBuilder signatureFix(Object object); + + /** + * Add a layer that uses a mappings file or directory. + * + * @param file the file notation for a {@link FileSpec} + * @return this builder + * @see FileMappingsSpecBuilder + */ + @ApiStatus.Experimental + default LayeredMappingSpecBuilder mappings(Object file) { + return mappings(file, builder -> { }); + } + + /** + * Configure and add a layer that uses a mappings file or directory. + * + * @param file the file notation for a {@link FileSpec} + * @param closure the configuration action + * @return this builder + * @see FileMappingsSpecBuilder + */ + @ApiStatus.Experimental + default LayeredMappingSpecBuilder mappings(Object file, @DelegatesTo(value = FileMappingsSpecBuilder.class, strategy = Closure.DELEGATE_FIRST) Closure closure) { + return mappings(file, new ClosureAction<>(closure)); + } + + /** + * Configure and add a layer that uses a mappings file or directory. + * + * @param file the file notation for a {@link FileSpec} + * @param action the configuration action + * @return this builder + * @see FileMappingsSpecBuilder + */ + @ApiStatus.Experimental + LayeredMappingSpecBuilder mappings(Object file, Action action); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java index b374625..26dd21b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java @@ -30,12 +30,14 @@ import java.util.List; import org.gradle.api.Action; +import net.fabricmc.loom.api.mappings.layered.spec.FileMappingsSpecBuilder; import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; import net.fabricmc.loom.api.mappings.layered.spec.MojangMappingsSpecBuilder; import net.fabricmc.loom.api.mappings.layered.spec.ParchmentMappingsSpecBuilder; import net.fabricmc.loom.configuration.providers.mappings.extras.signatures.SignatureFixesSpec; +import net.fabricmc.loom.configuration.providers.mappings.file.FileMappingsSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec; import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMappingsSpecBuilderImpl; @@ -68,6 +70,13 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder return addLayer(new SignatureFixesSpec(FileSpec.create(object))); } + @Override + public LayeredMappingSpecBuilder mappings(Object file, Action action) { + FileMappingsSpecBuilderImpl builder = FileMappingsSpecBuilderImpl.builder(FileSpec.create(file)); + action.execute(builder); + return addLayer(builder.build()); + } + public LayeredMappingSpec build() { List> builtLayers = new LinkedList<>(); // Intermediary is always the base layer diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java new file mode 100644 index 0000000..9cc0a5c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java @@ -0,0 +1,85 @@ +/* + * 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.providers.mappings.file; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import net.fabricmc.loom.api.mappings.layered.MappingLayer; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingLayer; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.MappingUtil; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.adapter.MappingNsRenamer; +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; +import net.fabricmc.mappingio.format.MappingFormat; + +public record FileMappingsLayer( + Path path, String mappingPath, + String fallbackSourceNamespace, String fallbackTargetNamespace, + boolean enigma, // Enigma cannot be automatically detected since it's stored in a directory. + String mergeNamespace +) implements MappingLayer { + @Override + public void visit(MappingVisitor mappingVisitor) throws IOException { + // Bare file + if (!ZipUtils.isZip(path)) { + visit(path, mappingVisitor); + } else { + try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(path)) { + visit(fileSystem.get().getPath(mappingPath), mappingVisitor); + } + } + } + + private void visit(Path path, MappingVisitor mappingVisitor) throws IOException { + MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingVisitor, mergeNamespace.toString()); + + // Replace the default fallback namespaces with + // our fallback namespaces if potentially needed. + Map fallbackNamespaceReplacements = Map.of( + MappingUtil.NS_SOURCE_FALLBACK, fallbackSourceNamespace, + MappingUtil.NS_TARGET_FALLBACK, fallbackTargetNamespace + ); + MappingNsRenamer renamer = new MappingNsRenamer(nsSwitch, fallbackNamespaceReplacements); + + MappingReader.read(path, enigma ? MappingFormat.ENIGMA : null, renamer); + } + + @Override + public MappingsNamespace getSourceNamespace() { + return MappingsNamespace.of(mergeNamespace); + } + + @Override + public List> dependsOn() { + return List.of(IntermediaryMappingLayer.class); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpec.java new file mode 100644 index 0000000..5d8a166 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpec.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.providers.mappings.file; + +import net.fabricmc.loom.api.mappings.layered.MappingContext; +import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; +import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; + +public record FileMappingsSpec( + FileSpec fileSpec, String mappingPath, + String fallbackSourceNamespace, String fallbackTargetNamespace, + boolean enigma, + String mergeNamespace +) implements MappingsSpec { + @Override + public FileMappingsLayer createLayer(MappingContext context) { + return new FileMappingsLayer(fileSpec.get(context), mappingPath, fallbackSourceNamespace, fallbackTargetNamespace, enigma, mergeNamespace); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpecBuilderImpl.java new file mode 100644 index 0000000..53681ad --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsSpecBuilderImpl.java @@ -0,0 +1,94 @@ +/* + * 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.providers.mappings.file; + +import java.util.Objects; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.api.mappings.layered.spec.FileMappingsSpecBuilder; +import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; + +public class FileMappingsSpecBuilderImpl implements FileMappingsSpecBuilder { + /** + * The mapping path of regular mapping dependencies. + */ + private static final String DEFAULT_MAPPING_PATH = "mappings/mappings.tiny"; + + private final FileSpec fileSpec; + private String mappingPath = DEFAULT_MAPPING_PATH; + private String fallbackSourceNamespace = MappingsNamespace.INTERMEDIARY.toString(); + private String fallbackTargetNamespace = MappingsNamespace.NAMED.toString(); + private boolean enigma = false; + private String mergeNamespace = MappingsNamespace.INTERMEDIARY.toString(); + + private FileMappingsSpecBuilderImpl(FileSpec fileSpec) { + this.fileSpec = fileSpec; + } + + public static FileMappingsSpecBuilderImpl builder(FileSpec fileSpec) { + return new FileMappingsSpecBuilderImpl(fileSpec); + } + + @Override + public FileMappingsSpecBuilderImpl mappingPath(String mappingPath) { + this.mappingPath = Objects.requireNonNull(mappingPath, "mapping path cannot be null"); + return this; + } + + @Override + public FileMappingsSpecBuilderImpl fallbackNamespaces(String sourceNamespace, String targetNamespace) { + fallbackSourceNamespace = Objects.requireNonNull(sourceNamespace, "fallback source namespace cannot be null"); + fallbackTargetNamespace = Objects.requireNonNull(targetNamespace, "fallback target namespace cannot be null"); + return this; + } + + @Override + public FileMappingsSpecBuilderImpl enigmaMappings() { + enigma = true; + return this; + } + + @Override + public FileMappingsSpecBuilderImpl mergeNamespace(MappingsNamespace namespace) { + mergeNamespace = Objects.requireNonNull(namespace, "merge namespace cannot be null").toString(); + return this; + } + + @Override + public FileMappingsSpecBuilderImpl mergeNamespace(String namespace) { + Objects.requireNonNull(namespace, "merge namespace cannot be null"); + + if (MappingsNamespace.of(namespace) == null) { + throw new IllegalArgumentException("Namespace '" + namespace + "' is unsupported! It must be either 'official', 'intermediary' or 'named'."); + } + + mergeNamespace = namespace; + return this; + } + + public FileMappingsSpec build() { + return new FileMappingsSpec(fileSpec, mappingPath, fallbackSourceNamespace, fallbackTargetNamespace, enigma, mergeNamespace); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/ZipUtils.java b/src/main/java/net/fabricmc/loom/util/ZipUtils.java index 2843a50..d1e2f1b 100644 --- a/src/main/java/net/fabricmc/loom/util/ZipUtils.java +++ b/src/main/java/net/fabricmc/loom/util/ZipUtils.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.util; import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UncheckedIOException; @@ -47,6 +48,22 @@ import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradlePlugin; public class ZipUtils { + public static boolean isZip(Path zip) throws IOException { + if (Files.notExists(zip)) { + throw new NoSuchFileException("Cannot check if '" + zip + "' is a zip because it doesn't exist!"); + } + + if (Files.isRegularFile(zip)) { + try (DataInputStream in = new DataInputStream(Files.newInputStream(zip))) { + int header = in.readInt(); + // See https://en.wikipedia.org/wiki/List_of_file_signatures + return header == 0x504B0304 || header == 0x504B0506 || header == 0x504B0708; + } + } else { + return false; + } + } + public static boolean contains(Path zip, String path) { try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(zip, false)) { Path fsPath = fs.get().getPath(path); diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy index 921edff..80709e1 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy @@ -121,4 +121,32 @@ class ZipUtilsTest extends Specification { outputFile.exists() outputFile.text == "This is a test of unpacking all" } + + def "is zip"() { + setup: + // Create zip + def dir = Files.createTempDirectory("loom-zip-test") + def zip = Files.createTempFile("loom-zip-test", ".zip") + def fileInside = dir.resolve("text.txt") + Files.writeString(fileInside, "hello world") + ZipUtils.pack(dir, zip) + + when: + def result = ZipUtils.isZip(zip) + + then: + result + } + + def "is not zip"() { + setup: + def textFile = Files.createTempFile("loom-zip-test", ".txt") + Files.writeString(textFile, "hello world") + + when: + def result = ZipUtils.isZip(textFile) + + then: + !result + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/FileMappingLayerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/FileMappingLayerTest.groovy new file mode 100644 index 0000000..b983cf4 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/FileMappingLayerTest.groovy @@ -0,0 +1,122 @@ +/* + * 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.layeredmappings + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace +import net.fabricmc.loom.api.mappings.layered.spec.FileSpec +import net.fabricmc.loom.configuration.providers.mappings.file.FileMappingsSpecBuilderImpl +import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec +import net.fabricmc.loom.util.DownloadUtil +import net.fabricmc.loom.util.ZipUtils +import spock.lang.Unroll + +import java.nio.file.Path +import java.util.function.Consumer + +class FileMappingLayerTest extends LayeredMappingsSpecification { + @Unroll + def "read Yarn mappings from #setupType.displayName"() { + setup: + intermediaryUrl = INTERMEDIARY_1_17_URL + mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17 + setupType.setup.delegate = this + def mappingFile = setupType.setup.call() + when: + def builder = FileMappingsSpecBuilderImpl.builder(FileSpec.create(mappingFile)) + setupType.mappingsSpec.accept(builder) + def mappings = getLayeredMappings( + new IntermediaryMappingsSpec(), + builder.build() + ) + then: + mappings.srcNamespace == "named" + mappings.dstNamespaces == ["intermediary", "official"] + mappings.classes.size() == 6111 + mappings.classes[0].srcName == "net/minecraft/block/FenceBlock" + mappings.classes[0].getDstName(0) == "net/minecraft/class_2354" + mappings.classes[0].fields[0].srcName == "cullingShapes" + mappings.classes[0].fields[0].getDstName(0) == "field_11066" + mappings.classes[0].methods[0].srcName == "canConnectToFence" + mappings.classes[0].methods[0].getDstName(0) == "method_26375" + mappings.classes[0].methods[0].args[0].srcName == "state" + where: + setupType << YarnSetupType.values() + } + + // Also tests the custom fallback namespace and source namespace functionality + def "read Mojang mappings from proguard"() { + setup: + intermediaryUrl = INTERMEDIARY_1_17_URL + mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17 + def mappingsDownload = VERSION_META_1_17.download('client_mappings') + def mappingsFile = new File(tempDir, 'mappings.txt') + DownloadUtil.downloadIfChanged(new URL(mappingsDownload.url()), mappingsFile, mappingContext.logger) + when: + def mappings = getLayeredMappings( + new IntermediaryMappingsSpec(), + FileMappingsSpecBuilderImpl.builder(FileSpec.create(mappingsFile)) + .fallbackNamespaces('named', 'official') + .mergeNamespace(MappingsNamespace.OFFICIAL) + .build() + ) + def tiny = getTiny(mappings) + then: + mappings.srcNamespace == "named" + mappings.dstNamespaces == ["intermediary", "official"] + mappings.classes.size() == 6113 + mappings.classes[0].srcName.hashCode() == 1869546970 // MojMap name, just check the hash + mappings.classes[0].getDstName(0) == "net/minecraft/class_2354" + mappings.classes[0].methods[0].args.size() == 0 // No Args + tiny.contains('this$0') + } + + enum YarnSetupType { + TINY_JAR('tiny jar', { downloadFile(YARN_1_17_URL, "yarn.jar") }, { }), + BARE_TINY('bare tiny file', { + def yarnJar = downloadFile(YARN_1_17_URL, "yarn.jar") + def yarnTiny = new File(tempDir, "yarn.tiny") + yarnTiny.bytes = ZipUtils.unpack(yarnJar.toPath(), "mappings/mappings.tiny") + yarnTiny + }, { }), + ENIGMA_ZIP('enigma zip', { + // Recent Yarn data is not published as Enigma zips, so this zip is just a copy + // of Yarn's repo at a60a3189 + Path.of("src/test/resources/mappings/yarn-1.17.zip") + }, { + it.mappingPath("mappings").enigmaMappings() + }), + ; + + final String displayName + final Closure setup + final Consumer mappingsSpec + + YarnSetupType(String displayName, Closure setup, Consumer mappingsSpec) { + this.displayName = displayName + this.setup = setup + this.mappingsSpec = mappingsSpec + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingSpecBuilderTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingSpecBuilderTest.groovy index 7164f4f..abbb524 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingSpecBuilderTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingSpecBuilderTest.groovy @@ -24,6 +24,7 @@ package net.fabricmc.loom.test.unit.layeredmappings +import net.fabricmc.loom.configuration.providers.mappings.file.FileMappingsSpec import net.fabricmc.loom.configuration.providers.mappings.utils.MavenFileSpec import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl @@ -106,6 +107,20 @@ class LayeredMappingSpecBuilderTest extends Specification { parchment.removePrefix() == false } + def "yarn through file mappings"() { + when: + def spec = layered { + mappings("net.fabricmc:yarn:1.18.1+build.1:v2") + } + def layers = spec.layers() + then: + spec.version == "layered+hash.1284206205" + layers.size() == 2 + layers[0].class == IntermediaryMappingsSpec + layers[1].class == FileMappingsSpec + ((layers[1] as FileMappingsSpec).fileSpec() as MavenFileSpec).dependencyNotation() == "net.fabricmc:yarn:1.18.1+build.1:v2" + } + LayeredMappingSpec layered(@DelegatesTo(LayeredMappingSpecBuilderImpl) Closure cl) { LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl() new ClosureAction(cl).execute(builder) diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsTestConstants.groovy index 45a4a9c..9dc25f0 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsTestConstants.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -44,4 +44,5 @@ interface LayeredMappingsTestConstants { public static final String PARCHMENT_NOTATION = "org.parchmentmc.data:parchment-1.16.5:20210608-SNAPSHOT@zip" public static final String PARCHMENT_URL = "https://maven.parchmentmc.net/org/parchmentmc/data/parchment-1.16.5/20210608-SNAPSHOT/parchment-1.16.5-20210608-SNAPSHOT.zip" + public static final String YARN_1_17_URL = "https://maven.fabricmc.net/net/fabricmc/yarn/1.17%2Bbuild.13/yarn-1.17%2Bbuild.13-v2.jar" } \ No newline at end of file diff --git a/src/test/resources/mappings/yarn-1.17.zip b/src/test/resources/mappings/yarn-1.17.zip new file mode 100644 index 0000000..b62cc49 Binary files /dev/null and b/src/test/resources/mappings/yarn-1.17.zip differ