diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingDropMissingSrcVisitor.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingDropMissingSrcVisitor.java new file mode 100644 index 0000000..7ebc5c6 --- /dev/null +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingDropMissingSrcVisitor.java @@ -0,0 +1,49 @@ +package dev.lukebemish.taskgraphrunner.runtime.mappings; + +import net.fabricmc.mappingio.FlatMappingVisitor; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + +public class MappingDropMissingSrcVisitor extends ForwardingFlatVisitor { + public MappingDropMissingSrcVisitor(FlatMappingVisitor delegate) { + super(delegate); + } + + public static MappingVisitor create(MappingVisitor delegate) { + return new MappingDropMissingSrcVisitor(new RegularAsFlatMappingVisitor(delegate)).asRegularVisitor(); + } + + private boolean checkNames(@Nullable String[] dstNames) { + return dstNames != null && dstNames.length > 0 && Arrays.stream(dstNames).allMatch(Objects::nonNull); + } + + @Override + public boolean visitClass(String srcName, @Nullable String[] dstNames) throws IOException { + return checkNames(dstNames) && super.visitClass(srcName, dstNames); + } + + @Override + public boolean visitMethod(String srcClsName, String srcName, @Nullable String srcDesc, @Nullable String[] dstClsNames, @Nullable String[] dstNames, @Nullable String[] dstDescs) throws IOException { + return checkNames(dstNames) && super.visitMethod(srcClsName, srcName, srcDesc, dstClsNames, dstNames, dstDescs); + } + + @Override + public boolean visitField(String srcClsName, String srcName, @Nullable String srcDesc, @Nullable String[] dstClsNames, @Nullable String[] dstNames, @Nullable String[] dstDescs) throws IOException { + return checkNames(dstNames) && super.visitField(srcClsName, srcName, srcDesc, dstClsNames, dstNames, dstDescs); + } + + @Override + public boolean visitMethodArg(String srcClsName, String srcMethodName, @Nullable String srcMethodDesc, int argPosition, int lvIndex, @Nullable String srcName, @Nullable String[] dstClsNames, @Nullable String[] dstMethodNames, @Nullable String[] dstMethodDescs, String[] dstNames) throws IOException { + return checkNames(dstNames) && super.visitMethodArg(srcClsName, srcMethodName, srcMethodDesc, argPosition, lvIndex, srcName, dstClsNames, dstMethodNames, dstMethodDescs, dstNames); + } + + @Override + public boolean visitMethodVar(String srcClsName, String srcMethodName, @Nullable String srcMethodDesc, int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName, @Nullable String[] dstClsNames, @Nullable String[] dstMethodNames, @Nullable String[] dstMethodDescs, String[] dstNames) throws IOException { + return checkNames(dstNames) && super.visitMethodVar(srcClsName, srcMethodName, srcMethodDesc, lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName, dstClsNames, dstMethodNames, dstMethodDescs, dstNames); + } +} diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingInheritance.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingInheritance.java new file mode 100644 index 0000000..291b993 --- /dev/null +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingInheritance.java @@ -0,0 +1,317 @@ +package dev.lukebemish.taskgraphrunner.runtime.mappings; + +import net.fabricmc.mappingio.FlatMappingVisitor; +import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.fabricmc.mappingio.tree.MemoryMappingTree; +import org.jspecify.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public final class MappingInheritance { + private final Map classes; + + private MappingInheritance(Map classes) { + this.classes = classes; + } + + public MappingTree fill(MappingTreeView input) throws IOException { + var output = new MemoryMappingTree(); + input.accept(output); + output.visitNamespaces(input.getSrcNamespace(), input.getDstNamespaces()); + for (var classInheritance : classes.values()) { + classInheritance.fill(input, new RegularAsFlatMappingVisitor(output)); + } + output.visitEnd(); + return output; + } + + public MappingInheritance remap(MappingTree tree, int namespace) { + var newClasses = classes.entrySet().stream().collect(Collectors.toMap(e -> { + var newName = tree.mapClassName(e.getKey(), namespace); + return newName == null ? e.getKey() : newName; + }, e -> { + return e.getValue().remap(tree, namespace); + })); + return new MappingInheritance(newClasses); + } + + public static MappingInheritance read(Path path) throws IOException { + try (var is = Files.newInputStream(path); + var zis = new ZipInputStream(is)) { + ZipEntry entry; + Map map = new LinkedHashMap<>(); + while ((entry = zis.getNextEntry()) != null) { + var entryName = entry.getName(); + if (!entryName.startsWith("META-INF/") && entryName.endsWith(".class")) { + var expectedDotsClassName = entryName.substring(0, entryName.length() - ".class".length()).replace('/', '.'); + AtomicReference nameHolder = new AtomicReference<>(); + AtomicReference parentHolder = new AtomicReference<>(); + List interfacesHolder = new ArrayList<>(); + Map methodsHolder = new LinkedHashMap<>(); + Map fieldsHolder = new LinkedHashMap<>(); + AtomicBoolean enabled = new AtomicBoolean(true); + Set bridges = new LinkedHashSet<>(); + var reader = new ClassReader(zis); + var visitor = new ClassVisitor(Opcodes.ASM9) { + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (!name.equals(expectedDotsClassName)) { + enabled.set(false); + } + if (enabled.getPlain()) { + nameHolder.setPlain(name.replace('.', '/')); + parentHolder.setPlain(superName.replace('.', '/')); + for (var interfaceName : interfaces) { + interfacesHolder.add(interfaceName.replace('.', '/')); + } + } + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if (enabled.getPlain()) { + fieldsHolder.put(name, new FieldInheritance(name, descriptor)); + } + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if (enabled.getPlain()) { + boolean isBridge = (access & Opcodes.ACC_BRIDGE) != 0; + if (isBridge) { + bridges.add(name+descriptor); + } + methodsHolder.put(name+descriptor, new MethodInheritance(name, descriptor, null, null)); + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + }; + reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + if (enabled.getPlain()) { + // Is this the _best_ heuristic? Eh. But it works, in theory + for (var bridge : bridges) { + var bridgeMethod = methodsHolder.get(bridge); + var name = bridgeMethod.name; + var eligible = methodsHolder.values().stream().filter(m -> !bridges.contains(m.name + m.descriptor) && m.name.equals(name)).toList(); + if (eligible.size() == 1) { + var method = eligible.getFirst(); + methodsHolder.put(bridge, new MethodInheritance(bridgeMethod.name, bridgeMethod.descriptor, null, method.descriptor)); + } + } + map.put(nameHolder.getPlain(), new ClassInheritance(nameHolder.getPlain(), parentHolder.getPlain(), interfacesHolder, methodsHolder, fieldsHolder)); + } + } + } + var inheritance = new MappingInheritance(map); + return inheritance.withParents(); + } + } + + private record ClassInheritance(String name, String parent, List interfaces, + Map methods, Map fields) { + + private ClassInheritance remap(MappingTree tree, int namespace) { + String newName = tree.mapClassName(name, namespace); + String newParent = tree.mapClassName(parent, namespace); + if (newName == null) { + newName = name; + } + if (newParent == null) { + newParent = parent; + } + List newInterfaces = interfaces.stream().map(i -> { + var name = tree.mapClassName(i, namespace); + return name == null ? i : name; + }).toList(); + Map newMethods = methods.values().stream().map(m -> m.remap(tree, namespace, this)) + .collect(Collectors.toMap(m -> m.name + m.descriptor, m -> m)); + Map newFields = fields.values().stream().map(f -> f.remap(tree, namespace, this)) + .collect(Collectors.toMap(f -> f.name, f -> f)); + return new ClassInheritance(newName, newParent, newInterfaces, newMethods, newFields); + } + + private ClassInheritance withParents(MappingInheritance mappingInheritance) { + return new ClassInheritance(name, parent, interfaces, methods.values().stream().map(methodInheritance -> methodInheritance.withParents(this, mappingInheritance)) + .collect(Collectors.toMap(m -> m.name + m.descriptor, m -> m)), fields); + } + + private void fill(MappingTreeView input, FlatMappingVisitor output) throws IOException { + // Fields are ignored -- they cannot be inherited + for (var methodInheritance : methods.values()) { + methodInheritance.fill(this, input, output); + } + } + } + + public MappingInheritance withParents() { + return new MappingInheritance(classes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().withParents(this)))); + } + + private record FieldInheritance(String name, String descriptor) { + + private FieldInheritance remap(MappingTree tree, int namespace, ClassInheritance clazz) { + String newName = name; + String newDescriptor = tree.mapDesc(descriptor, namespace); + var classMapping = tree.getClass(clazz.name); + if (classMapping != null) { + var fieldMapping = classMapping.getField(name, descriptor); + if (fieldMapping != null) { + newName = fieldMapping.getDstName(namespace); + } + } + if (newName == null) { + newName = name; + } + return new FieldInheritance(newName, newDescriptor); + } + } + + private record MethodInheritance(String name, String descriptor, @Nullable String from, + @Nullable String bridgeDescriptor) { + + private MethodInheritance withParents(ClassInheritance classInheritance, MappingInheritance mappingInheritance) { + if (from != null && bridgeDescriptor != null) { + return this; + } + String found = findMethod(mappingInheritance, classInheritance.name, name, descriptor); + if (found != null && !found.equals(classInheritance.name)) { + return new MethodInheritance(name, descriptor, found, bridgeDescriptor); + } + return this; + } + + private String findMethod(MappingInheritance mappingInheritance, String className, String name, String descriptor) { + var inheritance = mappingInheritance.classes.get(className); + if (inheritance != null) { + var found = findMethod(mappingInheritance, inheritance.parent, name, descriptor); + if (found != null) { + return found; + } + for (var interfaceName : inheritance.interfaces) { + found = findMethod(mappingInheritance, interfaceName, name, descriptor); + if (found != null) { + return found; + } + } + if (inheritance.methods.get(name + descriptor) != null) { + return className; + } + } + return null; + } + + private MethodInheritance remap(MappingTree tree, int namespace, ClassInheritance clazz) { + String newDescriptor = tree.mapDesc(descriptor, namespace); + String newFrom = from; + if (newFrom != null) { + newFrom = tree.mapClassName(from, namespace); + } + String newBridgeDescriptor = bridgeDescriptor; + if (newBridgeDescriptor != null) { + newBridgeDescriptor = tree.mapDesc(bridgeDescriptor, namespace); + } + String newName = null; + var classMapping = tree.getClass(clazz.name); + if (classMapping != null) { + var methodMapping = classMapping.getMethod(name, descriptor); + if (methodMapping != null) { + newName = methodMapping.getDstName(namespace); + } + if (newName == null && bridgeDescriptor != null) { + methodMapping = classMapping.getMethod(name, bridgeDescriptor); + if (methodMapping != null) { + newName = methodMapping.getDstName(namespace); + } + } + } + if (newName == null) { + var sourceClassMapping = tree.getClass(from); + if (sourceClassMapping != null) { + var methodMapping = sourceClassMapping.getMethod(name, descriptor); + if (methodMapping != null) { + newName = methodMapping.getDstName(namespace); + } + } + } + if (newName == null) { + newName = name; + } + return new MethodInheritance(newName, newDescriptor, newFrom, newBridgeDescriptor); + } + + public void fill(ClassInheritance classInheritance, MappingTreeView input, FlatMappingVisitor output) throws IOException { + if (from == null && bridgeDescriptor == null) { + return; + } + var thisClass = input.getClass(classInheritance.name); + var inheritArgs = true; + if (thisClass != null) { + var thisMethod = thisClass.getMethod(name, descriptor); + if (thisMethod != null) { + if (IntStream.range(0, input.getMaxNamespaceId()).noneMatch(i -> thisMethod.getDstName(i) != null)) { + inheritArgs = false; + } else { + return; + } + } + if (bridgeDescriptor != null) { + var bridgeMethod = thisClass.getMethod(name, bridgeDescriptor); + if (bridgeMethod != null && IntStream.range(0, input.getMaxNamespaceId()).anyMatch(i -> bridgeMethod.getDstName(i) != null)) { + output.visitMethod(thisClass.getSrcName(), name, descriptor, dstNames(input, bridgeMethod)); + if (inheritArgs) { + for (var arg : bridgeMethod.getArgs()) { + output.visitMethodArg(thisClass.getSrcName(), name, descriptor, arg.getArgPosition(), arg.getLvIndex(), arg.getSrcName(), dstNames(input, arg)); + } + } + return; + } + } + } + if (from != null) { + var fromClass = input.getClass(from); + if (fromClass != null) { + var fromMethod = fromClass.getMethod(name, descriptor); + if (fromMethod != null) { + output.visitMethod(fromClass.getSrcName(), name, descriptor, dstNames(input, fromMethod)); + if (inheritArgs) { + for (var arg : fromMethod.getArgs()) { + output.visitMethodArg(fromClass.getSrcName(), name, descriptor, arg.getArgPosition(), arg.getLvIndex(), arg.getSrcName(), dstNames(input, arg)); + } + } + } + } + } + } + } + + private static @Nullable String[] dstNames(MappingTreeView tree, MappingTreeView.ElementMappingView element) { + var dstNames = new String[tree.getDstNamespaces().size()]; + for (int i = 0; i < dstNames.length; i++) { + dstNames[i] = element.getDstName(i); + } + return dstNames; + } +} diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsSourceImpl.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsSourceImpl.java index 91fa473..34f5a21 100644 --- a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsSourceImpl.java +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsSourceImpl.java @@ -30,6 +30,8 @@ public sealed interface MappingsSourceImpl { MappingTree makeMappings(Context context); + MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context); + List inputs(); interface MappingConsumer extends AutoCloseable { @@ -120,6 +122,19 @@ public MappingTree makeMappings(Context context) { .toList()); } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + return inheritance -> MappingsUtil.filledChain(inheritance, files.paths(context).stream() + .map(p -> { + try { + return MappingsUtil.provider(loadMappings(p)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }) + .toList()); + } + @Override public List inputs() { return List.of(label, files); @@ -172,6 +187,13 @@ public MappingTree makeMappings(Context context) { .toList()); } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + return inheritance -> MappingsUtil.filledChain(inheritance, sources.stream() + .map(s -> s.makeMappingsFillInheritance(context)) + .toList()); + } + @Override public List inputs() { var list = sources.stream() @@ -204,6 +226,19 @@ public MappingTree makeMappings(Context context) { .toList()); } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + return inheritance -> MappingsUtil.filledMerge(inheritance, files.paths(context).stream() + .map(p -> { + try { + return MappingsUtil.provider(loadMappings(p)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }) + .toList()); + } + @Override public List inputs() { return List.of(label, files); @@ -226,6 +261,13 @@ public MappingTree makeMappings(Context context) { .toList()); } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + return inheritance -> MappingsUtil.filledMerge(inheritance, sources.stream() + .map(s -> s.makeMappingsFillInheritance(context)) + .toList()); + } + @Override public List inputs() { var list = sources.stream() @@ -251,6 +293,15 @@ public MappingTree makeMappings(Context context) { return MappingsUtil.reverse(mappings); } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + var sourceMappings = source.makeMappingsFillInheritance(context); + return inheritance -> { + MappingTree mappings = sourceMappings.make(inheritance); + return MappingsUtil.reverse(mappings); + }; + } + @Override public List inputs() { var list = new ArrayList<>(source.inputs()); @@ -292,6 +343,12 @@ public MappingTree makeMappings(Context context) { } } + @Override + public MappingsUtil.MappingProvider makeMappingsFillInheritance(Context context) { + var mappings = makeMappings(context); + return MappingsUtil.provider(mappings); + } + @Override public List inputs() { if (extension == null) { diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsUtil.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsUtil.java index 106e8ad..9965e21 100644 --- a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsUtil.java +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/mappings/MappingsUtil.java @@ -1,34 +1,28 @@ package dev.lukebemish.taskgraphrunner.runtime.mappings; -import net.fabricmc.mappingio.FlatMappingVisitor; import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingVisitor; import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor; import net.fabricmc.mappingio.adapter.MappingDstNsReorder; +import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingNsRenamer; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.tree.MappingTree; -import net.fabricmc.mappingio.tree.MappingTreeView; import net.fabricmc.mappingio.tree.MemoryMappingTree; -import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; public final class MappingsUtil { private MappingsUtil() {} public static MappingTree reverse(MappingTree tree) { - if (tree.getMaxNamespaceId() < 0) { - return tree; - } var newTree = new MemoryMappingTree(); - var reverser = new MappingSourceNsSwitch(newTree, tree.getDstNamespaces().getFirst(), true); + var reverser = tree.getDstNamespaces().isEmpty() ? newTree : new MappingSourceNsSwitch(newTree, tree.getDstNamespaces().getFirst(), true); try { tree.accept(reverser); } catch (IOException e) { @@ -37,7 +31,7 @@ public static MappingTree reverse(MappingTree tree) { return newTree; } - public static MappingTree merge(List trees) { + public static MappingTree merge(List trees) { var newTree = new MemoryMappingTree(); for (var tree : trees) { MappingVisitor visitor = newTree; @@ -59,9 +53,45 @@ public static MappingTree merge(List trees) { return newTree; } - public static MappingTree chain(List trees) { + public interface MappingProvider { + MappingTree make(MappingInheritance inheritance) throws IOException; + } + + public static MappingTree filledMerge(MappingInheritance inheritance, List trees) { + try { + var appliedTrees = new ArrayList(trees.size()); + for (var tree : trees) { + appliedTrees.add(tree.make(inheritance)); + } + return merge(appliedTrees); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static MappingProvider provider(MappingTree tree) { + return inheritance -> inheritance.fill(tree); + } + + public static MappingTree filledChain(MappingInheritance inheritance, List trees) { + try { + var tree = trees.getFirst().make(inheritance); + inheritance = inheritance.remap(tree, tree.getMaxNamespaceId() - 1); + for (var other : trees.subList(1, trees.size())) { + var mappings = other.make(inheritance); + tree = chain(List.of(tree, mappings)); + inheritance = inheritance.remap(mappings, mappings.getMaxNamespaceId() - 1); + } + return tree; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static MappingTree chain(List trees) { // This is a *non-transitive* chain, since some mappings (looking at you, intermediary) just... leave stuff out. // Thus, its results may not be the most intuitive if you expect mapping "composition" or the like + // This can be avoided by "completing" mappings by their inheritance var newTree = new MemoryMappingTree(); newTree.setSrcNamespace("namespace0"); int i = 0; @@ -92,39 +122,12 @@ public static MappingTree chain(List trees) { var trimmedTree = new MemoryMappingTree(); trimmedTree.setSrcNamespace("left"); - var flatVisitor = FlatMappingVisitor.fromRegularVisitor(trimmedTree); - flatVisitor = new ForwardingFlatVisitor(flatVisitor) { - private boolean checkNames(@Nullable String[] dstNames) { - return dstNames != null && dstNames.length > 0 && Arrays.stream(dstNames).allMatch(Objects::nonNull); - } - - @Override - public boolean visitClass(String srcName, @Nullable String[] dstNames) throws IOException { - return checkNames(dstNames) && super.visitClass(srcName, dstNames); - } - - @Override - public boolean visitMethod(String srcClsName, String srcName, @Nullable String srcDesc, @Nullable String[] dstClsNames, @Nullable String[] dstNames, @Nullable String[] dstDescs) throws IOException { - return checkNames(dstNames) && super.visitMethod(srcClsName, srcName, srcDesc, dstClsNames, dstNames, dstDescs); - } - - @Override - public boolean visitField(String srcClsName, String srcName, @Nullable String srcDesc, @Nullable String[] dstClsNames, @Nullable String[] dstNames, @Nullable String[] dstDescs) throws IOException { - return checkNames(dstNames) && super.visitField(srcClsName, srcName, srcDesc, dstClsNames, dstNames, dstDescs); - } - - @Override - public boolean visitMethodArg(String srcClsName, String srcMethodName, @Nullable String srcMethodDesc, int argPosition, int lvIndex, @Nullable String srcName, @Nullable String[] dstClsNames, @Nullable String[] dstMethodNames, @Nullable String[] dstMethodDescs, String[] dstNames) throws IOException { - return checkNames(dstNames) && super.visitMethodArg(srcClsName, srcMethodName, srcMethodDesc, argPosition, lvIndex, srcName, dstClsNames, dstMethodNames, dstMethodDescs, dstNames); - } - - @Override - public boolean visitMethodVar(String srcClsName, String srcMethodName, @Nullable String srcMethodDesc, int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName, @Nullable String[] dstClsNames, @Nullable String[] dstMethodNames, @Nullable String[] dstMethodDescs, String[] dstNames) throws IOException { - return checkNames(dstNames) && super.visitMethodVar(srcClsName, srcMethodName, srcMethodDesc, lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName, dstClsNames, dstMethodNames, dstMethodDescs, dstNames); - } - }; - MappingVisitor trimmedVisitor = new MappingDstNsReorder(flatVisitor.asRegularVisitor(), List.of("right")); + MappingVisitor trimmedVisitor = MappingDropMissingSrcVisitor.create(trimmedTree); + trimmedVisitor = new MappingDstNsReorder(trimmedVisitor, List.of("right")); trimmedVisitor = new MappingNsRenamer(trimmedVisitor, Map.of("namespace0", "left", "namespace"+i, "right")); + for (i = trees.size(); i > 0; i--) { + trimmedVisitor = new MappingNsCompleter(trimmedVisitor, Map.of("namespace" + i, "namespace" + (i - 1))); + } try { newTree.accept(trimmedVisitor); } catch (IOException e) { diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/JstTask.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/JstTask.java index defb456..722c574 100644 --- a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/JstTask.java +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/JstTask.java @@ -7,10 +7,12 @@ import dev.lukebemish.taskgraphrunner.runtime.Task; import dev.lukebemish.taskgraphrunner.runtime.TaskInput; import dev.lukebemish.taskgraphrunner.runtime.execution.ToolDaemonExecutor; +import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingInheritance; import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingsSourceImpl; import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingsUtil; import dev.lukebemish.taskgraphrunner.runtime.mappings.ParchmentMappingWriter; import dev.lukebemish.taskgraphrunner.runtime.util.Tools; +import net.fabricmc.mappingio.tree.MappingTree; import org.jspecify.annotations.Nullable; import java.io.File; @@ -34,6 +36,7 @@ public class JstTask extends Task { private final TaskInput.@Nullable FileListInput accessTransformers; private final TaskInput.@Nullable FileListInput interfaceInjection; private final @Nullable MappingsSourceImpl parchmentMappingsSource; + private final TaskInput.@Nullable HasFileInput binaryInput; private final List inputs; private final List args; private final Map outputExtensions; @@ -90,6 +93,11 @@ public JstTask(TaskModel.Jst model, WorkItem workItem, Context context) { this.inputs.addAll(args.stream().flatMap(ArgumentProcessor.Arg::inputs).toList()); + this.binaryInput = model.binaryInput == null ? null : TaskInput.file("binaryInput", model.binaryInput, workItem, context, PathSensitivity.NONE); + if (this.binaryInput != null) { + inputs.add(this.binaryInput); + } + this.classpathScopedJvm = model.classpathScopedJvm; } @@ -121,7 +129,18 @@ private void collectArguments(ArrayList command, Context context, Path w // Might eventually look at making this configurable command.add("--parchment-conflict-prefix=p"); var parchmentFile = workingDirectory.resolve("parchment.json"); - var mappings = MappingsUtil.fixInnerClasses(parchmentMappingsSource.makeMappings(context)); + MappingTree mappings; + if (binaryInput == null) { + mappings = MappingsUtil.fixInnerClasses(parchmentMappingsSource.makeMappings(context)); + } else { + try { + var path = binaryInput.path(context); + var inheritance = MappingInheritance.read(path); + mappings = MappingsUtil.fixInnerClasses(parchmentMappingsSource.makeMappingsFillInheritance(context).make(inheritance)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } try (var writer = Files.newBufferedWriter(parchmentFile, StandardCharsets.UTF_8); var mappingWriter = new ParchmentMappingWriter(writer)) { mappingWriter.accept(mappings); diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/TransformMappingsTask.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/TransformMappingsTask.java index 57aff9d..5ff3060 100644 --- a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/TransformMappingsTask.java +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/tasks/TransformMappingsTask.java @@ -1,16 +1,21 @@ package dev.lukebemish.taskgraphrunner.runtime.tasks; import dev.lukebemish.taskgraphrunner.model.MappingsFormat; +import dev.lukebemish.taskgraphrunner.model.PathSensitivity; import dev.lukebemish.taskgraphrunner.model.TaskModel; import dev.lukebemish.taskgraphrunner.model.Value; import dev.lukebemish.taskgraphrunner.model.WorkItem; import dev.lukebemish.taskgraphrunner.runtime.Context; import dev.lukebemish.taskgraphrunner.runtime.Task; import dev.lukebemish.taskgraphrunner.runtime.TaskInput; +import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingInheritance; import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingsSourceImpl; import dev.lukebemish.taskgraphrunner.runtime.mappings.MappingsUtil; +import net.fabricmc.mappingio.tree.MappingTree; +import org.jspecify.annotations.Nullable; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; @@ -22,18 +27,23 @@ public class TransformMappingsTask extends Task { private final MappingsFormat format; private final MappingsSourceImpl source; private final TaskInput.ValueInput formatValue; + private final TaskInput.@Nullable HasFileInput sourceJarInput; public TransformMappingsTask(TaskModel.TransformMappings model, WorkItem workItem, Context context) { super(model); this.format = model.format; this.formatValue = new TaskInput.ValueInput("format", new Value.StringValue(format.name())); this.source = MappingsSourceImpl.of(model.source, workItem, context, new AtomicInteger()); + this.sourceJarInput = model.sourceJar == null ? null : TaskInput.file("sourceJar", model.sourceJar, workItem, context, PathSensitivity.NONE); } @Override public List inputs() { var inputs = new ArrayList<>(source.inputs()); inputs.add(formatValue); + if (sourceJarInput != null) { + inputs.add(sourceJarInput); + } return inputs; } @@ -58,7 +68,18 @@ public Map outputTypes() { @Override protected void run(Context context) { - var mappings = MappingsUtil.fixInnerClasses(source.makeMappings(context)); + MappingTree mappings; + if (sourceJarInput == null) { + mappings = MappingsUtil.fixInnerClasses(source.makeMappings(context)); + } else { + try { + var path = sourceJarInput.path(context); + var inheritance = MappingInheritance.read(path); + mappings = MappingsUtil.fixInnerClasses(source.makeMappingsFillInheritance(context).make(inheritance)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } try (var writer = Files.newBufferedWriter(context.taskOutputPath(this, "output"), StandardCharsets.UTF_8); var mappingsWriter = MappingsSourceImpl.getWriter(writer, format)) { mappingsWriter.accept(mappings); diff --git a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/util/LockManager.java b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/util/LockManager.java index f7f07e9..aa26fd8 100644 --- a/src/main/java/dev/lukebemish/taskgraphrunner/runtime/util/LockManager.java +++ b/src/main/java/dev/lukebemish/taskgraphrunner/runtime/util/LockManager.java @@ -18,6 +18,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -34,7 +35,36 @@ public class LockManager { private static final Map parallelLocks = new ConcurrentHashMap<>(); + private static final Map parallelismGroups; + + static { + String groupsProperty = System.getProperty("dev.lukebemish.taskgraphrunner.parallelism.groups", ""); + Map map = new LinkedHashMap<>(); + for (var group : groupsProperty.split(":")) { + if (!group.isEmpty()) { + var parts = group.split("\\+"); + if (parts.length > 1) { + for (var part : parts) { + map.put(part, group); + } + } + } + } + parallelismGroups = map; + } + private static int findParallelism(String key) { + if (parallelismGroups.containsKey(key)) { + int maxInt = 0; + for (var part : parallelismGroups.get(key).split("\\+")) { + int value = findParallelism(part); + if (value > maxInt) { + maxInt = value; + } + } + return maxInt; + } + int result = Integer.getInteger("dev.lukebemish.taskgraphrunner.parallelism." + key, 1); if (result < 1) { throw new IllegalArgumentException("Property dev.lukebemish.taskgraphrunner.parallelism."+key+" must be positive"); @@ -43,6 +73,8 @@ private static int findParallelism(String key) { } public void enforcedParallelism(Context context, String key, Runnable action) { + key = parallelismGroups.getOrDefault(key, key); + var parallelism = findParallelism(key); var semaphore = parallelLocks.computeIfAbsent(key, k -> new Semaphore(parallelism)); try { diff --git a/src/model/java/dev/lukebemish/taskgraphrunner/model/TaskModel.java b/src/model/java/dev/lukebemish/taskgraphrunner/model/TaskModel.java index e12435a..6462625 100644 --- a/src/model/java/dev/lukebemish/taskgraphrunner/model/TaskModel.java +++ b/src/model/java/dev/lukebemish/taskgraphrunner/model/TaskModel.java @@ -110,6 +110,7 @@ public TaskModel read(JsonReader in) { public static final class TransformMappings extends TaskModel { public MappingsFormat format; public MappingsSource source; + public @Nullable Input sourceJar = null; public TransformMappings(String name, MappingsFormat format, MappingsSource source) { super(name); @@ -119,7 +120,10 @@ public TransformMappings(String name, MappingsFormat format, MappingsSource sour @Override public Stream inputs() { - return source.inputs(); + return Stream.concat( + source.inputs(), + Stream.of(InputHandle.of(() -> sourceJar, i -> this.sourceJar = i)) + ); } private static final class Specialized extends FieldAdapter { @@ -129,9 +133,11 @@ public Function build(Builder buil var parallelism = builder.field("parallelism", task -> task.parallelism, String.class); var format = builder.field("format", task -> task.format, MappingsFormat.class); var source = builder.field("source", task -> task.source, MappingsSource.class); + var sourceJar = builder.field("sourceJar", task -> task.sourceJar, Input.class); return values -> { var task = new TransformMappings(values.get(name), values.get(format), values.get(source)); task.parallelism = values.get(parallelism); + task.sourceJar = values.get(sourceJar); return task; }; } @@ -185,6 +191,7 @@ public static final class Jst extends TaskModel { public @Nullable Input accessTransformers = null; public @Nullable Input interfaceInjection = null; public @Nullable MappingsSource parchmentData = null; + public @Nullable Input binaryInput = null; public boolean classpathScopedJvm = false; public Jst(String name, List args, Input input, List classpath, @Nullable List executionClasspath) { @@ -202,6 +209,7 @@ public Stream inputs() { return Stream.of( Stream.of( InputHandle.of(() -> input, i -> this.input = i), + InputHandle.of(() -> binaryInput, i -> this.binaryInput = i), InputHandle.of(() -> accessTransformers, i -> this.accessTransformers = i), InputHandle.of(() -> interfaceInjection, i -> this.interfaceInjection = i) ).filter(h -> h.getInput() != null), @@ -225,6 +233,7 @@ public Function build(Builder builder) { var interfaceInjection = builder.field("interfaceInjection", task -> task.interfaceInjection, Input.class); var parchmentData = builder.field("parchmentData", task -> task.parchmentData, MappingsSource.class); var classpathScopedJvm = builder.field("classpathScopedJvm", task -> task.classpathScopedJvm, Boolean.class); + var binaryInput = builder.field("binaryInput", task -> task.binaryInput, Input.class); return values -> { var jst = new Jst(values.get(name), values.get(args), values.get(input), values.get(classpath), values.get(jstClasspath)); jst.accessTransformers = values.get(accessTransformers); @@ -232,6 +241,7 @@ public Function build(Builder builder) { jst.parchmentData = values.get(parchmentData); jst.parallelism = values.get(parallelism); jst.classpathScopedJvm = values.get(classpathScopedJvm) == Boolean.TRUE; + jst.binaryInput = values.get(binaryInput); return jst; }; } diff --git a/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/NeoFormGenerator.java b/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/NeoFormGenerator.java index 03a6122..2bfded5 100644 --- a/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/NeoFormGenerator.java +++ b/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/NeoFormGenerator.java @@ -384,6 +384,9 @@ public static Config convert(Path neoFormConfig, Value selfReference, Options op List.of(new Input.TaskInput(new Output(listLibrariesName, "output"))), List.of(new Input.ListInput(List.of(new Input.DirectInput(Value.tool("linemapper-jst"))))) ); + jst.parallelism = "jst"; + + jst.binaryInput = new Input.TaskInput(new Output("rename", "output")); if (options.accessTransformersParameter != null) { jst.accessTransformers = new Input.ParameterInput(options.accessTransformersParameter); diff --git a/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/SingleVersionGenerator.java b/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/SingleVersionGenerator.java index 9820745..7c2925a 100644 --- a/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/SingleVersionGenerator.java +++ b/src/model/java/dev/lukebemish/taskgraphrunner/model/conversion/SingleVersionGenerator.java @@ -252,6 +252,8 @@ public static Config convert(String version, Options options) throws IOException } }; + config.aliases.put("binaryObf", merged); + List additionalClasspath = new ArrayList<>(); // rename the merged jar @@ -377,6 +379,9 @@ public static Config convert(String version, Options options) throws IOException List.of(new Input.TaskInput(new Output(listLibrariesName, "output"))), List.of(new Input.ListInput(List.of(new Input.DirectInput(Value.tool("linemapper-jst"))))) ); + jst.parallelism = "jst"; + + jst.binaryInput = new Input.TaskInput(originalBinaries); if (options.mappingsParameter != null) { jst.parchmentData = new MappingsSource.File(new Input.ParameterInput(options.mappingsParameter));