From eca987a2d3f762d421efa9a695898ce3da2ff66b Mon Sep 17 00:00:00 2001
From: Finn Rades <64548817+zOnlyKroks@users.noreply.github.com>
Date: Fri, 7 Nov 2025 22:06:07 +0100
Subject: [PATCH 1/8] Add nestJars API for nesting locally built mod jars
(#1427)
* Add nestJars API for FileCollection support
* Add missin new-line
* Fix imports
* Rework constructor to avoid ugliness
* Update java docs
* Update java docs
* Implement asked improvements
* Fix checkstyle
---
.../loom/api/LoomGradleExtensionAPI.java | 24 +++++++++
.../extension/LoomGradleExtensionApiImpl.java | 7 +++
.../extension/LoomGradleExtensionImpl.java | 17 ++++++
.../fabricmc/loom/task/NestJarsAction.java | 40 +++++---------
.../task/NonRemappedJarTaskConfiguration.java | 5 +-
.../test/integration/NestJarsApiTest.groovy | 54 +++++++++++++++++++
.../projects/nestJarsApi/build.gradle | 30 +++++++++++
.../nested-mod-content/fabric.mod.json | 8 +++
.../projects/nestJarsApi/settings.gradle | 1 +
.../src/main/resources/fabric.mod.json | 8 +++
10 files changed, 166 insertions(+), 28 deletions(-)
create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/NestJarsApiTest.groovy
create mode 100644 src/test/resources/projects/nestJarsApi/build.gradle
create mode 100644 src/test/resources/projects/nestJarsApi/nested-mod-content/fabric.mod.json
create mode 100644 src/test/resources/projects/nestJarsApi/settings.gradle
create mode 100644 src/test/resources/projects/nestJarsApi/src/main/resources/fabric.mod.json
diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
index 4da936ba..f1284d9d 100644
--- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
+++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
@@ -40,6 +40,8 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.jvm.tasks.Jar;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
@@ -272,4 +274,26 @@ public interface LoomGradleExtensionAPI {
* @return A lazily evaluated {@link FileCollection} containing the named minecraft jars.
*/
FileCollection getNamedMinecraftJars();
+
+ /**
+ * Nest mod jars from a {@link FileCollection} into the specified jar task.
+ * This is useful for including locally built mod jars or jars that don't come from Maven.
+ *
+ *
Important: The jars must already be valid mod jars (containing a fabric.mod.json file).
+ * Non-mod jars will be rejected.
+ *
+ *
Example usage:
+ * {@snippet lang=groovy :
+ * loom {
+ * nestJars(tasks.jar, files('local-mod.jar'))
+ * nestJars(tasks.remapJar, tasks.named('buildOtherMod'))
+ * }
+ * }
+ *
+ * @param jarTask the jar task to nest jars into (can be jar or remapJar)
+ * @param jars the file collection containing mod jars to nest
+ * @since 1.14
+ */
+ @ApiStatus.Experimental
+ void nestJars(TaskProvider extends Jar> jarTask, FileCollection jars);
}
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
index 4ab91c17..f3cff2a8 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
@@ -45,6 +45,8 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
@@ -543,5 +545,10 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
public MixinExtension getMixin() {
throw new RuntimeException("Yeah... something is really wrong");
}
+
+ @Override
+ public void nestJars(TaskProvider extends Jar> jarTask, FileCollection jars) {
+ throw new RuntimeException("Yeah... something is really wrong");
+ }
}
}
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
index 4c332e44..ec07da21 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
@@ -40,6 +40,8 @@ import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomNoRemapGradlePlugin;
@@ -57,6 +59,8 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
+import net.fabricmc.loom.task.NestJarsAction;
+import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.download.Download;
import net.fabricmc.loom.util.download.DownloadBuilder;
@@ -339,4 +343,17 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
public boolean disableObfuscation() {
return disableObfuscation.get();
}
+
+ @Override
+ public void nestJars(TaskProvider extends Jar> jarTask, FileCollection jars) {
+ jarTask.configure(task -> {
+ if (task instanceof RemapJarTask remapJarTask) {
+ // For RemapJarTask, add to the nestedJars property
+ remapJarTask.getNestedJars().from(jars);
+ } else {
+ // For regular Jar tasks (non-remap mode), add a NestJarsAction with the FileCollection
+ task.doLast(new NestJarsAction(jars));
+ }
+ });
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/task/NestJarsAction.java b/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
index 200ef520..54f52059 100644
--- a/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
+++ b/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
@@ -26,12 +26,12 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.Serializable;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
import org.gradle.api.Action;
import org.gradle.api.Task;
-import org.gradle.api.file.Directory;
-import org.gradle.api.provider.Provider;
+import org.gradle.api.file.FileCollection;
import org.gradle.jvm.tasks.Jar;
import org.jetbrains.annotations.NotNull;
@@ -39,38 +39,26 @@ import net.fabricmc.loom.build.nesting.JarNester;
/**
* Configuration-cache-compatible action for nesting jars.
- * Uses a provider to avoid capturing task references at configuration time.
+ * Uses a FileCollection to avoid capturing task references at configuration time.
* Do NOT turn me into a record!
*/
-class NestJarsAction implements Action, Serializable {
- private final Provider nestedJarsDir;
+public class NestJarsAction implements Action, Serializable {
+ private final FileCollection jars;
- NestJarsAction(Provider nestedJarsDir) {
- this.nestedJarsDir = nestedJarsDir;
+ public NestJarsAction(FileCollection jars) {
+ this.jars = jars;
}
@Override
public void execute(@NotNull Task t) {
final Jar jarTask = (Jar) t;
final File jarFile = jarTask.getArchiveFile().get().getAsFile();
+ final List allJars = new ArrayList<>(jars.getFiles());
- if (!nestedJarsDir.isPresent()) {
- return;
- }
-
- final File outputDir = nestedJarsDir.get().getAsFile();
-
- if (outputDir.exists() && outputDir.isDirectory()) {
- final File[] jars = outputDir.listFiles((dir, name) -> name.endsWith(".jar"));
-
- if (jars != null && jars.length > 0) {
- JarNester.nestJars(
- Arrays.asList(jars),
- jarFile,
- jarTask.getLogger()
- );
- jarTask.getLogger().lifecycle("Nested {} jar(s) into {}", jars.length, jarFile.getName());
- }
+ // Nest all collected jars
+ if (!allJars.isEmpty()) {
+ JarNester.nestJars(allJars, jarFile, jarTask.getLogger());
+ jarTask.getLogger().lifecycle("Nested {} jar(s) into {}", allJars.size(), jarFile.getName());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
index 56a9e00e..57b5f186 100644
--- a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
@@ -52,10 +52,11 @@ public class NonRemappedJarTaskConfiguration {
project.getTasks().named(JavaPlugin.JAR_TASK_NAME, Jar.class).configure(task -> {
task.dependsOn(processIncludeJarsTask);
// Use JarNester to properly add jars and update fabric.mod.json
- task.doLast(new NestJarsAction(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory)));
+ task.doLast(new NestJarsAction(project.fileTree(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory))
+ .matching(pattern -> pattern.include("*.jar"))));
});
// Add jar task to unmapped collection
extension.getUnmappedModCollection().from(project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME));
}
-}
\ No newline at end of file
+}
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/NestJarsApiTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/NestJarsApiTest.groovy
new file mode 100644
index 00000000..2a880690
--- /dev/null
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/NestJarsApiTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.integration
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import net.fabricmc.loom.test.util.GradleProjectTestTrait
+
+import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
+import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
+
+class NestJarsApiTest extends Specification implements GradleProjectTestTrait {
+ @Unroll
+ def "nest jars using loom.nestJars() API (gradle #version)"() {
+ setup:
+ def gradle = gradleProject(project: "nestJarsApi", version: version)
+
+ when:
+ def result = gradle.run(tasks: ["remapJar"])
+
+ then:
+ result.task(":remapJar").outcome == SUCCESS
+ result.task(":createNestedMod").outcome == SUCCESS
+
+ // Assert the locally built mod jar is nested using the new API
+ gradle.hasOutputZipEntry("nestJarsApi.jar", "META-INF/jars/nested-mod.jar")
+
+ where:
+ version << STANDARD_TEST_VERSIONS
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/projects/nestJarsApi/build.gradle b/src/test/resources/projects/nestJarsApi/build.gradle
new file mode 100644
index 00000000..c230b03a
--- /dev/null
+++ b/src/test/resources/projects/nestJarsApi/build.gradle
@@ -0,0 +1,30 @@
+plugins {
+ id 'fabric-loom'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ minecraft 'com.mojang:minecraft:1.20.1'
+ mappings loom.officialMojangMappings()
+}
+
+// Create a simple mod jar file to nest
+task createNestedMod(type: Jar) {
+ archiveBaseName = 'nested-mod'
+ destinationDirectory = layout.buildDirectory.dir('nested-mods')
+
+ from('nested-mod-content') {
+ include 'fabric.mod.json'
+ }
+}
+
+// Use the new nestJars API to nest the locally built jar
+loom {
+ nestJars(tasks.named('remapJar'), files(createNestedMod.outputs.files))
+}
+
+// Make sure remapJar depends on the nested mod being created
+tasks.remapJar.dependsOn(createNestedMod)
\ No newline at end of file
diff --git a/src/test/resources/projects/nestJarsApi/nested-mod-content/fabric.mod.json b/src/test/resources/projects/nestJarsApi/nested-mod-content/fabric.mod.json
new file mode 100644
index 00000000..0c638f6c
--- /dev/null
+++ b/src/test/resources/projects/nestJarsApi/nested-mod-content/fabric.mod.json
@@ -0,0 +1,8 @@
+{
+ "schemaVersion": 1,
+ "id": "nestedmod",
+ "version": "1.0.0",
+ "name": "Nested Mod",
+ "description": "This mod will be nested using the nestJars API",
+ "environment": "*"
+}
\ No newline at end of file
diff --git a/src/test/resources/projects/nestJarsApi/settings.gradle b/src/test/resources/projects/nestJarsApi/settings.gradle
new file mode 100644
index 00000000..b242e076
--- /dev/null
+++ b/src/test/resources/projects/nestJarsApi/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'nestJarsApi'
\ No newline at end of file
diff --git a/src/test/resources/projects/nestJarsApi/src/main/resources/fabric.mod.json b/src/test/resources/projects/nestJarsApi/src/main/resources/fabric.mod.json
new file mode 100644
index 00000000..92d3280f
--- /dev/null
+++ b/src/test/resources/projects/nestJarsApi/src/main/resources/fabric.mod.json
@@ -0,0 +1,8 @@
+{
+ "schemaVersion": 1,
+ "id": "mainmod",
+ "version": "1.0.0",
+ "name": "Main Mod",
+ "description": "Main mod that nests another mod using nestJars API",
+ "environment": "*"
+}
\ No newline at end of file
From ec0026113618583ee2c58858d76f56fb71e0f5c1 Mon Sep 17 00:00:00 2001
From: modmuss
Date: Fri, 7 Nov 2025 21:54:51 +0000
Subject: [PATCH 2/8] Find and apply installer data (#1428)
---
.../configuration/DebofInstallerData.java | 82 +++++++++++++++++++
.../loom/configuration/InstallerData.java | 8 ++
.../configuration/LoomConfigurations.java | 5 ++
.../configuration/LoomDependencyManager.java | 2 +-
.../configuration/mods/ArtifactMetadata.java | 10 +--
.../speccontext/DeobfSpecContext.java | 2 -
.../noRemap/SimpleDebofTest.groovy | 3 +
7 files changed, 101 insertions(+), 11 deletions(-)
create mode 100644 src/main/java/net/fabricmc/loom/configuration/DebofInstallerData.java
diff --git a/src/main/java/net/fabricmc/loom/configuration/DebofInstallerData.java b/src/main/java/net/fabricmc/loom/configuration/DebofInstallerData.java
new file mode 100644
index 00000000..84ee0f31
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/DebofInstallerData.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.fabricmc.loom.configuration.processors.speccontext.DebofConfiguration;
+import net.fabricmc.loom.util.ZipUtils;
+import net.fabricmc.loom.util.fmj.FabricModJson;
+import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
+
+public class DebofInstallerData {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DebofInstallerData.class);
+
+ public static void findAndApply(Project project) {
+ for (DebofConfiguration debofConfiguration : DebofConfiguration.ALL) {
+ for (Configuration configuration : debofConfiguration.getConfigurations(project)) {
+ Optional installerData = configuration.getFiles().parallelStream()
+ .map(DebofInstallerData::getInstaller)
+ .filter(Objects::nonNull)
+ .findFirst();
+
+ if (installerData.isPresent()) {
+ LOGGER.info("Applying installer data from configuration '{}'", configuration.getName());
+ installerData.get().applyToProject(project);
+ return;
+ }
+ }
+ }
+
+ LOGGER.info("No installer data found in any configuration.");
+ }
+
+ @Nullable
+ private static InstallerData getInstaller(File file) {
+ try {
+ byte[] installerData = ZipUtils.unpackNullable(file.toPath(), InstallerData.INSTALLER_PATH);
+
+ if (installerData == null) {
+ return null;
+ }
+
+ FabricModJson fabricModJson = FabricModJsonFactory.createFromZip(file.toPath());
+ LOGGER.info("Found installer in mod {} version {}", fabricModJson.getId(), fabricModJson.getModVersion());
+ return InstallerData.fromBytes(installerData, fabricModJson.getModVersion());
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read " + file, e);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java b/src/main/java/net/fabricmc/loom/configuration/InstallerData.java
index ce8da78d..639e7b30 100644
--- a/src/main/java/net/fabricmc/loom/configuration/InstallerData.java
+++ b/src/main/java/net/fabricmc/loom/configuration/InstallerData.java
@@ -24,6 +24,8 @@
package net.fabricmc.loom.configuration;
+import java.nio.charset.StandardCharsets;
+
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -36,13 +38,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.LoomRepositoryPlugin;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.util.Constants;
public record InstallerData(String version, JsonObject installerJson) {
+ public static final String INSTALLER_PATH = "fabric-installer.json";
private static final Logger LOGGER = LoggerFactory.getLogger(InstallerData.class);
+ public static InstallerData fromBytes(byte[] bytes, String version) {
+ return new InstallerData(version, LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonObject.class));
+ }
+
public void applyToProject(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
index fad2cb1a..c3c098ab 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
@@ -45,6 +45,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.configuration.processors.speccontext.DebofConfiguration;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -161,6 +162,10 @@ public abstract class LoomConfigurations implements Runnable {
extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.LOADER_DEPENDENCIES);
register(Constants.Configurations.PRODUCTION_RUNTIME_MODS, Role.RESOLVABLE);
+
+ if (extension.disableObfuscation()) {
+ DebofConfiguration.create(getProject());
+ }
}
private NamedDomainObjectProvider register(String name, Role role) {
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
index 17610851..2237933d 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
@@ -56,6 +56,6 @@ public record LoomDependencyManager(Project project, ServiceFactory serviceFacto
}
private void handleNonRemapDependencies() {
- // TODO debof - do we need to do anything?
+ DebofInstallerData.findAndApply(project);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java
index 4ee3c72d..de6e81fd 100644
--- a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java
+++ b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java
@@ -26,7 +26,6 @@ package net.fabricmc.loom.configuration.mods;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -37,18 +36,14 @@ import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
-import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
-import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData, MixinRemapType mixinRemapType, List knownIdyBsms) {
- private static final String INSTALLER_PATH = "fabric-installer.json";
-
public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVersion) throws IOException {
boolean isFabricMod;
RemapRequirements remapRequirements = RemapRequirements.DEFAULT;
@@ -90,11 +85,10 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
}
}
- final Path installerPath = fs.getPath(INSTALLER_PATH);
+ final Path installerPath = fs.getPath(InstallerData.INSTALLER_PATH);
if (isFabricMod && Files.exists(installerPath)) {
- final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(installerPath, StandardCharsets.UTF_8), JsonObject.class);
- installerData = new InstallerData(artifact.version(), jsonObject);
+ installerData = InstallerData.fromBytes(Files.readAllBytes(installerPath), artifact.version());
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
index 78b9b307..2982046f 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
@@ -53,8 +53,6 @@ public record DeobfSpecContext(List modDependencies,
List modDependenciesCompileRuntimeClient
) implements SpecContext {
public static DeobfSpecContext create(Project project) {
- DebofConfiguration.create(project);
-
return create(new DeobfProjectView.Impl(project));
}
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
index 0f7fb59c..0574e411 100644
--- a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
@@ -41,6 +41,7 @@ class SimpleDebofTest extends Specification implements GradleProjectTestTrait {
gradle.buildGradle << '''
dependencies {
minecraft 'com.mojang:minecraft:25w45a_unobfuscated'
+ implementation "net.fabricmc:fabric-loader:0.17.3"
}
'''
def sourceFile = new File(gradle.projectDir, "src/main/java/example/Test.java")
@@ -50,6 +51,8 @@ class SimpleDebofTest extends Specification implements GradleProjectTestTrait {
import net.minecraft.resources.Identifier;
+ import org.spongepowered.asm.mixin.Mixin; // Make sure we applied loaders deps via the installer data
+
public class Test {
public static void main(String[] args) {
Identifier id = Identifier.fromNamespaceAndPath("loom", "test");
From 43e1ad7b3169a17ee666b30e140077adcb42ee35 Mon Sep 17 00:00:00 2001
From: modmuss
Date: Fri, 7 Nov 2025 22:07:13 +0000
Subject: [PATCH 3/8] Disable remap classpath system prop (#1426)
* Disable remap classpath system prop
* Fix build
---
.../net/fabricmc/loom/task/LoomTasks.java | 132 +++++++++++-------
.../task/launch/GenerateDLIConfigTask.java | 6 +-
.../noRemap/SimpleDebofTest.groovy | 6 +-
3 files changed, 88 insertions(+), 56 deletions(-)
diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
index 312d558d..c39023f2 100644
--- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java
+++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
@@ -25,6 +25,7 @@
package net.fabricmc.loom.task;
import java.io.File;
+import java.util.Objects;
import javax.inject.Inject;
@@ -36,6 +37,7 @@ import org.gradle.api.tasks.Sync;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskOutputs;
import org.gradle.api.tasks.TaskProvider;
+import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
@@ -60,6 +62,82 @@ public abstract class LoomTasks implements Runnable {
@Override
public void run() {
+ LoomGradleExtension extension = LoomGradleExtension.get(getProject());
+
+ if (!extension.disableObfuscation()) {
+ registerMigrateMappingsTasks();
+ }
+
+ var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
+ t.setDescription("Generate the log4j config file");
+ });
+
+ @Nullable TaskProvider generateRemapClasspath = null;
+
+ if (!extension.disableObfuscation()) {
+ generateRemapClasspath = getTasks().register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> {
+ t.setDescription("Generate the remap classpath file");
+ });
+ }
+
+ // Make the lambda happy
+ final @Nullable TaskProvider generateRemapClasspathTask = generateRemapClasspath;
+
+ getTasks().register("generateDLIConfig", GenerateDLIConfigTask.class, t -> {
+ t.setDescription("Generate the DevLaunchInjector config file");
+
+ // Must allow these IDE files to be generated first
+ t.mustRunAfter("eclipse");
+
+ t.dependsOn(generateLog4jConfig);
+
+ if (!extension.disableObfuscation()) {
+ GenerateRemapClasspathTask remapClasspath = Objects.requireNonNull(generateRemapClasspathTask.get());
+ t.getRemapClasspathFile().set(remapClasspath.getRemapClasspathFile());
+ }
+ });
+
+ getTasks().register("configureLaunch", task -> {
+ task.dependsOn(getTasks().named("generateDLIConfig"));
+ task.dependsOn(getTasks().named("generateLog4jConfig"));
+
+ if (!extension.disableObfuscation()) {
+ task.dependsOn(getTasks().named("generateRemapClasspath"));
+ }
+
+ task.setDescription("Setup the required files to launch Minecraft");
+ task.setGroup(Constants.TaskGroup.FABRIC);
+ });
+
+ TaskProvider validateAccessWidener = getTasks().register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> {
+ t.setDescription("Validate all the rules in the access widener against the Minecraft jar");
+ t.setGroup("verification");
+ });
+
+ getTasks().named("check").configure(task -> task.dependsOn(validateAccessWidener));
+
+ registerIDETasks();
+ registerRunTasks();
+
+ // Must be done in afterEvaluate to allow time for the build script to configure the jar config.
+ GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
+ if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
+ // Server only, nothing more to do.
+ return;
+ }
+
+ final MinecraftVersionMeta versionInfo = extension.getMinecraftProvider().getVersionInfo();
+
+ if (versionInfo == null) {
+ // Something has gone wrong, don't register the task.
+ return;
+ }
+
+ registerClientSetupTasks(getTasks(), versionInfo.hasNativesToExtract());
+ });
+ }
+
+ private void registerMigrateMappingsTasks() {
SourceSetHelper.getSourceSets(getProject()).all(sourceSet -> {
if (SourceSetHelper.isMainSourceSet(sourceSet)) {
getTasks().register("migrateMappings", MigrateMappingsTask.class, t -> {
@@ -83,60 +161,6 @@ public abstract class LoomTasks implements Runnable {
getTasks().register("migrateClassTweakerMappings", MigrateClassTweakerMappingsTask.class, t -> {
t.setDescription("Migrates access widener and class tweaker mappings to a new version.");
});
-
- var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
- t.setDescription("Generate the log4j config file");
- });
- var generateRemapClasspath = getTasks().register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> {
- t.setDescription("Generate the remap classpath file");
- });
- getTasks().register("generateDLIConfig", GenerateDLIConfigTask.class, t -> {
- t.setDescription("Generate the DevLaunchInjector config file");
-
- // Must allow these IDE files to be generated first
- t.mustRunAfter("eclipse");
-
- t.dependsOn(generateLog4jConfig);
- t.getRemapClasspathFile().set(generateRemapClasspath.get().getRemapClasspathFile());
- });
-
- getTasks().register("configureLaunch", task -> {
- task.dependsOn(getTasks().named("generateDLIConfig"));
- task.dependsOn(getTasks().named("generateLog4jConfig"));
- task.dependsOn(getTasks().named("generateRemapClasspath"));
-
- task.setDescription("Setup the required files to launch Minecraft");
- task.setGroup(Constants.TaskGroup.FABRIC);
- });
-
- TaskProvider validateAccessWidener = getTasks().register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> {
- t.setDescription("Validate all the rules in the access widener against the Minecraft jar");
- t.setGroup("verification");
- });
-
- getTasks().named("check").configure(task -> task.dependsOn(validateAccessWidener));
-
- registerIDETasks();
- registerRunTasks();
-
- // Must be done in afterEvaluate to allow time for the build script to configure the jar config.
- GradleUtils.afterSuccessfulEvaluation(getProject(), () -> {
- LoomGradleExtension extension = LoomGradleExtension.get(getProject());
-
- if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
- // Server only, nothing more to do.
- return;
- }
-
- final MinecraftVersionMeta versionInfo = extension.getMinecraftProvider().getVersionInfo();
-
- if (versionInfo == null) {
- // Something has gone wrong, don't register the task.
- return;
- }
-
- registerClientSetupTasks(getTasks(), versionInfo.hasNativesToExtract());
- });
}
private void registerIDETasks() {
diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
index a6ab0b26..8e04873a 100644
--- a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
+++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
@@ -89,6 +89,7 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
protected abstract Property getNativesDirectoryPath();
@InputFile
+ @Optional
public abstract RegularFileProperty getRemapClasspathFile();
@OutputFile
@@ -128,7 +129,6 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
final LaunchConfig launchConfig = new LaunchConfig()
.property("fabric.development", "true")
- .property("fabric.remapClasspathFile", getRemapClasspathFile().get().getAsFile().getAbsolutePath())
.property("log4j.configurationFile", getLog4jConfigPaths().get())
.property("log4j2.formatMsgNoLookups", "true")
@@ -137,6 +137,10 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.argument("client", "--assetsDir")
.argument("client", assetsDirectory.getAbsolutePath());
+ if (getRemapClasspathFile().isPresent()) {
+ launchConfig.property("fabric.remapClasspathFile", getRemapClasspathFile().get().getAsFile().getAbsolutePath());
+ }
+
if (versionInfo.hasNativesToExtract()) {
String nativesPath = getNativesDirectoryPath().get();
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
index 0574e411..55e21070 100644
--- a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
@@ -62,9 +62,13 @@ class SimpleDebofTest extends Specification implements GradleProjectTestTrait {
sourceFile.text = src
when:
- def result = gradle.run(task: "build")
+ def result = gradle.run(tasks: [
+ "build",
+ "configureClientLaunch"
+ ])
then:
result.task(":build").outcome == SUCCESS
+ result.task(":configureClientLaunch").outcome == SUCCESS
}
}
From f9dbaae926ca0eefdf36fceb41b3e1dad7486503 Mon Sep 17 00:00:00 2001
From: Finn Rades <64548817+zOnlyKroks@users.noreply.github.com>
Date: Fri, 7 Nov 2025 23:40:08 +0100
Subject: [PATCH 4/8] Add manifest attributes and jar filtering to non-remapped
jar task (#1429)
* Add manifest attributes and jar filtering to non-remapped jar task
* Fix checkstyle
* Implement feedback
---
.../loom/task/ManifestModificationAction.java | 111 ++++++++++++++++++
.../task/NonRemappedJarTaskConfiguration.java | 46 +++++++-
.../noRemap/IncludedJarsNoRemapTest.groovy | 16 +++
3 files changed, 170 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
diff --git a/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java b/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
new file mode 100644
index 00000000..328e1c7c
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
@@ -0,0 +1,111 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2016-2021 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.task;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UncheckedIOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Manifest;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.provider.Provider;
+import org.gradle.jvm.tasks.Jar;
+import org.jetbrains.annotations.NotNull;
+
+import net.fabricmc.loom.task.service.JarManifestService;
+import net.fabricmc.loom.util.Check;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.ZipUtils;
+
+/**
+ * Action that modifies the manifest of a jar file to add Loom metadata.
+ * Configuration-cache-compatible implementation using providers.
+ */
+public class ManifestModificationAction implements Action, Serializable {
+ private final Provider manifestService;
+ private final String targetNamespace;
+ private final boolean areEnvironmentSourceSetsSplit;
+ private final List clientOnlyEntries;
+
+ public ManifestModificationAction(
+ Provider manifestService,
+ String targetNamespace,
+ boolean areEnvironmentSourceSetsSplit,
+ List clientOnlyEntries) {
+ this.manifestService = manifestService;
+ this.targetNamespace = targetNamespace;
+ this.areEnvironmentSourceSetsSplit = areEnvironmentSourceSetsSplit;
+ this.clientOnlyEntries = clientOnlyEntries;
+ }
+
+ @Override
+ public void execute(@NotNull Task t) {
+ final Jar jarTask = (Jar) t;
+ final File jarFile = jarTask.getArchiveFile().get().getAsFile();
+
+ try {
+ modifyManifest(jarFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to modify jar manifest for " + jarFile.getName(), e);
+ }
+ }
+
+ private void modifyManifest(File jarFile) throws IOException {
+ Map manifestAttributes = new HashMap<>();
+
+ // Set the mapping namespace to "official" for non-remapped jars
+ manifestAttributes.put(Constants.Manifest.MAPPING_NAMESPACE, targetNamespace);
+
+ // Set split environment flag if source sets are split (even for common-only jars)
+ if (areEnvironmentSourceSetsSplit) {
+ manifestAttributes.put(Constants.Manifest.SPLIT_ENV, "true");
+ }
+
+ // Add client-only entries list if present
+ if (clientOnlyEntries != null && !clientOnlyEntries.isEmpty()) {
+ manifestAttributes.put(Constants.Manifest.CLIENT_ENTRIES, String.join(";", clientOnlyEntries));
+ }
+
+ int count = ZipUtils.transform(jarFile.toPath(), Map.of(Constants.Manifest.PATH, bytes -> {
+ var manifest = new Manifest(new ByteArrayInputStream(bytes));
+
+ // Apply standard Loom manifest attributes (Gradle version, Loom version, etc.)
+ manifestService.get().apply(manifest, manifestAttributes);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ manifest.write(out);
+ return out.toByteArray();
+ }));
+
+ Check.require(count > 0, "Did not transform any jar manifest");
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
index 57b5f186..06f98843 100644
--- a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
@@ -24,13 +24,25 @@
package net.fabricmc.loom.task;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.provider.Provider;
+import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.nesting.NestableJarGenerationTask;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
+import net.fabricmc.loom.task.service.ClientEntriesService;
+import net.fabricmc.loom.task.service.JarManifestService;
+import net.fabricmc.loom.util.gradle.SourceSetHelper;
+import net.fabricmc.loom.util.service.ScopedServiceFactory;
/**
* Configures the jar task for non-remapped (non-obfuscated) output.
@@ -48,15 +60,43 @@ public class NonRemappedJarTaskConfiguration {
}
public void configure() {
- // No remapping needed - use simplified JIJ approach directly on jar task
+ final Provider manifestServiceProvider = JarManifestService.get(project);
+
project.getTasks().named(JavaPlugin.JAR_TASK_NAME, Jar.class).configure(task -> {
task.dependsOn(processIncludeJarsTask);
- // Use JarNester to properly add jars and update fabric.mod.json
+
task.doLast(new NestJarsAction(project.fileTree(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory))
.matching(pattern -> pattern.include("*.jar"))));
+
+ task.doLast(new ManifestModificationAction(
+ manifestServiceProvider,
+ "official",
+ extension.areEnvironmentSourceSetsSplit(),
+ getClientOnlyEntries()
+ ));
+
+ task.usesService(manifestServiceProvider);
});
- // Add jar task to unmapped collection
extension.getUnmappedModCollection().from(project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME));
}
+
+ private List getClientOnlyEntries() {
+ if (!extension.areEnvironmentSourceSetsSplit()) {
+ return Collections.emptyList();
+ }
+
+ final SourceSet clientSourceSet = SourceSetHelper.getSourceSetByName(
+ MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME,
+ project
+ );
+ final Provider optionsProvider = ClientEntriesService.Classes.createOptions(project, clientSourceSet);
+
+ try (var serviceFactory = new ScopedServiceFactory()) {
+ ClientEntriesService service = serviceFactory.get(optionsProvider);
+ return new ArrayList<>(service.getClientOnlyEntries());
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to determine client-only entries", e);
+ }
+ }
}
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/IncludedJarsNoRemapTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/IncludedJarsNoRemapTest.groovy
index 8a1c78ae..63492178 100644
--- a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/IncludedJarsNoRemapTest.groovy
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/IncludedJarsNoRemapTest.groovy
@@ -24,6 +24,8 @@
package net.fabricmc.loom.test.integration.noRemap
+import java.util.jar.Manifest
+
import spock.lang.Specification
import spock.lang.Unroll
@@ -52,6 +54,20 @@ class IncludedJarsNoRemapTest extends Specification implements GradleProjectTest
!gradle.hasOutputZipEntry("includedJars.jar", "META-INF/jars/log4j-api-2.22.0.jar")
!gradle.hasOutputZipEntry("includedJars.jar", "META-INF/jars/adventure-api-4.14.0.jar")
+ // Verify manifest attributes
+ def manifestContent = gradle.getOutputZipEntry("includedJars.jar", "META-INF/MANIFEST.MF")
+ def manifest = new Manifest(new ByteArrayInputStream(manifestContent.bytes))
+ def attributes = manifest.getMainAttributes()
+
+ // Check that the namespace is set to "official" for non-remapped jars
+ attributes.getValue("Fabric-Mapping-Namespace") == "official"
+
+ // Check that Loom metadata is present
+ attributes.getValue("Fabric-Gradle-Version") != null
+ attributes.getValue("Fabric-Loom-Version") != null
+ attributes.getValue("Fabric-Minecraft-Version") != null
+ attributes.getValue("Fabric-Tiny-Remapper-Version") != null
+
where:
version << STANDARD_TEST_VERSIONS
}
From 03d4fd077b74f2a44ac44527467dbddf8c799908 Mon Sep 17 00:00:00 2001
From: modmuss
Date: Sat, 8 Nov 2025 09:04:40 +0000
Subject: [PATCH 5/8] Very many debof fixes (#1430)
---
.../vineflower/VineflowerDecompiler.java | 7 ++-
.../configuration/CompileConfiguration.java | 5 ++
.../configuration/LoomConfigurations.java | 5 --
.../configuration/RemapConfigurations.java | 5 ++
.../fabricapi/FabricApiAbstractSourceSet.java | 4 +-
.../speccontext/DebofConfiguration.java | 7 ++-
.../speccontext/DeobfSpecContext.java | 15 ++++-
.../extension/LoomGradleExtensionImpl.java | 1 +
.../loom/task/GenerateSourcesTask.java | 33 ++++++----
.../loom/task/ManifestModificationAction.java | 14 ++---
.../task/NonRemappedJarTaskConfiguration.java | 4 +-
.../noRemap/SimpleDebofTest.groovy | 60 +++++++++++++++++++
12 files changed, 130 insertions(+), 30 deletions(-)
diff --git a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java
index ad865d73..40f14d41 100644
--- a/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java
+++ b/src/decompilers/vineflower/net/fabricmc/loom/decompilers/vineflower/VineflowerDecompiler.java
@@ -48,11 +48,14 @@ public final class VineflowerDecompiler implements LoomInternalDecompiler {
IFernflowerPreferences.REMOVE_SYNTHETIC, "1",
IFernflowerPreferences.LOG_LEVEL, "trace",
IFernflowerPreferences.THREADS, String.valueOf(context.numberOfThreads()),
- IFernflowerPreferences.INDENT_STRING, "\t",
- IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile())
+ IFernflowerPreferences.INDENT_STRING, "\t"
)
);
+ if (context.javaDocs() != null) {
+ options.put(IFabricJavadocProvider.PROPERTY_NAME, new TinyJavadocProvider(context.javaDocs().toFile()));
+ }
+
options.putAll(context.options());
IResultSaver saver = new ThreadSafeResultSaver(sourcesDestination::toFile, linemapDestination::toFile);
diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
index fe8a640a..f34be070 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -63,6 +63,7 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.configuration.processors.ModJavadocProcessor;
+import net.fabricmc.loom.configuration.processors.speccontext.DebofConfiguration;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMetadataProvider;
@@ -103,6 +104,10 @@ public abstract class CompileConfiguration implements Runnable {
afterEvaluationWithService((serviceFactory) -> {
final ConfigContext configContext = new ConfigContextImpl(getProject(), serviceFactory, extension);
+ if (extension.disableObfuscation()) {
+ DebofConfiguration.create(getProject());
+ }
+
MinecraftSourceSets.get(getProject()).afterEvaluate(getProject());
final boolean previousRefreshDeps = extension.refreshDeps();
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
index c3c098ab..fad2cb1a 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
@@ -45,7 +45,6 @@ import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Provider;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.loom.configuration.processors.speccontext.DebofConfiguration;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.LoomVersions;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -162,10 +161,6 @@ public abstract class LoomConfigurations implements Runnable {
extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.LOADER_DEPENDENCIES);
register(Constants.Configurations.PRODUCTION_RUNTIME_MODS, Role.RESOLVABLE);
-
- if (extension.disableObfuscation()) {
- DebofConfiguration.create(getProject());
- }
}
private NamedDomainObjectProvider register(String name, Role role) {
diff --git a/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java
index 03349991..e0607b33 100644
--- a/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java
+++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfigurations.java
@@ -73,6 +73,11 @@ public final class RemapConfigurations {
public static void configureClientConfigurations(Project project, SourceSet clientSourceSet) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
+
+ if (extension.disableObfuscation()) {
+ return;
+ }
+
extension.createRemapConfigurations(clientSourceSet);
final NamedDomainObjectList configurations = extension.getRemapConfigurations();
diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java
index 9be2291d..3f74f279 100644
--- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java
+++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java
@@ -76,7 +76,9 @@ abstract class FabricApiAbstractSourceSet {
mod.sourceSet(getSourceSetName());
});
- extension.createRemapConfigurations(sourceSets.getByName(getSourceSetName()));
+ if (!extension.disableObfuscation()) {
+ extension.createRemapConfigurations(sourceSets.getByName(getSourceSetName()));
+ }
return sourceSet;
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DebofConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DebofConfiguration.java
index cca90f81..16fcb11b 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DebofConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DebofConfiguration.java
@@ -87,7 +87,12 @@ public record DebofConfiguration(String name, List>
LoomConfigurations.Role.RESOLVABLE.apply(c);
for (Function configProvider : configurationFunctions()) {
- Configuration sourceConfig = configurations.getByName(configProvider.apply(sourceSet));
+ Configuration sourceConfig = configurations.findByName(configProvider.apply(sourceSet));
+
+ if (sourceConfig == null) {
+ continue;
+ }
+
c.extendsFrom(sourceConfig);
}
});
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
index 2982046f..750394a7 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
@@ -25,12 +25,15 @@
package net.fabricmc.loom.configuration.processors.speccontext;
import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -101,7 +104,7 @@ public record DeobfSpecContext(List modDependencies,
for (File artifact : artifacts) {
futures.add(fmjCache.get(artifact.toPath().toAbsolutePath().toString(), () -> {
- return FabricModJsonFactory.createFromZipOptional(artifact.toPath())
+ return getMod(artifact.toPath())
.map(List::of)
.orElseGet(List::of);
}));
@@ -125,7 +128,7 @@ public record DeobfSpecContext(List modDependencies,
for (File artifact : artifacts) {
futures.add(fmjCache.get(artifact.toPath().toAbsolutePath().toString(), () -> {
- return FabricModJsonFactory.createFromZipOptional(artifact.toPath())
+ return getMod(artifact.toPath())
.map(List::of)
.orElseGet(List::of);
}));
@@ -136,6 +139,14 @@ public record DeobfSpecContext(List modDependencies,
.collect(HashSet::new, Set::add, Set::addAll);
}
+ private static Optional getMod(Path path) {
+ if (Files.isRegularFile(path)) {
+ return FabricModJsonFactory.createFromZipOptional(path);
+ }
+
+ return Optional.empty();
+ }
+
private static List getMods(Map mods, Set ids) {
List result = new ArrayList<>();
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
index ec07da21..08cbb2d6 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
@@ -174,6 +174,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
@Override
public MappingConfiguration getMappingConfiguration() {
if (disableObfuscation()) {
+ project.getLogger().lifecycle("help", new RuntimeException());
throw new UnsupportedOperationException("Cannot get mappings configuration in a non-obfuscated environment");
}
diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
index d1565202..1d7ada8f 100644
--- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
@@ -70,6 +70,7 @@ import org.gradle.workers.internal.WorkerDaemonClientsManager;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
+import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
@@ -140,6 +141,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
// Internal inputs
@ApiStatus.Internal
@Nested
+ @Optional
protected abstract Property getMappings();
// Internal outputs
@@ -221,15 +223,16 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
getUseCache().convention(true);
getResetCache().convention(getExtension().refreshDeps());
- getMappings().set(SourceMappingsService.create(getProject()));
+ if (!LoomGradleExtension.get(getProject()).disableObfuscation()) {
+ getMappings().set(SourceMappingsService.create(getProject()));
+ getUnpickOptions().set(UnpickService.createOptions(this));
+ }
getMaxCachedFiles().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_FILES).orElse(50_000));
getMaxCacheFileAge().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_AGE).orElse(90));
getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject()));
- getUnpickOptions().set(UnpickService.createOptions(this));
-
mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class));
}
@@ -407,11 +410,13 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
sj.add(unpick.getUnpickCacheKey());
}
- SourceMappingsService mappingsService = serviceFactory.get(getMappings());
- String mappingsHash = mappingsService.getProcessorHash();
+ if (getMappings().isPresent()) {
+ SourceMappingsService mappingsService = serviceFactory.get(getMappings());
+ String mappingsHash = mappingsService.getProcessorHash();
- if (mappingsHash != null) {
- sj.add(mappingsHash);
+ if (mappingsHash != null) {
+ sj.add(mappingsHash);
+ }
}
getLogger().info("Decompile cache data: {}", sj);
@@ -484,7 +489,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
params.getInputJar().set(inputJar.toFile());
params.getOutputJar().set(outputJar.toFile());
params.getLinemapFile().set(linemapFile.toFile());
- params.getMappings().set(getMappings());
+
+ if (getMappings().isPresent()) {
+ params.getMappings().set(getMappings());
+ }
if (ipcServer != null) {
params.getIPCPath().set(ipcServer.getPath().toFile());
@@ -587,11 +595,16 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
try (var serviceFactory = new ScopedServiceFactory()) {
- final SourceMappingsService mappingsService = serviceFactory.get(getParameters().getMappings());
+ Path javaDocs = null;
+
+ if (getParameters().getMappings().isPresent()) {
+ final SourceMappingsService mappingsService = serviceFactory.get(getParameters().getMappings());
+ javaDocs = mappingsService.getMappingsFile();
+ }
final var metadata = new DecompilationMetadata(
decompilerOptions.maxThreads(),
- mappingsService.getMappingsFile(),
+ javaDocs,
getLibraries(),
logger,
decompilerOptions.options()
diff --git a/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java b/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
index 328e1c7c..841773bf 100644
--- a/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
+++ b/src/main/java/net/fabricmc/loom/task/ManifestModificationAction.java
@@ -53,14 +53,14 @@ import net.fabricmc.loom.util.ZipUtils;
public class ManifestModificationAction implements Action, Serializable {
private final Provider manifestService;
private final String targetNamespace;
- private final boolean areEnvironmentSourceSetsSplit;
- private final List clientOnlyEntries;
+ private final Provider areEnvironmentSourceSetsSplit;
+ private final Provider> clientOnlyEntries;
public ManifestModificationAction(
Provider manifestService,
String targetNamespace,
- boolean areEnvironmentSourceSetsSplit,
- List clientOnlyEntries) {
+ Provider areEnvironmentSourceSetsSplit,
+ Provider> clientOnlyEntries) {
this.manifestService = manifestService;
this.targetNamespace = targetNamespace;
this.areEnvironmentSourceSetsSplit = areEnvironmentSourceSetsSplit;
@@ -86,13 +86,13 @@ public class ManifestModificationAction implements Action, Serializable {
manifestAttributes.put(Constants.Manifest.MAPPING_NAMESPACE, targetNamespace);
// Set split environment flag if source sets are split (even for common-only jars)
- if (areEnvironmentSourceSetsSplit) {
+ if (areEnvironmentSourceSetsSplit.get()) {
manifestAttributes.put(Constants.Manifest.SPLIT_ENV, "true");
}
// Add client-only entries list if present
- if (clientOnlyEntries != null && !clientOnlyEntries.isEmpty()) {
- manifestAttributes.put(Constants.Manifest.CLIENT_ENTRIES, String.join(";", clientOnlyEntries));
+ if (clientOnlyEntries != null && !clientOnlyEntries.get().isEmpty()) {
+ manifestAttributes.put(Constants.Manifest.CLIENT_ENTRIES, String.join(";", clientOnlyEntries.get()));
}
int count = ZipUtils.transform(jarFile.toPath(), Map.of(Constants.Manifest.PATH, bytes -> {
diff --git a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
index 06f98843..fb2218f1 100644
--- a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
@@ -71,8 +71,8 @@ public class NonRemappedJarTaskConfiguration {
task.doLast(new ManifestModificationAction(
manifestServiceProvider,
"official",
- extension.areEnvironmentSourceSetsSplit(),
- getClientOnlyEntries()
+ project.provider(extension::areEnvironmentSourceSetsSplit),
+ project.provider(this::getClientOnlyEntries)
));
task.usesService(manifestServiceProvider);
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
index 55e21070..2f5ee992 100644
--- a/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/noRemap/SimpleDebofTest.groovy
@@ -71,4 +71,64 @@ class SimpleDebofTest extends Specification implements GradleProjectTestTrait {
result.task(":build").outcome == SUCCESS
result.task(":configureClientLaunch").outcome == SUCCESS
}
+
+ @Unroll
+ def "split build"() {
+ setup:
+ def gradle = gradleProject(project: "minimalBaseNoRemap", version: PRE_RELEASE_GRADLE)
+ gradle.buildGradle << '''
+ loom {
+ splitEnvironmentSourceSets()
+ }
+
+ dependencies {
+ minecraft 'com.mojang:minecraft:25w45a_unobfuscated'
+ implementation "net.fabricmc:fabric-loader:0.17.3"
+ }
+ '''
+ def sourceFile = new File(gradle.projectDir, "src/main/java/example/Test.java")
+ sourceFile.parentFile.mkdirs()
+ @Language("JAVA") String src = """
+ package example;
+
+ import net.minecraft.resources.Identifier;
+
+ import org.spongepowered.asm.mixin.Mixin; // Make sure we applied loaders deps via the installer data
+
+ public class Test {
+ public static void main(String[] args) {
+ Identifier id = Identifier.fromNamespaceAndPath("loom", "test");
+ }
+ }
+ """
+ sourceFile.text = src
+
+ when:
+ def result = gradle.run(task: "build")
+
+ then:
+ result.task(":build").outcome == SUCCESS
+ }
+
+ @Unroll
+ def "genSources split build"() {
+ setup:
+ def gradle = gradleProject(project: "minimalBaseNoRemap", version: PRE_RELEASE_GRADLE)
+ gradle.buildGradle << '''
+ loom {
+ splitEnvironmentSourceSets()
+ }
+
+ dependencies {
+ minecraft 'com.mojang:minecraft:25w45a_unobfuscated'
+ implementation "net.fabricmc:fabric-loader:0.17.3"
+ }
+ '''
+
+ when:
+ def result = gradle.run(task: "genSources")
+
+ then:
+ result.task(":genSources").outcome == SUCCESS
+ }
}
From 549edb7ad98a993a2364cb224f290d5f28cc8bd8 Mon Sep 17 00:00:00 2001
From: modmuss
Date: Sat, 8 Nov 2025 23:20:06 +0000
Subject: [PATCH 6/8] Remove "namedElements" when debof (#1435)
---
.../loom/configuration/CompileConfiguration.java | 6 ++++--
.../loom/configuration/LoomConfigurations.java | 16 ++++++++--------
.../loom/util/gradle/SourceSetHelper.java | 4 +++-
3 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
index f34be070..2ba1575b 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -149,8 +149,10 @@ public abstract class CompileConfiguration implements Runnable {
finalizedBy("eclipse", "genEclipseRuns");
- // Add the "dev" jar to the "namedElements" configuration
- getProject().artifacts(artifactHandler -> artifactHandler.add(Configurations.NAMED_ELEMENTS, getTasks().named("jar")));
+ if (!extension.disableObfuscation()) {
+ // Add the "dev" jar to the "namedElements" configuration
+ getProject().artifacts(artifactHandler -> artifactHandler.add(Configurations.NAMED_ELEMENTS, getTasks().named("jar")));
+ }
// Ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
index fad2cb1a..92b166e0 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java
@@ -126,15 +126,15 @@ public abstract class LoomConfigurations implements Runnable {
});
});
- registerNonTransitive(Constants.Configurations.MAPPING_CONSTANTS, Role.RESOLVABLE);
-
- register(Constants.Configurations.NAMED_ELEMENTS, Role.CONSUMABLE).configure(configuration -> {
- configuration.extendsFrom(getConfigurations().getByName(JavaPlugin.API_CONFIGURATION_NAME));
- });
-
- extendsFrom(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Configurations.MAPPING_CONSTANTS);
-
if (!extension.disableObfuscation()) {
+ registerNonTransitive(Constants.Configurations.MAPPING_CONSTANTS, Role.RESOLVABLE);
+
+ register(Constants.Configurations.NAMED_ELEMENTS, Role.CONSUMABLE).configure(configuration -> {
+ configuration.extendsFrom(getConfigurations().getByName(JavaPlugin.API_CONFIGURATION_NAME));
+ });
+
+ extendsFrom(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Configurations.MAPPING_CONSTANTS);
+
register(Constants.Configurations.MAPPINGS, Role.RESOLVABLE);
register(Constants.Configurations.MAPPINGS_FINAL, Role.RESOLVABLE);
extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.MAPPINGS_FINAL);
diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java
index fa5a66aa..8560a4e9 100644
--- a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java
+++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java
@@ -146,7 +146,9 @@ public final class SourceSetHelper {
// Add dev jars from dependency projects if the source set is "main".
if (forExport && SourceSet.MAIN_SOURCE_SET_NAME.equals(reference.sourceSet().getName()) && GradleUtils.isLoomCompanionProject(reference.project())) {
- String configurationName = GradleUtils.isLoomProject(reference.project())
+ boolean isLoom = GradleUtils.isLoomProject(reference.project());
+ boolean isDebof = isLoom && LoomGradleExtension.get(reference.project()).disableObfuscation();
+ String configurationName = isLoom && !isDebof
? Constants.Configurations.NAMED_ELEMENTS : JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME;
final Configuration namedElements = reference.project().getConfigurations().getByName(configurationName);
From 9ad97b74b9158399ef3cbaff29e779bfcdd277fa Mon Sep 17 00:00:00 2001
From: modmuss
Date: Sun, 9 Nov 2025 09:45:21 +0000
Subject: [PATCH 7/8] Fix project deps (#1437)
---
.../speccontext/DeobfProjectView.java | 10 +++++
.../speccontext/DeobfSpecContext.java | 38 ++++++++++++++++++-
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfProjectView.java b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfProjectView.java
index a2788cdf..e3214348 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfProjectView.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfProjectView.java
@@ -24,6 +24,8 @@
package net.fabricmc.loom.configuration.processors.speccontext;
+import java.util.stream.Stream;
+
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
@@ -32,6 +34,8 @@ import org.gradle.api.file.FileCollection;
public interface DeobfProjectView extends ProjectView {
FileCollection getDependencies(DebofConfiguration debofConfiguration, DebofConfiguration.TargetSourceSet targetSourceSet);
+ Stream getProjectDependencies(DebofConfiguration debofConfiguration);
+
FileCollection getFullClasspath();
class Impl extends AbstractProjectView implements DeobfProjectView {
@@ -44,6 +48,12 @@ public interface DeobfProjectView extends ProjectView {
return debofConfiguration.getConfiguration(project, targetSourceSet);
}
+ @Override
+ public Stream getProjectDependencies(DebofConfiguration debofConfiguration) {
+ return debofConfiguration.getConfigurations(project).stream()
+ .flatMap(configuration -> getLoomProjectDependencies(configuration.getName()));
+ }
+
@Override
public FileCollection getFullClasspath() {
ConfigurableFileCollection classpath = project.files();
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
index 750394a7..c18d067c 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/speccontext/DeobfSpecContext.java
@@ -28,6 +28,7 @@ import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -36,6 +37,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
@@ -89,10 +91,16 @@ public record DeobfSpecContext(List modDependencies,
clientTransformingModIds = Set.of();
}
+ // All dependency mods that are on both the compile and runtime classpath
+ List modDependenciesCompileRuntime = new ArrayList<>(getMods(mods, combine(mainTransformingModIds, clientTransformingModIds)));
+
+ // Add all of the project depedencies that are on both the compile and runtime classpath
+ modDependenciesCompileRuntime.addAll(getCompileRuntimeProjectMods(projectView, fmjCache));
+
return new DeobfSpecContext(
dependentMods,
projectView.getMods(),
- getMods(mods, combine(mainTransformingModIds, clientTransformingModIds)),
+ modDependenciesCompileRuntime,
getMods(mods, onlyInLeft(clientTransformingModIds, mainTransformingModIds))
);
}
@@ -157,6 +165,34 @@ public record DeobfSpecContext(List modDependencies,
return result;
}
+ // Returns a list of mods that are on both to compile and runtime classpath
+ private static List getCompileRuntimeProjectMods(DeobfProjectView projectView, AsyncCache> fmjCache) {
+ var mods = new ArrayList();
+
+ for (Project dependentProject : getCompileRuntimeProjectDependencies(projectView).toList()) {
+ List projectMods = fmjCache.getBlocking(dependentProject.getPath(), () -> {
+ return FabricModJsonHelpers.getModsInProject(dependentProject);
+ });
+
+ mods.addAll(projectMods);
+ }
+
+ return Collections.unmodifiableList(mods);
+ }
+
+ // Returns a list of Loom Projects found in both the runtime and compile classpath
+ private static Stream getCompileRuntimeProjectDependencies(DeobfProjectView projectView) {
+ if (projectView.disableProjectDependantMods()) {
+ return Stream.empty();
+ }
+
+ final Stream runtimeProjects = projectView.getProjectDependencies(DebofConfiguration.RUNTIME);
+ final List compileProjects = projectView.getProjectDependencies(DebofConfiguration.COMPILE).toList();
+
+ return runtimeProjects
+ .filter(compileProjects::contains); // Use the intersection of the two configurations.
+ }
+
private static Set common(Set a, Set b) {
Set copy = new HashSet<>(a);
copy.retainAll(b);
From 91d2edefbfd93d74cd50511efc7ec14ecdcdf89e Mon Sep 17 00:00:00 2001
From: modmuss
Date: Sun, 9 Nov 2025 10:40:18 +0000
Subject: [PATCH 8/8] NestJarsAction improvements (#1438)
---
.../extension/LoomGradleExtensionImpl.java | 2 +-
.../fabricmc/loom/task/NestJarsAction.java | 61 +++++++++++++++----
.../task/NonRemappedJarTaskConfiguration.java | 4 +-
3 files changed, 52 insertions(+), 15 deletions(-)
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
index 08cbb2d6..23542e77 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
@@ -353,7 +353,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
remapJarTask.getNestedJars().from(jars);
} else {
// For regular Jar tasks (non-remap mode), add a NestJarsAction with the FileCollection
- task.doLast(new NestJarsAction(jars));
+ NestJarsAction.addToTask(task, jars);
}
});
}
diff --git a/src/main/java/net/fabricmc/loom/task/NestJarsAction.java b/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
index 54f52059..4715408a 100644
--- a/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
+++ b/src/main/java/net/fabricmc/loom/task/NestJarsAction.java
@@ -26,14 +26,24 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.Task;
+import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.InputFiles;
import org.gradle.jvm.tasks.Jar;
+import org.gradle.workers.WorkAction;
+import org.gradle.workers.WorkParameters;
+import org.gradle.workers.WorkQueue;
+import org.gradle.workers.WorkerExecutor;
import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import net.fabricmc.loom.build.nesting.JarNester;
@@ -42,23 +52,50 @@ import net.fabricmc.loom.build.nesting.JarNester;
* Uses a FileCollection to avoid capturing task references at configuration time.
* Do NOT turn me into a record!
*/
-public class NestJarsAction implements Action, Serializable {
- private final FileCollection jars;
+public abstract class NestJarsAction implements Action, Serializable {
+ @InputFiles
+ public abstract ConfigurableFileCollection getJars();
- public NestJarsAction(FileCollection jars) {
- this.jars = jars;
+ @Inject
+ protected abstract WorkerExecutor getWorkerExecutor();
+
+ public static void addToTask(Jar task, FileCollection jars) {
+ NestJarsAction nestJarsAction = task.getProject().getObjects().newInstance(NestJarsAction.class);
+ nestJarsAction.getJars().from(jars);
+ task.getInputs().files(nestJarsAction.getJars()); // I don't think @InputFiles works, so to be sure add the jars to the task input anyway.
+ task.doLast(nestJarsAction);
}
@Override
public void execute(@NotNull Task t) {
final Jar jarTask = (Jar) t;
- final File jarFile = jarTask.getArchiveFile().get().getAsFile();
- final List allJars = new ArrayList<>(jars.getFiles());
- // Nest all collected jars
- if (!allJars.isEmpty()) {
- JarNester.nestJars(allJars, jarFile, jarTask.getLogger());
- jarTask.getLogger().lifecycle("Nested {} jar(s) into {}", allJars.size(), jarFile.getName());
+ final WorkQueue workQueue = getWorkerExecutor().noIsolation();
+
+ workQueue.submit(NestAction.class, p -> {
+ p.getArchiveFile().set(jarTask.getArchiveFile());
+ p.getJars().setFrom(getJars());
+ });
+ }
+
+ public interface NestJarsParameters extends WorkParameters {
+ RegularFileProperty getArchiveFile();
+ ConfigurableFileCollection getJars();
+ }
+
+ public abstract static class NestAction implements WorkAction {
+ private static final Logger LOGGER = LoggerFactory.getLogger(NestJarsAction.class);
+
+ @Override
+ public void execute() {
+ final File jarFile = getParameters().getArchiveFile().get().getAsFile();
+ final Set jars = getParameters().getJars().getFiles();
+
+ // Nest all collected jars
+ if (!jars.isEmpty()) {
+ JarNester.nestJars(jars, jarFile, LOGGER);
+ LOGGER.info("Nested {} jar(s) into {}", jars.size(), jarFile.getName());
+ }
}
}
}
diff --git a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
index fb2218f1..4fc6fad6 100644
--- a/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/task/NonRemappedJarTaskConfiguration.java
@@ -65,8 +65,8 @@ public class NonRemappedJarTaskConfiguration {
project.getTasks().named(JavaPlugin.JAR_TASK_NAME, Jar.class).configure(task -> {
task.dependsOn(processIncludeJarsTask);
- task.doLast(new NestJarsAction(project.fileTree(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory))
- .matching(pattern -> pattern.include("*.jar"))));
+ NestJarsAction.addToTask(task, project.fileTree(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory))
+ .matching(pattern -> pattern.include("*.jar")));
task.doLast(new ManifestModificationAction(
manifestServiceProvider,