diff --git a/build.gradle b/build.gradle index 60b9ed6f..d4b14804 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ targetCompatibility = 1.8 group = 'net.fabricmc' archivesBaseName = project.name -version = '0.2.1-SNAPSHOT' +version = '0.2.2-SNAPSHOT' def build = "local" def ENV = System.getenv() diff --git a/src/main/java/net/fabricmc/loom/AbstractPlugin.java b/src/main/java/net/fabricmc/loom/AbstractPlugin.java index c7978784..0a8b1e1b 100644 --- a/src/main/java/net/fabricmc/loom/AbstractPlugin.java +++ b/src/main/java/net/fabricmc/loom/AbstractPlugin.java @@ -136,27 +136,8 @@ public class AbstractPlugin implements Plugin { configureMaven(); } - /** - * Permit to create a Task instance of the type in the project - * - * @param name The name of the task - * @param type The type of the task that will be used to create an instance - * @return The created task object for the project - */ - public T makeTask(String name, Class type) { - return makeTask(project, name, type); - } - - /** - * Permit to create a Task instance of the type in a project - * - * @param target The target project - * @param name The name of the task - * @param type The type of the task that will be used to create an instance - * @return The created task object for the specified project - */ - public static T makeTask(Project target, String name, Class type) { - return target.getTasks().create(name, type); + public Project getProject() { + return project; } /** diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 4add9364..ad7be738 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -24,30 +24,110 @@ package net.fabricmc.loom; +import net.fabricmc.loom.providers.MappingsProvider; +import net.fabricmc.loom.providers.MinecraftLibraryProvider; import net.fabricmc.loom.task.*; +import net.fabricmc.loom.task.fernflower.FernFlowerTask; +import net.fabricmc.loom.util.LineNumberRemapper; import org.gradle.api.Project; +import org.gradle.api.tasks.TaskContainer; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; public class LoomGradlePlugin extends AbstractPlugin { + private static File getMappedByproduct(Project project, String suffix) { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + MappingsProvider mappingsProvider = extension.getMappingsProvider(); + File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); + String path = mappedJar.getAbsolutePath(); + if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { + throw new RuntimeException("Invalid mapped JAR path: " + path); + } + + return new File(path.substring(0, path.length() - 4) + suffix); + } + @Override public void apply(Project target) { super.apply(target); - makeTask("cleanLoomBinaries", CleanLoomBinaries.class); - makeTask("cleanLoomMappings", CleanLoomMappings.class); + TaskContainer tasks = target.getTasks(); + + tasks.register("cleanLoomBinaries", CleanLoomBinaries.class); + tasks.register("cleanLoomMappings", CleanLoomMappings.class); - makeTask("remapJar", RemapJar.class); + tasks.register("remapJar", RemapJar.class); - makeTask("genSources", GenSourcesTask.class); + tasks.register("genSources", FernFlowerTask.class, t -> { + t.getOutputs().upToDateWhen((o) -> false); + }); + project.afterEvaluate((p) -> { + FernFlowerTask task = (FernFlowerTask) p.getTasks().getByName("genSources"); - makeTask("downloadAssets", DownloadAssetsTask.class); + Project project = this.getProject(); + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + MinecraftLibraryProvider libraryProvider = extension.getMinecraftProvider().libraryProvider; + MappingsProvider mappingsProvider = extension.getMappingsProvider(); + File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); + File sourcesJar = getMappedByproduct(project, "-sources-tmp.jar"); + File sourcesFinalJar = getMappedByproduct(project, "-sources.jar"); + File linemapFile = getMappedByproduct(project, "-sources.lmap"); - makeTask("genIdeaWorkspace", GenIdeaProjectTask.class).dependsOn("idea", "downloadAssets").setGroup("ide"); - makeTask("genEclipseRuns", GenEclipseRunsTask.class).dependsOn("downloadAssets").setGroup("ide"); - makeTask("vscode", GenVsCodeProjectTask.class).dependsOn("downloadAssets").setGroup("ide"); + task.setInput(mappedJar); + task.setOutput(sourcesJar); + task.setLineMapFile(linemapFile); + task.setLibraries(libraryProvider.getLibraries()); - makeTask("remapSourcesJar", RemapSourcesJar.class); + if (sourcesJar.exists()) { + sourcesJar.delete(); + } - makeTask("runClient", RunClientTask.class).dependsOn("buildNeeded", "downloadAssets").setGroup("minecraftMapped"); - makeTask("runServer", RunServerTask.class).dependsOn("buildNeeded").setGroup("minecraftMapped"); + task.doLast((tt) -> { + project.getLogger().lifecycle(":readjusting line numbers"); + LineNumberRemapper remapper = new LineNumberRemapper(); + remapper.readMappings(linemapFile); + + try { + remapper.process(sourcesJar.toPath(), sourcesFinalJar.toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + if (sourcesJar.exists()) { + sourcesJar.delete(); + } + }); + }); + + tasks.register("downloadAssets", DownloadAssetsTask.class); + + tasks.register("genIdeaWorkspace", GenIdeaProjectTask.class, t -> { + t.dependsOn("idea", "downloadAssets"); + t.setGroup("ide"); + }); + + tasks.register("genEclipseRuns", GenEclipseRunsTask.class, t -> { + t.dependsOn("downloadAssets"); + t.setGroup("ide"); + }); + + tasks.register("vscode", GenVsCodeProjectTask.class, t -> { + t.dependsOn("downloadAssets"); + t.setGroup("ide"); + }); + + tasks.register("remapSourcesJar", RemapSourcesJar.class); + + tasks.register("runClient", RunClientTask.class, t -> { + t.dependsOn("buildNeeded", "downloadAssets"); + t.setGroup("minecraftMapped"); + }); + + tasks.register("runServer", RunServerTask.class, t -> { + t.dependsOn("buildNeeded"); + t.setGroup("minecraftMapped"); + }); } } diff --git a/src/main/java/net/fabricmc/loom/task/ForkingJavaExecTask.java b/src/main/java/net/fabricmc/loom/task/ForkingJavaExecTask.java new file mode 100644 index 00000000..2d33fea7 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/ForkingJavaExecTask.java @@ -0,0 +1,53 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task; + +import org.gradle.api.Action; +import org.gradle.api.Task; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.file.FileCollection; +import org.gradle.process.ExecResult; +import org.gradle.process.JavaExecSpec; + +/** + * Simple trait like interface for a Task that wishes to execute a java process + * with the classpath of the gradle plugin plus groovy. + * + * Created by covers1624 on 11/02/19. + */ +public interface ForkingJavaExecTask extends Task { + + default ExecResult javaexec(Action action) { + ConfigurationContainer configurations = getProject().getBuildscript().getConfigurations(); + DependencyHandler handler = getProject().getDependencies(); + FileCollection classpath = configurations.getByName("classpath")// + .plus(configurations.detachedConfiguration(handler.localGroovy())); + return getProject().javaexec(spec -> { + spec.classpath(classpath); + action.execute(spec); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java b/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java deleted file mode 100644 index 4bb0568d..00000000 --- a/src/main/java/net/fabricmc/loom/task/GenSourcesCfrTask.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016, 2017, 2018 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.task; - -public class GenSourcesCfrTask extends DefaultLoomTask { - /* - @TaskAction - public void genSources() throws IOException { - Project project = this.getProject(); - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - MappingsProvider mappingsProvider = extension.getMappingsProvider(); - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); - File sourcesJar = getSourcesJar(project); - - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - Set addedDirectories = new HashSet<>(); - - try (FileOutputStream fos = new FileOutputStream(sourcesJar); - JarOutputStream jos = new JarOutputStream(fos, manifest)) { - - project.getLogger().lifecycle(":generating sources JAR"); - CfrDriver driver = new CfrDriver.Builder() - .withOptions(ImmutableMap.of("renameillegalidents","true")) - .withOutputSink(new OutputSinkFactory() { - @Override - public List getSupportedSinks(SinkType sinkType, Collection collection) { - switch (sinkType) { - case PROGRESS: - return Collections.singletonList(SinkClass.STRING); - case JAVA: - return Collections.singletonList(SinkClass.DECOMPILED); - default: - return Collections.emptyList(); - } - } - - @Override - public Sink getSink(SinkType sinkType, SinkClass sinkClass) { - switch (sinkType) { - case PROGRESS: - return (t) -> getLogger().debug((String) t); - case JAVA: - //noinspection unchecked - return (Sink) new Sink() { - @Override - public void write(SinkReturns.Decompiled decompiled) { - String filename = decompiled.getPackageName().replace('.', '/'); - if (!filename.isEmpty()) filename += "/"; - filename += decompiled.getClassName() + ".java"; - - String[] path = filename.split("/"); - String pathPart = ""; - for (int i = 0; i < path.length - 1; i++) { - pathPart += path[i] + "/"; - if (addedDirectories.add(pathPart)) { - JarEntry entry = new JarEntry(pathPart); - entry.setTime(new Date().getTime()); - - try { - jos.putNextEntry(entry); - jos.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - byte[] data = decompiled.getJava().getBytes(Charsets.UTF_8); - JarEntry entry = new JarEntry(filename); - entry.setTime(new Date().getTime()); - entry.setSize(data.length); - - try { - jos.putNextEntry(entry); - jos.write(data); - jos.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }; - default: - return (t) -> {}; - } - } - }) - .build(); - - driver.analyse(Collections.singletonList(mappedJar.getAbsolutePath())); - } - } - */ -} diff --git a/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java deleted file mode 100644 index 4154ea77..00000000 --- a/src/main/java/net/fabricmc/loom/task/GenSourcesTask.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016, 2017, 2018 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.task; - -import com.google.common.io.ByteStreams; -import com.google.common.io.Files; -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.providers.MappingsProvider; -import net.fabricmc.loom.providers.MinecraftLibraryProvider; -import org.gradle.api.Project; -import org.gradle.api.tasks.TaskAction; -import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.jar.*; - -public class GenSourcesTask extends DefaultLoomTask { - public static File getSourcesJar(Project project) { - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - MappingsProvider mappingsProvider = extension.getMappingsProvider(); - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); - String path = mappedJar.getAbsolutePath(); - if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { - throw new RuntimeException("Invalid mapped JAR path: " + path); - } - - return new File(path.substring(0, path.length() - 4) + "-sources.jar"); - } - - @TaskAction - public void genSources() throws IOException { - Project project = this.getProject(); - LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); - MinecraftLibraryProvider libraryProvider = extension.getMinecraftProvider().libraryProvider; - MappingsProvider mappingsProvider = extension.getMappingsProvider(); - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); - File sourcesJar = getSourcesJar(project); - - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - - project.getLogger().lifecycle(":preparing sources JAR"); - Map options = new HashMap<>(); - options.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1"); - options.put(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1"); - - LoomFernflowerDecompiler decompiler = new LoomFernflowerDecompiler(sourcesJar.getParentFile(), sourcesJar.getName(), options, new LoomFernflowerLogger()); - decompiler.addSource(mappedJar); - for (File lib : libraryProvider.getLibraries()) { - try { - decompiler.addLibrary(lib); - } catch (Exception e) { - // pass - } - } - - project.getLogger().lifecycle(":generating sources JAR"); - decompiler.decompileContext(); - - Map mapNumbers = decompiler.getDifferingMappings(); - if (!mapNumbers.isEmpty()) { - project.getLogger().lifecycle(":readjusting line numbers"); - - File tmpJar = new File(mappedJar.getAbsolutePath() + ".tmp"); - Files.move(mappedJar, tmpJar); - try ( - FileInputStream fis = new FileInputStream(tmpJar); - JarInputStream jis = new JarInputStream(fis); - FileOutputStream fos = new FileOutputStream(mappedJar); - JarOutputStream jos = new JarOutputStream(fos) - ) { - JarEntry entry; - - while ((entry = jis.getNextJarEntry()) != null) { - JarEntry outputEntry = new JarEntry(entry.getName()); - outputEntry.setTime(entry.getTime()); - outputEntry.setCreationTime(entry.getCreationTime()); - outputEntry.setLastAccessTime(entry.getLastAccessTime()); - outputEntry.setLastModifiedTime(entry.getLastModifiedTime()); - - if (!entry.getName().endsWith(".class")) { - jos.putNextEntry(outputEntry); - ByteStreams.copy(jis, jos); - jos.closeEntry(); - } else { - String idx = entry.getName().substring(0, entry.getName().length() - 6); - int dollarPos = idx.indexOf('$'); - if (dollarPos >= 0) { - idx = idx.substring(0, dollarPos); - } - - byte[] data = ByteStreams.toByteArray(jis); - if (mapNumbers.containsKey(idx)) { - ClassReader reader = new ClassReader(data); - ClassWriter writer = new ClassWriter(0); - - reader.accept(new LineNumberAdjustmentVisitor(Opcodes.ASM7, writer, mapNumbers.get(idx)), 0); - data = writer.toByteArray(); - } - - jos.putNextEntry(outputEntry); - jos.write(data); - jos.closeEntry(); - } - } - } - - //noinspection ResultOfMethodCallIgnored - tmpJar.delete(); - } - } -} diff --git a/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java b/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java deleted file mode 100644 index 6a3ad398..00000000 --- a/src/main/java/net/fabricmc/loom/task/LineNumberAdjustmentVisitor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016, 2017, 2018 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.task; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - -import java.util.HashMap; -import java.util.Map; - -public class LineNumberAdjustmentVisitor extends ClassVisitor { - public class Method extends MethodVisitor { - public Method(int api, MethodVisitor methodVisitor) { - super(api, methodVisitor); - } - - @Override - public void visitLineNumber(final int line, final Label start) { - int tLine = line; - if (tLine <= 0) { - super.visitLineNumber(1, start); - } else if (tLine >= maxLine) { - super.visitLineNumber(maxLineDst, start); - } else { - Integer matchedLine = null; - - while (tLine <= maxLine && ((matchedLine = lineNumberMap.get(tLine)) == null)) { - tLine++; - } - - super.visitLineNumber(matchedLine != null ? matchedLine : maxLineDst, start); - } - } - } - - private final Map lineNumberMap; - private int maxLine, maxLineDst; - - public LineNumberAdjustmentVisitor(int api, ClassVisitor classVisitor, int[] mapping) { - super(api, classVisitor); - - lineNumberMap = new HashMap<>(); - maxLine = 0; - - for (int i = 0; i < mapping.length; i += 2) { - lineNumberMap.put(mapping[i], mapping[i+1]); - if (mapping[i] > maxLine) { - maxLine = mapping[i]; - } - if (mapping[i+1] > maxLineDst) { - maxLineDst = mapping[i+1]; - } - } - } - - @Override - public MethodVisitor visitMethod( - final int access, - final String name, - final String descriptor, - final String signature, - final String[] exceptions) { - return new Method(api, super.visitMethod(access, name, descriptor, signature, exceptions)); - } -} diff --git a/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java b/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java deleted file mode 100644 index f4e82616..00000000 --- a/src/main/java/net/fabricmc/loom/task/LoomFernflowerDecompiler.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016, 2017, 2018 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.task; - -import net.fabricmc.fernflower.api.IFabricResultSaver; -import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler; -import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; -import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; -import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; -import org.jetbrains.java.decompiler.main.extern.IResultSaver; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; - -public class LoomFernflowerDecompiler extends ConsoleDecompiler implements IFabricResultSaver { - private final Map differingMappings = new HashMap<>(); - private final String jarName; - - public LoomFernflowerDecompiler(File destination, String jarName, Map options, IFernflowerLogger logger) { - super(destination, options, logger); - this.jarName = jarName; - } - - public Map getDifferingMappings() { - return differingMappings; - } - - @Override - public void saveFolder(String s) { - super.saveFolder(s); - } - - @Override - public void copyFile(String s, String s1, String s2) { - throw new RuntimeException("TODO copyFile " + s + " " + s1 + " " + s2); - } - - @Override - public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) { - throw new RuntimeException("TODO saveClassFile " + s + " " + s1 + " " + s2 + " " + s3); - } - - @Override - public void createArchive(String s, String s1, Manifest manifest) { - super.createArchive(s, jarName, manifest); - } - - @Override - public void saveDirEntry(String s, String s1, String s2) { - super.saveDirEntry(s, jarName, s2); - } - - @Override - public void copyEntry(String s, String s1, String s2, String s3) { - if (s3.endsWith(".java")) { - super.copyEntry(s, s1, jarName, s3); - } - } - - @Override - public void saveClassEntry(String s, String s1, String s2, String s3, String s4) { - if (s2 != null) { - System.err.println("Warning: No line mapping provided for " + s1 + " : " + s2 + "!"); - } - super.saveClassEntry(s, s1, s2, s3, s4); - } - - @Override - public void saveClassEntry(String s, String s1, String s2, String s3, String s4, int[] mapping) { - if (s2 != null && mapping != null) { - differingMappings.put(s2, mapping); - } - - super.saveClassEntry(s, jarName, s2, s3, s4); - } - - @Override - public void closeArchive(String s, String s1) { - super.closeArchive(s, jarName); - } -} diff --git a/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerTask.java b/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerTask.java new file mode 100644 index 00000000..c2f78176 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerTask.java @@ -0,0 +1,151 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.fernflower; + +import net.fabricmc.loom.task.DefaultLoomTask; +import net.fabricmc.loom.task.ForkingJavaExecTask; +import net.fabricmc.loom.util.ConsumingOutputStream; +import org.gradle.api.file.FileCollection; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.logging.progress.ProgressLogger; +import org.gradle.internal.logging.progress.ProgressLoggerFactory; +import org.gradle.internal.service.ServiceRegistry; +import org.gradle.process.ExecResult; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; + +import java.io.File; +import java.util.*; +import java.util.function.Supplier; + +import static java.text.MessageFormat.format; + +/** + * Created by covers1624 on 9/02/19. + */ +public class FernFlowerTask extends DefaultLoomTask implements ForkingJavaExecTask { + + private boolean noFork; + private Object input; + private Object output; + private Object lineMapFile; + private Object libraries; + private int numThreads = Runtime.getRuntime().availableProcessors(); + + @TaskAction + public void doTask() throws Throwable { + Map options = new HashMap<>(); + options.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1"); + options.put(IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1"); + options.put(IFernflowerPreferences.LOG_LEVEL, "trace"); + getLogging().captureStandardOutput(LogLevel.LIFECYCLE); + + List args = new ArrayList<>(); + + options.forEach((k, v) -> args.add(format("-{0}={1}", k, v))); + args.add(getInput().getAbsolutePath()); + args.add("-o=" + getOutput().getAbsolutePath()); + args.add("-l=" + getLineMapFile().getAbsolutePath()); + args.add("-t=" + getNumThreads()); + + //TODO, Decompiler breaks on jemalloc, J9 module-info.class? + getLibraries().forEach(f -> args.add("-e=" + f.getAbsolutePath())); + + ServiceRegistry registry = ((ProjectInternal) getProject()).getServices(); + ProgressLoggerFactory factory = registry.get(ProgressLoggerFactory.class); + ProgressLogger progressGroup = factory.newOperation(getClass()).setDescription("Decompile"); + Supplier loggerFactory = () -> { + ProgressLogger pl = factory.newOperation(getClass(), progressGroup); + pl.setDescription("decompile worker"); + pl.started(); + return pl; + }; + Stack freeLoggers = new Stack<>(); + Map inUseLoggers = new HashMap<>(); + + progressGroup.started(); + ExecResult result = javaexec(spec -> { + spec.setMain(ForkedFFExecutor.class.getName()); + spec.jvmArgs("-Xms200m", "-Xmx3G"); + spec.setArgs(args); + spec.setErrorOutput(System.err); + spec.setStandardOutput(new ConsumingOutputStream(line -> { + if (line.startsWith("Listening for transport")) { + System.out.println(line); + return; + } + + int sepIdx = line.indexOf("::"); + String id = line.substring(0, sepIdx).trim(); + String data = line.substring(sepIdx + 2).trim(); + + ProgressLogger logger = inUseLoggers.get(id); + + String[] segs = data.split(" "); + if (segs[0].equals("waiting")) { + if (logger != null) { + logger.progress("Idle.."); + inUseLoggers.remove(id); + freeLoggers.push(logger); + } + } else { + if (logger == null) { + if (!freeLoggers.isEmpty()) { + logger = freeLoggers.pop(); + } else { + logger = loggerFactory.get(); + } + inUseLoggers.put(id, logger); + } + logger.progress(data); + } + })); + }); + inUseLoggers.values().forEach(ProgressLogger::completed); + freeLoggers.forEach(ProgressLogger::completed); + progressGroup.completed(); + + result.rethrowFailure(); + result.assertNormalExitValue(); + } + + //@formatter:off + @Input public File getInput() { return getProject().file(input); } + @OutputFile public File getOutput() { return getProject().file(output); } + @OutputFile public File getLineMapFile() { return getProject().file(lineMapFile); } + @Input public FileCollection getLibraries() { return getProject().files(libraries); } + @Input public int getNumThreads() { return numThreads; } + public boolean isNoFork() { return noFork; } + public void setInput(Object input) { this.input = input; } + public void setOutput(Object output) { this.output = output; } + public void setLineMapFile(Object lineMapFile) { this.lineMapFile = lineMapFile; } + public void setLibraries(Object libraries) { this.libraries = libraries; } + public void setNoFork(boolean noFork) { this.noFork = noFork; } + public void setNumThreads(int numThreads) { this.numThreads = numThreads; } + //@formatter:on +} diff --git a/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerUtils.java b/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerUtils.java new file mode 100644 index 00000000..83af198d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/fernflower/FernFlowerUtils.java @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.fernflower; + +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class FernFlowerUtils { + public static byte[] getBytecode(String externalPath, String internalPath) throws IOException { + File file = new File(externalPath); + if (internalPath == null) { + return InterpreterUtil.getBytes(file); + } else { + try (ZipFile archive = new ZipFile(file)) { + ZipEntry entry = archive.getEntry(internalPath); + if (entry == null) { + throw new IOException("Entry not found: " + internalPath); + } + return InterpreterUtil.getBytes(archive, entry); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/fernflower/ForkedFFExecutor.java b/src/main/java/net/fabricmc/loom/task/fernflower/ForkedFFExecutor.java new file mode 100644 index 00000000..287c7999 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/fernflower/ForkedFFExecutor.java @@ -0,0 +1,105 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.fernflower; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Entry point for Forked FernFlower task. + * Takes one parameter, a single file, each line is treated as command line input. + * Forces one input file. + * Forces one output file using '-o=/path/to/output' + * + * Created by covers1624 on 11/02/19. + */ +public class ForkedFFExecutor { + + public static void main(String[] args) throws IOException { + Map options = new HashMap<>(); + File input = null; + File output = null; + File lineMap = null; + List libraries = new ArrayList<>(); + int numThreads = 0; + + boolean isOption = true; + for (String arg : args) { + if (isOption && arg.length() > 5 && arg.charAt(0) == '-' && arg.charAt(4) == '=') { + String value = arg.substring(5); + if ("true".equalsIgnoreCase(value)) { + value = "1"; + } else if ("false".equalsIgnoreCase(value)) { + value = "0"; + } + + options.put(arg.substring(1, 4), value); + } else { + isOption = false; + if (arg.startsWith("-e=")) { + libraries.add(new File(arg.substring(3))); + } else if (arg.startsWith("-o=")) { + if (output != null) { + throw new RuntimeException("Unable to set more than one output."); + } + output = new File(arg.substring(3)); + } else if (arg.startsWith("-l=")) { + if (lineMap != null) { + throw new RuntimeException("Unable to set more than one lineMap file."); + } + lineMap = new File(arg.substring(3)); + } else if (arg.startsWith("-t=")) { + numThreads = Integer.parseInt(arg.substring(3)); + } else { + if (input != null) { + throw new RuntimeException("Unable to set more than one input."); + } + input = new File(arg); + } + } + } + + Objects.requireNonNull(input, "Input not set."); + Objects.requireNonNull(output, "Output not set."); + + runFF(options, libraries, input, output, lineMap); + } + + public static void runFF(Map options, List libraries, File input, File output, File lineMap) { + IResultSaver saver = new ThreadSafeResultSaver(() -> output, () -> lineMap); + IFernflowerLogger logger = new ThreadIDFFLogger(); + Fernflower ff = new Fernflower(FernFlowerUtils::getBytecode, saver, options, logger); + for (File library : libraries) { + ff.addLibrary(library); + } + ff.addSource(input); + ff.decompileContext(); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java b/src/main/java/net/fabricmc/loom/task/fernflower/NoopFFLogger.java similarity index 72% rename from src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java rename to src/main/java/net/fabricmc/loom/task/fernflower/NoopFFLogger.java index 55da6c0d..83feb36a 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomFernflowerLogger.java +++ b/src/main/java/net/fabricmc/loom/task/fernflower/NoopFFLogger.java @@ -22,23 +22,23 @@ * SOFTWARE. */ -package net.fabricmc.loom.task; +package net.fabricmc.loom.task.fernflower; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; -public class LoomFernflowerLogger extends IFernflowerLogger { - @Override - public void writeMessage(String s, Severity severity) { - if (severity == Severity.WARN || severity == Severity.ERROR) { - System.err.println(s); - } - } +/** + * Literally does nothing. + * Created by covers1624 on 11/02/19. + */ +public class NoopFFLogger extends IFernflowerLogger { - @Override - public void writeMessage(String s, Severity severity, Throwable throwable) { - if (severity == Severity.WARN || severity == Severity.ERROR) { - System.err.println(s); - throwable.printStackTrace(System.err); - } - } + @Override + public void writeMessage(String message, Severity severity) { + + } + + @Override + public void writeMessage(String message, Severity severity, Throwable t) { + + } } diff --git a/src/main/java/net/fabricmc/loom/task/fernflower/ThreadIDFFLogger.java b/src/main/java/net/fabricmc/loom/task/fernflower/ThreadIDFFLogger.java new file mode 100644 index 00000000..d10baaa9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/fernflower/ThreadIDFFLogger.java @@ -0,0 +1,131 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.fernflower; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; + +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.Stack; + +/** + * This logger simply prints what each thread is doing + * to the console in a machine parsable way. + * + * Created by covers1624 on 11/02/19. + */ +public class ThreadIDFFLogger extends IFernflowerLogger { + + public final PrintStream stdOut; + public final PrintStream stdErr; + + private ThreadLocal> workingClass = ThreadLocal.withInitial(Stack::new); + private ThreadLocal> line = ThreadLocal.withInitial(Stack::new); + + public ThreadIDFFLogger() { + this(System.err, System.out); + } + + public ThreadIDFFLogger(PrintStream stdOut, PrintStream stdErr) { + this.stdOut = stdOut; + this.stdErr = stdErr; + } + + @Override + public void writeMessage(String message, Severity severity) { + System.err.println(message); + } + + @Override + public void writeMessage(String message, Severity severity, Throwable t) { + System.err.println(message); + t.printStackTrace(System.err); + } + + private void print() { + Thread thread = Thread.currentThread(); + long id = thread.getId(); + if (line.get().isEmpty()) { + System.out.println(MessageFormat.format("{0} :: waiting", id)); + return; + } + String line = this.line.get().peek(); + System.out.println(MessageFormat.format("{0} :: {1}", id, line).trim()); + } + + @Override + public void startReadingClass(String className) { + workingClass.get().push(className); + line.get().push("Decompiling " + className); + print(); + } + + @Override + public void startClass(String className) { + workingClass.get().push(className); + line.get().push("Decompiling " + className); + print(); + } + + @Override + public void startMethod(String methodName) { + String className = workingClass.get().peek(); + line.get().push("Decompiling " + className + "." + methodName.substring(0, methodName.indexOf(" "))); + print(); + } + + @Override + public void endMethod() { + line.get().pop(); + print(); + } + + @Override + public void endClass() { + line.get().pop(); + workingClass.get().pop(); + print(); + } + + @Override + public void startWriteClass(String className) { + line.get().push("Writing " + className); + print(); + } + + @Override + public void endWriteClass() { + line.get().pop(); + print(); + } + + @Override + public void endReadingClass() { + line.get().pop(); + workingClass.get().pop(); + print(); + } + +} diff --git a/src/main/java/net/fabricmc/loom/task/fernflower/ThreadSafeResultSaver.java b/src/main/java/net/fabricmc/loom/task/fernflower/ThreadSafeResultSaver.java new file mode 100644 index 00000000..cdf64ab5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/fernflower/ThreadSafeResultSaver.java @@ -0,0 +1,150 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.fernflower; + +import net.fabricmc.fernflower.api.IFabricResultSaver; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * Created by covers1624 on 18/02/19. + */ +public class ThreadSafeResultSaver implements IResultSaver, IFabricResultSaver { + private final Supplier output; + private final Supplier lineMapFile; + + public Map outputStreams = new HashMap<>(); + public Map saveExecutors = new HashMap<>(); + public PrintWriter lineMapWriter; + + public ThreadSafeResultSaver(Supplier output, Supplier lineMapFile) { + this.output = output; + this.lineMapFile = lineMapFile; + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + String key = path + "/" + archiveName; + File file = output.get(); + try { + FileOutputStream fos = new FileOutputStream(file); + ZipOutputStream zos = manifest == null ? new ZipOutputStream(fos) : new JarOutputStream(fos, manifest); + outputStreams.put(key, zos); + saveExecutors.put(key, Executors.newSingleThreadExecutor()); + } catch (IOException e) { + throw new RuntimeException("Unable to create archive: " + file, e); + } + if (lineMapFile.get() != null) { + try { + lineMapWriter = new PrintWriter(new FileWriter(lineMapFile.get())); + } catch (IOException e) { + throw new RuntimeException("Unable to create LineMap file.", e); + } + } + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) { + this.saveClassEntry(path, archiveName, qualifiedName, entryName, content, null); + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content, int[] mapping) { + String key = path + "/" + archiveName; + ExecutorService executor = saveExecutors.get(key); + executor.submit(() -> { + ZipOutputStream zos = outputStreams.get(key); + try { + zos.putNextEntry(new ZipEntry(entryName)); + if (content != null) { + zos.write(content.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + DecompilerContext.getLogger().writeMessage("Cannot write entry " + entryName, e); + } + if (mapping != null && lineMapWriter != null) { + int maxLine = 0; + int maxLineDist = 0; + // 2 loops here is meh, perhaps merge with a buffer? + for (int i = 0; i < mapping.length; i += 2) { + maxLine = Math.max(maxLine, mapping[i]); + maxLineDist = Math.max(maxLineDist, mapping[i + 1]); + } + lineMapWriter.println(qualifiedName + "\t" + maxLine + "\t" + maxLineDist); + for (int i = 0; i < mapping.length; i += 2) { + lineMapWriter.println("\t" + mapping[i] + "\t" + mapping[i + 1]); + } + } + }); + } + + @Override + public void closeArchive(String path, String archiveName) { + String key = path + "/" + archiveName; + ExecutorService executor = saveExecutors.get(key); + Future closeFuture = executor.submit(() -> { + ZipOutputStream zos = outputStreams.get(key); + try { + zos.close(); + } catch (IOException e) { + throw new RuntimeException("Unable to close zip. " + key, e); + } + }); + executor.shutdown(); + try { + closeFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + outputStreams.remove(key); + saveExecutors.remove(key); + if (lineMapWriter != null) { + lineMapWriter.flush(); + lineMapWriter.close(); + } + } + + //@formatter:off + @Override public void saveFolder(String path) { } + @Override public void copyFile(String source, String path, String entryName) { } + @Override public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { } + @Override public void saveDirEntry(String path, String archiveName, String entryName) { } + @Override public void copyEntry(String source, String path, String archiveName, String entry) { } + //@formatter:on +} diff --git a/src/main/java/net/fabricmc/loom/util/ConsumingOutputStream.java b/src/main/java/net/fabricmc/loom/util/ConsumingOutputStream.java new file mode 100644 index 00000000..524da837 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ConsumingOutputStream.java @@ -0,0 +1,64 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.function.Consumer; + +/** + * Created by covers1624 on 20/12/18. + */ +public class ConsumingOutputStream extends OutputStream { + + private final Consumer consumer; + + private StringBuilder buffer = new StringBuilder(); + + public ConsumingOutputStream(Consumer consumer) { + this.consumer = consumer; + } + + @Override + public void write(int b) throws IOException { + char ch = (char) (b & 0xFF); + buffer.append(ch); + if (ch == '\n' || ch == '\r') { + flush(); + } + } + + @Override + public void flush() throws IOException { + String str = buffer.toString(); + if (str.endsWith("\r") || str.endsWith("\n")) { + str = str.trim(); + if (!str.isEmpty()) { + consumer.accept(str); + } + buffer = new StringBuilder(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/LineNumberRemapper.java b/src/main/java/net/fabricmc/loom/util/LineNumberRemapper.java new file mode 100644 index 00000000..cc4a7e8e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/LineNumberRemapper.java @@ -0,0 +1,155 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import org.objectweb.asm.*; + +import java.io.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; + +import static java.text.MessageFormat.format; + +/** + * TODO, Move to stitch. + * Created by covers1624 on 18/02/19. + */ +public class LineNumberRemapper { + + private final Map lineMap = new HashMap<>(); + + public void readMappings(File lineMappings) { + try (BufferedReader reader = new BufferedReader(new FileReader(lineMappings))) { + RClass clazz = null; + String line = null; + int i = 0; + try { + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + String[] segs = line.trim().split("\t"); + if (line.charAt(0) != '\t') { + clazz = lineMap.computeIfAbsent(segs[0], RClass::new); + clazz.maxLine = Integer.parseInt(segs[1]); + clazz.maxLineDist = Integer.parseInt(segs[2]); + } else { + clazz.lineMap.put(Integer.parseInt(segs[0]), (Integer) Integer.parseInt(segs[1])); + } + i++; + } + } catch (Exception e) { + throw new RuntimeException(format("Exception reading mapping line @{0}: {1}", i, line), e); + } + } catch (IOException e) { + throw new RuntimeException("Exception reading LineMappings file.", e); + } + } + + public void process(Path input, Path output) throws IOException { + Files.walkFileTree(input, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String rel = input.relativize(file).toString(); + Path dst = output.resolve(rel); + Path parent = dst.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + String fName = file.getFileName().toString(); + if (fName.endsWith(".class")) { + if (Files.exists(dst)) { + Files.delete(dst); + } + String idx = rel.substring(0, rel.length() - 6); + int dollarPos = idx.indexOf('$');//This makes the assumption that only Java classes are to be remapped. + if (dollarPos >= 0) { + idx = idx.substring(0, dollarPos); + } + if (lineMap.containsKey(idx)) { + try (InputStream is = Files.newInputStream(file)) { + ClassReader reader = new ClassReader(is); + ClassWriter writer = new ClassWriter(0); + + reader.accept(new LineNumberVisitor(Opcodes.ASM7, writer, lineMap.get(idx)), 0); + Files.write(dst, writer.toByteArray()); + } + } + + } else { + Files.copy(file, dst, StandardCopyOption.REPLACE_EXISTING); + } + + return FileVisitResult.CONTINUE; + } + }); + } + + private static class LineNumberVisitor extends ClassVisitor { + + private final RClass rClass; + + public LineNumberVisitor(int api, ClassVisitor classVisitor, RClass rClass) { + super(api, classVisitor); + this.rClass = rClass; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodVisitor(api, super.visitMethod(access, name, descriptor, signature, exceptions)) { + @Override + public void visitLineNumber(int line, Label start) { + int tLine = line; + if (tLine <= 0) { + super.visitLineNumber(line, start); + } else if (tLine >= rClass.maxLine) { + super.visitLineNumber(rClass.maxLineDist, start); + } else { + Integer matchedLine = null; + while (tLine <= rClass.maxLine && ((matchedLine = rClass.lineMap.get(tLine)) != null)) { + tLine++; + } + super.visitLineNumber(matchedLine != null ? matchedLine : rClass.maxLineDist, start); + } + } + }; + } + } + + private static class RClass { + + private final String name; + private int maxLine; + private int maxLineDist; + private Map lineMap = new HashMap<>(); + + private RClass(String name) { + this.name = name; + } + } + +}