diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java index 35dcbfa6..6524b3c3 100644 --- a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java +++ b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilationMetadata.java @@ -26,15 +26,24 @@ package net.fabricmc.loom.api.decompilers; import java.nio.file.Path; import java.util.Collection; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.task.GenerateSourcesTask; public class DecompilationMetadata { public final int numberOfThreads; public final Path javaDocs; public final Collection libraries; + @Nullable + public final Predicate classFilter; - public DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection libraries) { + public DecompilationMetadata(int numberOfThreads, Path javaDocs, Collection libraries, Function classFilter) { this.numberOfThreads = numberOfThreads; this.javaDocs = javaDocs; this.libraries = libraries; + this.classFilter = s -> GenerateSourcesTask.SkipState.SKIP != classFilter.apply(s); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java new file mode 100644 index 00000000..2f1c2590 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/accesstransformer/AccessTransformerJarProcessor.java @@ -0,0 +1,232 @@ +package net.fabricmc.loom.configuration.accesstransformer; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import me.shedaniel.architectury.refmapremapper.remapper.SimpleReferenceRemapper; +import net.minecraftforge.accesstransformer.AccessTransformerEngine; +import net.minecraftforge.accesstransformer.TransformerProcessor; +import net.minecraftforge.accesstransformer.parser.AccessTransformerList; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.util.Strings; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.Nullable; +import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; +import net.fabricmc.loom.util.Checksum; +import net.fabricmc.loom.util.JarUtil; +import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.mapping.tree.TinyTree; + +public class AccessTransformerJarProcessor implements JarProcessor { + private final Path projectAt; + private final Project project; + private final Set affectedClasses = new HashSet<>(); + private Path remappedProjectAt; + + public AccessTransformerJarProcessor(Path projectAt, Project project) { + this.projectAt = projectAt; + this.project = project; + } + + public static void addTo(Project project, LoomGradleExtension extension) { + SourceSet main = project.getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + + for (File srcDir : main.getResources().getSrcDirs()) { + File projectAt = new File(srcDir, "META-INF/accesstransformer.cfg"); + + if (projectAt.exists()) { + extension.addJarProcessor(new AccessTransformerJarProcessor(projectAt.toPath(), project)); + return; + } + } + } + + @Override + public void setup() { + affectedClasses.clear(); + remappedProjectAt = null; + getRemappedAt(); + } + + @Override + public void process(File file) { + try { + accessTransformForge(file, file, false, getRemappedAt()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isInvalid(File file) { + byte[] hash = ZipUtil.unpackEntry(file, "loom-at.sha256"); + + if (hash == null) { + return true; + } + + return !Arrays.equals(getInputHash(false, getRemappedAt()), hash); + } + + @Override + public boolean doesProcessClass(String classInternalName) { + getRemappedAt(); + return affectedClasses.contains(classInternalName); + } + + private File getRemappedAt() { + if (remappedProjectAt == null) { + try { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + MappingsProvider mappingsProvider = extension.getMappingsProvider(); + TinyTree mappings = extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(); + + File file = File.createTempFile("at-remapped-", ".at"); + file.delete(); + String sourceAt = FileUtils.readFileToString(projectAt.toFile(), StandardCharsets.UTF_8); + String remappedAt = AtRemapper.remapAt(project.getLogger(), sourceAt, new SimpleReferenceRemapper.Remapper() { + @Override + @Nullable + public String mapClass(String value) { + return mappings.getClasses().stream() + .filter(classDef -> Objects.equals(classDef.getName("srg"), value)) + .findFirst() + .map(classDef -> classDef.getName("named")) + .orElse(null); + } + + @Override + @Nullable + public String mapMethod(@Nullable String className, String methodName, String methodDescriptor) { + return mappings.getClasses().stream() + .flatMap(classDef -> classDef.getMethods().stream()) + .filter(methodDef -> Objects.equals(methodDef.getName("srg"), methodName)) + .filter(methodDef -> Objects.equals(methodDef.getDescriptor("srg"), methodDescriptor)) + .findFirst() + .map(classDef -> classDef.getName("named")) + .orElse(null); + } + + @Override + @Nullable + public String mapField(@Nullable String className, String fieldName, String fieldDescriptor) { + return mappings.getClasses().stream() + .flatMap(classDef -> classDef.getFields().stream()) + .filter(methodDef -> Objects.equals(methodDef.getName("srg"), fieldName)) + .findFirst() + .map(classDef -> classDef.getName("named")) + .orElse(null); + } + }); + + for (String line : remappedAt.split("\n")) { + { + int indexOf = line.indexOf('#'); + + if (indexOf != -1) { + line = line.substring(0, indexOf); + } + + line = line.trim(); + } + + if (!Strings.isBlank(line)) { + affectedClasses.add(line.split("\\s+")[1].replace('.', '/')); + } + } + + FileUtils.write(file, remappedAt, StandardCharsets.UTF_8); + file.deleteOnExit(); + return (remappedProjectAt = file.toPath()).toFile(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + return remappedProjectAt.toFile(); + } + + public static byte[] getInputHash(boolean addEmbeddedAt, File... extraAts) { + byte[] inputHash = new byte[] {(byte) (addEmbeddedAt ? 1 : 0)}; + + for (File extraAt : extraAts) { + inputHash = concat(inputHash, Checksum.sha256(extraAt)); + } + + return inputHash; + } + + public static byte[] concat(byte[] a, byte[] b) { + byte[] out = new byte[a.length + b.length]; + System.arraycopy(a, 0, out, 0, a.length); + System.arraycopy(b, 0, out, a.length, b.length); + return out; + } + + public static void accessTransformForge(File input, File target, boolean addEmbeddedAt, File... extraAts) throws Exception { + byte[] inputHash = getInputHash(addEmbeddedAt, extraAts); + File inputCopied = File.createTempFile("at-input-", ".jar"); + File at = File.createTempFile("at-", ".cfg"); + FileUtils.copyFile(input, inputCopied); + + if (target.exists()) { + byte[] hash = ZipUtil.unpackEntry(target, "loom-at.sha256"); + + if (hash != null && Arrays.equals(inputHash, hash)) { + return; + } + } + + target.delete(); + String[] args = new String[] { + "--inJar", inputCopied.getAbsolutePath(), + "--outJar", target.getAbsolutePath() + }; + + if (addEmbeddedAt) { + JarUtil.extractFile(inputCopied, "META-INF/accesstransformer.cfg", at); + args = Arrays.copyOf(args, args.length + 2); + args[args.length - 2] = "--atFile"; + args[args.length - 1] = at.getAbsolutePath(); + } + + for (File extraAt : extraAts) { + args = Arrays.copyOf(args, args.length + 2); + args[args.length - 2] = "--atFile"; + args[args.length - 1] = extraAt.getAbsolutePath(); + } + + resetAccessTransformerEngine(); + TransformerProcessor.main(args); + inputCopied.delete(); + at.delete(); + resetAccessTransformerEngine(); + + ZipUtil.addEntry(target, "loom-at.sha256", inputHash); + } + + private static void resetAccessTransformerEngine() throws Exception { + // Thank you Forge, I love you + Field field = AccessTransformerEngine.class.getDeclaredField("masterList"); + field.setAccessible(true); + AccessTransformerList list = (AccessTransformerList) field.get(AccessTransformerEngine.INSTANCE); + field = AccessTransformerList.class.getDeclaredField("accessTransformers"); + field.setAccessible(true); + ((Map) field.get(list)).clear(); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 42f0e7bf..df0cf9e1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -117,6 +117,11 @@ public class AccessWidenerJarProcessor implements JarProcessor { ZipUtil.addEntry(file, "aw.sha256", inputHash); } + @Override + public boolean doesProcessClass(String classInternalName) { + return accessWidener.getTargets().contains(classInternalName.replace('/', '.')); + } + private ZipEntryTransformerEntry[] getTransformers(Set classes) { return classes.stream() .map(string -> new ZipEntryTransformerEntry(string.replaceAll("\\.", "/") + ".class", getTransformer(string))) diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 990c042c..901ff2d8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -211,7 +211,7 @@ public class ModProcessor { } if (extension.isForge()) { - AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings); + AtRemapper.remapSrgToNamed(project.getLogger(), info.getRemappedOutput().toPath(), mappings); CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger()); if (ZipUtil.containsEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF")) { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java index 85624ce1..86ec8ca0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java @@ -38,4 +38,8 @@ public interface JarProcessor { * Return true to make all jar processors run again, return false to use the existing results of jar processing. */ boolean isInvalid(File file); + + default boolean doesProcessClass(String classInternalName) { + return false; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java index 634f4bba..4c533903 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java @@ -50,6 +50,10 @@ public class JarProcessorManager { return jarProcessors.stream().anyMatch(jarProcessor -> jarProcessor.isInvalid(file)); } + public boolean doesProcessClass(String classInternalName) { + return jarProcessors.stream().anyMatch(jarProcessor -> jarProcessor.doesProcessClass(classInternalName)); + } + public void process(File file) { for (JarProcessor jarProcessor : jarProcessors) { jarProcessor.process(file); diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java index 9673a9a4..adf85fd2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java @@ -50,9 +50,7 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider { @Override protected void addDependencies(DependencyInfo dependency, Consumer postPopulationScheduler) { - boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); - - if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps() || isForgeAtDirty) { + if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps()) { getProject().getLogger().info(":processing mapped jar"); invalidateJars(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index cccfc7f8..73800c78 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -26,15 +26,12 @@ package net.fabricmc.loom.configuration.providers.forge; import java.io.BufferedWriter; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.UncheckedIOException; -import java.lang.reflect.Field; import java.net.URI; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -44,7 +41,7 @@ import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collections; import java.util.Locale; -import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -54,18 +51,13 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonParser; import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer; -import net.minecraftforge.accesstransformer.AccessTransformerEngine; -import net.minecraftforge.accesstransformer.TransformerProcessor; -import net.minecraftforge.accesstransformer.parser.AccessTransformerList; import net.minecraftforge.binarypatcher.ConsoleTool; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; import org.gradle.api.logging.Logger; -import org.gradle.api.plugins.JavaPluginConvention; -import org.gradle.api.tasks.SourceSet; -import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -73,14 +65,12 @@ import org.objectweb.asm.tree.ClassNode; import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor; import net.fabricmc.loom.configuration.providers.MinecraftProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; -import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.JarUtil; import net.fabricmc.loom.util.ThreadingUtils; import net.fabricmc.loom.util.TinyRemapperMappingsHelper; import net.fabricmc.loom.util.function.FsPathConsumer; @@ -98,18 +88,11 @@ public class MinecraftPatchedProvider extends DependencyProvider { // Step 2: Binary Patch private File minecraftClientPatchedSrgJar; private File minecraftServerPatchedSrgJar; - // Step 3: Access Transform - private File minecraftClientPatchedSrgATJar; - private File minecraftServerPatchedSrgATJar; - // Step 4: Remap Patched AT to Official + // Step 3: Remap Patched Jar to Official private File minecraftClientPatchedOfficialJar; private File minecraftServerPatchedOfficialJar; - // Step 5: Merge + // Step 4: Merge private File minecraftMergedPatchedJar; - private File projectAtHash; - @Nullable - private File projectAt = null; - private boolean atDirty = false; public MinecraftPatchedProvider(MappingsProvider mappingsProvider, Project project) { super(project); @@ -117,34 +100,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { } public void initFiles() throws IOException { - projectAtHash = new File(getExtension().getProjectPersistentCache(), "at.sha256"); - - SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); - - for (File srcDir : main.getResources().getSrcDirs()) { - File projectAt = new File(srcDir, "META-INF/accesstransformer.cfg"); - - if (projectAt.exists()) { - this.projectAt = projectAt; - break; - } - } - - if (isRefreshDeps() || !projectAtHash.exists()) { - writeAtHash(); - atDirty = projectAt != null; - } else { - byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); - byte[] current = projectAt != null ? Checksum.sha256(projectAt) : Checksum.sha256(""); - boolean mismatched = !Arrays.equals(current, expected); - - if (mismatched) { - writeAtHash(); - } - - atDirty = mismatched; - } - MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider(); PatchProvider patchProvider = getExtension().getPatchProvider(); String minecraftVersion = minecraftProvider.getMinecraftVersion(); @@ -157,21 +112,18 @@ public class MinecraftPatchedProvider extends DependencyProvider { minecraftProvider.setJarSuffix(jarSuffix); File globalCache = getExtension().getUserCache(); - File cache = usesProjectCache() ? getExtension().getProjectPersistentCache() : globalCache; minecraftClientSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg.jar"); minecraftServerSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg.jar"); - minecraftClientPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg" + jarSuffix + ".jar"); - minecraftServerPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg" + jarSuffix + ".jar"); - minecraftClientPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-client-srg-at" + jarSuffix + ".jar"); - minecraftServerPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-server-srg-at" + jarSuffix + ".jar"); - minecraftClientPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-client" + jarSuffix + ".jar"); - minecraftServerPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-server" + jarSuffix + ".jar"); - minecraftMergedPatchedJar = new File(cache, "minecraft-" + minecraftVersion + "-merged" + jarSuffix + ".jar"); + minecraftClientPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg-patched" + jarSuffix + ".jar"); + minecraftServerPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg-patched" + jarSuffix + ".jar"); + minecraftClientPatchedOfficialJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-patched" + jarSuffix + ".jar"); + minecraftServerPatchedOfficialJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-patched" + jarSuffix + ".jar"); + minecraftMergedPatchedJar = new File(globalCache, "minecraft-" + minecraftVersion + "-merged-patched" + jarSuffix + ".jar"); if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(Predicates.not(File::exists))) { cleanAllCache(); - } else if (atDirty || Stream.of(getProjectCache()).anyMatch(Predicates.not(File::exists))) { + } else if (Stream.of(getProjectCache()).anyMatch(Predicates.not(File::exists))) { cleanProjectCache(); } } @@ -201,8 +153,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { private File[] getProjectCache() { return new File[] { - minecraftClientPatchedSrgATJar, - minecraftServerPatchedSrgATJar, minecraftClientPatchedOfficialJar, minecraftServerPatchedOfficialJar, minecraftMergedPatchedJar @@ -213,10 +163,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { initFiles(); - if (atDirty) { - getProject().getLogger().lifecycle(":found dirty access transformers"); - } - boolean dirty = false; if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { @@ -229,10 +175,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { dirty = true; patchJars(getProject().getLogger()); injectForgeClasses(getProject().getLogger()); - } - - if (atDirty || !minecraftClientPatchedSrgATJar.exists() || !minecraftServerPatchedSrgATJar.exists()) { - dirty = true; accessTransformForge(getProject().getLogger()); } @@ -245,16 +187,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { } } - private void writeAtHash() throws IOException { - try (FileOutputStream out = new FileOutputStream(projectAtHash)) { - if (projectAt != null) { - out.write(Checksum.sha256(projectAt)); - } else { - out.write(Checksum.sha256("")); - } - } - } - private void createSrgJars(Logger logger) throws Exception { McpConfigProvider mcpProvider = getExtension().getMcpConfigProvider(); @@ -280,8 +212,10 @@ public class MinecraftPatchedProvider extends DependencyProvider { throw new IllegalStateException("Failed to find mappings '" + mappingsPath[0] + "' in " + mcpProvider.getMcp().getAbsolutePath() + "!"); } - File specialSourceJar = new File(getExtension().getUserCache(), "SpecialSource-1.8.3-shaded.jar"); - DownloadUtil.downloadIfChanged(new URL("https://repo1.maven.org/maven2/net/md-5/SpecialSource/1.8.3/SpecialSource-1.8.3-shaded.jar"), specialSourceJar, getProject().getLogger(), true); + Dependency dependency = getProject().getDependencies().create("net.md-5:SpecialSource:1.8.3:shaded"); + Set specialSourceJars = getProject().getConfigurations().detachedConfiguration(dependency).resolve(); + if (specialSourceJars.isEmpty()) throw new IllegalStateException("Failed to resolve net.md-5:SpecialSource:1.8.3:shaded"); + File specialSourceJar = specialSourceJars.iterator().next(); ThreadingUtils.run(() -> { Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "client", specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); @@ -348,65 +282,30 @@ public class MinecraftPatchedProvider extends DependencyProvider { String side = environment.side(); logger.lifecycle(":access transforming minecraft (" + side + ")"); - File input = environment.patchedSrgJar.apply(this); - File inputCopied = File.createTempFile("at" + side, ".jar"); - FileUtils.copyFile(input, inputCopied); - File target = environment.patchedSrgATJar.apply(this); - target.delete(); - File at = File.createTempFile("at" + side, ".cfg"); - JarUtil.extractFile(inputCopied, "META-INF/accesstransformer.cfg", at); - String[] args = new String[] { - "--inJar", inputCopied.getAbsolutePath(), - "--outJar", target.getAbsolutePath(), - "--atFile", at.getAbsolutePath() - }; - - if (usesProjectCache()) { - args = Arrays.copyOf(args, args.length + 2); - args[args.length - 2] = "--atFile"; - args[args.length - 1] = projectAt.getAbsolutePath(); - } - - resetAccessTransformerEngine(); - TransformerProcessor.main(args); - inputCopied.delete(); + File file = environment.patchedSrgJar.apply(this); + AccessTransformerJarProcessor.accessTransformForge(file, file, true); } } - private void resetAccessTransformerEngine() throws Exception { - // Thank you Forge, I love you - Field field = AccessTransformerEngine.class.getDeclaredField("masterList"); - field.setAccessible(true); - AccessTransformerList list = (AccessTransformerList) field.get(AccessTransformerEngine.INSTANCE); - field = AccessTransformerList.class.getDeclaredField("accessTransformers"); - field.setAccessible(true); - ((Map) field.get(list)).clear(); - } - private enum Environment { CLIENT(provider -> provider.minecraftClientSrgJar, provider -> provider.minecraftClientPatchedSrgJar, - provider -> provider.minecraftClientPatchedSrgATJar, provider -> provider.minecraftClientPatchedOfficialJar ), SERVER(provider -> provider.minecraftServerSrgJar, provider -> provider.minecraftServerPatchedSrgJar, - provider -> provider.minecraftServerPatchedSrgATJar, provider -> provider.minecraftServerPatchedOfficialJar ); final Function srgJar; final Function patchedSrgJar; - final Function patchedSrgATJar; final Function patchedOfficialJar; Environment(Function srgJar, Function patchedSrgJar, - Function patchedSrgATJar, Function patchedOfficialJar) { this.srgJar = srgJar; this.patchedSrgJar = patchedSrgJar; - this.patchedSrgATJar = patchedSrgATJar; this.patchedOfficialJar = patchedOfficialJar; } @@ -422,7 +321,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)"); TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - Path input = environment.patchedSrgATJar.apply(this).toPath(); + Path input = environment.patchedSrgJar.apply(this).toPath(); Path output = environment.patchedOfficialJar.apply(this).toPath(); Files.deleteIfExists(output); @@ -570,14 +469,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { return minecraftMergedPatchedJar; } - public boolean usesProjectCache() { - return projectAt != null; - } - - public boolean isAtDirty() { - return atDirty; - } - @Override public String getTargetConfig() { return Constants.Configurations.MINECRAFT; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java index 40ae05d0..da31d8f7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java @@ -47,6 +47,8 @@ import org.apache.commons.io.FileUtils; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; import org.zeroturnaround.zip.FileSource; import org.zeroturnaround.zip.ZipEntrySource; import org.zeroturnaround.zip.ZipUtil; @@ -54,6 +56,7 @@ import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; @@ -240,6 +243,10 @@ public class MappingsProvider extends DependencyProvider { extension.addJarProcessor(new AccessWidenerJarProcessor(getProject())); } + if (extension.isForge()) { + AccessTransformerJarProcessor.addTo(getProject(), extension); + } + JarProcessorManager processorManager = new JarProcessorManager(extension.getJarProcessors()); extension.setJarProcessorManager(processorManager); processorManager.setupProcessors(); @@ -249,7 +256,7 @@ public class MappingsProvider extends DependencyProvider { patchedProvider.provide(dependency, postPopulationScheduler); } - if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { + if (processorManager.active()) { mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager); getProject().getLogger().lifecycle("Using project based jar storage"); } else { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index 891054b6..7979e43c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -89,9 +89,7 @@ public class MinecraftMappedProvider extends DependencyProvider { throw new RuntimeException("input merged jar not found"); } - boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); - - if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) { + if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps()) { if (minecraftMappedJar.exists()) { minecraftMappedJar.delete(); } @@ -199,7 +197,7 @@ public class MinecraftMappedProvider extends DependencyProvider { } TinyTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg); + AtRemapper.remapSrgToNamed(getProject().getLogger(), output, yarnWithSrg); CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger()); } } @@ -281,6 +279,10 @@ public class MinecraftMappedProvider extends DependencyProvider { return minecraftMappedJar; } + public final File getUnprocessedMappedJar() { + return minecraftMappedJar; + } + public File getUnpickedJar() { return new File(getJarDirectory(getExtension().getUserCache(), "mapped"), "minecraft-" + getJarVersionString("unpicked") + ".jar"); } diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java index ee2801ab..388bdd60 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/FabricCFRDecompiler.java @@ -177,6 +177,22 @@ public class FabricCFRDecompiler implements LoomDecompiler { List> futures = new LinkedList<>(); for (String clazz : classes) { + if (metaData.classFilter != null) { + String classInternalName = clazz; + + if (classInternalName.length() >= 1 && classInternalName.charAt(0) == '/') { + classInternalName = classInternalName.substring(1); + } + + classInternalName = classInternalName.substring(0, classInternalName.length() - 6); + + if (!metaData.classFilter.test(classInternalName)) { + return; + } else { + System.out.println(classInternalName); + } + } + futures.add(executorService.submit(() -> { loggerMap.computeIfAbsent(Thread.currentThread().getId(), createLogger).progress(clazz); driver.analyse(Collections.singletonList(clazz)); diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java index a8844f22..d12eedfd 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java +++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java @@ -26,13 +26,25 @@ package net.fabricmc.loom.decompilers.fernflower; import static java.text.MessageFormat.format; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.function.Supplier; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.gradle.api.Project; import org.gradle.api.internal.project.ProjectInternal; @@ -77,7 +89,7 @@ public abstract class AbstractFernFlowerDecompiler implements LoomDecompiler { List args = new ArrayList<>(); options.forEach((k, v) -> args.add(format("-{0}={1}", k, v))); - args.add(absolutePathOf(compiledJar)); + args.add(absolutePathOf(transformBinary(compiledJar, metaData))); args.add("-o=" + absolutePathOf(sourcesDestination)); args.add("-l=" + absolutePathOf(linemapDestination)); args.add("-m=" + absolutePathOf(metaData.javaDocs)); @@ -150,6 +162,52 @@ public abstract class AbstractFernFlowerDecompiler implements LoomDecompiler { result.assertNormalExitValue(); } + private Path transformBinary(Path compiledJar, DecompilationMetadata metaData) { + if (metaData.classFilter == null) return compiledJar; + + try { + Path tempFile = Files.createTempFile("loom-", null); + Files.deleteIfExists(tempFile); + tempFile.toFile().deleteOnExit(); + + try (ZipOutputStream tempZipOut = new ZipOutputStream(new FileOutputStream(tempFile.toFile()))) { + try (FileSystem compiledJarSystem = FileSystems.newFileSystem(URI.create("jar:" + compiledJar.toUri()), new HashMap<>())) { + Files.walkFileTree(compiledJarSystem.getPath("/"), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".class")) { + String sourcePath = file.toString(); + + if (sourcePath.length() >= 1 && sourcePath.charAt(0) == '/') { + sourcePath = sourcePath.substring(1); + } + + sourcePath = sourcePath.substring(0, sourcePath.length() - 6); + + if (!metaData.classFilter.test(sourcePath)) { + return FileVisitResult.CONTINUE; + } else { + System.out.println(sourcePath); + } + } + + String resourcePath = file.toString(); + tempZipOut.putNextEntry(new ZipEntry(resourcePath)); + tempZipOut.write(Files.readAllBytes(file)); + tempZipOut.closeEntry(); + + return FileVisitResult.CONTINUE; + } + }); + } + } + + return tempFile; + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + } + private static String absolutePathOf(Path path) { return path.toAbsolutePath().toString(); } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractLoomTask.java b/src/main/java/net/fabricmc/loom/task/AbstractLoomTask.java index 9dada304..e1f4db16 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractLoomTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractLoomTask.java @@ -28,10 +28,11 @@ import org.gradle.api.DefaultTask; import org.gradle.api.tasks.Internal; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.Constants; public abstract class AbstractLoomTask extends DefaultTask { public AbstractLoomTask() { - setGroup("fabric"); + setGroup(Constants.TASK_CATEGORY); } @Internal diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 34f2dac8..111d074a 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -35,13 +35,14 @@ import org.gradle.api.Project; import org.gradle.api.tasks.JavaExec; import net.fabricmc.loom.configuration.ide.RunConfig; +import net.fabricmc.loom.util.Constants; public abstract class AbstractRunTask extends JavaExec { private final RunConfig config; public AbstractRunTask(Function configProvider) { super(); - setGroup("fabric"); + setGroup(Constants.TASK_CATEGORY); this.config = configProvider.apply(getProject()); setClasspath(config.sourceSet.getRuntimeClasspath()); diff --git a/src/main/java/net/fabricmc/loom/task/CleanSourcesTask.java b/src/main/java/net/fabricmc/loom/task/CleanSourcesTask.java new file mode 100644 index 00000000..de43fa19 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/CleanSourcesTask.java @@ -0,0 +1,15 @@ +package net.fabricmc.loom.task; + +import static net.fabricmc.loom.task.GenerateSourcesTask.getMappedJarFileWithSuffix; + +import java.nio.file.Files; + +import org.gradle.api.tasks.TaskAction; + +public class CleanSourcesTask extends AbstractLoomTask { + @TaskAction + public void doTask() throws Throwable { + Files.deleteIfExists(getMappedJarFileWithSuffix(getProject(), "-sources.jar", false).toPath()); + Files.deleteIfExists(getMappedJarFileWithSuffix(getProject(), "-sources.jar", true).toPath()); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/GenerateIncrementalSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateIncrementalSourcesTask.java new file mode 100644 index 00000000..cd5d9683 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/GenerateIncrementalSourcesTask.java @@ -0,0 +1,128 @@ +package net.fabricmc.loom.task; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import javax.inject.Inject; + +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.configuration.processors.JarProcessorManager; +import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; +import net.fabricmc.stitch.util.StitchUtil; + +public class GenerateIncrementalSourcesTask extends AbstractLoomTask { + public final LoomDecompiler decompiler; + + @Inject + public GenerateIncrementalSourcesTask(LoomDecompiler decompiler) { + this.decompiler = decompiler; + + getOutputs().upToDateWhen((o) -> false); + } + + @TaskAction + public void doTask() throws Throwable { + GenerateSourcesTask.generateSources(getProject(), getClass(), decompiler, null, false, true); + + if (getExtension().getMappingsProvider().mappedProvider instanceof MinecraftProcessedProvider) { + JarProcessorManager processorManager = getExtension().getJarProcessorManager(); + Path unprocessedCompiledJar = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), null, false).toPath(); + Path compiledJar = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), null, true).toPath(); + Path unprocessedSourcesDestination = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-sources.jar", false).toPath(); + Map unprocessedSources = new HashMap<>(); + Set affectedSources = new HashSet<>(); + + try (FileSystem system = FileSystems.newFileSystem(URI.create("jar:" + unprocessedSourcesDestination.toUri()), new HashMap<>())) { + Files.walkFileTree(system.getPath("/"), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".java")) { + String sourcePath = file.toString(); + + if (sourcePath.length() >= 1 && sourcePath.charAt(0) == '/') { + sourcePath = sourcePath.substring(1); + } + + sourcePath = sourcePath.substring(0, sourcePath.length() - 5); + unprocessedSources.put(sourcePath, Files.readAllBytes(file)); + + if (processorManager.doesProcessClass(sourcePath)) { + affectedSources.add(sourcePath); + } + } + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException exception) { + exception.printStackTrace(); + unprocessedSources.clear(); + } + + Consumer linemapConsumer = linemap -> { + try { + try (StitchUtil.FileSystemDelegate unprocessedFs = StitchUtil.getJarFileSystem(unprocessedCompiledJar.toFile(), true); + StitchUtil.FileSystemDelegate processedFs = StitchUtil.getJarFileSystem(compiledJar.toFile(), true)) { + int i = 0; + + for (Path path : (Iterable) Files.walk(processedFs.get().getPath("/"))::iterator) { + if (path.toString().endsWith(".class")) { + String sourcePath = path.toString(); + + if (sourcePath.length() >= 1 && sourcePath.charAt(0) == '/') { + sourcePath = sourcePath.substring(1); + } + + sourcePath = sourcePath.substring(0, sourcePath.length() - 6); + + Path unprocessedPath = unprocessedFs.get().getPath(path.toString()); + + if (Files.exists(unprocessedPath) && !processorManager.doesProcessClass(sourcePath)) { + i++; + Files.copy(unprocessedPath, path, StandardCopyOption.REPLACE_EXISTING); + } + } + } + + getLogger().lifecycle(":copied {} linemap remapped classes from unprocessed jar", i); + } + + GenerateSourcesTask.remapLinemap(getProject(), getClass(), compiledJar, linemap, true); + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + }; + + getLogger().lifecycle(":skipping {} unprocessed classes (excluding {} processed classes)", unprocessedSources.size() - affectedSources.size(), affectedSources.size()); + GenerateSourcesTask.generateSources(getProject(), getClass(), decompiler, className -> { + int i = className.indexOf('$'); + if (i >= 0) className = className.substring(0, i); + if (affectedSources.contains(className)) return GenerateSourcesTask.SkipState.GENERATE; + if (unprocessedSources.containsKey(className)) return GenerateSourcesTask.SkipState.SKIP; + return null; + }, true, true, linemapConsumer); + } + } + + public GenerateIncrementalSourcesTask setInputJar(File inputJar) { + this.inputJar = inputJar; + return this; + } +} diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 25d6fe47..a9a0e0d4 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -26,19 +26,34 @@ package net.fabricmc.loom.task; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; +import com.google.common.base.MoreObjects; +import org.gradle.api.Project; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.TaskAction; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; @@ -56,25 +71,100 @@ public class GenerateSourcesTask extends AbstractLoomTask { public GenerateSourcesTask(LoomDecompiler decompiler) { this.decompiler = decompiler; - setGroup("fabric"); getOutputs().upToDateWhen((o) -> false); } @TaskAction public void doTask() throws Throwable { - int threads = Runtime.getRuntime().availableProcessors(); - Path javaDocs = getExtension().getMappingsProvider().tinyMappings.toPath(); - Collection libraries = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles() - .stream().map(File::toPath).collect(Collectors.toSet()); + generateSources(getProject(), getClass(), decompiler, null, true, false); + } - DecompilationMetadata metadata = new DecompilationMetadata(threads, javaDocs, libraries); - Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath(); - Path sourcesDestination = getMappedJarFileWithSuffix("-sources.jar").toPath(); - Path linemap = getMappedJarFileWithSuffix("-sources.lmap").toPath(); + public static void generateSources(Project project, Class taskClass, LoomDecompiler decompiler, Function additionalClassFilter, boolean processed, boolean incremental) + throws IOException { + generateSources(project, taskClass, decompiler, additionalClassFilter, processed, incremental, linemap -> { + try { + Path compiledJar = getMappedJarFileWithSuffix(project, null, processed).toPath(); + remapLinemap(project, taskClass, compiledJar, linemap, processed); + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + }); + } + + public static void generateSources(Project project, Class taskClass, LoomDecompiler decompiler, Function additionalClassFilter, boolean processed, boolean incremental, Consumer linemapConsumer) + throws IOException { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + int threads = Runtime.getRuntime().availableProcessors(); + Path javaDocs = extension.getMappingsProvider().tinyMappings.toPath(); + Collection libraries = project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles() + .stream().map(File::toPath).collect(Collectors.toSet()); + + Path runtimeJar = getMappedJarFileWithSuffix(project, null, processed).toPath(); + Path sourcesDestination = getMappedJarFileWithSuffix(project, "-sources.jar", processed).toPath(); + Path linemap = getMappedJarFileWithSuffix(project, "-sources.lmap", processed).toPath(); + Map remappedClasses = new HashMap<>(); + + if (!LoomGradlePlugin.refreshDeps && incremental && Files.exists(sourcesDestination)) { + try (FileSystem system = FileSystems.newFileSystem(URI.create("jar:" + sourcesDestination.toUri()), new HashMap<>())) { + Files.walkFileTree(system.getPath("/"), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".java")) { + String sourcePath = file.toString(); + + if (sourcePath.length() >= 1 && sourcePath.charAt(0) == '/') { + sourcePath = sourcePath.substring(1); + } + + remappedClasses.put(sourcePath.substring(0, sourcePath.length() - 5), Files.readAllBytes(file)); + } + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException exception) { + exception.printStackTrace(); + remappedClasses.clear(); + } + } + + Files.deleteIfExists(sourcesDestination); + + if (incremental) { + project.getLogger().lifecycle(":incremental source generation: skipping {} classes", remappedClasses.size()); + } + + Function function = MoreObjects.firstNonNull(additionalClassFilter, s -> null); + Function classFilter = remappedClasses.isEmpty() ? function : s -> { + SkipState apply = function.apply(s); + if (apply != null) return apply; + int i = s.indexOf('$'); + if (i >= 0) s = s.substring(0, i); + return remappedClasses.containsKey(s) ? SkipState.SKIP : SkipState.GENERATE; + }; + DecompilationMetadata metadata = new DecompilationMetadata(threads, javaDocs, libraries, classFilter); decompiler.decompile(inputJar.toPath(), sourcesDestination, linemap, metadata); + if (incremental && !remappedClasses.isEmpty()) { + try (FileSystem system = FileSystems.newFileSystem(URI.create("jar:" + sourcesDestination.toUri()), new HashMap() { + { + put("create", "true"); + } + })) { + for (Map.Entry entry : remappedClasses.entrySet()) { + if (additionalClassFilter != null && SkipState.SKIP != additionalClassFilter.apply(entry.getKey())) continue; + Path path = system.getPath(entry.getKey() + ".java"); + Path parent = path.getParent(); + if (parent != null) Files.createDirectories(parent); + Files.write(path, entry.getValue(), StandardOpenOption.CREATE); + } + } + } + } + + public static void remapLinemap(Project project, Class taskClass, Path compiledJar, Path linemap, boolean processed) throws IOException { if (Files.exists(linemap)) { - Path linemappedJarDestination = getMappedJarFileWithSuffix("-linemapped.jar").toPath(); + Path linemappedJarDestination = getMappedJarFileWithSuffix(project, "-linemapped.jar", processed).toPath(); // Line map the actually jar used to run the game, not the one used to decompile remapLineNumbers(runtimeJar, linemap, linemappedJarDestination); @@ -84,12 +174,13 @@ public class GenerateSourcesTask extends AbstractLoomTask { } } - private void remapLineNumbers(Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException { - getProject().getLogger().info(":adjusting line numbers"); + private static void remapLineNumbers(Project project, Class taskClass, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) + throws IOException { + project.getLogger().info(":adjusting line numbers"); LineNumberRemapper remapper = new LineNumberRemapper(); remapper.readMappings(linemap.toFile()); - ProgressLogger progressLogger = ProgressLogger.getProgressFactory(getProject(), getClass().getName()); + ProgressLogger progressLogger = ProgressLogger.getProgressFactory(project, taskClass.getName()); progressLogger.start("Adjusting line numbers", "linemap"); try (StitchUtil.FileSystemDelegate inFs = StitchUtil.getJarFileSystem(oldCompiledJar.toFile(), true); @@ -100,19 +191,27 @@ public class GenerateSourcesTask extends AbstractLoomTask { progressLogger.completed(); } - private File getMappedJarFileWithSuffix(String suffix) { - LoomGradleExtension extension = getProject().getExtensions().getByType(LoomGradleExtension.class); + public static File getMappedJarFileWithSuffix(Project project, String suffix, boolean processed) { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); MappingsProvider mappingsProvider = extension.getMappingsProvider(); - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); + File mappedJar = processed ? mappingsProvider.mappedProvider.getMappedJar() : mappingsProvider.mappedProvider.getUnprocessedMappedJar(); String path = mappedJar.getAbsolutePath(); if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { throw new RuntimeException("Invalid mapped JAR path: " + path); } + if (suffix == null) { + return new File(path); + } + return new File(path.substring(0, path.length() - 4) + suffix); } + public enum SkipState { + SKIP, GENERATE; + } + @InputFile public File getInputJar() { return inputJar; diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 1b1de6a7..55cf282a 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -35,6 +35,7 @@ import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler; +import net.fabricmc.loom.util.Constants; public final class LoomTasks { private LoomTasks() { @@ -50,7 +51,7 @@ public final class LoomTasks { tasks.register("remapJar", RemapJarTask.class, t -> { t.setDescription("Remaps the built project jar to intermediary mappings."); - t.setGroup("fabric"); + t.setGroup(Constants.TASK_CATEGORY); }); tasks.register("downloadAssets", DownloadAssetsTask.class, t -> t.setDescription("Downloads required assets for Fabric.")); @@ -59,6 +60,8 @@ public final class LoomTasks { registerIDETasks(tasks); registerRunTasks(tasks, project); registerDecompileTasks(tasks, project); + + tasks.register("cleanSources", CleanSourcesTask.class); } private static void registerIDETasks(TaskContainer tasks) { @@ -97,7 +100,6 @@ public final class LoomTasks { tasks.register(taskName, RunGameTask.class, config).configure(t -> { t.setDescription("Starts the '" + config.getConfigName() + "' run configuration"); - t.setGroup("fabric"); if (config.getEnvironment().equals("client")) { t.dependsOn("downloadAssets"); @@ -130,6 +132,7 @@ public final class LoomTasks { for (LoomDecompiler decompiler : extension.getDecompilers()) { String taskName = decompiler instanceof FabricFernFlowerDecompiler ? "genSources" : "genSourcesWith" + decompiler.name(); + String incrementalTaskName = decompiler instanceof FabricFernFlowerDecompiler ? "genIncrementalSources" : "genIncrementalSourcesWith" + decompiler.name(); // decompiler will be passed to the constructor of GenerateSourcesTask GenerateSourcesTask generateSourcesTask = tasks.register(taskName, GenerateSourcesTask.class, decompiler).get(); generateSourcesTask.setInputJar(inputJar); @@ -137,6 +140,13 @@ public final class LoomTasks { if (mappingsProvider.hasUnpickDefinitions()) { generateSourcesTask.dependsOn(tasks.getByName("unpickJar")); } + + GenerateIncrementalSourcesTask generateIncrementalSourcesTask = tasks.register(incrementalTaskName, GenerateIncrementalSourcesTask.class, decompiler).get(); + generateIncrementalSourcesTask.setInputJar(inputJar); + + if (mappingsProvider.hasUnpickDefinitions()) { + generateIncrementalSourcesTask.dependsOn(tasks.getByName("unpickJar")); + } } }); } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index b9059217..4e47c5dd 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -35,6 +35,7 @@ import net.fabricmc.loom.util.gradle.GradleSupport; public class Constants { public static final String PLUGIN_ID = "forgified-fabric-loom"; + public static final String TASK_CATEGORY = "loom"; public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/"; public static final String RESOURCES_BASE = "http://resources.download.minecraft.net/"; public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; diff --git a/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java index 467c15a4..e14afeb9 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java @@ -24,111 +24,108 @@ package net.fabricmc.loom.util.srg; +import static me.shedaniel.architectury.refmapremapper.utils.DescriptorRemapper.remapDescriptor; + import java.io.IOException; -import java.io.StringReader; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; +import java.util.Objects; import java.util.zip.ZipEntry; +import me.shedaniel.architectury.refmapremapper.remapper.SimpleReferenceRemapper; import org.apache.logging.log4j.util.Strings; import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.Nullable; import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.transform.StringZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; -import net.fabricmc.loom.util.function.CollectionUtil; import net.fabricmc.mapping.tree.TinyTree; /** - * Remaps AT classes from SRG to Yarn. + * Remaps AT contents. * * @author Juuz */ public final class AtRemapper { - public static void remap(Logger logger, Path jar, TinyTree mappings) throws IOException { - ZipUtil.transformEntries(jar.toFile(), new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("META-INF/accesstransformer.cfg", new StringZipEntryTransformer() { + public static void remapSrgToNamed(Logger logger, Path jar, TinyTree mappings) throws IOException { + ZipUtil.transformEntry(jar.toFile(), "META-INF/accesstransformer.cfg", new StringZipEntryTransformer() { @Override protected String transform(ZipEntry zipEntry, String input) { - String[] lines = input.split("\n"); - List output = new ArrayList<>(lines.length); - - for (int i = 0; i < lines.length; i++) { - String line = lines[i].trim(); - - if (line.startsWith("#") || Strings.isBlank(line)) { - output.add(i, line); - continue; + return remapAt(logger, input, new SimpleReferenceRemapper.Remapper() { + @Override + @Nullable + public String mapClass(String value) { + return mappings.getClasses().stream() + .filter(classDef -> Objects.equals(classDef.getName("srg"), value)) + .findFirst() + .map(classDef -> classDef.getName("named")) + .orElse(null); } - String[] parts = line.split("\\s+"); - - if (parts.length < 2) { - logger.warn("Invalid AT Line: " + line); - output.add(i, line); - continue; + @Override + @Nullable + public String mapMethod(@Nullable String className, String methodName, String methodDescriptor) { + return null; } - String name = parts[1].replace('.', '/'); - parts[1] = CollectionUtil.find( - mappings.getClasses(), - def -> def.getName("srg").equals(name) - ).map(def -> def.getName("named")).orElse(name).replace('/', '.'); - - if (parts.length >= 3) { - if (parts[2].contains("(")) { - parts[2] = parts[2].substring(0, parts[2].indexOf('(')) + remapDescriptor(parts[2].substring(parts[2].indexOf('(')), s -> { - return CollectionUtil.find( - mappings.getClasses(), - def -> def.getName("srg").equals(s) - ).map(def -> def.getName("named")).orElse(s); - }); - } + @Override + @Nullable + public String mapField(@Nullable String className, String fieldName, String fieldDescriptor) { + return null; } - - output.add(i, String.join(" ", parts)); - } - - return String.join("\n", output); + }); } - }))}); + }); } - private static String remapDescriptor(String original, UnaryOperator classMappings) { - try { - StringReader reader = new StringReader(original); - StringBuilder result = new StringBuilder(); - boolean insideClassName = false; - StringBuilder className = new StringBuilder(); + public static String remapAt(Logger logger, String sourceAt, SimpleReferenceRemapper.Remapper remapper) { + String[] lines = sourceAt.split("\n"); + StringBuilder builder = new StringBuilder(); - while (true) { - int c = reader.read(); + for (String line : lines) { + { + int indexOf = line.indexOf('#'); - if (c == -1) { - break; + if (indexOf != -1) { + line = line.substring(0, indexOf); } - if ((char) c == ';') { - insideClassName = false; - result.append(classMappings.apply(className.toString())); - } + line = line.trim(); + } - if (insideClassName) { - className.append((char) c); + if (Strings.isBlank(line)) { + builder.append(line).append('\n'); + continue; + } + + String[] parts = line.split("\\s+"); + + if (parts.length < 2) { + logger.warn("Invalid AT Line: " + line); + builder.append(line).append('\n'); + continue; + } + + String originalClassName = parts[1].replace('.', '/'); + parts[1] = either(remapper.mapClass(originalClassName), parts[1]).replace('/', '.'); + + if (parts.length >= 3) { + if (parts[2].contains("(")) { + String methodName = parts[2].substring(0, parts[2].indexOf('(')); + String methodDescriptor = parts[2].substring(parts[2].indexOf('(')); + parts[2] = either(remapper.mapMethod(originalClassName, methodName, methodDescriptor), methodName) + + remapDescriptor(methodDescriptor, it -> either(remapper.mapClass(it), it)); } else { - result.append((char) c); - } - - if (!insideClassName && (char) c == 'L') { - insideClassName = true; - className.setLength(0); + parts[2] = either(remapper.mapField(originalClassName, parts[2], null), parts[2]); } } - return result.toString(); - } catch (IOException e) { - throw new AssertionError(e); + builder.append(String.join(" ", parts)).append('\n'); } + + return builder.toString(); + } + + private static T either(@Nullable T first, @Nullable T second) { + return first == null ? second : first; } }