Read "Fabric-Loom-Remap" manifest entry to allow an artifact to control if its remapped or not. (#749)

This allows for none mod jars to opt-into remapping, as well as mods or mod loaders to opt-out.

Setting "Fabric-Loom-Remap" to true/false in the jar's manifest.
This commit is contained in:
modmuss50
2022-11-04 19:29:10 +00:00
committed by GitHub
parent 03e1369adc
commit aedfd09657
7 changed files with 318 additions and 99 deletions

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021 FabricMC
* Copyright (c) 2021-2022 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
@@ -25,6 +25,55 @@
package net.fabricmc.loom.configuration;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
import net.fabricmc.loom.LoomGradleExtension;
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 void applyToProject(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getInstallerData() != null) {
throw new IllegalStateException("Already applied installer data");
}
extension.setInstallerData(this);
JsonObject libraries = installerJson.get("libraries").getAsJsonObject();
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor");
libraries.get("common").getAsJsonArray().forEach(jsonElement -> {
String name = jsonElement.getAsJsonObject().get("name").getAsString();
project.getLogger().debug("Adding dependency ({}) from installer JSON", name);
ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name);
modDep.setTransitive(false);
loaderDepsConfig.getDependencies().add(modDep);
// TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed.
if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
apDepsConfig.getDependencies().add(modDep);
}
// If user choose to use dependencyResolutionManagement, then they should declare
// these repositories manually in the settings file.
if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
String url = jsonElement.getAsJsonObject().get("url").getAsString();
long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
.filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count();
if (count == 0) {
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString()));
}
}
});
}
}

View File

@@ -24,58 +24,18 @@
package net.fabricmc.loom.configuration;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
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.configuration.mods.ModConfigurationRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedServiceManager;
public class LoomDependencyManager {
public void handleDependencies(Project project, SharedServiceManager serviceManager) {
List<Runnable> afterTasks = new ArrayList<>();
project.getLogger().info(":setting up loom dependencies");
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.getInstallerData() == null) {
//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking
project.getLogger().info("Searching through modCompileClasspath for installer JSON");
final Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH);
for (Dependency dependency : configuration.getAllDependencies()) {
for (File input : configuration.files(dependency)) {
JsonObject jsonObject = readInstallerJson(input);
if (jsonObject != null) {
if (extension.getInstallerData() != null) {
project.getLogger().info("Found another installer JSON in, ignoring it! " + input);
continue;
}
project.getLogger().info("Found installer JSON in " + input);
extension.setInstallerData(new InstallerData(dependency.getVersion(), jsonObject));
handleInstallerJson(jsonObject, project);
}
}
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, serviceManager, true);
String mappingsIdentifier = extension.getMappingConfiguration().mappingsIdentifier();
@@ -84,61 +44,7 @@ public class LoomDependencyManager {
sourceRemapper.remapAll();
if (extension.getInstallerData() == null) {
project.getLogger().warn("fabric-installer.json not found in classpath!");
project.getLogger().warn("fabric-installer.json not found in dependencies!");
}
for (Runnable runnable : afterTasks) {
runnable.run();
}
}
public static JsonObject readInstallerJson(File file) {
try {
byte[] bytes = ZipUtils.unpackNullable(file.toPath(), "fabric-installer.json");
if (bytes == null) {
return null;
}
return LoomGradlePlugin.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonObject.class);
} catch (Exception e) {
throw new RuntimeException("Failed to try and read installer json from " + file, e);
}
}
private static void handleInstallerJson(JsonObject jsonObject, Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
JsonObject libraries = jsonObject.get("libraries").getAsJsonObject();
Configuration loaderDepsConfig = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES);
Configuration apDepsConfig = project.getConfigurations().getByName("annotationProcessor");
libraries.get("common").getAsJsonArray().forEach(jsonElement -> {
String name = jsonElement.getAsJsonObject().get("name").getAsString();
ExternalModuleDependency modDep = (ExternalModuleDependency) project.getDependencies().create(name);
modDep.setTransitive(false);
loaderDepsConfig.getDependencies().add(modDep);
// TODO: work around until https://github.com/FabricMC/Mixin/pull/60 and https://github.com/FabricMC/fabric-mixin-compile-extensions/issues/14 is fixed.
if (!IdeaUtils.isIdeaSync() && extension.getMixin().getUseLegacyMixinAp().get()) {
apDepsConfig.getDependencies().add(modDep);
}
project.getLogger().debug("Loom adding " + name + " from installer JSON");
// If user choose to use dependencyResolutionManagement, then they should declare
// these repositories manually in the settings file.
if (jsonElement.getAsJsonObject().has("url") && !project.getGradle().getPlugins().hasPlugin(LoomRepositoryPlugin.class)) {
String url = jsonElement.getAsJsonObject().get("url").getAsString();
long count = project.getRepositories().stream().filter(artifactRepository -> artifactRepository instanceof MavenArtifactRepository)
.map(artifactRepository -> (MavenArtifactRepository) artifactRepository)
.filter(mavenArtifactRepository -> mavenArtifactRepository.getUrl().toString().equalsIgnoreCase(url)).count();
if (count == 0) {
project.getRepositories().maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(jsonElement.getAsJsonObject().get("url").getAsString()));
}
}
});
}
}

View File

@@ -0,0 +1,103 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.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.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.FileSystemUtil;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData) {
private static final String INSTALLER_PATH = "fabric-installer.json";
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
private static final String MANIFEST_REMAP_KEY = "Fabric-Loom-Remap";
public static ArtifactMetadata create(ArtifactRef artifact) throws IOException {
boolean isFabricMod;
RemapRequirements remapRequirements = RemapRequirements.DEFAULT;
InstallerData installerData = null;
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(artifact.path())) {
isFabricMod = FabricModJsonFactory.containsMod(fs);
final Path manifestPath = fs.getPath(MANIFEST_PATH);
if (Files.exists(manifestPath)) {
final var manifest = new Manifest(new ByteArrayInputStream(Files.readAllBytes(manifestPath)));
final Attributes mainAttributes = manifest.getMainAttributes();
final String value = mainAttributes.getValue(MANIFEST_REMAP_KEY);
if (value != null) {
// Support opting into and out of remapping with "Fabric-Loom-Remap" manifest entry
remapRequirements = Boolean.parseBoolean(value) ? RemapRequirements.OPT_IN : RemapRequirements.OPT_OUT;
}
}
final Path installerPath = fs.getPath(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);
}
}
return new ArtifactMetadata(isFabricMod, remapRequirements, installerData);
}
public boolean shouldRemap() {
return remapRequirements().getShouldRemap().test(this);
}
public enum RemapRequirements {
DEFAULT(ArtifactMetadata::isFabricMod),
OPT_IN(true),
OPT_OUT(false);
private final Predicate<ArtifactMetadata> shouldRemap;
RemapRequirements(Predicate<ArtifactMetadata> shouldRemap) {
this.shouldRemap = shouldRemap;
}
RemapRequirements(final boolean shouldRemap) {
this.shouldRemap = artifactMetadata -> shouldRemap;
}
private Predicate<ArtifactMetadata> getShouldRemap() {
return shouldRemap;
}
}
}

View File

@@ -57,7 +57,6 @@ import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.OperatingSystem;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.service.SharedServiceManager;
@SuppressWarnings("UnstableApiUsage")
@@ -89,7 +88,24 @@ public class ModConfigurationRemapper {
final List<ModDependency> modDependencies = new ArrayList<>();
for (ArtifactRef artifact : resolveArtifacts(project, sourceConfig)) {
if (!FabricModJsonFactory.isModJar(artifact.path())) {
final ArtifactMetadata artifactMetadata;
try {
artifactMetadata = ArtifactMetadata.create(artifact);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read metadata from" + artifact.path(), e);
}
if (artifactMetadata.installerData() != null) {
if (extension.getInstallerData() != null) {
project.getLogger().info("Found another installer JSON in ({}), ignoring", artifact.path());
} else {
project.getLogger().info("Applying installer data from {}", artifact.path());
artifactMetadata.installerData().applyToProject(project);
}
}
if (!artifactMetadata.shouldRemap()) {
artifact.applyToConfiguration(project, targetConfig);
continue;
}

View File

@@ -41,6 +41,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
@@ -124,4 +125,8 @@ public final class FabricModJsonFactory {
public static boolean isModJar(Path input) {
return ZipUtils.contains(input, FABRIC_MOD_JSON);
}
public static boolean containsMod(FileSystemUtil.Delegate fs) {
return Files.exists(fs.getPath(FABRIC_MOD_JSON));
}
}

View File

@@ -49,7 +49,7 @@ class FabricAPIBenchmark implements GradleProjectTestTrait {
def timeStart = new Date()
def result = gradle.run(tasks: ["clean", "build"], args: ["--parallel", "-x", "check", "-x", "test", "-x", ":fabric-data-generation-api-v1:runDatagen", "-x", "javadoc"])
def result = gradle.run(tasks: ["clean"], args: [])
def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)

View File

@@ -0,0 +1,140 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.unit
import net.fabricmc.loom.configuration.mods.ArtifactMetadata
import net.fabricmc.loom.configuration.mods.ArtifactRef
import net.fabricmc.loom.util.FileSystemUtil
import spock.lang.Specification
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.Manifest
import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.RemapRequirements.*
class ArtifactMetadataTest extends Specification {
def "is fabric mod"() {
given:
def zip = createZip(entries)
when:
def metadata = createMetadata(zip)
then:
isMod == metadata.isFabricMod()
where:
isMod | entries
false | ["hello.json": "{}"] // None Mod jar
true | ["fabric.mod.json": "{}"] // Fabric mod
}
def "remap requirements"() {
given:
def zip = createZip(entries)
when:
def metadata = createMetadata(zip)
then:
requirements == metadata.remapRequirements()
where:
requirements | entries
DEFAULT | ["fabric.mod.json": "{}"] // Default
OPT_OUT | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // opt-out
OPT_IN | ["META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // opt-in
}
def "Should Remap" () {
given:
def zip = createZip(entries)
when:
def metadata = createMetadata(zip)
def result = metadata.shouldRemap()
then:
result == shouldRemap
where:
shouldRemap | entries
false | ["hello.json": "{}"] // None Mod jar
true | ["fabric.mod.json": "{}"] // Fabric mod
false | ["fabric.mod.json": "{}",
"META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // Fabric mod opt-out
true | ["fabric.mod.json": "{}",
"META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // Fabric mod opt-in
false | ["hello.json": "{}",
"META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "false")] // None opt-out
true | ["hello.json": "{}",
"META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "true")] // None opt-int
false | ["hello.json": "{}",
"META-INF/MANIFEST.MF": manifest("Fabric-Loom-Remap", "broken")]// Invalid format
false | ["hello.json": "{}",
"META-INF/MANIFEST.MF": manifest("Something", "Hello")] // Invalid format
}
def "Installer data"() {
given:
def zip = createZip(entries)
when:
def metadata = createMetadata(zip)
then:
isLoader == (metadata.installerData() != null)
where:
isLoader | entries
true | ["fabric.mod.json": "{}", "fabric-installer.json": "{}"] // Fabric mod, with installer data
false | ["fabric.mod.json": "{}"] // Fabric mod, no installer data
}
private static ArtifactMetadata createMetadata(Path zip) {
return ArtifactMetadata.create(createArtifact(zip))
}
private static ArtifactRef createArtifact(Path zip) {
return new ArtifactRef.FileArtifactRef(zip, "net.fabric", "loom-test", "1.0")
}
private static Path createZip(Map<String, String> entries) {
def file = Files.createTempFile("loom-test", ".zip")
Files.delete(file)
FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip ->
entries.forEach { path, value ->
def fsPath = zip.getPath(path)
def fsPathParent = fsPath.getParent()
if (fsPathParent != null) Files.createDirectories(fsPathParent)
Files.writeString(fsPath, value, StandardCharsets.UTF_8)
}
}
return file
}
private String manifest(String key, String value) {
def manifest = new Manifest()
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes().putValue(key, value)
def out = new ByteArrayOutputStream()
manifest.write(out)
return out.toString(StandardCharsets.UTF_8)
}
}