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
This commit is contained in:
Finn Rades
2025-11-07 23:40:08 +01:00
committed by GitHub
parent 43e1ad7b31
commit f9dbaae926
3 changed files with 170 additions and 3 deletions

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 boolean areEnvironmentSourceSetsSplit;
private final List<String> clientOnlyEntries;
public ManifestModificationAction(
Provider<JarManifestService> manifestService,
String targetNamespace,
boolean areEnvironmentSourceSetsSplit,
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) {
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");
}
}

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,15 +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(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<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

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