Merge remote-tracking branch 'upstream/dev/1.13' into dev/1.13

# Conflicts:
#	gradle/libs.versions.toml
This commit is contained in:
Juuz
2025-11-03 14:06:35 +02:00
10 changed files with 492 additions and 126 deletions

View File

@@ -6,10 +6,10 @@ gson = "2.10.1"
stitch = "0.6.2"
tiny-remapper = "0.12.0"
clazz-tweaker = "0.1.1"
mapping-io = "0.7.1"
mapping-io = "0.8.0"
lorenz-tiny = "4.0.2"
mercury = "0.4.3.18"
mercury-mixin = "0.2.1"
mercury-mixin = "0.2.2"
loom-native = "0.2.0"
unpick = "3.0.0-beta.9"

View File

@@ -29,9 +29,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
@@ -110,32 +109,11 @@ public final class MappingsMerger {
* Currently, Yarn does not export mappings for these inner classes.
*/
private static void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int intermediaryIdx = tree.getNamespaceId("intermediary");
int namedIdx = tree.getNamespaceId("named");
assert tree.getNamespaceId("intermediary") > MappingTree.SRC_NAMESPACE_ID;
// The tree does not have an index by intermediary names by default
// Create an index by intermediary names for faster lookups during the propagation
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = classEntry.getDstName(intermediaryIdx);
String namedName = classEntry.getDstName(namedIdx);
if (intermediaryName.equals(namedName) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, intermediaryIdx, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
tree.propagateOuterClassNames("intermediary", List.of("named"), false);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.task;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.options.Option;
public abstract class AbstractMigrateMappingsTask extends AbstractLoomTask {
@Input
@Option(option = "mappings", description = "Target mappings")
public abstract Property<String> getMappings();
@Input
@Option(option = "overrideInputsIHaveABackup", description = "Override input files with the remapped files")
public abstract Property<Boolean> getOverrideInputs();
public AbstractMigrateMappingsTask() {
getOverrideInputs().convention(false);
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016-2022 FabricMC
* Copyright (c) 2016-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
@@ -63,7 +63,7 @@ public abstract class LoomTasks implements Runnable {
SourceSetHelper.getSourceSets(getProject()).all(sourceSet -> {
if (SourceSetHelper.isMainSourceSet(sourceSet)) {
getTasks().register("migrateMappings", MigrateMappingsTask.class, t -> {
t.setDescription("Migrates mappings to a new version.");
t.setDescription("Migrates source code mappings to a new version.");
});
return;
@@ -74,12 +74,16 @@ public abstract class LoomTasks implements Runnable {
}
getTasks().register(sourceSet.getTaskName("migrate", "mappings"), MigrateMappingsTask.class, t -> {
t.setDescription("Migrates mappings to a new version.");
t.setDescription("Migrates source code mappings to a new version.");
t.getInputDir().set(SourceSetHelper.getFirstSrcDir(sourceSet));
t.getOutputDir().convention(getProject().getLayout().getProjectDirectory().dir(sourceSet.getTaskName("remapped", "src")));
});
});
getTasks().register("migrateClassTweakerMappings", MigrateClassTweakerMappingsTask.class, t -> {
t.setDescription("Migrates access widener and class tweaker mappings to a new version.");
});
var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> {
t.setDescription("Generate the log4j config file");
});

View File

@@ -0,0 +1,84 @@
/*
* 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.task;
import java.nio.file.Files;
import java.nio.file.Path;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.UntrackedTask;
import org.gradle.api.tasks.options.Option;
import net.fabricmc.classtweaker.api.ClassTweakerReader;
import net.fabricmc.classtweaker.api.ClassTweakerWriter;
import net.fabricmc.classtweaker.visitors.ClassTweakerRemapperVisitor;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.task.service.MigrateClassTweakerMappingsService;
import net.fabricmc.loom.util.service.ScopedServiceFactory;
@UntrackedTask(because = "Always rerun this task.")
public abstract class MigrateClassTweakerMappingsTask extends AbstractMigrateMappingsTask {
@InputFile
@SkipWhenEmpty
@Option(option = "input", description = "Access widener file")
public abstract RegularFileProperty getInputFile();
@OutputFile
@Option(option = "output", description = "Remapped access widener file")
public abstract RegularFileProperty getOutputFile();
@Nested
protected abstract Property<MigrateClassTweakerMappingsService.Options> getMigrationServiceOptions();
public MigrateClassTweakerMappingsTask() {
getInputFile().convention(getExtension().getAccessWidenerPath());
getOutputFile().convention(getProject().getLayout().getProjectDirectory().file("remapped.accesswidener"));
getMigrationServiceOptions().set(MigrateClassTweakerMappingsService.createOptions(getProject(), getMappings()));
}
@TaskAction
public void doTask() throws Throwable {
try (var serviceFactory = new ScopedServiceFactory()) {
final MigrateClassTweakerMappingsService service = serviceFactory.get(getMigrationServiceOptions().get());
final Path inputFile = getInputFile().get().getAsFile().toPath();
final byte[] inputBytes = Files.readAllBytes(inputFile);
final int ctVersion = ClassTweakerReader.readVersion(inputBytes);
final ClassTweakerWriter writer = ClassTweakerWriter.create(ctVersion);
final var remapper = new ClassTweakerRemapperVisitor(writer, service.getRemapper(), MappingsNamespace.NAMED.toString(), MappingsNamespace.NAMED.toString());
ClassTweakerReader.create(remapper).read(inputBytes, "unused_id");
final boolean inPlace = getOverrideInputs().get();
final Path targetFile = inPlace ? inputFile : getOutputFile().get().getAsFile().toPath();
Files.write(targetFile, writer.getOutput());
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2019-2024 FabricMC
* Copyright (c) 2019-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
@@ -29,7 +29,6 @@ import java.nio.file.Path;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
@@ -38,16 +37,12 @@ import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.UntrackedTask;
import org.gradle.api.tasks.options.Option;
import net.fabricmc.loom.task.service.MigrateMappingsService;
import net.fabricmc.loom.task.service.MigrateSourceCodeMappingsService;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.service.ScopedServiceFactory;
@UntrackedTask(because = "Always rerun this task.")
public abstract class MigrateMappingsTask extends AbstractLoomTask {
@Input
@Option(option = "mappings", description = "Target mappings")
public abstract Property<String> getMappings();
public abstract class MigrateMappingsTask extends AbstractMigrateMappingsTask {
@InputDirectory
@SkipWhenEmpty
@Option(option = "input", description = "Java source file directory")
@@ -57,25 +52,20 @@ public abstract class MigrateMappingsTask extends AbstractLoomTask {
@Option(option = "output", description = "Remapped source output directory")
public abstract DirectoryProperty getOutputDir();
@Input
@Option(option = "overrideInputsIHaveABackup", description = "Override input files with the remapped files")
public abstract Property<Boolean> getOverrideInputs();
@Nested
protected abstract Property<MigrateMappingsService.Options> getMigrationServiceOptions();
protected abstract Property<MigrateSourceCodeMappingsService.Options> getMigrationServiceOptions();
public MigrateMappingsTask() {
getInputDir().convention(getProject().getLayout().getProjectDirectory().dir("src/main/java"));
getOutputDir().convention(getProject().getLayout().getProjectDirectory().dir("remappedSrc"));
getMigrationServiceOptions().set(MigrateMappingsService.createOptions(getProject(), getMappings(), getInputDir(), getOutputDir()));
getOverrideInputs().convention(false);
getMigrationServiceOptions().set(MigrateSourceCodeMappingsService.createOptions(getProject(), getMappings(), getInputDir(), getOutputDir()));
}
@TaskAction
public void doTask() throws Throwable {
try (var serviceFactory = new ScopedServiceFactory()) {
MigrateMappingsService service = serviceFactory.get(getMigrationServiceOptions().get());
service.migrateMapppings();
MigrateSourceCodeMappingsService service = serviceFactory.get(getMigrationServiceOptions().get());
service.migrateMappings();
}
if (getOverrideInputs().get()) {

View File

@@ -0,0 +1,155 @@
/*
* 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.task.service;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Nested;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.service.Service;
import net.fabricmc.loom.util.service.ServiceFactory;
import net.fabricmc.loom.util.service.ServiceType;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingNsRenamer;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MigrateClassTweakerMappingsService extends Service<MigrateClassTweakerMappingsService.Options> implements Closeable {
private static final ServiceType<Options, MigrateClassTweakerMappingsService> TYPE = new ServiceType<>(Options.class, MigrateClassTweakerMappingsService.class);
private static final String MIGRATION_TARGET_NS = "migrationTarget";
public MigrateClassTweakerMappingsService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
public interface Options extends Service.Options {
@Nested
Property<MigrateMappingsService.Options> getMappings();
@Nested
Property<TinyRemapperService.Options> getTinyRemapperOptions();
@InputFile
RegularFileProperty getMergedMappings();
}
public static Provider<Options> createOptions(Project project, Provider<String> targetMappings) {
ConfigurableFileCollection minecraftLibraryClasspath = project.getObjects().fileCollection();
minecraftLibraryClasspath.from(project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
minecraftLibraryClasspath.from(project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES));
return TYPE.create(project, (o) -> {
o.getMappings().set(MigrateMappingsService.createOptions(project, targetMappings));
Provider<RegularFile> mergedMappings = o.getMappings().flatMap(m -> {
return createMergedMappingFile(project, targetMappings, m.getSourceMappings(), m.getTargetMappings());
});
o.getMergedMappings().set(mergedMappings);
o.getTinyRemapperOptions().set(TinyRemapperService.TYPE.create(project, o2 -> {
o2.getClasspath().from(o.getMappings().map(m -> m.getClasspath().minus(minecraftLibraryClasspath)));
o2.getFrom().set(MappingsNamespace.NAMED.toString());
o2.getTo().set(MIGRATION_TARGET_NS);
o2.getMappings().add(MappingsService.TYPE.create(project, o3 -> {
o3.getMappingsFile().set(mergedMappings);
o3.getFrom().set(MappingsNamespace.NAMED.toString());
o3.getTo().set(MIGRATION_TARGET_NS);
o3.getRemapLocals().set(false);
}));
o2.getUselegacyMixinAP().set(false);
}));
});
}
public Remapper getRemapper() {
final TinyRemapperService service = getServiceFactory().get(getOptions().getTinyRemapperOptions());
return service.getTinyRemapperForRemapping().getEnvironment().getRemapper();
}
private static Provider<RegularFile> createMergedMappingFile(Project project, Provider<String> targetMappingsId, Provider<MappingsService.Options> sourceOptions, Provider<TinyMappingsService.Options> targetOptions) {
return sourceOptions.flatMap(sourceOpt -> targetOptions.flatMap(targetOpt -> {
final Provider<RegularFile> fileProvider = project.getLayout()
.getBuildDirectory()
.file(targetMappingsId.map(id -> "migrate-class-tweaker-mappings-" + Checksum.of(id).sha256().hex(16) + ".tiny"));
return fileProvider.map(file -> {
final Path path = file.getAsFile().toPath();
if (!Files.exists(path) || LoomGradleExtension.get(project).refreshDeps()) {
try {
final MemoryMappingTree tree = mergeMappings(sourceOpt, targetOpt);
Files.createDirectories(path.getParent());
try (var writer = new Tiny2FileWriter(Files.newBufferedWriter(path, StandardCharsets.UTF_8), false)) {
tree.accept(writer);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return file;
});
}));
}
private static MemoryMappingTree mergeMappings(MappingsService.Options sourceOptions, TinyMappingsService.Options targetOptions) throws IOException {
final var tree = new MemoryMappingTree();
MappingReader.read(sourceOptions.getMappingsFile().get().getAsFile().toPath(), tree);
final var renamer = new MappingNsRenamer(tree, Map.of(MappingsNamespace.NAMED.toString(), MIGRATION_TARGET_NS));
final Path mappingFile = targetOptions.getMappings().getSingleFile().toPath();
if (targetOptions.getZipEntryPath().isPresent()) {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(mappingFile)) {
MappingReader.read(fs.getPath(targetOptions.getZipEntryPath().get()), renamer);
}
} else {
MappingReader.read(mappingFile, renamer);
}
return tree;
}
@Override
public void close() throws IOException {
Files.deleteIfExists(getOptions().getMergedMappings().get().getAsFile().toPath());
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024 FabricMC
* Copyright (c) 2024-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
@@ -24,32 +24,19 @@
package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.mixin.MixinRemapper;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.IllegalDependencyNotation;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,14 +45,11 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsFactory;
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.service.Service;
import net.fabricmc.loom.util.service.ServiceFactory;
import net.fabricmc.loom.util.service.ServiceType;
import net.fabricmc.lorenztiny.TinyMappingsJoiner;
public class MigrateMappingsService extends Service<MigrateMappingsService.Options> {
public final class MigrateMappingsService extends Service<MigrateMappingsService.Options> {
private static final Logger LOGGER = LoggerFactory.getLogger(MigrateMappingsService.class);
private static final ServiceType<Options, MigrateMappingsService> TYPE = new ServiceType<>(Options.class, MigrateMappingsService.class);
@@ -78,21 +62,14 @@ public class MigrateMappingsService extends Service<MigrateMappingsService.Optio
Property<MappingsService.Options> getSourceMappings();
@Nested
Property<TinyMappingsService.Options> getTargetMappings();
@InputDirectory
DirectoryProperty getInputDir();
@Input
Property<String> getSourceCompatibility();
@InputFiles
ConfigurableFileCollection getClasspath();
@OutputDirectory
DirectoryProperty getOutputDir();
}
public static Provider<Options> createOptions(Project project, Provider<String> targetMappings, DirectoryProperty inputDir, DirectoryProperty outputDir) {
public static Provider<Options> createOptions(Project project, Provider<String> targetMappings) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
final Provider<String> from = project.provider(() -> "intermediary");
final Provider<String> to = project.provider(() -> "named");
final JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility();
ConfigurableFileCollection classpath = project.getObjects().fileCollection();
classpath.from(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
@@ -111,64 +88,20 @@ public class MigrateMappingsService extends Service<MigrateMappingsService.Optio
FileCollection targetMappingsFile = getTargetMappingsFile(project, targetMappings.get());
o.getSourceMappings().set(MappingsService.createOptionsWithProjectMappings(project, from, to));
o.getTargetMappings().set(TinyMappingsService.createOptions(project, targetMappingsFile, "mappings/mappings.tiny"));
o.getSourceCompatibility().set(javaVersion.toString());
o.getInputDir().set(inputDir);
o.getClasspath().from(classpath);
o.getOutputDir().set(outputDir);
});
}
public void migrateMapppings() throws IOException {
final Path inputDir = getOptions().getInputDir().get().getAsFile().toPath();
final Path outputDir = getOptions().getOutputDir().get().getAsFile().toPath();
public MappingsService getSourceMappingsService() {
return getServiceFactory().get(getOptions().getSourceMappings().get());
}
if (!Files.exists(inputDir) || !Files.isDirectory(inputDir)) {
throw new IllegalArgumentException("Could not find input directory: " + inputDir.toAbsolutePath());
}
public TinyMappingsService getTargetMappingsService() {
return getServiceFactory().get(getOptions().getTargetMappings().get());
}
if (Files.exists(outputDir)) {
DeletingFileVisitor.deleteDirectory(outputDir);
}
Files.createDirectories(outputDir);
Mercury mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.setSourceCompatibility(getOptions().getSourceCompatibility().get());
final MappingsService sourceMappingsService = getServiceFactory().get(getOptions().getSourceMappings().get());
final TinyMappingsService targetMappingsService = getServiceFactory().get(getOptions().getTargetMappings().get());
final MappingSet mappingSet = new TinyMappingsJoiner(
sourceMappingsService.getMemoryMappingTree(), MappingsNamespace.NAMED.toString(),
targetMappingsService.getMappingTree(), MappingsNamespace.NAMED.toString(),
MappingsNamespace.INTERMEDIARY.toString()
).read();
mercury.getProcessors().add(MixinRemapper.create(mappingSet));
mercury.getProcessors().add(MercuryRemapper.create(mappingSet));
for (File file : getOptions().getClasspath().getFiles()) {
mercury.getClassPath().add(file.toPath());
}
try {
mercury.rewrite(
inputDir,
outputDir
);
} catch (Exception e) {
try {
DeletingFileVisitor.deleteDirectory(outputDir);
} catch (IOException ignored) {
// Nope
}
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to migrate mappings", e);
}
// clean file descriptors
System.gc();
public FileCollection getClasspath() {
return getOptions().getClasspath();
}
/**

View File

@@ -0,0 +1,138 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2024-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.task.service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.mixin.MixinRemapper;
import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.service.Service;
import net.fabricmc.loom.util.service.ServiceFactory;
import net.fabricmc.loom.util.service.ServiceType;
import net.fabricmc.lorenztiny.TinyMappingsJoiner;
public final class MigrateSourceCodeMappingsService extends Service<MigrateSourceCodeMappingsService.Options> {
private static final ServiceType<Options, MigrateSourceCodeMappingsService> TYPE = new ServiceType<>(Options.class, MigrateSourceCodeMappingsService.class);
public MigrateSourceCodeMappingsService(Options options, ServiceFactory serviceFactory) {
super(options, serviceFactory);
}
public interface Options extends Service.Options {
@Nested
Property<MigrateMappingsService.Options> getMappings();
@InputDirectory
DirectoryProperty getInputDir();
@Input
Property<String> getSourceCompatibility();
@OutputDirectory
DirectoryProperty getOutputDir();
}
public static Provider<Options> createOptions(Project project, Provider<String> targetMappings, DirectoryProperty inputDir, DirectoryProperty outputDir) {
final JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility();
return TYPE.create(project, (o) -> {
o.getMappings().set(MigrateMappingsService.createOptions(project, targetMappings));
o.getSourceCompatibility().set(javaVersion.toString());
o.getInputDir().set(inputDir);
o.getOutputDir().set(outputDir);
});
}
public void migrateMappings() throws IOException {
final Path inputDir = getOptions().getInputDir().get().getAsFile().toPath();
final Path outputDir = getOptions().getOutputDir().get().getAsFile().toPath();
if (!Files.exists(inputDir) || !Files.isDirectory(inputDir)) {
throw new IllegalArgumentException("Could not find input directory: " + inputDir.toAbsolutePath());
}
if (Files.exists(outputDir)) {
DeletingFileVisitor.deleteDirectory(outputDir);
}
Files.createDirectories(outputDir);
Mercury mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.setSourceCompatibility(getOptions().getSourceCompatibility().get());
final MigrateMappingsService migrateMappingsService = getServiceFactory().get(getOptions().getMappings());
final MappingsService sourceMappingsService = migrateMappingsService.getSourceMappingsService();
final TinyMappingsService targetMappingsService = migrateMappingsService.getTargetMappingsService();
final MappingSet mappingSet = new TinyMappingsJoiner(
sourceMappingsService.getMemoryMappingTree(), MappingsNamespace.NAMED.toString(),
targetMappingsService.getMappingTree(), MappingsNamespace.NAMED.toString(),
MappingsNamespace.INTERMEDIARY.toString()
).read();
mercury.getProcessors().add(MixinRemapper.create(mappingSet));
mercury.getProcessors().add(MercuryRemapper.create(mappingSet));
for (File file : migrateMappingsService.getClasspath().getFiles()) {
mercury.getClassPath().add(file.toPath());
}
try {
mercury.rewrite(
inputDir,
outputDir
);
} catch (Exception e) {
try {
DeletingFileVisitor.deleteDirectory(outputDir);
} catch (IOException ignored) {
// Nope
}
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to migrate mappings", e);
}
// clean file descriptors
System.gc();
}
}

View File

@@ -244,4 +244,45 @@ class MigrateMappingsTest extends Specification implements GradleProjectTestTrai
where:
version << STANDARD_TEST_VERSIONS
}
def "Migrate AW (in place: #inPlace, header: #header)"() {
setup:
def gradle = gradleProject(project: "minimalBase")
gradle.buildGradle << """
loom.accessWidenerPath = file('src/main/resources/test.accesswidener')
dependencies {
minecraft 'com.mojang:minecraft:24w36a'
mappings 'net.fabricmc:yarn:24w36a+build.6:v2'
}
""".stripIndent()
def awFile = new File(gradle.projectDir, 'src/main/resources/test.accesswidener')
awFile.parentFile.mkdirs()
awFile.text = header + '\n' + 'extendable method net/minecraft/block/PaneBlock connectsTo (Lnet/minecraft/block/BlockState;Z)Z'
when:
def tasks = [
"migrateClassTweakerMappings",
"--mappings",
"net.minecraft:mappings:24w36a"
]
if (inPlace) {
tasks.add("--overrideInputsIHaveABackup")
}
def result = gradle.run(tasks: tasks)
def remapped = (inPlace ? awFile : new File(gradle.projectDir, 'remapped.accesswidener')).text
then:
result.task(":migrateClassTweakerMappings").outcome == SUCCESS
remapped == header + '\n' + 'extendable\tmethod\tnet/minecraft/world/level/block/IronBarsBlock\tattachsTo\t(Lnet/minecraft/world/level/block/state/BlockState;Z)Z\n'
where:
// Check that the header is kept intact and that the in place remapping works
header | inPlace
'accessWidener\tv1\tnamed' | false
'accessWidener\tv1\tnamed' | true // the code is the same so we only need one case for in place remapping
'accessWidener\tv2\tnamed' | false
'classTweaker\tv1\tnamed' | false
}
}