diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index f2846ee2..4bea2333 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -27,7 +27,9 @@ package net.fabricmc.loom.task; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -56,6 +58,8 @@ import org.gradle.workers.WorkParameters; import org.gradle.workers.WorkQueue; import org.gradle.workers.WorkerExecutor; import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -63,6 +67,7 @@ import net.fabricmc.loom.task.service.ClientEntriesService; import net.fabricmc.loom.task.service.JarManifestService; import net.fabricmc.loom.util.Check; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.ZipReprocessorUtil; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; @@ -115,6 +120,7 @@ public abstract class AbstractRemapJarTask extends Jar { @Inject public AbstractRemapJarTask() { + from(getProject().zipTree(getInputFile())); getSourceNamespace().convention(MappingsNamespace.NAMED.toString()).finalizeValueOnRead(); getTargetNamespace().convention(MappingsNamespace.INTERMEDIARY.toString()).finalizeValueOnRead(); getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead(); @@ -133,17 +139,11 @@ public abstract class AbstractRemapJarTask extends Jar { usesService(jarManifestServiceProvider); } - @Override - protected void copy() { - // Skip the default copy behaviour of AbstractCopyTask. - } - public final

void submitWork(Class> workAction, Action

action) { final WorkQueue workQueue = getWorkerExecutor().noIsolation(); workQueue.submit(workAction, params -> { - params.getInputFile().set(getInputFile()); - params.getOutputFile().set(getArchiveFile()); + params.getArchiveFile().set(getArchiveFile()); params.getSourceNamespace().set(getSourceNamespace()); params.getTargetNamespace().set(getTargetNamespace()); @@ -181,8 +181,7 @@ public abstract class AbstractRemapJarTask extends Jar { protected abstract Provider getClientOnlyEntriesOptionsProvider(SourceSet clientSourceSet); public interface AbstractRemapParams extends WorkParameters { - RegularFileProperty getInputFile(); - RegularFileProperty getOutputFile(); + RegularFileProperty getArchiveFile(); Property getSourceNamespace(); Property getTargetNamespace(); @@ -217,15 +216,34 @@ public abstract class AbstractRemapJarTask extends Jar { } public abstract static class AbstractRemapAction implements WorkAction { - protected final Path inputFile; + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRemapAction.class); protected final Path outputFile; @Inject public AbstractRemapAction() { - inputFile = getParameters().getInputFile().getAsFile().get().toPath(); - outputFile = getParameters().getOutputFile().getAsFile().get().toPath(); + outputFile = getParameters().getArchiveFile().getAsFile().get().toPath(); } + @Override + public final void execute() { + try { + Path tempInput = Files.createTempFile("loom-remapJar-", "-input.jar"); + Files.copy(outputFile, tempInput, StandardCopyOption.REPLACE_EXISTING); + execute(tempInput); + Files.delete(tempInput); + } catch (Exception e) { + try { + Files.deleteIfExists(outputFile); + } catch (IOException ex) { + LOGGER.error("Failed to delete output file", ex); + } + + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to remap " + outputFile.toAbsolutePath(), e); + } + } + + protected abstract void execute(Path inputFile) throws IOException; + protected void modifyJarManifest() throws IOException { int count = ZipUtils.transform(outputFile, Map.of(Constants.Manifest.PATH, bytes -> { var manifest = new Manifest(new ByteArrayInputStream(bytes)); diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 745da380..7686e882 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021-2024 FabricMC + * Copyright (c) 2021-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 @@ -45,7 +45,6 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -64,7 +63,6 @@ import net.fabricmc.loom.task.service.ClientEntriesService; import net.fabricmc.loom.task.service.MixinRefmapService; import net.fabricmc.loom.task.service.TinyRemapperService; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.SidedClassVisitor; import net.fabricmc.loom.util.ZipUtils; @@ -122,8 +120,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { getMixinRefmapServiceOptions().set(MixinRefmapService.createOptions(this)); } - @TaskAction - public void run() { + @Override + protected void copy() { + super.copy(); + submitWork(RemapAction.class, params -> { if (getAddNestedDependencies().get()) { params.getNestedJars().from(getNestedJars()); @@ -165,14 +165,16 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { private @Nullable TinyRemapperService tinyRemapperService; private @Nullable TinyRemapper tinyRemapper; + private Path inputFile; public RemapAction() { } @Override - public void execute() { + protected void execute(Path inputFile) throws IOException { try (var serviceFactory = new ScopedServiceFactory()) { LOGGER.info("Remapping {} to {}", inputFile, outputFile); + this.inputFile = inputFile; this.tinyRemapperService = getParameters().getTinyRemapperServiceOptions().isPresent() ? serviceFactory.get(getParameters().getTinyRemapperServiceOptions().get()) @@ -207,20 +209,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } LOGGER.debug("Finished remapping {}", inputFile); - } catch (Exception e) { - try { - Files.deleteIfExists(outputFile); - } catch (IOException ex) { - LOGGER.error("Failed to delete output file", ex); - } - - throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to remap", e); } } private void prepare() { - final Path inputFile = getParameters().getInputFile().getAsFile().get().toPath(); - if (tinyRemapperService != null) { tinyRemapperService.getTinyRemapperForInputs().readInputsAsync(tinyRemapperService.getOrCreateTag(inputFile), inputFile); } diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java index ffa2adca..4a92c257 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-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 @@ -26,6 +26,7 @@ package net.fabricmc.loom.task; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; import javax.inject.Inject; @@ -35,9 +36,6 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskAction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import net.fabricmc.loom.task.service.ClientEntriesService; import net.fabricmc.loom.task.service.SourceRemapperService; @@ -56,8 +54,10 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { getSourcesRemapperServiceOptions().set(SourceRemapperService.createOptions(this)); } - @TaskAction - public void run() { + @Override + protected void copy() { + super.copy(); + submitWork(RemapSourcesAction.class, params -> { if (!params.namespacesMatch()) { params.getSourcesRemapperServiceOptions().set(getSourcesRemapperServiceOptions()); @@ -75,35 +75,23 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { } public abstract static class RemapSourcesAction extends AbstractRemapAction { - private static final Logger LOGGER = LoggerFactory.getLogger(RemapSourcesAction.class); - public RemapSourcesAction() { super(); } @Override - public void execute() { - try { - if (!getParameters().namespacesMatch()) { - try (var serviceFactory = new ScopedServiceFactory()) { - SourceRemapperService sourceRemapperService = serviceFactory.get(getParameters().getSourcesRemapperServiceOptions()); - sourceRemapperService.remapSourcesJar(inputFile, outputFile); - } - } else { - Files.copy(inputFile, outputFile, StandardCopyOption.REPLACE_EXISTING); + protected void execute(Path inputFile) throws IOException { + if (!getParameters().namespacesMatch()) { + try (var serviceFactory = new ScopedServiceFactory()) { + SourceRemapperService sourceRemapperService = serviceFactory.get(getParameters().getSourcesRemapperServiceOptions()); + sourceRemapperService.remapSourcesJar(inputFile, outputFile); } - - modifyJarManifest(); - rewriteJar(); - } catch (Exception e) { - try { - Files.deleteIfExists(outputFile); - } catch (IOException ex) { - LOGGER.error("Failed to delete output file", ex); - } - - throw new RuntimeException("Failed to remap sources", e); + } else { + Files.copy(inputFile, outputFile, StandardCopyOption.REPLACE_EXISTING); } + + modifyJarManifest(); + rewriteJar(); } } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RemapJarContentsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RemapJarContentsTest.groovy new file mode 100644 index 00000000..fdf556b5 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RemapJarContentsTest.groovy @@ -0,0 +1,62 @@ +/* + * 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 java.util.jar.JarFile +import java.util.jar.Manifest + +import spock.lang.Specification +import spock.lang.Unroll + +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.util.ZipUtils + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class RemapJarContentsTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "use jar apis on remapJar and remapSourcesJar (gradle #version)"() { + setup: + def gradle = gradleProject(project: "remapJarContents", version: version) + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + ZipUtils.contains(gradle.getOutputFile('fabric-example-mod-1.0.0.jar').toPath(), 'test_file.txt') + ZipUtils.contains(gradle.getOutputFile('fabric-example-mod-1.0.0-sources.jar').toPath(), 'test_src_file.txt') + def manifest = readManifest(gradle.getOutputFile('fabric-example-mod-1.0.0.jar')) + manifest.mainAttributes.getValue('Hello-World') == 'test' + + where: + version << STANDARD_TEST_VERSIONS + } + + private static Manifest readManifest(File file) { + return new JarFile(file).withCloseable { it.manifest } + } +} diff --git a/src/test/resources/projects/remapJarContents/build.gradle b/src/test/resources/projects/remapJarContents/build.gradle new file mode 100644 index 00000000..cd411be5 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/build.gradle @@ -0,0 +1,91 @@ +plugins { + id 'fabric-loom' + id 'maven-publish' +} + +version = loom.modVersion +group = project.maven_group + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. +} + +test { + enabled = false +} + +base { + archivesName = project.archives_base_name +} + +tasks.withType(JavaCompile).configureEach { + // 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 + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + + // The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too + // JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used. + // We'll use that if it's available, but otherwise we'll use the older option. + def targetVersion = 8 + if (JavaVersion.current().isJava9Compatible()) { + it.options.release = targetVersion + } +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} + +remapJar { + from 'test_file.txt' + + manifest { + attributes 'Hello-World': 'test' + } +} + +remapSourcesJar { + from 'test_src_file.txt' +} diff --git a/src/test/resources/projects/remapJarContents/gradle.properties b/src/test/resources/projects/remapJarContents/gradle.properties new file mode 100644 index 00000000..845c3560 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/gradle.properties @@ -0,0 +1,16 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G + +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version=1.16.5 + yarn_mappings=1.16.5+build.5 + loader_version=0.11.2 + +# Mod Properties + maven_group = com.example + archives_base_name = fabric-example-mod + +# Dependencies + # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api + fabric_version=0.31.0+1.16 diff --git a/src/test/resources/projects/remapJarContents/settings.gradle b/src/test/resources/projects/remapJarContents/settings.gradle new file mode 100644 index 00000000..c162c363 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "fabric-example-mod" + diff --git a/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/ExampleMod.java b/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/ExampleMod.java new file mode 100644 index 00000000..bdbfb83a --- /dev/null +++ b/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/ExampleMod.java @@ -0,0 +1,21 @@ +package net.fabricmc.example; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.fabricmc.api.ModInitializer; + +public class ExampleMod implements ModInitializer { + public static final Logger LOGGER = LogManager.getLogger("modid"); + + /** + * @see net.fabricmc.example + */ + @Override + public void onInitialize() { + LOGGER.info("Hello simple Fabric mod!"); + LOGGER.info("Hello World!"); + + net.minecraft.util.Identifier id = null; + } +} diff --git a/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java b/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java new file mode 100644 index 00000000..83ee1a89 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java @@ -0,0 +1,15 @@ +package net.fabricmc.example.mixin; + +import net.minecraft.client.gui.screen.TitleScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TitleScreen.class) +public class ExampleMixin { + @Inject(at = @At("HEAD"), method = "init()V") + private void init(CallbackInfo info) { + System.out.println("This line is printed by an example mod mixin!"); + } +} diff --git a/src/test/resources/projects/remapJarContents/src/main/resources/fabric.mod.json b/src/test/resources/projects/remapJarContents/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..14ba01fd --- /dev/null +++ b/src/test/resources/projects/remapJarContents/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "modid", + "version": "1.0.0", + + "name": "Example Mod", + "description": "This is an example description! Tell everyone what your mod is about!", + "authors": [ + "Me!" + ], + "contact": { + "homepage": "https://fabricmc.net/", + "sources": "https://github.com/FabricMC/fabric-example-mod" + }, + + "license": "CC0-1.0", + + "environment": "*", + "entrypoints": { + "main": [ + "net.fabricmc.example.ExampleMod" + ] + }, + "mixins": [ + "modid.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.7.4", + "fabric": "*", + "minecraft": "1.16.x" + }, + "suggests": { + "another-mod": "*" + } +} diff --git a/src/test/resources/projects/remapJarContents/src/main/resources/modid.mixins.json b/src/test/resources/projects/remapJarContents/src/main/resources/modid.mixins.json new file mode 100644 index 00000000..21fe73a4 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/src/main/resources/modid.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.fabricmc.example.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "ExampleMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/test/resources/projects/remapJarContents/test_file.txt b/src/test/resources/projects/remapJarContents/test_file.txt new file mode 100644 index 00000000..40e81bd8 --- /dev/null +++ b/src/test/resources/projects/remapJarContents/test_file.txt @@ -0,0 +1 @@ +This file should end up inside the output file of remapJar. diff --git a/src/test/resources/projects/remapJarContents/test_src_file.txt b/src/test/resources/projects/remapJarContents/test_src_file.txt new file mode 100644 index 00000000..a97263ff --- /dev/null +++ b/src/test/resources/projects/remapJarContents/test_src_file.txt @@ -0,0 +1 @@ +This file should end up inside the output file of remapSourcesJar.