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 }