diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 1eef6031..8c7ae953 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -43,6 +43,7 @@ import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.ApiStatus; import net.fabricmc.loom.api.decompilers.DecompilerOptions; +import net.fabricmc.loom.api.manifest.VersionsManifestsAPI; import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.api.processor.MinecraftJarProcessor; @@ -51,6 +52,7 @@ import net.fabricmc.loom.api.remapping.RemapperParameters; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.providers.mappings.NoOpIntermediateMappingsProvider; +import net.fabricmc.loom.configuration.providers.minecraft.ManifestLocations; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.loom.util.DeprecationHelper; @@ -132,7 +134,23 @@ public interface LoomGradleExtensionAPI { InterfaceInjectionExtensionAPI getInterfaceInjection(); - Property getCustomMinecraftManifest(); + @ApiStatus.Experimental + default void versionsManifests(Action action) { + action.execute(getVersionsManifests()); + } + + @ApiStatus.Experimental + ManifestLocations getVersionsManifests(); + + /** + * @deprecated use {@linkplain #getCustomMinecraftMetadata} instead + */ + @Deprecated + default Property getCustomMinecraftManifest() { + return getCustomMinecraftMetadata(); + } + + Property getCustomMinecraftMetadata(); SetProperty getKnownIndyBsms(); diff --git a/src/main/java/net/fabricmc/loom/api/manifest/VersionsManifestsAPI.java b/src/main/java/net/fabricmc/loom/api/manifest/VersionsManifestsAPI.java new file mode 100644 index 00000000..e4fbc063 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/manifest/VersionsManifestsAPI.java @@ -0,0 +1,46 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.manifest; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface VersionsManifestsAPI { + /** + * Adds a URL to a versions manifest json with the default priority of {@code 0}. + * @param url the String-representation of the URL to the manifest json + */ + default void add(String url) { + add(url, 0); + } + + /** + * Adds a URL to a versions manifest json with the given priority. + * @param url the String-representation of the URL to the manifest json + * @param priority the priority with which this URL gets sorted against other entries + * entries are sorted by priority, from lowest to highest + */ + void add(String url, int priority); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestLocations.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestLocations.java new file mode 100644 index 00000000..f127e7d3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestLocations.java @@ -0,0 +1,93 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.minecraft; + +import java.nio.file.Path; +import java.util.Iterator; +import java.util.PriorityQueue; +import java.util.Queue; + +import net.fabricmc.loom.api.manifest.VersionsManifestsAPI; +import net.fabricmc.loom.configuration.providers.minecraft.ManifestLocations.ManifestLocation; + +public class ManifestLocations implements VersionsManifestsAPI, Iterable { + private static final String FILE_EXTENSION = ".json"; + private final Queue locations = new PriorityQueue<>(); + private final String baseFileName; + + public ManifestLocations(String baseFileName) { + this.baseFileName = baseFileName; + } + + public void addBuiltIn(int priority, String url, String fileName) { + locations.add(new ManifestLocation(priority, url, fileName)); + } + + @Override + public void add(String url, int priority) { + locations.add(new ManifestLocation(priority, url)); + } + + @Override + public Iterator iterator() { + return locations.iterator(); + } + + public class ManifestLocation implements Comparable { + private final int priority; + private final String url; + private final String builtInFileName; + + private ManifestLocation(int priority, String url) { + this(priority, url, null); + } + + private ManifestLocation(int priority, String url, String builtInFileName) { + this.priority = priority; + this.url = url; + this.builtInFileName = builtInFileName; + } + + public boolean isBuiltIn() { + return builtInFileName != null; + } + + public String url() { + return url; + } + + public Path cacheFile(Path dir) { + String fileName = (builtInFileName != null) + ? builtInFileName + FILE_EXTENSION + : baseFileName + "-" + Integer.toHexString(url.hashCode()) + FILE_EXTENSION; + return dir.resolve(fileName); + } + + @Override + public int compareTo(ManifestLocation o) { + return Integer.compare(priority, o.priority); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMetadataProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMetadataProvider.java index fd2eea8e..afcf3d27 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMetadataProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMetadataProvider.java @@ -27,25 +27,27 @@ package net.fabricmc.loom.configuration.providers.minecraft; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.function.Function; import org.gradle.api.Project; +import org.gradle.api.provider.Property; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.DependencyInfo; +import net.fabricmc.loom.configuration.providers.minecraft.ManifestLocations.ManifestLocation; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.download.DownloadBuilder; public final class MinecraftMetadataProvider { private final Options options; private final Function download; - private ManifestVersion.Versions versionEntry; + private ManifestEntryLocation versionEntry; private MinecraftVersionMeta versionMeta; private MinecraftMetadataProvider(Options options, Function download) { @@ -55,13 +57,11 @@ public final class MinecraftMetadataProvider { public static MinecraftMetadataProvider create(ConfigContext configContext) { final String minecraftVersion = resolveMinecraftVersion(configContext.project()); - final Path workingDir = MinecraftProvider.minecraftWorkingDirectory(configContext.project(), minecraftVersion).toPath(); return new MinecraftMetadataProvider( MinecraftMetadataProvider.Options.create( minecraftVersion, - configContext.project(), - workingDir.resolve("minecraft-info.json") + configContext.project() ), configContext.extension()::download ); @@ -92,28 +92,29 @@ public final class MinecraftMetadataProvider { return versionMeta; } - private ManifestVersion.Versions getVersionEntry() throws IOException { + private ManifestEntryLocation getVersionEntry() throws IOException { // Custom URL always takes priority if (options.customManifestUrl() != null) { - ManifestVersion.Versions customVersion = new ManifestVersion.Versions(); + VersionsManifest.Version customVersion = new VersionsManifest.Version(); customVersion.id = options.minecraftVersion(); customVersion.url = options.customManifestUrl(); - return customVersion; + return new ManifestEntryLocation(null, customVersion); } - final List suppliers = List.of( - // First try finding the version with caching - () -> getVersions(false), - // Then try finding the experimental version with caching - () -> getExperimentalVersions(false), - // Then force download Mojang's metadata to find the version - () -> getVersions(true), - // Finally try a force downloaded experimental metadata. - () -> getExperimentalVersions(true) - ); + final List suppliers = new ArrayList<>(); - for (ManifestVersionSupplier supplier : suppliers) { - final ManifestVersion.Versions version = supplier.get().getVersion(options.minecraftVersion()); + // First try finding the version with caching + for (ManifestLocation location : options.versionsManifests()) { + suppliers.add(() -> getManifestEntry(location, false)); + } + + // Then force download the manifest to find the version + for (ManifestLocation location : options.versionsManifests()) { + suppliers.add(() -> getManifestEntry(location, true)); + } + + for (ManifestEntrySupplier supplier : suppliers) { + final ManifestEntryLocation version = supplier.get(); if (version != null) { return version; @@ -123,16 +124,8 @@ public final class MinecraftMetadataProvider { throw new RuntimeException("Failed to find minecraft version: " + options.minecraftVersion()); } - private ManifestVersion getVersions(boolean forceDownload) throws IOException { - return getVersions(options.versionManifestUrl(), options.versionManifestPath(), forceDownload); - } - - private ManifestVersion getExperimentalVersions(boolean forceDownload) throws IOException { - return getVersions(options.experimentalVersionManifestUrl(), options.experimentalVersionManifestPath(), forceDownload); - } - - private ManifestVersion getVersions(String url, Path cacheFile, boolean forceDownload) throws IOException { - DownloadBuilder builder = download.apply(url); + private ManifestEntryLocation getManifestEntry(ManifestLocation location, boolean forceDownload) throws IOException { + DownloadBuilder builder = download.apply(location.url()); if (forceDownload) { builder = builder.forceDownload(); @@ -140,48 +133,77 @@ public final class MinecraftMetadataProvider { builder = builder.defaultCache(); } + final Path cacheFile = location.cacheFile(options.userCache()); final String versionManifest = builder.downloadString(cacheFile); - return LoomGradlePlugin.GSON.fromJson(versionManifest, ManifestVersion.class); + final VersionsManifest manifest = LoomGradlePlugin.GSON.fromJson(versionManifest, VersionsManifest.class); + final VersionsManifest.Version version = manifest.getVersion(options.minecraftVersion()); + + if (version != null) { + return new ManifestEntryLocation(location, version); + } + + return null; } private MinecraftVersionMeta readVersionMeta() throws IOException { - final DownloadBuilder builder = download.apply(versionEntry.url); + final DownloadBuilder builder = download.apply(versionEntry.entry.url); - if (versionEntry.sha1 != null) { - builder.sha1(versionEntry.sha1); + if (versionEntry.entry.sha1 != null) { + builder.sha1(versionEntry.entry.sha1); } else { builder.defaultCache(); } - final String json = builder.downloadString(options.minecraftMetadataPath()); + final String fileName = getVersionMetaFileName(); + final Path cacheFile = options.workingDir().resolve(fileName); + final String json = builder.downloadString(cacheFile); return LoomGradlePlugin.GSON.fromJson(json, MinecraftVersionMeta.class); } + private String getVersionMetaFileName() { + String base = "minecraft-info"; + + // custom version metadata + if (versionEntry.manifest == null) { + return base + Integer.toHexString(versionEntry.entry.url.hashCode()) + ".json"; + } + + // custom versions manifest + if (!versionEntry.manifest.isBuiltIn()) { + return base + Integer.toHexString(versionEntry.manifest.url().hashCode()) + ".json"; + } + + return base + ".json"; + } + public record Options(String minecraftVersion, - String versionManifestUrl, - String experimentalVersionManifestUrl, + ManifestLocations versionsManifests, @Nullable String customManifestUrl, - Path versionManifestPath, - Path experimentalVersionManifestPath, - Path minecraftMetadataPath) { - public static Options create(String minecraftVersion, Project project, Path minecraftMetadataPath) { + Path userCache, + Path workingDir) { + public static Options create(String minecraftVersion, Project project) { final LoomGradleExtension extension = LoomGradleExtension.get(project); final Path userCache = extension.getFiles().getUserCache().toPath(); + final Path workingDir = MinecraftProvider.minecraftWorkingDirectory(project, minecraftVersion).toPath(); + + final ManifestLocations manifestLocations = extension.getVersionsManifests(); + final Property customMetaUrl = extension.getCustomMinecraftMetadata(); return new Options( minecraftVersion, - MirrorUtil.getVersionManifests(project), - MirrorUtil.getExperimentalVersions(project), - extension.getCustomMinecraftManifest().getOrNull(), - userCache.resolve("version_manifest.json"), - userCache.resolve("experimental_version_manifest.json"), - minecraftMetadataPath + manifestLocations, + customMetaUrl.getOrNull(), + userCache, + workingDir ); } } @FunctionalInterface - private interface ManifestVersionSupplier { - ManifestVersion get() throws IOException; + private interface ManifestEntrySupplier { + ManifestEntryLocation get() throws IOException; + } + + private record ManifestEntryLocation(ManifestLocation manifest, VersionsManifest.Version entry) { } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/VersionsManifest.java similarity index 91% rename from src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java rename to src/main/java/net/fabricmc/loom/configuration/providers/minecraft/VersionsManifest.java index 391683da..f4870d7c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ManifestVersion.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/VersionsManifest.java @@ -29,13 +29,13 @@ import java.util.Map; import org.jetbrains.annotations.Nullable; -public record ManifestVersion(List versions, Map latest) { - public static class Versions { +public record VersionsManifest(List versions, Map latest) { + public static class Version { public String id, url, sha1; } @Nullable - public Versions getVersion(String id) { + public Version getVersion(String id) { return versions.stream() .filter(versions -> versions.id.equalsIgnoreCase(id)) .findFirst() diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 49bc25b4..5e650087 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -66,10 +66,12 @@ import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory; +import net.fabricmc.loom.configuration.providers.minecraft.ManifestLocations; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.loom.util.DeprecationHelper; +import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.fmj.FabricModJson; import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.loom.util.gradle.SourceSetHelper; @@ -83,7 +85,8 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final ListProperty jarProcessors; protected final ConfigurableFileCollection log4jConfigs; protected final RegularFileProperty accessWidener; - protected final Property customManifest; + protected final ManifestLocations versionsManifests; + protected final Property customMetadata; protected final SetProperty knownIndyBsms; protected final Property transitiveAccessWideners; protected final Property modProvidedJavadoc; @@ -114,7 +117,10 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA .empty(); this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile()); this.accessWidener = project.getObjects().fileProperty(); - this.customManifest = project.getObjects().property(String.class); + this.versionsManifests = new ManifestLocations("versions_manifest"); + this.versionsManifests.addBuiltIn(-2, MirrorUtil.getVersionManifests(project), "versions_manifest"); + this.versionsManifests.addBuiltIn(-1, MirrorUtil.getExperimentalVersions(project), "experimental_versions_manifest"); + this.customMetadata = project.getObjects().property(String.class); this.knownIndyBsms = project.getObjects().setProperty(String.class).convention(Set.of( "java/lang/invoke/StringConcatFactory", "java/lang/runtime/ObjectMethods", @@ -254,8 +260,13 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA } @Override - public Property getCustomMinecraftManifest() { - return customManifest; + public ManifestLocations getVersionsManifests() { + return versionsManifests; + } + + @Override + public Property getCustomMinecraftMetadata() { + return customMetadata; } @Override diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/providers/MinecraftMetadataProviderTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/providers/MinecraftMetadataProviderTest.groovy index 815d34b1..4566a881 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/providers/MinecraftMetadataProviderTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/providers/MinecraftMetadataProviderTest.groovy @@ -29,6 +29,7 @@ import java.nio.file.Path import org.intellij.lang.annotations.Language +import net.fabricmc.loom.configuration.providers.minecraft.ManifestLocations import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMetadataProvider import net.fabricmc.loom.test.LoomTestConstants import net.fabricmc.loom.test.unit.download.DownloadTest @@ -152,14 +153,16 @@ class MinecraftMetadataProviderTest extends DownloadTest { } private MinecraftMetadataProvider.Options options(String version, String customUrl) { + ManifestLocations manifests = new ManifestLocations("versions_manifest") + manifests.addBuiltIn(0, "$PATH/versionManifest", "versions_manifest") + manifests.addBuiltIn(1, "$PATH/experimentalVersionManifest", "experimental_versions_manifest") + return new MinecraftMetadataProvider.Options( version, - "$PATH/versionManifest", - "$PATH/experimentalVersionManifest", + manifests, customUrl, - testDir.resolve("version_manifest.json"), - testDir.resolve("experimental_version_manifest.json"), - testDir.resolve("${version}.json") + testDir, + testDir ) } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/MinecraftTestUtils.groovy b/src/test/groovy/net/fabricmc/loom/test/util/MinecraftTestUtils.groovy index 8fb35383..42084f0d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/MinecraftTestUtils.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/MinecraftTestUtils.groovy @@ -29,8 +29,8 @@ import java.time.Duration import com.google.gson.Gson import com.google.gson.GsonBuilder -import net.fabricmc.loom.configuration.providers.minecraft.ManifestVersion import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta +import net.fabricmc.loom.configuration.providers.minecraft.VersionsManifest import net.fabricmc.loom.test.LoomTestConstants import net.fabricmc.loom.util.Constants import net.fabricmc.loom.util.download.Download @@ -41,7 +41,7 @@ class MinecraftTestUtils { static MinecraftVersionMeta getVersionMeta(String id) { def versionManifest = download(Constants.VERSION_MANIFESTS, "version_manifest.json") - def manifest = GSON.fromJson(versionManifest, ManifestVersion.class) + def manifest = GSON.fromJson(versionManifest, VersionsManifest.class) def version = manifest.versions().find { it.id == id } def metaJson = download(version.url, "${id}.json")