Access Widener 2.0 with support for Transitive Access Wideners (#484)

* Added global access widener support.

* Adapt loom to changed API of latest AW PR.

* Fix expected access widener to fix the test. Since the access widener is now streamed directly into the writer, the expanded rules (i.e. accessible field makes the owning class also accessible) are no longer found in the remapped file.

* Add basic transitive accesswidener test

* Extracted applying transitive access wideners into their own jar processor since they also need to be applied if there is no AW in the mod itself.

* Misc assortment of fixes

* Set up the processor lazily to allow for adding the intermediary MC jar, which is needed to correctly remap intermediary AWs to named.

* Rework to setup the tiny remapper classpath with the mc jar
Add an extension prop to disable

* Add TransitiveDetectorVisitor

* Minor refactoring.

* Use release-version of access-widener.

Co-authored-by: modmuss50 <modmuss50@gmail.com>
dev/0.11
shartte 2021-09-14 23:40:47 +02:00 committed by GitHub
parent 08e548b6c6
commit d48c74161e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 553 additions and 187 deletions

View File

@ -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') {

View File

@ -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<Boolean> getEnableTransitiveAccessWideners();
}

View File

@ -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
);
}
}

View File

@ -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<String> 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<String> 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;

View File

@ -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<String> 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();
}
};
}
}

View File

@ -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<AccessWidenerFile> 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<AccessWidenerFile> getTransitiveAccessWideners() {
List<AccessWidenerFile> 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<File> 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;
}
}
}

View File

@ -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<ModDependencyInfo> 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<ModDependencyInfo> 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);

View File

@ -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());

View File

@ -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<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
.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<Runnable> postPopulationScheduler) {
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
getProject().getDependencies().module("net.minecraft:minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));

View File

@ -60,6 +60,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
protected final Property<Boolean> remapArchives;
protected final Property<String> customManifest;
protected final Property<Boolean> setupRemappedVariants;
protected final Property<Boolean> 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<Boolean> getEnableTransitiveAccessWideners() {
return transitiveAccessWideners;
}
protected abstract Project getProject();
protected abstract LoomFiles getFiles();

View File

@ -150,4 +150,10 @@ public class MinecraftGradleExtension implements LoomGradleExtensionAPI {
reportDeprecation();
throw new UnsupportedOperationException("Use loom extension");
}
@Override
public Property<Boolean> getEnableTransitiveAccessWideners() {
reportDeprecation();
throw new UnsupportedOperationException();
}
}

View File

@ -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);

View File

@ -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<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
.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));
}
}
}

View File

@ -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
}
}

View File

@ -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 <init> (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 <init> (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

View File

@ -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")
}

View File

@ -0,0 +1,3 @@
accessWidener v2 intermediary
transitive-accessible method net/minecraft/class_1972 method_8775 (Ljava/lang/String;)Lnet/minecraft/class_5321;

View File

@ -0,0 +1,7 @@
{
"schemaVersion": 1,
"id": "dummy",
"version": "1",
"name": "Dummy Mod",
"accessWidener" : "dummy.accesswidener"
}

View File

@ -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<Biome> biomeRegistryKey = BiomeKeys.register("dummy");
}
}