diff --git a/build.gradle b/build.gradle index 3a37ecd..305b659 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation ('net.fabricmc:tiny-remapper:0.5.0') implementation ('net.fabricmc:tiny-mappings-parser:0.3.0+build.17') - implementation 'net.fabricmc:access-widener:1.1.0' + implementation 'net.fabricmc:access-widener:2.0.0' implementation 'net.fabricmc:mapping-io:0.2.1' implementation ('net.fabricmc:lorenz-tiny:3.0.0') { diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 1a84efc..4020ecd 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -204,4 +204,11 @@ public interface LoomGradleExtensionAPI { * @return the version defined in the fabric.mod.json */ String getModVersion(); + + /** + * When true loom will apply transitive access wideners from compile dependencies. + * + * @return the property controlling the transitive access wideners + */ + Property getEnableTransitiveAccessWideners(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java new file mode 100644 index 0000000..680aae9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java @@ -0,0 +1,66 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 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.accesswidener; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.zeroturnaround.zip.ZipUtil; + +public record AccessWidenerFile( + String name, + String modId, + byte[] content +) { + /** + * Reads the access-widener contained in a mod jar, or returns null if there is none. + */ + public static AccessWidenerFile fromModJar(Path modJarPath) { + byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json"); + + if (modJsonBytes == null) { + return null; + } + + JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); + + if (!jsonObject.has("accessWidener")) { + return null; + } + + String awPath = jsonObject.get("accessWidener").getAsString(); + String modId = jsonObject.get("id").getAsString(); + + byte[] content = ZipUtil.unpackEntry(modJarPath.toFile(), awPath); + + return new AccessWidenerFile( + awPath, + modId, + content + ); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 6ac63ac..0970717 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -24,46 +24,34 @@ package net.fabricmc.loom.configuration.accesswidener; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.zip.ZipEntry; -import com.google.gson.Gson; -import com.google.gson.JsonObject; +import com.google.common.hash.Hashing; import org.gradle.api.Project; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.Remapper; import org.zeroturnaround.zip.ZipUtil; -import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformer; -import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; -import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.accesswidener.AccessWidenerWriter; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.processors.JarProcessor; -import net.fabricmc.loom.util.Checksum; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.tinyremapper.TinyRemapper; public class AccessWidenerJarProcessor implements JarProcessor { - private AccessWidener accessWidener = new AccessWidener(); - private AccessWidenerReader accessWidenerReader = new AccessWidenerReader(accessWidener); + // Filename used to store hash of input access widener in processed jar file + private static final String HASH_FILENAME = "aw.sha256"; + // The mod's own access widener file + private byte[] modAccessWidener; + private final AccessWidener accessWidener = new AccessWidener(); private final Project project; + // This is a SHA256 hash across the mod's and all transitive AWs private byte[] inputHash; public AccessWidenerJarProcessor(Project project) { @@ -77,119 +65,53 @@ public class AccessWidenerJarProcessor implements JarProcessor { @Override public void setup() { - LoomGradleExtension loomGradleExtension = LoomGradleExtension.get(project); - File awPath = loomGradleExtension.getAccessWidenerPath().get().getAsFile(); + LoomGradleExtension extension = LoomGradleExtension.get(project); + Path awPath = extension.getAccessWidenerPath().get().getAsFile().toPath(); - if (!awPath.exists()) { - throw new RuntimeException("Could not find access widener file @ " + awPath.getAbsolutePath()); - } - - inputHash = Checksum.sha256(awPath); - - try (BufferedReader reader = new BufferedReader(new FileReader(awPath))) { - accessWidenerReader.read(reader); + // Read our own mod's access widener, used later for producing a version remapped to intermediary + try { + modAccessWidener = Files.readAllBytes(awPath); + } catch (NoSuchFileException e) { + throw new RuntimeException("Could not find access widener file @ " + awPath.toAbsolutePath()); } catch (IOException e) { - throw new RuntimeException("Failed to read project access widener file"); + throw new RuntimeException("Failed to read access widener: " + awPath); } - //Remap accessWidener if its not named, allows for AE's to be written in intermediary - if (!accessWidener.getNamespace().equals(MappingsNamespace.NAMED.toString())) { - try { - List validNamespaces = loomGradleExtension.getMappingsProvider().getMappings().getMetadata().getNamespaces(); + AccessWidenerReader reader = new AccessWidenerReader(accessWidener); + reader.read(modAccessWidener); - if (!validNamespaces.contains(accessWidener.getNamespace())) { - throw new UnsupportedOperationException(String.format("Access Widener namespace '%s' is not a valid namespace, it must be one of: '%s'", accessWidener.getNamespace(), String.join(", ", validNamespaces))); - } - - TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.NAMED.toString()); - tinyRemapper.readClassPath(loomGradleExtension.getMinecraftMappedProvider().getRemapClasspath()); - - AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, tinyRemapper.getRemapper(), MappingsNamespace.NAMED.toString()); - accessWidener = remapper.remap(); - - tinyRemapper.finish(); - } catch (IOException e) { - throw new RuntimeException("Failed to remap access widener", e); - } - } + inputHash = Hashing.sha256().hashBytes(modAccessWidener).asBytes(); } @Override public void process(File file) { - project.getLogger().lifecycle("Processing file: " + file.getName()); - ZipUtil.transformEntries(file, getTransformers(accessWidener.getTargets())); - ZipUtil.addEntry(file, "aw.sha256", inputHash); + AccessWidenerTransformer applier = new AccessWidenerTransformer(project.getLogger(), accessWidener); + applier.apply(file); + ZipUtil.addEntry(file, HASH_FILENAME, inputHash); } - private ZipEntryTransformerEntry[] getTransformers(Set classes) { - return classes.stream() - .map(string -> new ZipEntryTransformerEntry(string.replaceAll("\\.", "/") + ".class", getTransformer(string))) - .toArray(ZipEntryTransformerEntry[]::new); - } + /** + * Get this mods access widener remapped to the intermediary namespace. + */ + public byte[] getRemappedAccessWidener(Remapper asmRemapper, String targetNamespace) throws IOException { + int version = AccessWidenerReader.readVersion(modAccessWidener); - private ZipEntryTransformer getTransformer(String className) { - return new ByteArrayZipEntryTransformer() { - @Override - protected byte[] transform(ZipEntry zipEntry, byte[] input) { - ClassReader reader = new ClassReader(input); - ClassWriter writer = new ClassWriter(0); - ClassVisitor classVisitor = AccessWidenerVisitor.createClassVisitor(Constants.ASM_VERSION, writer, accessWidener); + AccessWidenerWriter writer = new AccessWidenerWriter(version); + AccessWidenerRemapper remapper = new AccessWidenerRemapper( + writer, + asmRemapper, + MappingsNamespace.NAMED.toString(), + targetNamespace + ); + AccessWidenerReader reader = new AccessWidenerReader(remapper); + reader.read(modAccessWidener); - project.getLogger().lifecycle("Applying access widener to " + className); - - reader.accept(classVisitor, 0); - return writer.toByteArray(); - } - }; - } - - //Called when remapping the mod - public void remapAccessWidener(Path modJarPath, Remapper asmRemapper) throws IOException { - byte[] bytes = getRemappedAccessWidener(asmRemapper); - - String path = getAccessWidenerPath(modJarPath); - - if (path == null) { - throw new RuntimeException("Failed to find accessWidener in fabric.mod.json"); - } - - boolean replaced = ZipUtil.replaceEntry(modJarPath.toFile(), path, bytes); - - if (!replaced) { - project.getLogger().warn("Failed to replace access widener file at " + path); - } - } - - public byte[] getRemappedAccessWidener(Remapper asmRemapper) throws IOException { - AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, asmRemapper, MappingsNamespace.INTERMEDIARY.toString()); - AccessWidener remapped = remapper.remap(); - AccessWidenerWriter accessWidenerWriter = new AccessWidenerWriter(remapped); - - try (StringWriter writer = new StringWriter()) { - accessWidenerWriter.write(writer); - return writer.toString().getBytes(); - } - } - - public String getAccessWidenerPath(Path modJarPath) { - byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json"); - - if (modJsonBytes == null) { - return null; - } - - JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); - - if (!jsonObject.has("accessWidener")) { - return null; - } - - return jsonObject.get("accessWidener").getAsString(); + return writer.write(); } @Override public boolean isInvalid(File file) { - byte[] hash = ZipUtil.unpackEntry(file, "aw.sha256"); + byte[] hash = ZipUtil.unpackEntry(file, HASH_FILENAME); if (hash == null) { return true; diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java new file mode 100644 index 0000000..bdde740 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerTransformer.java @@ -0,0 +1,82 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 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.accesswidener; + +import java.io.File; +import java.util.Set; +import java.util.zip.ZipEntry; + +import org.gradle.api.logging.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.zeroturnaround.zip.ZipUtil; +import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer; +import org.zeroturnaround.zip.transform.ZipEntryTransformer; +import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; + +import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.accesswidener.AccessWidenerClassVisitor; +import net.fabricmc.loom.util.Constants; + +final class AccessWidenerTransformer { + private final Logger logger; + private final AccessWidener accessWidener; + + AccessWidenerTransformer(Logger logger, AccessWidener accessWidener) { + this.logger = logger; + this.accessWidener = accessWidener; + } + + /** + * Apply the rules from an access-widener to the given jar or zip file. + */ + void apply(File jarFile) { + logger.lifecycle("Processing file: " + jarFile.getName()); + ZipUtil.transformEntries(jarFile, getTransformers(accessWidener.getTargets())); + } + + private ZipEntryTransformerEntry[] getTransformers(Set classes) { + return classes.stream() + .map(string -> new ZipEntryTransformerEntry(string.replaceAll("\\.", "/") + ".class", getTransformer(string))) + .toArray(ZipEntryTransformerEntry[]::new); + } + + private ZipEntryTransformer getTransformer(String className) { + return new ByteArrayZipEntryTransformer() { + @Override + protected byte[] transform(ZipEntry zipEntry, byte[] input) { + ClassReader reader = new ClassReader(input); + ClassWriter writer = new ClassWriter(0); + ClassVisitor classVisitor = AccessWidenerClassVisitor.createClassVisitor(Constants.ASM_VERSION, writer, accessWidener); + + logger.info("Applying access widener to " + className); + + reader.accept(classVisitor, 0); + return writer.toByteArray(); + } + }; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java new file mode 100644 index 0000000..5412975 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java @@ -0,0 +1,200 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 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.accesswidener; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Preconditions; +import org.gradle.api.Project; + +import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.accesswidener.AccessWidenerReader; +import net.fabricmc.accesswidener.AccessWidenerRemapper; +import net.fabricmc.accesswidener.AccessWidenerVisitor; +import net.fabricmc.accesswidener.TransitiveOnlyFilter; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.RemappedConfigurationEntry; +import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.tinyremapper.TinyRemapper; + +/** + * Applies transitive access wideners that are inherited from mod and api dependencies. + */ +public class TransitiveAccessWidenerJarProcessor implements JarProcessor { + private final Project project; + private final LoomGradleExtension extension; + + private final List transitiveAccessWideners; + + public TransitiveAccessWidenerJarProcessor(Project project) { + this.project = project; + this.extension = LoomGradleExtension.get(project); + + transitiveAccessWideners = getTransitiveAccessWideners(); + } + + @Override + public void setup() { + } + + public boolean isEmpty() { + return transitiveAccessWideners.isEmpty(); + } + + @Override + public String getId() { + Preconditions.checkArgument(!isEmpty()); + + return "loom:transitive_access_wideners:" + transitiveAccessWideners.hashCode(); + } + + private List getTransitiveAccessWideners() { + List accessWideners = new ArrayList<>(); + + for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) { + // Only apply global AWs from mods that are part of the compile classpath + if (!entry.compileClasspath()) { + continue; + } + + Set artifacts = extension.getLazyConfigurationProvider(entry.sourceConfiguration()) + .get() + .resolve(); + + for (File artifact : artifacts) { + AccessWidenerFile accessWidener = AccessWidenerFile.fromModJar(artifact.toPath()); + + if (accessWidener == null) { + continue; + } + + if (!TransitiveDetectorVisitor.isTransitive(accessWidener.content())) { + // AW does not contain anything transitive, skip over it + continue; + } + + accessWideners.add(accessWidener); + } + } + + return accessWideners; + } + + @Override + public void process(File file) { + Preconditions.checkArgument(!isEmpty()); + + AccessWidener accessWidener = createAccessWidener(); + AccessWidenerTransformer transformer = new AccessWidenerTransformer(project.getLogger(), accessWidener); + transformer.apply(file); + } + + private AccessWidener createAccessWidener() { + AccessWidener accessWidener = new AccessWidener(); + // For other mods, only consider transitive AWs and remap from intermediary->named + TinyRemapper tinyRemapper = createTinyRemapper(); + + try { + AccessWidenerRemapper remappingVisitor = new AccessWidenerRemapper( + accessWidener, + tinyRemapper.getRemapper(), + MappingsNamespace.INTERMEDIARY.toString(), + MappingsNamespace.NAMED.toString() + ); + AccessWidenerReader transitiveReader = new AccessWidenerReader(new TransitiveOnlyFilter(remappingVisitor)); + + for (AccessWidenerFile accessWidenerFile : transitiveAccessWideners) { + project.getLogger().info("Reading transitive access widener from {}", accessWidenerFile.modId()); + transitiveReader.read(accessWidenerFile.content()); + } + } finally { + tinyRemapper.finish(); + } + + return accessWidener; + } + + private TinyRemapper createTinyRemapper() { + try { + TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named"); + + tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project)); + tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath()); + + return tinyRemapper; + } catch (IOException e) { + throw new RuntimeException("Failed to create tiny remapper for intermediary->named", e); + } + } + + @Override + public boolean isInvalid(File file) { + // The hash is handled by getId() + return false; + } + + private static class TransitiveDetectorVisitor implements AccessWidenerVisitor { + private boolean transitive = false; + + @Override + public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { + if (transitive) { + this.transitive = true; + } + } + + @Override + public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + if (transitive) { + this.transitive = true; + } + } + + @Override + public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { + if (transitive) { + this.transitive = true; + } + } + + public static boolean isTransitive(byte[] content) { + if (AccessWidenerReader.readVersion(content) < 2) { + // Transitive AWs are only in v2 or higher, so we can save parsing the file to find out... + return false; + } + + TransitiveDetectorVisitor transitiveDetector = new TransitiveDetectorVisitor(); + new AccessWidenerReader(transitiveDetector).read(content); + return transitiveDetector.transitive; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 7cdc667..2cd519f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -24,13 +24,9 @@ package net.fabricmc.loom.configuration.mods; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; @@ -48,7 +44,6 @@ import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.transform.StringZipEntryTransformer; import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; -import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerWriter; @@ -60,7 +55,7 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; @@ -98,7 +93,7 @@ public class ModProcessor { private static void stripNestedJars(File file) { // Strip out all contained jar info as we dont want loader to try and load the jars contained in dev. - ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() { + ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[]{(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() { @Override protected String transform(ZipEntry zipEntry, String input) { JsonObject json = LoomGradlePlugin.GSON.fromJson(input, JsonObject.class); @@ -108,23 +103,22 @@ public class ModProcessor { }))}); } + /** + * Remap another mod's access widener from intermediary to named, so that loader can apply it in our dev-env. + */ private static byte[] remapAccessWidener(byte[] input, Remapper remapper) { - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(input), StandardCharsets.UTF_8))) { - AccessWidener accessWidener = new AccessWidener(); - AccessWidenerReader accessWidenerReader = new AccessWidenerReader(accessWidener); - accessWidenerReader.read(bufferedReader); + int version = AccessWidenerReader.readVersion(input); - AccessWidenerRemapper accessWidenerRemapper = new AccessWidenerRemapper(accessWidener, remapper, MappingsNamespace.NAMED.toString()); - AccessWidener remapped = accessWidenerRemapper.remap(); - AccessWidenerWriter accessWidenerWriter = new AccessWidenerWriter(remapped); - - try (StringWriter writer = new StringWriter()) { - accessWidenerWriter.write(writer); - return writer.toString().getBytes(StandardCharsets.UTF_8); - } - } catch (IOException e) { - throw new RuntimeException(e); - } + AccessWidenerWriter writer = new AccessWidenerWriter(version); + AccessWidenerRemapper awRemapper = new AccessWidenerRemapper( + writer, + remapper, + MappingsNamespace.INTERMEDIARY.toString(), + MappingsNamespace.NAMED.toString() + ); + AccessWidenerReader reader = new AccessWidenerReader(awRemapper); + reader.read(input); + return writer.write(); } private static void remapJars(Project project, List processList) throws IOException { @@ -137,16 +131,16 @@ public class ModProcessor { Path mc = mappedProvider.getIntermediaryJar().toPath(); Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() - .stream().map(File::toPath).toArray(Path[]::new); + .stream().map(File::toPath).toArray(Path[]::new); List remapList = processList.stream().filter(ModDependencyInfo::requiresRemapping).collect(Collectors.toList()); project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")"); TinyRemapper remapper = TinyRemapper.newRemapper() - .withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false)) - .renameInvalidLocals(false) - .build(); + .withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false)) + .renameInvalidLocals(false) + .build(); remapper.readClassPathAsync(mc); remapper.readClassPathAsync(mcDeps); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 0456ada..603df94 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -51,6 +51,7 @@ import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; +import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; @@ -141,6 +142,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(getProject())); } + if (extension.getEnableTransitiveAccessWideners().get()) { + TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(getProject()); + + if (!transitiveAccessWidenerJarProcessor.isEmpty()) { + extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor); + } + } + extension.getAccessWidenerPath().finalizeValue(); extension.getGameJarProcessors().finalizeValue(); JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get()); 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 7535e1a..b5f175f 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 @@ -29,10 +29,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.Map; import java.util.function.Consumer; -import com.google.common.collect.ImmutableMap; import org.gradle.api.Project; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -40,17 +38,11 @@ import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; public class MinecraftMappedProvider extends DependencyProvider { - private 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") - .build(); - private File minecraftMappedJar; private File minecraftIntermediaryJar; @@ -115,11 +107,11 @@ public class MinecraftMappedProvider extends DependencyProvider { Files.deleteIfExists(output); - TinyRemapper remapper = getTinyRemapper(fromM, toM); + TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM); try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { outputConsumer.addNonClassFiles(input); - remapper.readClassPath(getRemapClasspath()); + remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(getProject())); remapper.readInputs(input); remapper.apply(outputConsumer); } catch (Exception e) { @@ -130,20 +122,6 @@ public class MinecraftMappedProvider extends DependencyProvider { } } - public TinyRemapper getTinyRemapper(String fromM, String toM) throws IOException { - return TinyRemapper.newRemapper() - .withMappings(TinyRemapperMappingsHelper.create(getExtension().getMappingsProvider().getMappings(), fromM, toM, true)) - .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)) - .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .build(); - } - - public Path[] getRemapClasspath() { - return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles() - .stream().map(File::toPath).toArray(Path[]::new); - } - protected void addDependencies(DependencyInfo dependency, Consumer postPopulationScheduler) { getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED, getProject().getDependencies().module("net.minecraft:minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 93e8852..0a7b0ca 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -60,6 +60,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final Property remapArchives; protected final Property customManifest; protected final Property setupRemappedVariants; + protected final Property transitiveAccessWideners; private final ModVersionParser versionParser; @@ -81,6 +82,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.customManifest = project.getObjects().property(String.class); this.setupRemappedVariants = project.getObjects().property(Boolean.class) .convention(true); + this.transitiveAccessWideners = project.getObjects().property(Boolean.class) + .convention(true); + this.transitiveAccessWideners.finalizeValueOnRead(); this.versionParser = new ModVersionParser(project); @@ -160,6 +164,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return versionParser.getModVersion(); } + @Override + public Property getEnableTransitiveAccessWideners() { + return transitiveAccessWideners; + } + protected abstract Project getProject(); protected abstract LoomFiles getFiles(); diff --git a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java index 5b02a7d..4d1e395 100644 --- a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java @@ -150,4 +150,10 @@ public class MinecraftGradleExtension implements LoomGradleExtensionAPI { reportDeprecation(); throw new UnsupportedOperationException("Use loom extension"); } + + @Override + public Property getEnableTransitiveAccessWideners() { + reportDeprecation(); + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index bd83c79..2fc6b3c 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -66,10 +66,11 @@ import net.fabricmc.loom.build.nesting.NestedDependencyProvider; import net.fabricmc.loom.build.nesting.NestedJarPathProvider; import net.fabricmc.loom.build.nesting.NestedJarProvider; import net.fabricmc.loom.configuration.JarManifestConfiguration; +import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipReprocessorUtil; import net.fabricmc.stitch.util.Pair; import net.fabricmc.tinyremapper.TinyRemapper; @@ -137,7 +138,7 @@ public class RemapJarTask extends Jar { if (isMainRemapTask) { jarRemapper.addToClasspath(getRemapClasspath()); - jarRemapper.addMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false)); + jarRemapper.addMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false)); } for (File mixinMapFile : extension.getAllMixinMappings()) { @@ -159,15 +160,15 @@ public class RemapJarTask extends Jar { byte[] data; try { - data = accessWidenerJarProcessor.getRemappedAccessWidener(remapper); + data = accessWidenerJarProcessor.getRemappedAccessWidener(remapper, toM); } catch (IOException e) { - throw new RuntimeException("Failed to remap access widener"); + throw new RuntimeException("Failed to remap access widener", e); } - String awPath = accessWidenerJarProcessor.getAccessWidenerPath(remapData.input); - Preconditions.checkNotNull(awPath, "Failed to find accessWidener in fabric.mod.json: " + remapData.input); + AccessWidenerFile awFile = AccessWidenerFile.fromModJar(remapData.input); + Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json: " + remapData.input); - return Pair.of(awPath, data); + return Pair.of(awFile.name(), data); } return null; @@ -285,7 +286,8 @@ public class RemapJarTask extends Jar { return this; } - @ApiStatus.Experimental // This only allows mod jars, proceed with care when trying to pass in configurations with projects, or something that depends on a task. + @ApiStatus.Experimental + // This only allows mod jars, proceed with care when trying to pass in configurations with projects, or something that depends on a task. public RemapJarTask include(Object... paths) { Collections.addAll(nestedPaths, paths); this.addNestedDependencies.set(true); diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperMappingsHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java similarity index 63% rename from src/main/java/net/fabricmc/loom/util/TinyRemapperMappingsHelper.java rename to src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 7470c0f..bec7756 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperMappingsHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2019 FabricMC + * Copyright (c) 2021 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,15 @@ package net.fabricmc.loom.util; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.mapping.tree.ClassDef; import net.fabricmc.mapping.tree.FieldDef; import net.fabricmc.mapping.tree.LocalVariableDef; @@ -31,9 +40,36 @@ import net.fabricmc.mapping.tree.MethodDef; import net.fabricmc.mapping.tree.ParameterDef; import net.fabricmc.mapping.tree.TinyTree; import net.fabricmc.tinyremapper.IMappingProvider; +import net.fabricmc.tinyremapper.TinyRemapper; -public class TinyRemapperMappingsHelper { - private TinyRemapperMappingsHelper() { } +/** + * Contains shortcuts to create tiny remappers using the mappings accessibly to the project. + */ +public final class TinyRemapperHelper { + private 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") + .build(); + + private TinyRemapperHelper() { + } + + public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM) throws IOException { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + return TinyRemapper.newRemapper() + .withMappings(create(extension.getMappingsProvider().getMappings(), fromM, toM, true)) + .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .build(); + } + + public static Path[] getMinecraftDependencies(Project project) { + return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles() + .stream().map(File::toPath).toArray(Path[]::new); + } private static IMappingProvider.Member memberOf(String className, String memberName, String descriptor) { return new IMappingProvider.Member(className, memberName, descriptor); @@ -60,8 +96,8 @@ public class TinyRemapperMappingsHelper { for (LocalVariableDef localVariable : method.getLocalVariables()) { acceptor.acceptMethodVar(methodIdentifier, localVariable.getLocalVariableIndex(), - localVariable.getLocalVariableStartOffset(), localVariable.getLocalVariableTableIndex(), - localVariable.getName(to)); + localVariable.getLocalVariableStartOffset(), localVariable.getLocalVariableTableIndex(), + localVariable.getName(to)); } } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy index 8fed624..43c793c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/AccessWidenerTest.groovy @@ -25,6 +25,7 @@ package net.fabricmc.loom.test.integration import net.fabricmc.loom.test.util.GradleProjectTestTrait +import org.zeroturnaround.zip.ZipUtil import spock.lang.Specification import spock.lang.Unroll @@ -48,4 +49,20 @@ class AccessWidenerTest extends Specification implements GradleProjectTestTrait String expected() { new File("src/test/resources/accesswidener/expected.accesswidener").text } + + @Unroll + def "transitive accesswidener (gradle #version)"() { + setup: + def gradle = gradleProject(project: "transitiveAccesswidener", version: version) + ZipUtil.pack(new File(gradle.projectDir, "dummyDependency"), new File(gradle.projectDir, "dummy.jar")) + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/resources/accesswidener/expected.accesswidener b/src/test/resources/accesswidener/expected.accesswidener index 53bf68a..66f5542 100644 --- a/src/test/resources/accesswidener/expected.accesswidener +++ b/src/test/resources/accesswidener/expected.accesswidener @@ -1,9 +1,6 @@ accessWidener v1 intermediary -accessible class net/minecraft/class_1928$class_5199 -accessible class net/minecraft/class_1735 -accessible class net/minecraft/class_1928$class_4314 -extendable class net/minecraft/class_1928$class_4314 -accessible class net/minecraft/class_5235$class_5238 accessible method net/minecraft/class_1928$class_4314 (Ljava/util/function/Supplier;Ljava/util/function/Function;Ljava/util/function/BiConsumer;Lnet/minecraft/class_1928$class_5199;)V extendable method net/minecraft/class_1928$class_4314 (Ljava/util/function/Supplier;Ljava/util/function/Function;Ljava/util/function/BiConsumer;Lnet/minecraft/class_1928$class_5199;)V +accessible class net/minecraft/class_1928$class_5199 +accessible class net/minecraft/class_5235$class_5238 accessible field net/minecraft/class_1735 field_7873 I diff --git a/src/test/resources/projects/transitiveAccesswidener/build.gradle b/src/test/resources/projects/transitiveAccesswidener/build.gradle new file mode 100644 index 0000000..52f1fc3 --- /dev/null +++ b/src/test/resources/projects/transitiveAccesswidener/build.gradle @@ -0,0 +1,18 @@ +// This is used by a range of tests that append to this file before running the gradle tasks. +// Can be used for tests that require minimal custom setup +plugins { + id 'fabric-loom' + id 'maven-publish' +} + +archivesBaseName = "fabric-example-mod" +version = "1.0.0" +group = "com.example" + +dependencies { + minecraft "com.mojang:minecraft:1.17.1" + mappings "net.fabricmc:yarn:1.17.1+build.59:v2" + modImplementation "net.fabricmc:fabric-loader:0.11.6" + + modImplementation files("dummy.jar") +} \ No newline at end of file diff --git a/src/test/resources/projects/transitiveAccesswidener/dummyDependency/dummy.accesswidener b/src/test/resources/projects/transitiveAccesswidener/dummyDependency/dummy.accesswidener new file mode 100644 index 0000000..05537ad --- /dev/null +++ b/src/test/resources/projects/transitiveAccesswidener/dummyDependency/dummy.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 intermediary + +transitive-accessible method net/minecraft/class_1972 method_8775 (Ljava/lang/String;)Lnet/minecraft/class_5321; \ No newline at end of file diff --git a/src/test/resources/projects/transitiveAccesswidener/dummyDependency/fabric.mod.json b/src/test/resources/projects/transitiveAccesswidener/dummyDependency/fabric.mod.json new file mode 100644 index 0000000..b89a826 --- /dev/null +++ b/src/test/resources/projects/transitiveAccesswidener/dummyDependency/fabric.mod.json @@ -0,0 +1,7 @@ +{ + "schemaVersion": 1, + "id": "dummy", + "version": "1", + "name": "Dummy Mod", + "accessWidener" : "dummy.accesswidener" +} diff --git a/src/test/resources/projects/transitiveAccesswidener/src/main/java/ExampleMod.java b/src/test/resources/projects/transitiveAccesswidener/src/main/java/ExampleMod.java new file mode 100644 index 0000000..e5442d2 --- /dev/null +++ b/src/test/resources/projects/transitiveAccesswidener/src/main/java/ExampleMod.java @@ -0,0 +1,13 @@ +import net.minecraft.world.biome.BiomeKeys; +import net.minecraft.world.biome.Biome; +import net.minecraft.util.registry.RegistryKey; + +import net.fabricmc.api.ModInitializer; + +public class ExampleMod implements ModInitializer { + @Override + public void onInitialize() { + // BiomeKeys.register has been made public by a transitive AW + RegistryKey biomeRegistryKey = BiomeKeys.register("dummy"); + } +}