Add Fabric-Loom-Mixin-Remap-Type manifest entry (#980)

This commit is contained in:
modmuss
2023-11-20 15:19:48 +00:00
committed by GitHub
parent f63a4f4d25
commit 99380d98e5
21 changed files with 327 additions and 304 deletions

View File

@@ -29,6 +29,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
@@ -38,31 +39,45 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData) {
public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequirements, @Nullable InstallerData installerData, MixinRemapType mixinRemapType) {
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 {
public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVersion) throws IOException {
boolean isFabricMod;
RemapRequirements remapRequirements = RemapRequirements.DEFAULT;
InstallerData installerData = null;
MixinRemapType refmapRemapType = MixinRemapType.MIXIN;
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(artifact.path())) {
isFabricMod = FabricModJsonFactory.containsMod(fs);
final Path manifestPath = fs.getPath(MANIFEST_PATH);
final Path manifestPath = fs.getPath(Constants.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);
final String remapValue = mainAttributes.getValue(Constants.Manifest.REMAP_KEY);
final String loomVersion = mainAttributes.getValue(Constants.Manifest.LOOM_VERSION);
final String mixinRemapType = mainAttributes.getValue(Constants.Manifest.MIXIN_REMAP_TYPE);
if (value != null) {
if (remapValue != null) {
// Support opting into and out of remapping with "Fabric-Loom-Remap" manifest entry
remapRequirements = Boolean.parseBoolean(value) ? RemapRequirements.OPT_IN : RemapRequirements.OPT_OUT;
remapRequirements = Boolean.parseBoolean(remapValue) ? RemapRequirements.OPT_IN : RemapRequirements.OPT_OUT;
}
if (mixinRemapType != null) {
try {
refmapRemapType = MixinRemapType.valueOf(mixinRemapType.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Unknown mixin remap type: " + mixinRemapType);
}
}
if (loomVersion != null && refmapRemapType != MixinRemapType.STATIC) {
validateLoomVersion(loomVersion, currentLoomVersion);
}
}
@@ -74,7 +89,32 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
}
}
return new ArtifactMetadata(isFabricMod, remapRequirements, installerData);
return new ArtifactMetadata(isFabricMod, remapRequirements, installerData, refmapRemapType);
}
// Validates that the version matches or is less than the current loom version
// This is only done for jars with tiny-remapper remapped mixins.
private static void validateLoomVersion(String version, String currentLoomVersion) {
if ("0.0.0+unknown".equals(currentLoomVersion)) {
// Unknown version, skip validation. This is the case when running from source (tests)
return;
}
final String[] versionParts = version.split("\\.");
final String[] currentVersionParts = currentLoomVersion.split("\\.");
// Check major and minor version
for (int i = 0; i < 2; i++) {
final int versionPart = Integer.parseInt(versionParts[i]);
final int currentVersionPart = Integer.parseInt(currentVersionParts[i]);
if (versionPart > currentVersionPart) {
throw new IllegalStateException("Mod was built with a newer version of Loom (%s), you are using Loom (%s)".formatted(version, currentLoomVersion));
} else if (versionPart < currentVersionPart) {
// Older version, no need to check further
break;
}
}
}
public boolean shouldRemap() {
@@ -100,4 +140,15 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
return shouldRemap;
}
}
public enum MixinRemapType {
// Jar uses refmaps, so will be remapped by mixin
MIXIN,
// Jar does not use refmaps, so will be remapped by tiny-remapper
STATIC;
public String manifestValue() {
return name().toLowerCase(Locale.ROOT);
}
}
}

View File

@@ -43,11 +43,12 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
public class JarSplitter {
public static final String MANIFEST_SPLIT_ENV_NAME_KEY = "Fabric-Loom-Split-Environment-Name";
private static final Attributes.Name MANIFEST_SPLIT_ENV_NAME = new Attributes.Name(Constants.Manifest.SPLIT_ENV);
private static final Attributes.Name MANIFEST_CLIENT_ENTRIES_NAME = new Attributes.Name(Constants.Manifest.CLIENT_ENTRIES);
final Path inputJar;
@@ -58,9 +59,9 @@ public class JarSplitter {
@Nullable
public Target analyseTarget() {
try (FileSystemUtil.Delegate input = FileSystemUtil.getJarFileSystem(inputJar)) {
final Manifest manifest = input.fromInputStream(Manifest::new, AbstractRemapJarTask.MANIFEST_PATH);
final Manifest manifest = input.fromInputStream(Manifest::new, Constants.Manifest.PATH);
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY))) {
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(Constants.Manifest.SPLIT_ENV))) {
// Jar was not built with splitting enabled.
return null;
}
@@ -122,9 +123,9 @@ public class JarSplitter {
Files.deleteIfExists(clientOutputJar);
try (FileSystemUtil.Delegate input = FileSystemUtil.getJarFileSystem(inputJar)) {
final Manifest manifest = input.fromInputStream(Manifest::new, AbstractRemapJarTask.MANIFEST_PATH);
final Manifest manifest = input.fromInputStream(Manifest::new, Constants.Manifest.PATH);
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_KEY))) {
if (!Boolean.parseBoolean(manifest.getMainAttributes().getValue(Constants.Manifest.SPLIT_ENV))) {
throw new UnsupportedOperationException("Cannot split jar that has not been built with a split env");
}
@@ -157,7 +158,7 @@ public class JarSplitter {
final String entryPath = relativePath.toString();
if (entryPath.equals(AbstractRemapJarTask.MANIFEST_PATH)) {
if (entryPath.equals(Constants.Manifest.PATH)) {
continue;
}
@@ -183,11 +184,11 @@ public class JarSplitter {
stripSignatureData(outManifest);
attributes.remove(Attributes.Name.SIGNATURE_VERSION);
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_SPLIT_ENV_NAME));
Objects.requireNonNull(attributes.remove(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_NAME));
Objects.requireNonNull(attributes.remove(MANIFEST_SPLIT_ENV_NAME));
Objects.requireNonNull(attributes.remove(MANIFEST_CLIENT_ENTRIES_NAME));
writeBytes(writeWithEnvironment(outManifest, "common"), commonOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
writeBytes(writeWithEnvironment(outManifest, "client"), clientOutput.getPath(AbstractRemapJarTask.MANIFEST_PATH));
writeBytes(writeWithEnvironment(outManifest, "common"), commonOutput.getPath(Constants.Manifest.PATH));
writeBytes(writeWithEnvironment(outManifest, "client"), clientOutput.getPath(Constants.Manifest.PATH));
}
}
@@ -197,7 +198,7 @@ public class JarSplitter {
private byte[] writeWithEnvironment(Manifest in, String value) throws IOException {
final Manifest manifest = new Manifest(in);
final Attributes attributes = manifest.getMainAttributes();
attributes.putValue(MANIFEST_SPLIT_ENV_NAME_KEY, value);
attributes.putValue(Constants.Manifest.SPLIT_ENV_NAME, value);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
@@ -206,7 +207,7 @@ public class JarSplitter {
private List<String> readClientEntries(Manifest manifest) {
final Attributes attributes = manifest.getMainAttributes();
final String clientEntriesValue = attributes.getValue(AbstractRemapJarTask.MANIFEST_CLIENT_ENTRIES_KEY);
final String clientEntriesValue = attributes.getValue(Constants.Manifest.CLIENT_ENTRIES);
if (clientEntriesValue == null || clientEntriesValue.isBlank()) {
return Collections.emptyList();

View File

@@ -1,74 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 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.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
public final class MixinDetector {
public static boolean hasMixinsWithoutRefmap(Path modJar) throws IOException {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(modJar)) {
final List<String> mixinConfigs = getMixinConfigs(modJar);
if (!mixinConfigs.isEmpty()) {
for (String mixinConfig : mixinConfigs) {
final Path configPath = fs.getPath(mixinConfig);
if (Files.notExists(configPath)) continue;
try (BufferedReader reader = Files.newBufferedReader(configPath)) {
final JsonObject json = LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class);
if (!json.has("refmap")) {
// We found a mixin config with no refmap, exit the loop.
return true;
}
} catch (JsonParseException e) {
throw new RuntimeException("Could not parse mixin config %s from jar %s".formatted(mixinConfig, modJar.toAbsolutePath()), e);
}
}
}
return false;
}
}
private static List<String> getMixinConfigs(Path modJar) {
// Nullable because we don't care here if we can't read it.
// We can just assume there are no mixins.
final FabricModJson fabricModJson = FabricModJsonFactory.createFromZipNullable(modJar);
return fabricModJson != null ? fabricModJson.getMixinConfigurations() : List.of();
}
}

View File

@@ -56,6 +56,7 @@ import org.gradle.language.base.artifact.SourcesArtifact;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.configuration.RemapConfigurations;
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
@@ -63,6 +64,7 @@ import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
import net.fabricmc.loom.util.service.SharedServiceManager;
@@ -137,9 +139,9 @@ public class ModConfigurationRemapper {
final ArtifactMetadata artifactMetadata;
try {
artifactMetadata = ArtifactMetadata.create(artifact);
artifactMetadata = ArtifactMetadata.create(artifact, LoomGradlePlugin.LOOM_VERSION);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read metadata from" + artifact.path(), e);
throw ExceptionUtil.createDescriptiveWrapper(UncheckedIOException::new, "Failed to read metadata from " + artifact.path(), e);
}
if (artifactMetadata.installerData() != null) {
@@ -158,7 +160,7 @@ public class ModConfigurationRemapper {
continue;
}
final ModDependency modDependency = ModDependencyFactory.create(artifact, remappedConfig, clientRemappedConfig, mappingsSuffix, project);
final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, mappingsSuffix, project);
scheduleSourcesRemapping(project, sourceRemapper, modDependency);
modDependencies.add(modDependency);
}

View File

@@ -51,7 +51,6 @@ import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.mods.dependency.ModDependency;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.TinyRemapperHelper;
@@ -149,9 +148,14 @@ public class ModProcessor {
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
}
final Set<InputTag> hasMixinsWithoutRefmaps = new HashSet<>();
// Configure the mixin extension to remap mixins from mod jars detected not to contain refmaps.
builder.extension(new MixinExtension(hasMixinsWithoutRefmaps::contains));
final Set<InputTag> remapMixins = new HashSet<>();
final boolean requiresStaticMixinRemap = remapList.stream()
.anyMatch(modDependency -> modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC);
if (requiresStaticMixinRemap) {
// Configure the mixin extension to remap mixins from mod jars that were remapped with the mixin extension.
builder.extension(new MixinExtension(remapMixins::contains));
}
final TinyRemapper remapper = builder.build();
@@ -182,8 +186,14 @@ public class ModProcessor {
// Note: this is done at a jar level, not at the level of an individual mixin config.
// If a mod has multiple mixin configs, it's assumed that either all or none of them have refmaps.
if (MixinDetector.hasMixinsWithoutRefmap(info.getInputFile())) {
hasMixinsWithoutRefmaps.add(tag);
if (info.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC) {
if (!requiresStaticMixinRemap) {
// Should be impossible but stranger things have happened.
throw new IllegalStateException("Was not configured for static remap, but a mod required it?!");
}
project.getLogger().info("Remapping mixins in {} statically", info.getInputFile());
remapMixins.add(tag);
}
remapper.readInputsAsync(tag, info.getInputFile());
@@ -243,10 +253,10 @@ public class ModProcessor {
}
private void remapJarManifestEntries(Path jar) throws IOException {
ZipUtils.transform(jar, Map.of(RemapJarTask.MANIFEST_PATH, bytes -> {
ZipUtils.transform(jar, Map.of(Constants.Manifest.PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));
manifest.getMainAttributes().putValue(RemapJarTask.MANIFEST_NAMESPACE_KEY, toM);
manifest.getMainAttributes().putValue(Constants.Manifest.MAPPING_NAMESPACE, toM);
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);

View File

@@ -31,10 +31,12 @@ import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency {
private final ArtifactRef artifact;
private final ArtifactMetadata metadata;
protected final String group;
protected final String name;
protected final String version;
@@ -43,8 +45,9 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod
protected final String mappingsSuffix;
protected final Project project;
public ModDependency(ArtifactRef artifact, String mappingsSuffix, Project project) {
public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Project project) {
this.artifact = artifact;
this.metadata = metadata;
this.group = artifact.group();
this.name = artifact.name();
this.version = artifact.version();
@@ -78,6 +81,10 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod
return artifact;
}
public ArtifactMetadata getMetadata() {
return metadata;
}
protected String getRemappedGroup() {
return getMappingsPrefix() + "." + group;
}

View File

@@ -33,6 +33,7 @@ import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
import net.fabricmc.loom.configuration.mods.JarSplitter;
import net.fabricmc.loom.util.AttributeHelper;
@@ -40,7 +41,7 @@ import net.fabricmc.loom.util.AttributeHelper;
public class ModDependencyFactory {
private static final String TARGET_ATTRIBUTE_KEY = "loom-target";
public static ModDependency create(ArtifactRef artifact, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) {
public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) {
if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) {
final Optional<JarSplitter.Target> cachedTarget = readTarget(artifact);
JarSplitter.Target target;
@@ -53,11 +54,11 @@ public class ModDependencyFactory {
}
if (target != null) {
return new SplitModDependency(artifact, mappingsSuffix, targetConfig, targetClientConfig, target, project);
return new SplitModDependency(artifact, metadata, mappingsSuffix, targetConfig, targetClientConfig, target, project);
}
}
return new SimpleModDependency(artifact, mappingsSuffix, targetConfig, project);
return new SimpleModDependency(artifact, metadata, mappingsSuffix, targetConfig, project);
}
private static Optional<JarSplitter.Target> readTarget(ArtifactRef artifact) {

View File

@@ -32,6 +32,7 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
// Single jar in and out
@@ -39,8 +40,8 @@ public final class SimpleModDependency extends ModDependency {
private final Configuration targetConfig;
private final LocalMavenHelper maven;
public SimpleModDependency(ArtifactRef artifact, String mappingsSuffix, Configuration targetConfig, Project project) {
super(artifact, mappingsSuffix, project);
public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetConfig, Project project) {
super(artifact, metadata, mappingsSuffix, project);
this.targetConfig = Objects.requireNonNull(targetConfig);
this.maven = createMaven(name);
}

View File

@@ -34,6 +34,7 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ModSettings;
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
import net.fabricmc.loom.configuration.mods.ArtifactRef;
import net.fabricmc.loom.configuration.mods.JarSplitter;
@@ -47,8 +48,8 @@ public final class SplitModDependency extends ModDependency {
@Nullable
private final LocalMavenHelper clientMaven;
public SplitModDependency(ArtifactRef artifact, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) {
super(artifact, mappingsSuffix, project);
public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) {
super(artifact, metadata, mappingsSuffix, project);
this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig);
this.targetClientConfig = Objects.requireNonNull(targetClientConfig);
this.target = Objects.requireNonNull(target);

View File

@@ -39,7 +39,7 @@ import java.util.stream.Stream;
import com.google.common.collect.Sets;
import net.fabricmc.loom.configuration.mods.JarSplitter;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
public class MinecraftJarSplitter implements AutoCloseable {
@@ -132,11 +132,11 @@ public class MinecraftJarSplitter implements AutoCloseable {
private void writeManifest(FileSystemUtil.Delegate outputFs, String env) throws IOException {
final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().putValue(JarSplitter.MANIFEST_SPLIT_ENV_NAME_KEY, env);
manifest.getMainAttributes().putValue(Constants.Manifest.SPLIT_ENV_NAME, env);
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);
Files.createDirectories(outputFs.get().getPath("META-INF"));
Files.write(outputFs.get().getPath("META-INF/MANIFEST.MF"), out.toByteArray());
Files.write(outputFs.get().getPath(Constants.Manifest.PATH), out.toByteArray());
}
@Override

View File

@@ -35,7 +35,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.inject.Inject;
@@ -66,19 +65,12 @@ import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.task.service.JarManifestService;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ZipReprocessorUtil;
import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
public abstract class AbstractRemapJarTask extends Jar {
public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
public static final String MANIFEST_NAMESPACE_KEY = "Fabric-Mapping-Namespace";
public static final String MANIFEST_SPLIT_ENV_KEY = "Fabric-Loom-Split-Environment";
public static final String MANIFEST_CLIENT_ENTRIES_KEY = "Fabric-Loom-Client-Only-Entries";
public static final String MANIFEST_JAR_TYPE_KEY = "Fabric-Jar-Type";
public static final Attributes.Name MANIFEST_SPLIT_ENV_NAME = new Attributes.Name(MANIFEST_SPLIT_ENV_KEY);
public static final Attributes.Name MANIFEST_CLIENT_ENTRIES_NAME = new Attributes.Name(MANIFEST_CLIENT_ENTRIES_KEY);
@InputFile
public abstract RegularFileProperty getInputFile();
@@ -157,7 +149,7 @@ public abstract class AbstractRemapJarTask extends Jar {
}
if (getJarType().isPresent()) {
params.getManifestAttributes().put(MANIFEST_JAR_TYPE_KEY, getJarType().get());
params.getManifestAttributes().put(Constants.Manifest.JAR_TYPE, getJarType().get());
}
action.execute(params);
@@ -197,8 +189,8 @@ public abstract class AbstractRemapJarTask extends Jar {
protected void applyClientOnlyManifestAttributes(AbstractRemapParams params, List<String> entries) {
params.getManifestAttributes().set(Map.of(
MANIFEST_SPLIT_ENV_KEY, "true",
MANIFEST_CLIENT_ENTRIES_KEY, String.join(";", entries)
Constants.Manifest.SPLIT_ENV, "true",
Constants.Manifest.CLIENT_ENTRIES, String.join(";", entries)
));
}
@@ -213,11 +205,11 @@ public abstract class AbstractRemapJarTask extends Jar {
}
protected void modifyJarManifest() throws IOException {
int count = ZipUtils.transform(outputFile, Map.of(MANIFEST_PATH, bytes -> {
int count = ZipUtils.transform(outputFile, Map.of(Constants.Manifest.PATH, bytes -> {
var manifest = new Manifest(new ByteArrayInputStream(bytes));
getParameters().getJarManifestService().get().apply(manifest, getParameters().getManifestAttributes().get());
manifest.getMainAttributes().putValue(MANIFEST_NAMESPACE_KEY, getParameters().getTargetNamespace().get());
manifest.getMainAttributes().putValue(Constants.Manifest.MAPPING_NAMESPACE, getParameters().getTargetNamespace().get());
ByteArrayOutputStream out = new ByteArrayOutputStream();
manifest.write(out);

View File

@@ -64,6 +64,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.mods.ArtifactMetadata;
import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.Constants;
@@ -150,6 +151,12 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
if (mixinAp) {
setupLegacyMixinRefmapRemapping(params);
}
// Add the mixin refmap remap type to the manifest
// This is used by the mod dependency remapper to determine if it should remap the refmap
// or if the refmap should be remapped by mixin at runtime.
final var refmapRemapType = mixinAp ? ArtifactMetadata.MixinRemapType.MIXIN : ArtifactMetadata.MixinRemapType.STATIC;
params.getManifestAttributes().put(Constants.Manifest.MIXIN_REMAP_TYPE, refmapRemapType.manifestValue());
}
});
}

View File

@@ -25,7 +25,6 @@
package net.fabricmc.loom.task.service;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.jar.Attributes;
@@ -78,7 +77,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
Attributes attributes = manifest.getMainAttributes();
extraValues.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> {
attributes.putValue(entry.getKey(), entry.getValue());
});
@@ -90,17 +89,17 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
Params p = getParameters();
attributes.putValue("Fabric-Gradle-Version", p.getGradleVersion().get());
attributes.putValue("Fabric-Loom-Version", p.getLoomVersion().get());
attributes.putValue("Fabric-Mixin-Compile-Extensions-Version", p.getMCEVersion().get());
attributes.putValue("Fabric-Minecraft-Version", p.getMinecraftVersion().get());
attributes.putValue("Fabric-Tiny-Remapper-Version", p.getTinyRemapperVersion().get());
attributes.putValue("Fabric-Loader-Version", p.getFabricLoaderVersion().get());
attributes.putValue(Constants.Manifest.GRADLE_VERSION, p.getGradleVersion().get());
attributes.putValue(Constants.Manifest.LOOM_VERSION, p.getLoomVersion().get());
attributes.putValue(Constants.Manifest.MIXIN_COMPILE_EXTENSIONS_VERSION, p.getMCEVersion().get());
attributes.putValue(Constants.Manifest.MINECRAFT_VERSION, p.getMinecraftVersion().get());
attributes.putValue(Constants.Manifest.TINY_REMAPPER_VERSION, p.getTinyRemapperVersion().get());
attributes.putValue(Constants.Manifest.FABRIC_LOADER_VERSION, p.getFabricLoaderVersion().get());
// This can be overridden by mods if required
if (!attributes.containsKey("Fabric-Mixin-Version")) {
attributes.putValue("Fabric-Mixin-Version", p.getMixinVersion().get().version());
attributes.putValue("Fabric-Mixin-Group", p.getMixinVersion().get().group());
if (!attributes.containsKey(Constants.Manifest.MIXIN_VERSION)) {
attributes.putValue(Constants.Manifest.MIXIN_VERSION, p.getMixinVersion().get().version());
attributes.putValue(Constants.Manifest.MIXIN_GROUP, p.getMixinVersion().get().group());
}
}

View File

@@ -124,4 +124,24 @@ public class Constants {
public static final String DISABLE_PROJECT_DEPENDENT_MODS = "fabric.loom.disableProjectDependentMods";
public static final String LIBRARY_PROCESSORS = "fabric.loom.libraryProcessors";
}
public static final class Manifest {
public static final String PATH = "META-INF/MANIFEST.MF";
public static final String REMAP_KEY = "Fabric-Loom-Remap";
public static final String MIXIN_REMAP_TYPE = "Fabric-Loom-Mixin-Remap-Type";
public static final String MAPPING_NAMESPACE = "Fabric-Mapping-Namespace";
public static final String SPLIT_ENV = "Fabric-Loom-Split-Environment";
public static final String SPLIT_ENV_NAME = "Fabric-Loom-Split-Environment-Name";
public static final String CLIENT_ENTRIES = "Fabric-Loom-Client-Only-Entries";
public static final String JAR_TYPE = "Fabric-Jar-Type";
public static final String GRADLE_VERSION = "Fabric-Gradle-Version";
public static final String LOOM_VERSION = "Fabric-Loom-Version";
public static final String MIXIN_COMPILE_EXTENSIONS_VERSION = "Fabric-Mixin-Compile-Extensions-Version";
public static final String MINECRAFT_VERSION = "Fabric-Minecraft-Version";
public static final String TINY_REMAPPER_VERSION = "Fabric-Tiny-Remapper-Version";
public static final String FABRIC_LOADER_VERSION = "Fabric-Loader-Version";
public static final String MIXIN_VERSION = "Fabric-Mixin-Version";
public static final String MIXIN_GROUP = "Fabric-Mixin-Group";
}
}

View File

@@ -43,7 +43,6 @@ import org.intellij.lang.annotations.MagicConstant;
public class ZipReprocessorUtil {
private ZipReprocessorUtil() { }
private static final String MANIFEST_LOCATION = "META-INF/MANIFEST.MF";
private static final String META_INF = "META-INF/";
// See https://docs.oracle.com/en/java/javase/20/docs/specs/jar/jar.html#signed-jar-file
@@ -68,9 +67,9 @@ public class ZipReprocessorUtil {
private static int specialOrdering(String name1, String name2) {
if (name1.equals(name2)) {
return 0;
} else if (name1.equals(MANIFEST_LOCATION)) {
} else if (name1.equals(Constants.Manifest.PATH)) {
return -1;
} else if (name2.equals(MANIFEST_LOCATION)) {
} else if (name2.equals(Constants.Manifest.PATH)) {
return 1;
}

View File

@@ -32,20 +32,19 @@ import spock.lang.Unroll
import net.fabricmc.loom.test.util.GradleProjectTestTrait
import net.fabricmc.loom.test.util.ServerRunner
import net.fabricmc.loom.util.ZipUtils
import static net.fabricmc.loom.test.LoomTestConstants.*
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
@Timeout(value = 30, unit = TimeUnit.MINUTES)
class FabricAPITest extends Specification implements GradleProjectTestTrait {
private static final String API_VERSION = "0.0.0+loom"
@Unroll
def "build and run (gradle #version, mixin ap disabled: #disableMixinAp)"() {
setup:
def gradle = gradleProject(
repo: "https://github.com/FabricMC/fabric.git",
commit: "f091af96c53963fadf9dbc391c67bb40e5678a96",
commit: "23e8616e7457d7d4a65119b93952d134607ffc5c",
version: version,
patch: "fabric_api"
)
@@ -53,24 +52,39 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
gradle.enableMultiProjectOptimisation()
// Disable the mixin ap if needed. Fabric API is a large enough test project to see if something breaks.
def mixinApPatch = ""
if (disableMixinAp) {
mixinApPatch = """
gradle.buildGradle << """
allprojects {
loom.mixin.useLegacyMixinAp = false
}
""".stripIndent()
}
// Set the version to something constant
gradle.buildGradle.text = gradle.buildGradle.text.replace('project.version + "+" + (ENV.GITHUB_RUN_NUMBER ? "" : "local-") + getBranch()', "\"$API_VERSION\"") + mixinApPatch
def minecraftVersion = "23w45a"
def server = ServerRunner.create(gradle.projectDir, minecraftVersion)
.withMod(gradle.getOutputFile("fabric-api-999.0.0.jar"))
def server = ServerRunner.create(gradle.projectDir, "23w33a")
.withMod(gradle.getOutputFile("fabric-api-${API_VERSION}.jar"))
// Test that the dependent mod can be built against the previously built fabric-api
def dependentMod = gradleProject(project: "minimalBase", version: version)
dependentMod.buildGradle << """
repositories {
mavenLocal()
}
loom {
loom.mixin.useLegacyMixinAp = ${!disableMixinAp}
}
dependencies {
minecraft "com.mojang:minecraft:${minecraftVersion}"
mappings "net.fabricmc:yarn:${minecraftVersion}+build.1:v2"
modImplementation "net.fabricmc.fabric-api:fabric-api:999.0.0"
}
"""
when:
def result = gradle.run(tasks: [
"clean",
"build",
"publishToMavenLocal"
], args: [
@@ -85,19 +99,31 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
gradle.printOutputFiles()
def serverResult = server.run()
def dependentModResult = dependentMod.run(task: "build")
then:
result.task(":build").outcome == SUCCESS
result.task(":prepareRemapJar").outcome == SUCCESS
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.11/fabric-biome-api-v1-13.0.11.jar").exists()
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/13.0.11/fabric-biome-api-v1-13.0.11-sources.jar").exists()
def biomeApiJar = new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/999.0.0/fabric-biome-api-v1-999.0.0.jar")
def manifest = ZipUtils.unpack(biomeApiJar.toPath(), "META-INF/MANIFEST.MF").toString()
new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/999.0.0/fabric-biome-api-v1-999.0.0-sources.jar").exists()
if (disableMixinAp) {
manifest.contains("Fabric-Loom-Mixin-Remap-Type=static")
} else {
manifest.contains("Fabric-Loom-Mixin-Remap-Type=mixin")
}
serverResult.successful()
serverResult.output.contains("- fabric-api $API_VERSION")
serverResult.output.contains("- fabric-api 999.0.0")
dependentModResult.task(":build").outcome == SUCCESS
where:
[version, disableMixinAp] << [
[DEFAULT_GRADLE],
[false, true]
[PRE_RELEASE_GRADLE],
[false, true].shuffled()
].combinations()
}
}

View File

@@ -55,11 +55,11 @@ class ReproducibleBuildTest extends Specification implements GradleProjectTestTr
where:
version | modHash | sourceHash
DEFAULT_GRADLE | "4bb8acb5e575a4080a8fe1282f8e1994" | [
DEFAULT_GRADLE | "207bd75aa34fc996a97e962dd98b61d5" | [
"8e8fac2a5e32fc872e6cf0f9ccc55cfd",
"ed331b6fae5677797a0104eba014e255"
]
PRE_RELEASE_GRADLE | "4bb8acb5e575a4080a8fe1282f8e1994" | [
PRE_RELEASE_GRADLE | "207bd75aa34fc996a97e962dd98b61d5" | [
"8e8fac2a5e32fc872e6cf0f9ccc55cfd",
"ed331b6fae5677797a0104eba014e255"
]

View File

@@ -31,8 +31,11 @@ import spock.lang.Specification
import net.fabricmc.loom.configuration.mods.ArtifactMetadata
import net.fabricmc.loom.configuration.mods.ArtifactRef
import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.MixinRemapType.MIXIN
import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.MixinRemapType.STATIC
import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.RemapRequirements.*
import static net.fabricmc.loom.test.util.ZipTestUtils.*
import static net.fabricmc.loom.test.util.ZipTestUtils.createZip
import static net.fabricmc.loom.test.util.ZipTestUtils.manifest
class ArtifactMetadataTest extends Specification {
def "is fabric mod"() {
@@ -101,8 +104,92 @@ class ArtifactMetadataTest extends Specification {
false | ["fabric.mod.json": "{}"] // Fabric mod, no installer data
}
private static ArtifactMetadata createMetadata(Path zip) {
return ArtifactMetadata.create(createArtifact(zip))
def "Refmap remap type" () {
given:
def zip = createZip(entries)
when:
def metadata = createMetadata(zip)
def result = metadata.mixinRemapType()
then:
result == type
where:
type | entries
MIXIN | ["hello.json": "{}"] // None Mod jar
MIXIN | ["fabric.mod.json": "{}"] // Fabric mod without manfiest file
MIXIN | ["fabric.mod.json": "{}", "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Mixin-Remap-Type", "mixin")] // Fabric mod without remap type entry
STATIC | ["fabric.mod.json": "{}", "META-INF/MANIFEST.MF": manifest("Fabric-Loom-Mixin-Remap-Type", "static")] // Fabric mod opt-in
}
// Test that a mod with the same or older version of loom can be read
def "Valid loom version"() {
given:
def zip = createMod(modLoomVersion, "mixin")
when:
def metadata = createMetadata(zip, loomVersion)
then:
metadata != null
where:
loomVersion | modLoomVersion
"1.4" | "1.0.1"
"1.4" | "1.0.99"
"1.4" | "1.4"
"1.4" | "1.4.0"
"1.4" | "1.4.1"
"1.4" | "1.4.99"
"1.4" | "1.4.local"
"1.5" | "1.4.99"
"2.0" | "1.4.99"
}
// Test that a mod with the same or older version of loom can be read
def "Invalid loom version"() {
given:
def zip = createMod(modLoomVersion, "mixin")
when:
def metadata = createMetadata(zip, loomVersion)
then:
def e = thrown(IllegalStateException)
e.message == "Mod was built with a newer version of Loom ($modLoomVersion), you are using Loom ($loomVersion)"
where:
loomVersion | modLoomVersion
"1.4" | "1.5"
"1.4" | "1.5.00"
"1.4" | "2.0"
"1.4" | "2.4"
}
def "Accepts all Loom versions"() {
given:
def zip = createMod(modLoomVersion, "static")
when:
def metadata = createMetadata(zip, loomVersion)
then:
metadata != null
where:
loomVersion | modLoomVersion
// Valid
"1.4" | "1.0.1"
"1.4" | "1.0.99"
"1.4" | "1.4"
"1.4" | "1.4.0"
"1.4" | "1.4.1"
"1.4" | "1.4.99"
"1.4" | "1.4.local"
"1.5" | "1.4.99"
"2.0" | "1.4.99"
// Usually invalid
"1.4" | "1.5"
"1.4" | "1.5.00"
"1.4" | "2.0"
"1.4" | "2.4"
}
private static Path createMod(String loomVersion, String remapType) {
return createZip(["fabric.mod.json": "{}", "META-INF/MANIFEST.MF": manifest(["Fabric-Loom-Version": loomVersion, "Fabric-Loom-Mixin-Remap-Type": remapType])])
}
private static ArtifactMetadata createMetadata(Path zip, String loomVersion = "1.4") {
return ArtifactMetadata.create(createArtifact(zip), loomVersion)
}
private static ArtifactRef createArtifact(Path zip) {

View File

@@ -1,129 +0,0 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 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 java.nio.file.Path
import groovy.json.JsonOutput
import spock.lang.Specification
import spock.lang.TempDir
import net.fabricmc.loom.configuration.mods.MixinDetector
import net.fabricmc.loom.util.FileSystemUtil
class MixinDetectorTest extends Specification {
@TempDir
Path tempDir
private Path makeJar(Map<String, String> mixinConfigs) {
def path = tempDir.resolve("test.jar")
def fs = FileSystemUtil.getJarFileSystem(path, true)
try {
// Create fabric.mod.json
def fabricModJson = JsonOutput.toJson([
schemaVersion: 1,
id: 'test',
version: '1',
mixins: mixinConfigs.keySet()
])
fs.getPath('fabric.mod.json').text = fabricModJson
// Write all mixin configs
mixinConfigs.forEach { name, content ->
fs.getPath(name).text = content
}
} finally {
fs.close()
}
return path
}
def "jar without mixins has no mixins without refmaps"() {
setup:
def jarPath = makeJar([:])
when:
def hasMixinsWithoutRefmaps = MixinDetector.hasMixinsWithoutRefmap(jarPath)
then:
!hasMixinsWithoutRefmaps // no mixins
}
def "jar with one mixin config with refmap has no mixins without refmaps"() {
setup:
def jarPath = makeJar([
'test.mixins.json': JsonOutput.toJson([
'package': 'com.example.test',
'mixins': ['TestMixin'],
'refmap': 'test-refmap.json'
])
])
when:
def hasMixinsWithoutRefmaps = MixinDetector.hasMixinsWithoutRefmap(jarPath)
then:
!hasMixinsWithoutRefmaps // no mixins with refmaps
}
def "jar with one mixin config without refmap has mixins without refmaps"() {
setup:
def jarPath = makeJar([
'test.mixins.json': JsonOutput.toJson([
'package': 'com.example.test',
'mixins': ['TestMixin']
])
])
when:
def hasMixinsWithoutRefmaps = MixinDetector.hasMixinsWithoutRefmap(jarPath)
then:
hasMixinsWithoutRefmaps // mixins with refmaps
}
def "jar with mixed mixin configs has mixins without refmaps"() {
setup:
def jarPath = makeJar([
'test.mixins.json': JsonOutput.toJson([
'package': 'com.example.test',
'mixins': ['TestMixin']
]),
'test2.mixins.json': JsonOutput.toJson([
'package': 'com.example.test2',
'mixins': ['TestMixin2'],
'refmap': 'test2-refmap.json'
])
])
when:
def hasMixinsWithoutRefmaps = MixinDetector.hasMixinsWithoutRefmap(jarPath)
then:
hasMixinsWithoutRefmaps // mixins with refmaps
}
}

View File

@@ -50,9 +50,15 @@ class ZipTestUtils {
}
static String manifest(String key, String value) {
return manifest(Map.of(key, value))
}
static String manifest(Map<String, String> entries) {
def manifest = new Manifest()
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes().putValue(key, value)
entries.forEach { key, value ->
manifest.getMainAttributes().putValue(key, value)
}
def out = new ByteArrayOutputStream()
manifest.write(out)

View File

@@ -1,10 +1,26 @@
diff --git a/build.gradle b/build.gradle
--- a/build.gradle (revision 14d319c0729baf781e171e3c9f845fda55670f1b)
+++ b/build.gradle (date 1688330748664)
@@ -37,17 +37,7 @@
throw new NullPointerException("Could not find version for " + project.name)
}
--- a/build.gradle (revision 23e8616e7457d7d4a65119b93952d134607ffc5c)
+++ b/build.gradle (date 1699535194191)
@@ -13,7 +13,7 @@
def ENV = System.getenv()
-version = project.version + "+" + (ENV.GITHUB_RUN_NUMBER ? "" : "local-") + getBranch()
+version = "999.0.0"
logger.lifecycle("Building Fabric: " + version)
@@ -22,24 +22,7 @@
import org.apache.commons.codec.digest.DigestUtils
def getSubprojectVersion(project) {
- // Get the version from the gradle.properties file
- def version = project.properties["${project.name}-version"]
-
- if (!version) {
- throw new NullPointerException("Could not find version for " + project.name)
- }
-
- if (grgit == null) {
- return version + "+nogit"
- }
@@ -16,7 +32,7 @@ diff --git a/build.gradle b/build.gradle
- }
-
- return version + "+" + latestCommits.get(0).id.substring(0, 8) + DigestUtils.sha256Hex(project.rootProject.minecraft_version).substring(0, 2)
+ return version
+ return "999.0.0"
}
def getBranch() {