Merge commit '91d2edef' into dev/1.14

# Conflicts:
#	src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
#	src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
#	src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
#	src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
#	src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java
This commit is contained in:
shedaniel
2026-03-21 01:30:53 +09:00
29 changed files with 748 additions and 115 deletions

View File

@@ -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);

View File

@@ -41,6 +41,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;
@@ -334,4 +336,26 @@ public interface LoomGradleExtensionAPI {
NeoForgeExtensionAPI getNeoForge();
void neoForge(Action<NeoForgeExtensionAPI> action);
/**
* 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.
*
* <p>Important: The jars must already be valid mod jars (containing a fabric.mod.json file).
* Non-mod jars will be rejected.
*
* <p>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);
}

View File

@@ -79,6 +79,7 @@ import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor;
import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper;
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;
@@ -121,6 +122,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();
@@ -181,8 +186,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

View File

@@ -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> 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);
}
}
}

View File

@@ -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);

View File

@@ -127,15 +127,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);

View File

@@ -61,6 +61,6 @@ public record LoomDependencyManager(Project project, ServiceFactory serviceFacto
}
private void handleNonRemapDependencies() {
// TODO debof - do we need to do anything?
DebofInstallerData.findAndApply(project);
}
}

View File

@@ -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<RemapConfigurationSettings> configurations = extension.getRemapConfigurations();

View File

@@ -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;
}

View File

@@ -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;
@@ -125,8 +124,7 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
final Path installerPath = fs.getPath(installerFile);
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());
}
}

View File

@@ -87,7 +87,12 @@ public record DebofConfiguration(String name, List<Function<SourceSet, String>>
LoomConfigurations.Role.RESOLVABLE.apply(c);
for (Function<SourceSet, String> configProvider : configurationFunctions()) {
Configuration sourceConfig = configurations.getByName(configProvider.apply(sourceSet));
Configuration sourceConfig = configurations.findByName(configProvider.apply(sourceSet));
if (sourceConfig == null) {
continue;
}
c.extendsFrom(sourceConfig);
}
});

View File

@@ -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<Project> 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<Project> getProjectDependencies(DebofConfiguration debofConfiguration) {
return debofConfiguration.getConfigurations(project).stream()
.flatMap(configuration -> getLoomProjectDependencies(configuration.getName()));
}
@Override
public FileCollection getFullClasspath() {
ConfigurableFileCollection classpath = project.files();

View File

@@ -25,14 +25,19 @@
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.Collections;
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;
import java.util.stream.Stream;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
@@ -53,8 +58,6 @@ public record DeobfSpecContext(List<FabricModJson> modDependencies,
List<FabricModJson> modDependenciesCompileRuntimeClient
) implements SpecContext {
public static DeobfSpecContext create(Project project) {
DebofConfiguration.create(project);
return create(new DeobfProjectView.Impl(project));
}
@@ -88,10 +91,16 @@ public record DeobfSpecContext(List<FabricModJson> modDependencies,
clientTransformingModIds = Set.of();
}
// All dependency mods that are on both the compile and runtime classpath
List<FabricModJson> 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))
);
}
@@ -103,7 +112,7 @@ public record DeobfSpecContext(List<FabricModJson> 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);
}));
@@ -127,7 +136,7 @@ public record DeobfSpecContext(List<FabricModJson> 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);
}));
@@ -138,6 +147,14 @@ public record DeobfSpecContext(List<FabricModJson> modDependencies,
.collect(HashSet::new, Set::add, Set::addAll);
}
private static Optional<FabricModJson> getMod(Path path) {
if (Files.isRegularFile(path)) {
return FabricModJsonFactory.createFromZipOptional(path);
}
return Optional.empty();
}
private static List<FabricModJson> getMods(Map<String, FabricModJson> mods, Set<String> ids) {
List<FabricModJson> result = new ArrayList<>();
@@ -148,6 +165,34 @@ public record DeobfSpecContext(List<FabricModJson> modDependencies,
return result;
}
// Returns a list of mods that are on both to compile and runtime classpath
private static List<FabricModJson> getCompileRuntimeProjectMods(DeobfProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
var mods = new ArrayList<FabricModJson>();
for (Project dependentProject : getCompileRuntimeProjectDependencies(projectView).toList()) {
List<FabricModJson> 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<Project> getCompileRuntimeProjectDependencies(DeobfProjectView projectView) {
if (projectView.disableProjectDependantMods()) {
return Stream.empty();
}
final Stream<Project> runtimeProjects = projectView.getProjectDependencies(DebofConfiguration.RUNTIME);
final List<Project> compileProjects = projectView.getProjectDependencies(DebofConfiguration.COMPILE).toList();
return runtimeProjects
.filter(compileProjects::contains); // Use the intersection of the two configurations.
}
private static Set<String> common(Set<String> a, Set<String> b) {
Set<String> copy = new HashSet<>(a);
copy.retainAll(b);

View File

@@ -50,6 +50,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.ForgeExtensionAPI;
@@ -661,5 +663,10 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
public NeoForgeExtensionAPI getNeoForge() {
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");
}
}
}

View File

@@ -43,6 +43,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;
@@ -64,6 +66,8 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMi
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
import net.fabricmc.loom.task.NestJarsAction;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Lazy;
import net.fabricmc.loom.util.ModPlatform;
@@ -191,6 +195,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");
}
@@ -415,4 +420,17 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
ModPlatform.assertForgeLike(this);
this.forgeRunsProvider = forgeRunsProvider;
}
@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
NestJarsAction.addToTask(task, jars, getPlatform().get());
}
});
}
}

View File

@@ -75,6 +75,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;
@@ -145,6 +146,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
// Internal inputs
@ApiStatus.Internal
@Nested
@Optional
protected abstract Property<SourceMappingsService.Options> getMappings();
// Internal outputs
@@ -230,7 +232,10 @@ 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));
@@ -422,11 +427,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);
@@ -573,7 +580,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());
@@ -682,11 +692,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()

View File

@@ -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<GenerateRemapClasspathTask> 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> 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<ValidateAccessWidenerTask> 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<ValidateAccessWidenerTask> 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() {

View File

@@ -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<Task>, Serializable {
private final Provider<JarManifestService> manifestService;
private final String targetNamespace;
private final Provider<Boolean> areEnvironmentSourceSetsSplit;
private final Provider<List<String>> clientOnlyEntries;
public ManifestModificationAction(
Provider<JarManifestService> manifestService,
String targetNamespace,
Provider<Boolean> areEnvironmentSourceSetsSplit,
Provider<List<String>> 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<String, String> 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.get()) {
manifestAttributes.put(Constants.Manifest.SPLIT_ENV, "true");
}
// Add client-only entries list if present
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 -> {
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");
}
}

View File

@@ -26,51 +26,89 @@ package net.fabricmc.loom.task;
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Set;
import javax.inject.Inject;
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.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
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;
import net.fabricmc.loom.util.ModPlatform;
/**
* 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<Task>, Serializable {
private final Provider<Directory> nestedJarsDir;
public abstract class NestJarsAction implements Action<Task>, Serializable {
@InputFiles
public abstract ConfigurableFileCollection getJars();
NestJarsAction(Provider<Directory> nestedJarsDir) {
this.nestedJarsDir = nestedJarsDir;
@Input
public abstract Property<ModPlatform> getPlatform();
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
public static void addToTask(Jar task, FileCollection jars) {
addToTask(task, jars, ModPlatform.FABRIC);
}
public static void addToTask(Jar task, FileCollection jars, ModPlatform platform) {
NestJarsAction nestJarsAction = task.getProject().getObjects().newInstance(NestJarsAction.class);
nestJarsAction.getJars().from(jars);
nestJarsAction.getPlatform().set(platform);
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();
if (!nestedJarsDir.isPresent()) {
return;
}
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
final File outputDir = nestedJarsDir.get().getAsFile();
workQueue.submit(NestAction.class, p -> {
p.getArchiveFile().set(jarTask.getArchiveFile());
p.getJars().setFrom(getJars());
p.getPlatform().set(getPlatform());
});
}
if (outputDir.exists() && outputDir.isDirectory()) {
final File[] jars = outputDir.listFiles((dir, name) -> name.endsWith(".jar"));
public interface NestJarsParameters extends WorkParameters {
RegularFileProperty getArchiveFile();
ConfigurableFileCollection getJars();
Property<ModPlatform> getPlatform();
}
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());
public abstract static class NestAction implements WorkAction<NestJarsParameters> {
private static final Logger LOGGER = LoggerFactory.getLogger(NestJarsAction.class);
@Override
public void execute() {
final File jarFile = getParameters().getArchiveFile().get().getAsFile();
final Set<File> jars = getParameters().getJars().getFiles();
// Nest all collected jars
if (!jars.isEmpty()) {
JarNester.nestJars(jars, jarFile, getParameters().getPlatform().get(), LOGGER);
LOGGER.info("Nested {} jar(s) into {}", jars.size(), jarFile.getName());
}
}
}
}
}

View File

@@ -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,14 +60,43 @@ public class NonRemappedJarTaskConfiguration {
}
public void configure() {
// No remapping needed - use simplified JIJ approach directly on jar task
final Provider<JarManifestService> 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(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory)));
NestJarsAction.addToTask(task, project.fileTree(processIncludeJarsTask.flatMap(NestableJarGenerationTask::getOutputDirectory))
.matching(pattern -> pattern.include("*.jar")));
task.doLast(new ManifestModificationAction(
manifestServiceProvider,
"official",
project.provider(extension::areEnvironmentSourceSetsSplit),
project.provider(this::getClientOnlyEntries)
));
task.usesService(manifestServiceProvider);
});
// Add jar task to unmapped collection
extension.getUnmappedModCollection().from(project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME));
}
}
private List<String> getClientOnlyEntries() {
if (!extension.areEnvironmentSourceSetsSplit()) {
return Collections.emptyList();
}
final SourceSet clientSourceSet = SourceSetHelper.getSourceSetByName(
MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME,
project
);
final Provider<ClientEntriesService.Classes.Options> optionsProvider = ClientEntriesService.Classes.createOptions(project, clientSourceSet);
try (var serviceFactory = new ScopedServiceFactory()) {
ClientEntriesService<ClientEntriesService.Classes.Options> service = serviceFactory.get(optionsProvider);
return new ArrayList<>(service.getClientOnlyEntries());
} catch (IOException e) {
throw new RuntimeException("Failed to determine client-only entries", e);
}
}
}

View File

@@ -102,6 +102,7 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
protected abstract Property<String> getNativesDirectoryPath();
@InputFile
@Optional
public abstract RegularFileProperty getRemapClasspathFile();
@OutputFile
@@ -180,10 +181,13 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
boolean quilt = platform == ModPlatform.QUILT;
final LaunchConfig launchConfig = new LaunchConfig()
.property(!quilt ? "fabric.development" : "loader.development", "true")
.property(!quilt ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getRemapClasspathFile().get().getAsFile().getAbsolutePath())
.property("log4j.configurationFile", getLog4jConfigPaths().get())
.property("log4j2.formatMsgNoLookups", "true");
if (getRemapClasspathFile().isPresent()) {
launchConfig.property(!quilt ? "fabric.remapClasspathFile" : "loader.remapClasspathFile", getRemapClasspathFile().get().getAsFile().getAbsolutePath());
}
if (versionInfo.hasNativesToExtract()) {
String nativesPath = getNativesDirectoryPath().get();

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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,50 @@ 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");
}
}
"""
sourceFile.text = src
when:
def result = gradle.run(tasks: [
"build",
"configureClientLaunch"
])
then:
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");
@@ -64,4 +109,26 @@ class SimpleDebofTest extends Specification implements GradleProjectTestTrait {
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
}
}

View File

@@ -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)

View File

@@ -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": "*"
}

View File

@@ -0,0 +1 @@
rootProject.name = 'nestJarsApi'

View File

@@ -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": "*"
}