From a0d73dc3b7cc4e019b3070e5d99a1c391b308cf0 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:12:56 +0200 Subject: [PATCH] Add task to migrate CT/AW mappings (#1412) * Add task to migrate CT/AW mappings * Remove unused logger * Add option to map CT in place, add common base class * Add test for migrating AW/CT mappings * Add SkipWhenEmpty to MigrateClassTweakerMappingsTask.inputFile * Split CT migration service from base service --- .../task/AbstractMigrateMappingsTask.java | 43 +++++ .../net/fabricmc/loom/task/LoomTasks.java | 10 +- .../task/MigrateClassTweakerMappingsTask.java | 84 ++++++++++ .../loom/task/MigrateMappingsTask.java | 24 +-- .../MigrateClassTweakerMappingsService.java | 155 ++++++++++++++++++ .../task/service/MigrateMappingsService.java | 89 ++-------- .../MigrateSourceCodeMappingsService.java | 138 ++++++++++++++++ .../integration/MigrateMappingsTest.groovy | 41 +++++ 8 files changed, 486 insertions(+), 98 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/task/AbstractMigrateMappingsTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/MigrateClassTweakerMappingsTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/service/MigrateClassTweakerMappingsService.java create mode 100644 src/main/java/net/fabricmc/loom/task/service/MigrateSourceCodeMappingsService.java diff --git a/src/main/java/net/fabricmc/loom/task/AbstractMigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/AbstractMigrateMappingsTask.java new file mode 100644 index 00000000..d00871f1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/AbstractMigrateMappingsTask.java @@ -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 getMappings(); + + @Input + @Option(option = "overrideInputsIHaveABackup", description = "Override input files with the remapped files") + public abstract Property getOverrideInputs(); + + public AbstractMigrateMappingsTask() { + getOverrideInputs().convention(false); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 98cf79b2..312d558d 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -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"); }); diff --git a/src/main/java/net/fabricmc/loom/task/MigrateClassTweakerMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateClassTweakerMappingsTask.java new file mode 100644 index 00000000..5774bc3b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/MigrateClassTweakerMappingsTask.java @@ -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 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()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java index 5c60d2ff..e23af8f6 100644 --- a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java +++ b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java @@ -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 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 getOverrideInputs(); - @Nested - protected abstract Property getMigrationServiceOptions(); + protected abstract Property 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()) { diff --git a/src/main/java/net/fabricmc/loom/task/service/MigrateClassTweakerMappingsService.java b/src/main/java/net/fabricmc/loom/task/service/MigrateClassTweakerMappingsService.java new file mode 100644 index 00000000..8d2db843 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/MigrateClassTweakerMappingsService.java @@ -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 implements Closeable { + private static final ServiceType 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 getMappings(); + @Nested + Property getTinyRemapperOptions(); + @InputFile + RegularFileProperty getMergedMappings(); + } + + public static Provider createOptions(Project project, Provider 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 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 createMergedMappingFile(Project project, Provider targetMappingsId, Provider sourceOptions, Provider targetOptions) { + return sourceOptions.flatMap(sourceOpt -> targetOptions.flatMap(targetOpt -> { + final Provider 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()); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java b/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java index 4ab5415f..4e6f3f02 100644 --- a/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java +++ b/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java @@ -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 { +public final class MigrateMappingsService extends Service { private static final Logger LOGGER = LoggerFactory.getLogger(MigrateMappingsService.class); private static final ServiceType TYPE = new ServiceType<>(Options.class, MigrateMappingsService.class); @@ -78,21 +62,14 @@ public class MigrateMappingsService extends Service getSourceMappings(); @Nested Property getTargetMappings(); - @InputDirectory - DirectoryProperty getInputDir(); - @Input - Property getSourceCompatibility(); @InputFiles ConfigurableFileCollection getClasspath(); - @OutputDirectory - DirectoryProperty getOutputDir(); } - public static Provider createOptions(Project project, Provider targetMappings, DirectoryProperty inputDir, DirectoryProperty outputDir) { + public static Provider createOptions(Project project, Provider targetMappings) { LoomGradleExtension extension = LoomGradleExtension.get(project); final Provider from = project.provider(() -> "intermediary"); final Provider 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)); @@ -104,64 +81,20 @@ public class MigrateMappingsService extends Service { + private static final ServiceType TYPE = new ServiceType<>(Options.class, MigrateSourceCodeMappingsService.class); + + public MigrateSourceCodeMappingsService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + public interface Options extends Service.Options { + @Nested + Property getMappings(); + @InputDirectory + DirectoryProperty getInputDir(); + @Input + Property getSourceCompatibility(); + @OutputDirectory + DirectoryProperty getOutputDir(); + } + + public static Provider createOptions(Project project, Provider 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(); + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MigrateMappingsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MigrateMappingsTest.groovy index f784b012..8061f1c2 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MigrateMappingsTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MigrateMappingsTest.groovy @@ -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 + } }