diff --git a/build.gradle b/build.gradle index 5920eafb..5b965a9e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ tasks.withType(JavaCompile).configureEach { group = "dev.architectury" archivesBaseName = project.name -def baseVersion = '0.7.1' +def baseVersion = '0.7.2' def runNumber = System.getenv("GITHUB_RUN_NUMBER") ?: "9999" def isSnapshot = System.getenv("PR_NUM") != null @@ -83,6 +83,7 @@ dependencies { // tinyfile management implementation ('dev.architectury:tiny-remapper:1.0.0') + implementation ('dev.architectury:mappings-layers-core:1.1.6') implementation ('net.fabricmc:tiny-mappings-parser:0.3.0+build.17') implementation 'net.fabricmc:access-widener:1.0.0' @@ -310,4 +311,4 @@ task writeActionsTestMatrix() { output.parentFile.mkdir() output.text = json } -} \ No newline at end of file +} diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 421503c2..1e630921 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -61,6 +61,7 @@ import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; @@ -392,7 +393,7 @@ public class LoomGradleExtension { } public MappingsProvider getMappingsProvider() { - return getDependencyManager().getProvider(MappingsProvider.class); + return getDependencyManager().getProvider(isForge() ? FieldMigratedMappingsProvider.class : MappingsProvider.class); } public McpConfigProvider getMcpConfigProvider() { diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 38c619dc..2b9a88c0 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -57,7 +57,7 @@ public class LoomGradlePlugin implements Plugin { project.getLogger().lifecycle("Architectury Loom: " + loomVersion); } - refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies(); + refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies() || "true".equals(System.getProperty("loom.refresh")); if (refreshDeps) { MappingsCache.INSTANCE.invalidate(); diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java index f71bf3fb..b2a6cc4d 100644 --- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java @@ -133,6 +133,11 @@ public class ModCompileRemapper { File input = artifact.getFile(); try (ZipFile zipFile = new ZipFile(input)) { + if (zipFile.getEntry("architectury.common.marker") != null) { + logger.info("Found architectury common mod in " + config + ": {}", artifact.getId()); + return true; + } + if (forge) { if (zipFile.getEntry("META-INF/mods.toml") != null) { logger.info("Found Forge mod in " + config + ": {}", artifact.getId()); diff --git a/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java index df9f7cab..e91496e6 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java @@ -1,3 +1,27 @@ +/* + * 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.build.nesting; import java.io.File; diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index c0d8e4ba..82ecada7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -40,6 +40,7 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker; import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs; import net.fabricmc.loom.configuration.providers.LaunchProvider; import net.fabricmc.loom.configuration.providers.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider; @@ -177,7 +178,7 @@ public final class CompileConfiguration { dependencyManager.addProvider(new ForgeUniversalProvider(project)); } - dependencyManager.addProvider(new MappingsProvider(project)); + dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProvider(project)); dependencyManager.addProvider(new LaunchProvider(project)); dependencyManager.handleDependencies(project); diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java index f610c5ee..d325457b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java @@ -72,7 +72,7 @@ public class LoomDependencyManager { return provider; } - public T getProvider(Class clazz) { + public T getProvider(Class clazz) { for (DependencyProvider provider : dependencyProviderList) { if (provider.getClass() == clazz) { return (T) provider; @@ -140,7 +140,7 @@ public class LoomDependencyManager { String platformSuffix = extension.isForge() ? "_forge" : ""; String mappingsKey = mappingsProvider.getMappingsKey() + platformSuffix; - if (extension.getInstallerJson() == null) { + if (extension.getInstallerJson() == null && !extension.isForge()) { //If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking project.getLogger().info("Searching through modCompileClasspath for installer JSON"); final Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH); @@ -159,10 +159,10 @@ public class LoomDependencyManager { handleInstallerJson(extension.getInstallerJson(), project); } } - } - if (extension.getInstallerJson() == null && !extension.isForge()) { - project.getLogger().warn("fabric-installer.json not found in classpath!"); + if (extension.getInstallerJson() == null) { + project.getLogger().warn("fabric-installer.json not found in classpath!"); + } } ModCompileRemapper.remapDependencies(project, mappingsKey, extension, sourceRemapper); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java new file mode 100644 index 00000000..4895e879 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -0,0 +1,282 @@ +/* + * 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.configuration.providers.forge; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import dev.architectury.mappingslayers.api.mutable.MappingsEntry; +import dev.architectury.mappingslayers.api.mutable.MutableClassDef; +import dev.architectury.mappingslayers.api.mutable.MutableFieldDef; +import dev.architectury.mappingslayers.api.mutable.MutableTinyTree; +import dev.architectury.mappingslayers.api.utils.MappingsUtils; +import dev.architectury.refmapremapper.utils.DescriptorRemapper; +import org.gradle.api.Project; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SrgMerger; +import net.fabricmc.mapping.tree.ClassDef; +import net.fabricmc.mapping.tree.FieldDef; +import net.fabricmc.mapping.tree.TinyMappingFactory; +import net.fabricmc.mapping.tree.TinyTree; + +public class FieldMigratedMappingsProvider extends MappingsProvider { + private List> migratedFields = new ArrayList<>(); + public Path migratedFieldsCache; + public Path rawTinyMappings; + public Path rawTinyMappingsWithSrg; + + public FieldMigratedMappingsProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { + LoomGradleExtension extension = getExtension(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + migratedFieldsCache = patchProvider.getProjectCacheFolder().resolve("migrated-fields.json"); + migratedFields.clear(); + + if (LoomGradlePlugin.refreshDeps) { + Files.deleteIfExists(migratedFieldsCache); + } else if (Files.exists(migratedFieldsCache)) { + try (BufferedReader reader = Files.newBufferedReader(migratedFieldsCache)) { + Map map = new Gson().fromJson(reader, new TypeToken>() { + }.getType()); + migratedFields = new ArrayList<>(); + map.forEach((key, newDescriptor) -> { + String[] split = key.split("#"); + migratedFields.add(new AbstractMap.SimpleEntry<>(new FieldMember(split[0], split[1]), newDescriptor)); + }); + } + } + + super.provide(dependency, postPopulationScheduler); + } + + @Override + public void manipulateMappings(Path mappingsJar) throws IOException { + LoomGradleExtension extension = getExtension(); + Path mappingsFolder = mappingsDir.resolve(extension.getMinecraftProvider().getMinecraftVersion() + "/forge-" + extension.getPatchProvider().forgeVersion); + this.rawTinyMappings = tinyMappings.toPath(); + this.rawTinyMappingsWithSrg = tinyMappingsWithSrg; + String mappingsJarName = mappingsJar.getFileName().toString(); + + if (getExtension().shouldGenerateSrgTiny()) { + if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) { + // Merge tiny mappings with srg + SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), rawTinyMappings, rawTinyMappingsWithSrg, true); + } + } + + try { + Files.createDirectories(mappingsFolder); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + tinyMappings = mappingsFolder.resolve("mappings.tiny").toFile(); + tinyMappingsJar = mappingsFolder.resolve("mappings.jar").toFile(); + tinyMappingsWithSrg = mappingsFolder.resolve("mappings-srg.tiny"); + mixinTinyMappingsWithSrg = mappingsFolder.resolve("mixin-srg.tiny").toFile(); + srgToNamedSrg = mappingsFolder.resolve("srg-to-named.srg").toFile(); + + try { + updateFieldMigration(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void updateFieldMigration() throws IOException { + if (!Files.exists(migratedFieldsCache)) { + generateNewFieldMigration(); + Map map = new HashMap<>(); + migratedFields.forEach(entry -> { + map.put(entry.getKey().owner + "#" + entry.getKey().field, entry.getValue()); + }); + Files.write(migratedFieldsCache, new Gson().toJson(map).getBytes(StandardCharsets.UTF_8)); + Files.deleteIfExists(tinyMappings.toPath()); + } + + if (!Files.exists(tinyMappings.toPath())) { + Table fieldDescriptorMap = HashBasedTable.create(); + + for (Map.Entry entry : migratedFields) { + fieldDescriptorMap.put(entry.getKey().owner, entry.getKey().field, entry.getValue()); + } + + MutableTinyTree mappings; + + try (BufferedReader reader = Files.newBufferedReader(rawTinyMappings)) { + mappings = MappingsUtils.copyAsMutable(TinyMappingFactory.loadWithDetection(reader)); + + for (MutableClassDef classDef : mappings.getClassesMutable()) { + Map row = fieldDescriptorMap.row(classDef.getIntermediary()); + + if (!row.isEmpty()) { + for (MutableFieldDef fieldDef : classDef.getFieldsMutable()) { + String newDescriptor = row.get(fieldDef.getIntermediary()); + + if (newDescriptor != null) { + fieldDef.setDescriptor(MappingsEntry.NS_INTERMEDIARY, newDescriptor); + } + } + } + } + } + + Files.write(tinyMappings.toPath(), MappingsUtils.serializeToString(mappings).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } + } + + private void generateNewFieldMigration() throws IOException { + Map fieldDescriptorMap = new ConcurrentHashMap<>(); + LoomGradleExtension extension = getExtension(); + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + class Visitor extends ClassVisitor { + private final ThreadLocal lastClass = new ThreadLocal<>(); + + Visitor(int api) { + super(api); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + lastClass.set(name); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + fieldDescriptorMap.put(new FieldMember(lastClass.get(), name), descriptor); + return super.visitField(access, name, descriptor, signature, value); + } + } + + Visitor visitor = new Visitor(Opcodes.ASM9); + + for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) { + File patchedSrgJar = environment.patchedSrgJar.apply(extension.getMappingsProvider().patchedProvider); + FileSystemUtil.FileSystemDelegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); + completer.onComplete(value -> system.close()); + + for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { + completer.add(() -> { + byte[] bytes = Files.readAllBytes(fsPath); + new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + }); + } + } + } + + completer.complete(); + Map migratedFields = new HashMap<>(); + + try (BufferedReader reader = Files.newBufferedReader(rawTinyMappingsWithSrg)) { + TinyTree mappings = TinyMappingFactory.loadWithDetection(reader); + Map srgToIntermediary = new HashMap<>(); + + for (ClassDef aClass : mappings.getClasses()) { + srgToIntermediary.put(aClass.getName("srg"), aClass.getName("intermediary")); + } + + for (ClassDef classDef : mappings.getClasses()) { + String ownerSrg = classDef.getName("srg"); + String ownerIntermediary = classDef.getName("intermediary"); + + for (FieldDef fieldDef : classDef.getFields()) { + String fieldSrg = fieldDef.getName("srg"); + String descriptorSrg = fieldDef.getDescriptor("srg"); + + FieldMember member = new FieldMember(ownerSrg, fieldSrg); + String newDescriptor = fieldDescriptorMap.get(member); + + if (newDescriptor != null && !newDescriptor.equals(descriptorSrg)) { + String fieldIntermediary = fieldDef.getName("intermediary"); + String descriptorIntermediary = fieldDef.getDescriptor("intermediary"); + String newDescriptorRemapped = DescriptorRemapper.remapDescriptor(newDescriptor, + clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); + migratedFields.put(new FieldMember(ownerIntermediary, fieldIntermediary), newDescriptorRemapped); + getProject().getLogger().info(ownerIntermediary + "#" + fieldIntermediary + ": " + descriptorIntermediary + " -> " + newDescriptorRemapped); + } + } + } + } + + this.migratedFields.clear(); + this.migratedFields.addAll(migratedFields.entrySet()); + } + + public static class FieldMember { + public String owner; + public String field; + + public FieldMember(String owner, String field) { + this.owner = owner; + this.field = field; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldMember that = (FieldMember) o; + return Objects.equals(owner, that.owner) && Objects.equals(field, that.field); + } + + @Override + public int hashCode() { + return Objects.hash(owner, field); + } + } +} 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 170f2b65..5440854f 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 @@ -77,7 +77,6 @@ import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.configuration.DependencyProvider; 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; @@ -92,7 +91,6 @@ import net.fabricmc.loom.util.srg.SpecialSourceExecutor; import net.fabricmc.mapping.tree.TinyTree; public class MinecraftPatchedProvider extends DependencyProvider { - private final MappingsProvider mappingsProvider; // Step 1: Remap Minecraft to SRG private File minecraftClientSrgJar; private File minecraftServerSrgJar; @@ -112,9 +110,8 @@ public class MinecraftPatchedProvider extends DependencyProvider { private File projectAt = null; private boolean atDirty = false; - public MinecraftPatchedProvider(MappingsProvider mappingsProvider, Project project) { + public MinecraftPatchedProvider(Project project) { super(project); - this.mappingsProvider = mappingsProvider; } public void initFiles() throws IOException { @@ -149,26 +146,30 @@ public class MinecraftPatchedProvider extends DependencyProvider { MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider(); PatchProvider patchProvider = getExtension().getPatchProvider(); String minecraftVersion = minecraftProvider.getMinecraftVersion(); - String jarSuffix = "-patched-forge-" + patchProvider.forgeVersion; + String patchId = "forge-" + patchProvider.forgeVersion; if (getExtension().useFabricMixin) { - jarSuffix += "-fabric-mixin"; + patchId += "-fabric-mixin"; } - minecraftProvider.setJarSuffix(jarSuffix); + minecraftProvider.setJarSuffix(patchId); File globalCache = getExtension().getUserCache(); File cache = usesProjectCache() ? getExtension().getProjectPersistentCache() : globalCache; + File globalDir = new File(globalCache, patchId); + File projectDir = new File(cache, patchId); + globalDir.mkdirs(); + projectDir.mkdirs(); 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(globalDir, "client-srg-patched.jar"); + minecraftServerPatchedSrgJar = new File(globalDir, "server-srg-patched.jar"); + minecraftClientPatchedSrgATJar = new File(projectDir, "client-srg-at-patched.jar"); + minecraftServerPatchedSrgATJar = new File(projectDir, "server-srg-at-patched.jar"); + minecraftClientPatchedOfficialJar = new File(projectDir, "client-patched.jar"); + minecraftServerPatchedOfficialJar = new File(projectDir, "server-patched.jar"); + minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar"); if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(Predicates.not(File::exists))) { cleanAllCache(); @@ -210,6 +211,8 @@ public class MinecraftPatchedProvider extends DependencyProvider { }; } + private boolean dirty; + @Override public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { initFiles(); @@ -218,22 +221,24 @@ public class MinecraftPatchedProvider extends DependencyProvider { getProject().getLogger().lifecycle(":found dirty access transformers"); } - boolean dirty = false; + this.dirty = false; if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { - dirty = true; + this.dirty = true; // Remap official jars to MCPConfig remapped srg jars createSrgJars(getProject().getLogger()); } if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { - dirty = true; + this.dirty = true; patchJars(getProject().getLogger()); injectForgeClasses(getProject().getLogger()); } + } + public void finishProvide() throws Exception { if (atDirty || !minecraftClientPatchedSrgATJar.exists() || !minecraftServerPatchedSrgATJar.exists()) { - dirty = true; + this.dirty = true; accessTransformForge(getProject().getLogger()); } @@ -244,6 +249,8 @@ public class MinecraftPatchedProvider extends DependencyProvider { if (dirty || !minecraftMergedPatchedJar.exists()) { mergeJars(getProject().getLogger()); } + + this.dirty = false; } private void writeAtHash() throws IOException { @@ -285,9 +292,9 @@ public class MinecraftPatchedProvider extends DependencyProvider { 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); ThreadingUtils.run(() -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "client", specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); + Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), "client", specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); }, () -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "server", specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath()); + Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), "server", specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath()); }); } @@ -327,7 +334,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { private void injectForgeClasses(Logger logger) throws IOException { logger.lifecycle(":injecting forge classes into minecraft"); - ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { + ThreadingUtils.run(Environment.values(), environment -> { copyAll(getExtension().getForgeUniversalProvider().getForge(), environment.patchedSrgJar.apply(this)); copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), environment.patchedSrgJar.apply(this)); }); @@ -392,7 +399,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { ((Map) field.get(list)).clear(); } - private enum Environment { + public enum Environment { CLIENT(provider -> provider.minecraftClientSrgJar, provider -> provider.minecraftClientPatchedSrgJar, provider -> provider.minecraftClientPatchedSrgATJar, @@ -427,7 +434,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { private void remapPatchedJars(Logger logger) throws Exception { Path[] libraries = MinecraftMappedProvider.getRemapClasspath(getProject()); - ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { + ThreadingUtils.run(Environment.values(), environment -> { logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)"); TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); @@ -458,23 +465,26 @@ public class MinecraftPatchedProvider extends DependencyProvider { } private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); logger.lifecycle(":patching jars"); PatchProvider patchProvider = getExtension().getPatchProvider(); patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); - ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { + ThreadingUtils.run(Environment.values(), environment -> { copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); fixParameterAnnotation(environment.patchedSrgJar.apply(this)); }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); } private void patchJars(File clean, File output, Path patches) throws IOException { PrintStream previous = System.out; try { - System.setOut(new PrintStream(new NullOutputStream())); + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); } catch (SecurityException ignored) { // Failed to replace logger filter, just ignore } @@ -507,7 +517,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) throws IOException { try (FileSystemUtil.FileSystemDelegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); - FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { for (Path sourceDir : toWalk.apply(sourceFs.get())) { Path dir = sourceDir.toAbsolutePath(); Files.walk(dir) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index 24f1c866..b9924a16 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -24,6 +24,8 @@ package net.fabricmc.loom.configuration.providers.forge; +import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -42,6 +44,7 @@ public class PatchProvider extends DependencyProvider { public Path clientPatches; public Path serverPatches; public String forgeVersion; + public Path projectCacheFolder; public PatchProvider(Project project) { super(project); @@ -65,8 +68,19 @@ public class PatchProvider extends DependencyProvider { private void init(String forgeVersion) { this.forgeVersion = forgeVersion; - clientPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-client.lzma"); - serverPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-server.lzma"); + projectCacheFolder = getExtension().getProjectPersistentCache().toPath().resolve(forgeVersion); + clientPatches = projectCacheFolder.resolve("patches-client.lzma"); + serverPatches = projectCacheFolder.resolve("patches-server.lzma"); + + try { + Files.createDirectories(projectCacheFolder); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public Path getProjectCacheFolder() { + return projectCacheFolder; } @Override 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 c7160bea..c1fd5f83 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 @@ -42,8 +42,11 @@ import java.util.List; import java.util.function.Consumer; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import com.google.common.net.UrlEscapers; import com.google.gson.JsonObject; +import dev.architectury.mappingslayers.api.utils.MappingsModificationUtils; +import dev.architectury.mappingslayers.api.utils.MappingsUtils; import org.apache.commons.io.FileUtils; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; @@ -86,7 +89,7 @@ public class MappingsProvider extends DependencyProvider { public String minecraftVersion; public String mappingsVersion; - private final Path mappingsDir; + protected final Path mappingsDir; private final Path mappingsStepsDir; private Path intermediaryTiny; private boolean hasRefreshed = false; @@ -94,8 +97,8 @@ public class MappingsProvider extends DependencyProvider { private Path baseTinyMappings; // The mappings we use in practice public File tinyMappings; + // tinyMappings wrapped in a jar public File tinyMappingsJar; - public File mappingsMixinExport; public Path tinyMappingsWithSrg; public File mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary. public File srgToNamedSrg; // FORGE: srg to named in srg file format @@ -181,12 +184,13 @@ public class MappingsProvider extends DependencyProvider { jarClassifier = jarClassifier + depStringSplit[3]; } - tinyMappings = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".tiny").toFile(); - unpickDefinitionsFile = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".unpick").toFile(); - tinyMappingsJar = new File(getExtension().getUserCache(), mappingsJar.getName().replace(".jar", "-" + jarClassifier + ".jar")); - tinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg.tiny"); - mixinTinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-mixin-srg.tiny").toFile(); - srgToNamedSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg-named.srg").toFile(); + String removeSuffix = StringUtils.removeSuffix(mappingsJar.getName(), ".jar"); + tinyMappings = mappingsDir.resolve(removeSuffix + ".tiny").toFile(); + unpickDefinitionsFile = mappingsDir.resolve(removeSuffix + ".unpick").toFile(); + tinyMappingsJar = new File(getExtension().getUserCache(), removeSuffix + "-" + jarClassifier + ".jar"); + tinyMappingsWithSrg = mappingsDir.resolve(removeSuffix + "-srg.tiny"); + mixinTinyMappingsWithSrg = mappingsDir.resolve(removeSuffix + "-mixin-srg.tiny").toFile(); + srgToNamedSrg = mappingsDir.resolve(removeSuffix + "-srg-named.srg").toFile(); if (!tinyMappings.exists() || isRefreshDeps()) { storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler); @@ -196,6 +200,20 @@ public class MappingsProvider extends DependencyProvider { } } + if (getExtension().isForge()) { + patchedProvider = new MinecraftPatchedProvider(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } + + manipulateMappings(mappingsJar.toPath()); + + if (getExtension().shouldGenerateSrgTiny()) { + if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { + // Merge tiny mappings with srg + SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), tinyMappings.toPath(), tinyMappingsWithSrg, true); + } + } + if (!tinyMappingsJar.exists() || isRefreshDeps()) { ZipUtil.pack(new ZipEntrySource[] {new FileSource("mappings/mappings.tiny", tinyMappings)}, tinyMappingsJar); } @@ -211,12 +229,6 @@ public class MappingsProvider extends DependencyProvider { populateUnpickClasspath(); } - if (getExtension().shouldGenerateSrgTiny()) { - if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { - SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), tinyMappings.toPath(), tinyMappingsWithSrg, true); - } - } - if (getExtension().isForge()) { if (!getExtension().shouldGenerateSrgTiny()) { throw new IllegalStateException("We have to generate srg tiny in a forge environment!"); @@ -247,8 +259,7 @@ public class MappingsProvider extends DependencyProvider { processorManager.setupProcessors(); if (extension.isForge()) { - patchedProvider = new MinecraftPatchedProvider(this, getProject()); - patchedProvider.provide(dependency, postPopulationScheduler); + patchedProvider.finishProvide(); } if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { @@ -262,6 +273,8 @@ public class MappingsProvider extends DependencyProvider { mappedProvider.provide(dependency, postPopulationScheduler); } + public void manipulateMappings(Path mappingsJar) throws IOException { } + private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar, Consumer postPopulationScheduler) throws Exception { project.getLogger().info(":extracting " + yarnJar.getFileName()); @@ -407,21 +420,21 @@ public class MappingsProvider extends DependencyProvider { extractMappings(unmergedYarnJarFs, unmergedYarn); } + Stopwatch stopwatch = Stopwatch.createStarted(); + project.getLogger().info(":merging mappings"); Path invertedIntermediary = Paths.get(mappingsStepsDir.toString(), "inverted-intermediary.tiny"); reorderMappings(getIntermediaryTiny(), invertedIntermediary, "intermediary", "official"); Path unorderedMergedMappings = Paths.get(mappingsStepsDir.toString(), "unordered-merged.tiny"); - project.getLogger().info(":merging"); mergeMappings(invertedIntermediary, unmergedYarn, unorderedMergedMappings); reorderMappings(unorderedMergedMappings, tinyMappings.toPath(), "official", "intermediary", "named"); + Files.deleteIfExists(invertedIntermediary); + Files.deleteIfExists(unorderedMergedMappings); + project.getLogger().info(":merged mappings in " + stopwatch.stop()); } - private void reorderMappings(Path oldMappings, Path newMappings, String... newOrder) { - Command command = new CommandReorderTinyV2(); - String[] args = new String[2 + newOrder.length]; - args[0] = oldMappings.toAbsolutePath().toString(); - args[1] = newMappings.toAbsolutePath().toString(); - System.arraycopy(newOrder, 0, args, 2, newOrder.length); - runCommand(command, args); + private void reorderMappings(Path oldMappings, Path newMappings, String... newOrder) throws IOException { + MappingsModificationUtils.modify(oldMappings, newMappings, tree -> + MappingsUtils.reorderNamespaces(tree, Arrays.asList(newOrder))); } private void mergeMappings(Path intermediaryMappings, Path yarnMappings, Path newMergedMappings) { 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 90af6c80..14333000 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 @@ -47,6 +47,8 @@ import java.util.jar.Manifest; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import dev.architectury.tinyremapper.IMappingProvider; +import dev.architectury.tinyremapper.NonClassCopyMode; +import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; import org.gradle.api.Project; import org.jetbrains.annotations.Nullable; @@ -55,9 +57,11 @@ import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.providers.MinecraftProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; import net.fabricmc.loom.configuration.providers.minecraft.tr.OutputRemappingHandler; +import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.OperatingSystem; import net.fabricmc.loom.util.ThreadingUtils; import net.fabricmc.loom.util.TinyRemapperMappingsHelper; import net.fabricmc.loom.util.srg.AtRemapper; @@ -66,7 +70,7 @@ import net.fabricmc.loom.util.srg.InnerClassRemapper; import net.fabricmc.mapping.tree.TinyTree; public class MinecraftMappedProvider extends DependencyProvider { - private static final Map JSR_TO_JETBRAINS = new ImmutableMap.Builder() + public static final Map JSR_TO_JETBRAINS = new ImmutableMap.Builder() .put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable") .put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull") .put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable") @@ -132,6 +136,16 @@ public class MinecraftMappedProvider extends DependencyProvider { } addDependencies(dependency, postPopulationScheduler); + + getProject().afterEvaluate(project -> { + if (getExtension().isForge() && !OperatingSystem.isCIBuild()) { + try { + ForgeSourcesRemapper.addBaseForgeSources(project); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } private void mapMinecraftJar() throws Exception { @@ -158,32 +172,28 @@ public class MinecraftMappedProvider extends DependencyProvider { try (FileSystemUtil.FileSystemDelegate inputFs = FileSystemUtil.getJarFileSystem(input, false)) { ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); - try (FileSystemUtil.FileSystemDelegate assetsFs = FileSystemUtil.getJarFileSystem(tmpAssets, true)) { - for (Path path : (Iterable) Files.walk(inputFs.get().getPath("/"))::iterator) { - if (Files.isRegularFile(path)) { - if (path.getFileName().toString().endsWith(".class")) { - taskCompleter.add(() -> { - byte[] bytes = Files.readAllBytes(path); + for (Path path : (Iterable) Files.walk(inputFs.get().getPath("/"))::iterator) { + if (Files.isRegularFile(path)) { + if (path.getFileName().toString().endsWith(".class")) { + taskCompleter.add(() -> { + byte[] bytes = Files.readAllBytes(path); - synchronized (inputByteList) { - inputByteList.add(bytes); - } - }); - } else { - Path p = assetsFs.get().getPath(path.toString()); - - if (p.getParent() != null) { - Files.createDirectories(p.getParent()); + synchronized (inputByteList) { + inputByteList.add(bytes); } - - taskCompleter.add(() -> { - Files.copy(path, p); - }); - } + }); } } + } - taskCompleter.complete(); + taskCompleter.complete(); + } + + try (OutputConsumerPath tmpAssetsPath = new OutputConsumerPath.Builder(tmpAssets).assumeArchive(true).build()) { + if (getExtension().isForge()) { + tmpAssetsPath.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null); + } else { + tmpAssetsPath.addNonClassFiles(input); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java index 44af6b2c..e6b2c5e6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java @@ -1,3 +1,27 @@ +/* + * 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.configuration.providers.minecraft.tr; import org.cadixdev.mercury.Mercury; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java index f69beb0c..ad7abe39 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java @@ -1,3 +1,27 @@ +/* + * 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.configuration.providers.minecraft.tr; import java.io.IOException; diff --git a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java new file mode 100644 index 00000000..ea3cba27 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java @@ -0,0 +1,239 @@ +/* + * 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.configuration.sources; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.mercury.Mercury; +import org.cadixdev.mercury.remapper.MercuryRemapper; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.plugins.JavaPlugin; +import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.build.ModCompileRemapper; +import net.fabricmc.loom.configuration.providers.LaunchProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DeletingFileVisitor; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.SourceRemapper; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.lorenztiny.TinyMappingsReader; + +public class ForgeSourcesRemapper { + public static void addBaseForgeSources(Project project) throws IOException { + Path sourcesJar = GenerateSourcesTask.getMappedJarFileWithSuffix(project, "-sources.jar").toPath(); + + if (!Files.exists(sourcesJar)) { + addForgeSources(project, sourcesJar); + } + } + + public static void addForgeSources(Project project, Path sourcesJar) throws IOException { + try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(sourcesJar, true)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + provideForgeSources(project, (path, bytes) -> { + Path fsPath = delegate.get().getPath(path); + + if (fsPath.getParent() != null) { + try { + Files.createDirectories(fsPath.getParent()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + taskCompleter.add(() -> { + Files.write(fsPath, bytes, StandardOpenOption.CREATE); + }); + }); + + taskCompleter.complete(); + } + } + + public static void provideForgeSources(Project project, BiConsumer consumer) throws IOException { + List forgeInstallerSources = new ArrayList<>(); + + for (ResolvedArtifact artifact : project.getConfigurations().getByName(Constants.Configurations.FORGE_INSTALLER).getResolvedConfiguration().getResolvedArtifacts()) { + File forgeInstallerSource = ModCompileRemapper.findSources(project.getDependencies(), artifact); + + if (forgeInstallerSource != null) { + forgeInstallerSources.add(forgeInstallerSource.toPath()); + } + } + + project.getLogger().lifecycle(":found {} forge source jars", forgeInstallerSources.size()); + Map forgeSources = extractSources(forgeInstallerSources); + project.getLogger().lifecycle(":extracted {} forge source classes", forgeSources.size()); + remapSources(project, forgeSources); + forgeSources.forEach(consumer); + } + + private static void remapSources(Project project, Map sources) throws IOException { + File tmpInput = File.createTempFile("tmpInputForgeSources", null); + tmpInput.delete(); + tmpInput.deleteOnExit(); + File tmpOutput = File.createTempFile("tmpOutputForgeSources", null); + tmpOutput.delete(); + tmpOutput.deleteOnExit(); + + try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(tmpInput, true)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Map.Entry entry : sources.entrySet()) { + Path path = delegate.get().getPath(entry.getKey()); + + if (path.getParent() != null) { + Files.createDirectories(path.getParent()); + } + + taskCompleter.add(() -> { + Files.write(path, entry.getValue(), StandardOpenOption.CREATE); + }); + } + + taskCompleter.complete(); + } + + remapForgeSourcesInner(project, tmpInput.toPath(), tmpOutput.toPath()); + tmpInput.delete(); + int[] failedToRemap = {0}; + + try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(tmpOutput, false)) { + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Map.Entry entry : new HashSet<>(sources.entrySet())) { + taskCompleter.add(() -> { + Path path = delegate.get().getPath(entry.getKey()); + + if (Files.exists(path)) { + sources.put(entry.getKey(), Files.readAllBytes(path)); + } else { + sources.remove(entry.getKey()); + project.getLogger().error("forge source failed to remap " + entry.getKey()); + failedToRemap[0]++; + } + }); + } + + taskCompleter.complete(); + } + + tmpOutput.delete(); + + if (failedToRemap[0] > 0) { + project.getLogger().error("{} forge sources failed to remap", failedToRemap[0]); + } + } + + private static void remapForgeSourcesInner(Project project, Path tmpInput, Path tmpOutput) throws IOException { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + Mercury mercury = SourceRemapper.createMercuryWithClassPath(project, false); + + MappingSet mappings = new TinyMappingsReader(extension.getMappingsProvider().getMappingsWithSrg(), "srg", "named").read(); + + for (Map.Entry entry : MinecraftMappedProvider.JSR_TO_JETBRAINS.entrySet()) { + mappings.getOrCreateClassMapping(entry.getKey()).setDeobfuscatedName(entry.getValue()); + } + + Dependency annotationDependency = extension.getDependencyManager().getProvider(LaunchProvider.class).annotationDependency; + Set files = project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) + .files(annotationDependency); + + for (File file : files) { + mercury.getClassPath().add(file.toPath()); + } + + // Distinct and add the srg jar at the top, so it gets prioritized + mercury.getClassPath().add(0, extension.getMinecraftMappedProvider().getSrgJar().toPath()); + List newClassPath = mercury.getClassPath().stream() + .distinct() + .filter(Files::isRegularFile) + .collect(Collectors.toList()); + mercury.getClassPath().clear(); + mercury.getClassPath().addAll(newClassPath); + + mercury.getProcessors().add(MercuryRemapper.create(mappings)); + boolean isSrcTmp = false; + + if (!Files.isDirectory(tmpInput)) { + Path tmpInput1 = tmpInput; + // create tmp directory + isSrcTmp = true; + tmpInput = Files.createTempDirectory("fabric-loom-src"); + ZipUtil.unpack(tmpInput1.toFile(), tmpInput.toFile()); + } + + try (FileSystemUtil.FileSystemDelegate outputFs = FileSystemUtil.getJarFileSystem(tmpOutput, true)) { + Path outputFsRoot = outputFs.get().getPath("/"); + mercury.rewrite(tmpInput, outputFsRoot); + } catch (Exception e) { + project.getLogger().warn("Could not remap " + tmpInput + " fully!", e); + } + + if (isSrcTmp) { + Files.walkFileTree(tmpInput, new DeletingFileVisitor()); + } + } + + private static Map extractSources(List forgeInstallerSources) throws IOException { + Map sources = new ConcurrentHashMap<>(); + ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); + + for (Path path : forgeInstallerSources) { + FileSystemUtil.FileSystemDelegate system = FileSystemUtil.getJarFileSystem(path, false); + taskCompleter.onComplete(stopwatch -> system.close()); + + for (Path filePath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(filePath) && filePath.getFileName().toString().endsWith(".java")) { + taskCompleter.add(() -> sources.put(filePath.toString(), Files.readAllBytes(filePath))); + } + } + } + + taskCompleter.complete(); + return sources; + } +} diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index eb028d40..87706073 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -35,6 +35,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import org.gradle.api.Project; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.TaskAction; @@ -42,6 +43,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; +import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.gradle.ProgressLogger; @@ -81,6 +83,10 @@ public class GenerateSourcesTask extends AbstractLoomTask { Files.copy(linemappedJarDestination, runtimeJar, StandardCopyOption.REPLACE_EXISTING); Files.delete(linemappedJarDestination); } + + if (getExtension().isForge()) { + ForgeSourcesRemapper.addForgeSources(getProject(), sourcesDestination); + } } private void remapLineNumbers(Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException { @@ -100,7 +106,11 @@ public class GenerateSourcesTask extends AbstractLoomTask { } private File getMappedJarFileWithSuffix(String suffix) { - LoomGradleExtension extension = getProject().getExtensions().getByType(LoomGradleExtension.class); + return getMappedJarFileWithSuffix(getProject(), suffix); + } + + public static File getMappedJarFileWithSuffix(Project project, String suffix) { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); MappingsProvider mappingsProvider = extension.getMappingsProvider(); File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); String path = mappedJar.getAbsolutePath(); diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index f61ec8f3..da7bf898 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -59,6 +59,7 @@ public final class LoomTasks { registerIDETasks(tasks); registerRunTasks(tasks, project); + registerLaunchSettings(project); registerDecompileTasks(tasks, project); } @@ -109,6 +110,17 @@ public final class LoomTasks { extension.getRunConfigs().create("server", RunConfigSettings::server); } + private static void registerLaunchSettings(Project project) { + LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); + Preconditions.checkArgument(extension.getLaunchConfigs().size() == 0, "Launch configurations must not be registered before loom"); + extension.getLaunchConfigs().create("client"); + extension.getLaunchConfigs().create("server"); + + if (extension.isForge()) { + extension.getLaunchConfigs().create("data"); + } + } + private static void registerDecompileTasks(TaskContainer tasks, Project project) { LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); diff --git a/src/main/java/net/fabricmc/loom/util/ModPlatform.java b/src/main/java/net/fabricmc/loom/util/ModPlatform.java index cd751cb7..11b2c4ef 100644 --- a/src/main/java/net/fabricmc/loom/util/ModPlatform.java +++ b/src/main/java/net/fabricmc/loom/util/ModPlatform.java @@ -1,3 +1,27 @@ +/* + * 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; public enum ModPlatform { diff --git a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java index 53c2d221..0645f319 100644 --- a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java +++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java @@ -34,13 +34,18 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import com.google.common.base.Stopwatch; public class ThreadingUtils { + public static void run(T[] values, UnsafeConsumer action) { + run(Arrays.stream(values) + .map(t -> () -> action.accept(t)) + .collect(Collectors.toList())); + } + public static void run(Collection values, UnsafeConsumer action) { run(values.stream() .map(t -> () -> action.accept(t)) @@ -134,7 +139,7 @@ public class ThreadingUtils { Stopwatch stopwatch = Stopwatch.createUnstarted(); List> tasks = new ArrayList<>(); ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - List> completionListener = new ArrayList<>(); + List> completionListener = new ArrayList<>(); public TaskCompleter add(UnsafeRunnable job) { if (!stopwatch.isRunning()) { @@ -152,7 +157,7 @@ public class ThreadingUtils { return this; } - public TaskCompleter onComplete(Consumer consumer) { + public TaskCompleter onComplete(UnsafeConsumer consumer) { completionListener.add(consumer); return this; } @@ -161,13 +166,20 @@ public class ThreadingUtils { try { CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).exceptionally(this).get(); service.shutdownNow(); - stopwatch.stop(); - for (Consumer consumer : completionListener) { - consumer.accept(stopwatch); + if (stopwatch.isRunning()) { + stopwatch.stop(); } - } catch (InterruptedException | ExecutionException e) { + } catch (Throwable e) { throw new RuntimeException(e); + } finally { + try { + for (UnsafeConsumer consumer : completionListener) { + consumer.accept(stopwatch); + } + } catch (Throwable e) { + e.printStackTrace(); + } } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 72a482cf..11fae1a0 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -40,10 +40,9 @@ import org.gradle.api.Project; import org.zeroturnaround.zip.ZipUtil; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; public class SpecialSourceExecutor { - public static Path produceSrgJar(Project project, MappingsProvider provider, String side, File specialSourceJar, Path officialJar, Path srgPath) + public static Path produceSrgJar(Project project, String side, File specialSourceJar, Path officialJar, Path srgPath) throws Exception { Set filter = Files.readAllLines(srgPath, StandardCharsets.UTF_8).stream() .filter(s -> !s.startsWith("\t"))