diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index e5cfca02..9d8b0d49 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -58,6 +58,8 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.jvm.JvmLibrary; import org.gradle.language.base.artifact.SourcesArtifact; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; @@ -65,6 +67,7 @@ import net.fabricmc.loom.api.RemapConfigurationSettings; import net.fabricmc.loom.configuration.RemapConfigurations; import net.fabricmc.loom.configuration.mods.dependency.ModDependency; import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory; +import net.fabricmc.loom.configuration.mods.dependency.ModDependencyOptions; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; @@ -79,6 +82,8 @@ public class ModConfigurationRemapper { // This can happen when the dependency is a FileCollectionDependency or from a flatDir repository. public static final String MISSING_GROUP = "unspecified"; + private static final Logger LOGGER = LoggerFactory.getLogger(ModConfigurationRemapper.class); + public static void supplyModConfigurations(Project project, ServiceFactory serviceFactory, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) { final DependencyHandler dependencies = project.getDependencies(); // The configurations where the source and remapped artifacts go. @@ -135,6 +140,14 @@ public class ModConfigurationRemapper { } } + final ModDependencyOptions modDependencyOptions = ModDependencyOptions.create(project, ModDependencyOptions.class, options -> { + options.getMappings().set(mappingsSuffix); + }); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Mod dependency options: {}", modDependencyOptions.getJson()); + } + // Round 1: Discovery // Go through all the configs to find artifacts to remap and // the installer data. The installer data has to be added before @@ -176,7 +189,7 @@ public class ModConfigurationRemapper { continue; } - final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, mappingsSuffix, project); + final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, modDependencyOptions, project); scheduleSourcesRemapping(project, sourceRemapper, modDependency); modDependencies.add(modDependency); } @@ -332,7 +345,7 @@ public class ModConfigurationRemapper { } if (dependency.isCacheInvalid(project, "sources")) { - final Path output = dependency.getWorkingFile("sources"); + final Path output = dependency.getWorkingFile(project, "sources"); sourceRemapper.scheduleRemapSources(sourcesInput.toFile(), output.toFile(), false, true, () -> { try { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 32f5f66d..de026ac5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -253,8 +253,8 @@ public class ModProcessor { } } - private static Path getRemappedOutput(ModDependency dependency) { - return dependency.getWorkingFile(null); + private Path getRemappedOutput(ModDependency dependency) { + return dependency.getWorkingFile(project, null); } private void remapJarManifestEntries(Path jar) throws IOException { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java index 166de041..0a6ca669 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java @@ -37,23 +37,21 @@ import net.fabricmc.loom.configuration.mods.ArtifactRef; public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency { private final ArtifactRef artifact; private final ArtifactMetadata metadata; - protected final String group; - protected final String name; - protected final String version; + private final String group; + private final String name; + private final String version; @Nullable - protected final String classifier; - protected final String mappingsSuffix; - protected final Project project; + private final String classifier; + private final ModDependencyOptions options; - public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Project project) { + public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options) { this.artifact = artifact; this.metadata = metadata; this.group = artifact.group(); this.name = artifact.name(); this.version = artifact.version(); this.classifier = artifact.classifier(); - this.mappingsSuffix = mappingsSuffix; - this.project = project; + this.options = options; } /** @@ -71,10 +69,15 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod */ public abstract void applyToProject(Project project); - protected LocalMavenHelper createMaven(String name) { + /** + * Create a maven helper for the local cache. + * @param type The jar type, e.g "common" or "client" for split dependencies. + */ + protected LocalMavenHelper createMavenHelper(Project project, @Nullable String type) { final LoomGradleExtension extension = LoomGradleExtension.get(project); final Path root = extension.getFiles().getRemappedModCache().toPath(); - return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, root); + final String fullName = getName() + (type != null ? "-" + type : ""); + return new LocalMavenHelper(getGroup(), fullName, this.version, this.classifier, root); } public ArtifactRef getInputArtifact() { @@ -85,22 +88,26 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod return metadata; } - protected String getRemappedGroup() { - return getMappingsPrefix() + "." + group; + protected String getName() { + return "%s-%s".formatted(name, options.getCacheKey()); } - private String getMappingsPrefix() { - return mappingsSuffix.replace(".", "_").replace("-", "_").replace("+", "_"); + protected String getGroup() { + return "remapped.%s".formatted(group); + } + + protected String getVersion() { + return version; } public Path getInputFile() { return artifact.path(); } - public Path getWorkingFile(@Nullable String classifier) { + public Path getWorkingFile(Project project, @Nullable String classifier) { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getRemappedGroup(), name, version) - : String.format("%s-%s-%s-%s.jar", getRemappedGroup(), name, version, classifier); + final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getGroup(), getName(), version) + : String.format("%s-%s-%s-%s.jar", getGroup(), getName(), version, classifier); return extension.getFiles().getProjectBuildCache().toPath().resolve("remapped_working").resolve(fileName); } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java index c1909b57..04a03619 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java @@ -41,7 +41,7 @@ import net.fabricmc.loom.util.AttributeHelper; public class ModDependencyFactory { private static final String TARGET_ATTRIBUTE_KEY = "loom-target"; - public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) { + public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, ModDependencyOptions options, Project project) { if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) { final Optional cachedTarget = readTarget(artifact); JarSplitter.Target target; @@ -54,11 +54,11 @@ public class ModDependencyFactory { } if (target != null) { - return new SplitModDependency(artifact, metadata, mappingsSuffix, targetConfig, targetClientConfig, target, project); + return new SplitModDependency(artifact, metadata, options, targetConfig, targetClientConfig, target, project); } } - return new SimpleModDependency(artifact, metadata, mappingsSuffix, targetConfig, project); + return new SimpleModDependency(artifact, metadata, options, targetConfig, project); } private static Optional readTarget(ArtifactRef artifact) { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java new file mode 100644 index 00000000..25098fba --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java @@ -0,0 +1,36 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.mods.dependency; + +import org.gradle.api.provider.Property; + +import net.fabricmc.loom.util.CacheKey; + +/** + * Inputs used to process a mod dependency. The output jar is cached based on these properties. + */ +public abstract class ModDependencyOptions extends CacheKey { + public abstract Property getMappings(); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java index 34c82cf3..37078fcc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java @@ -40,10 +40,10 @@ public final class SimpleModDependency extends ModDependency { private final Configuration targetConfig; private final LocalMavenHelper maven; - public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetConfig, Project project) { - super(artifact, metadata, mappingsSuffix, project); + public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetConfig, Project project) { + super(artifact, metadata, options); this.targetConfig = Objects.requireNonNull(targetConfig); - this.maven = createMaven(name); + this.maven = createMavenHelper(project, null); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java index 7f088357..0d01c5d1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java @@ -48,13 +48,13 @@ public final class SplitModDependency extends ModDependency { @Nullable private final LocalMavenHelper clientMaven; - public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) { - super(artifact, metadata, mappingsSuffix, project); + public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) { + super(artifact, metadata, options); this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig); this.targetClientConfig = Objects.requireNonNull(targetClientConfig); this.target = Objects.requireNonNull(target); - this.commonMaven = target.common() ? createMaven(name + "-common") : null; - this.clientMaven = target.client() ? createMaven(name + "-client") : null; + this.commonMaven = target.common() ? createMavenHelper(project, "common") : null; + this.clientMaven = target.client() ? createMavenHelper(project, "client") : null; } @Override @@ -86,8 +86,8 @@ public final class SplitModDependency extends ModDependency { // Split the jar into 2 case SPLIT -> { final String suffix = variant == null ? "" : "-" + variant; - final Path commonTempJar = getWorkingFile("common" + suffix); - final Path clientTempJar = getWorkingFile("client" + suffix); + final Path commonTempJar = getWorkingFile(project, "common" + suffix); + final Path clientTempJar = getWorkingFile(project, "client" + suffix); final JarSplitter splitter = new JarSplitter(path); splitter.split(commonTempJar, clientTempJar); @@ -114,15 +114,16 @@ public final class SplitModDependency extends ModDependency { if (target == JarSplitter.Target.SPLIT) { createModGroup( + project, getCommonMaven().getOutputFile(null), getClientMaven().getOutputFile(null) ); } } - private void createModGroup(Path commonJar, Path clientJar) { + private void createModGroup(Project project, Path commonJar, Path clientJar) { LoomGradleExtension extension = LoomGradleExtension.get(project); - final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getRemappedGroup(), name, version)); + final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getGroup(), getName(), getVersion())); modSettings.getModFiles().from( commonJar.toFile(), clientJar.toFile() diff --git a/src/main/java/net/fabricmc/loom/util/CacheKey.java b/src/main/java/net/fabricmc/loom/util/CacheKey.java new file mode 100644 index 00000000..b98d28ce --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/CacheKey.java @@ -0,0 +1,60 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.nio.charset.StandardCharsets; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.tasks.Internal; + +import net.fabricmc.loom.util.gradle.GradleTypeAdapter; + +/** + * A simple base class for creating cache keys. Extend this class and create abstract properties to be included in the cache key. + */ +public abstract class CacheKey { + private static final int CHECKSUM_LENGTH = 8; + private final transient Supplier jsonSupplier = Suppliers.memoize(() -> GradleTypeAdapter.GSON.toJson(this)); + private final transient Supplier cacheKeySupplier = Suppliers.memoize(() -> Checksum.sha1Hex(jsonSupplier.get().getBytes(StandardCharsets.UTF_8)).substring(0, CHECKSUM_LENGTH)); + + public static T create(Project project, Class clazz, Action action) { + T instance = project.getObjects().newInstance(clazz); + action.execute(instance); + return instance; + } + + @Internal + public final String getJson() { + return jsonSupplier.get(); + } + + @Internal + public final String getCacheKey() { + return cacheKeySupplier.get(); + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ModDependencyOptionsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ModDependencyOptionsTest.groovy new file mode 100644 index 00000000..08a28505 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ModDependencyOptionsTest.groovy @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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 + +import spock.lang.Specification + +import net.fabricmc.loom.configuration.mods.dependency.ModDependencyOptions +import net.fabricmc.loom.test.util.GradleTestUtil +import net.fabricmc.loom.util.CacheKey + +class ModDependencyOptionsTest extends Specification { + def "test ModDependencyOptions cache key and json value"() { + given: + def project = GradleTestUtil.mockProject() + def modDependencyOptions = CacheKey.create(project, ModDependencyOptions) { + it.getMappings().set("testMappings") + } + + when: + def json = modDependencyOptions.getJson() + def cacheKey = modDependencyOptions.getCacheKey() + + then: + json == '{"__mappings__":"testMappings"}' + cacheKey == "c97692d3" + } +}