Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class Tweaker support #750

Draft
wants to merge 4 commits into
base: exp/1.1
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ repositories {
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
mavenLocal()
}

configurations {
Expand Down Expand Up @@ -81,6 +82,7 @@ dependencies {
// tinyfile management
implementation ('net.fabricmc:tiny-remapper:0.8.5')
implementation 'net.fabricmc:access-widener:2.1.0'
implementation 'net.fabricmc:class-tweaker:0.0.0'
implementation 'net.fabricmc:mapping-io:0.2.1'

implementation ('net.fabricmc:lorenz-tiny:4.0.2') {
Expand Down Expand Up @@ -113,6 +115,7 @@ dependencies {
}
testImplementation 'net.fabricmc:fabric-installer:0.9.0'
testImplementation 'org.mockito:mockito-core:4.8.0'
testImplementation 'com.google.jimfs:jimfs:1.2'

compileOnly 'org.jetbrains:annotations:23.0.0'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.classtweaker;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.jetbrains.annotations.VisibleForTesting;

import net.fabricmc.classtweaker.api.ClassTweaker;
import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.ModEnvironment;

public record ClassTweakerEntry(FabricModJson modJson, String path, ModEnvironment environment, boolean local) {
public static List<ClassTweakerEntry> readAll(FabricModJson modJson, boolean local) {
var entries = new ArrayList<ClassTweakerEntry>();

for (Map.Entry<String, ModEnvironment> entry : modJson.getClassTweakers().entrySet()) {
entries.add(new ClassTweakerEntry(modJson, entry.getKey(), entry.getValue(), local));
}

return Collections.unmodifiableList(entries);
}

@VisibleForTesting
public int maxSupportedVersion() {
if (modJson.getMetadataVersion() >= 2) {
// FMJ 2.0 is required for class tweaker support. Otherwise limited to access wideners.
return ClassTweaker.CT_V1;
}

return ClassTweaker.AW_V2;
}

public void read(ClassTweakerFactory factory, ClassTweakerVisitor classTweakerVisitor) throws IOException {
final byte[] data = readRaw();
final int ctVersion = factory.readVersion(data);

if (ctVersion > maxSupportedVersion()) {
throw new UnsupportedOperationException("Class tweaker '%s' with version '%d' from mod '%s' is not supported with by this metadata version '%d'.".formatted(path, ctVersion, modJson.getId(), modJson().getMetadataVersion()));
}

factory.read(classTweakerVisitor, data, modJson.getId());
}

private byte[] readRaw() throws IOException {
return modJson.getSource().read(path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.classtweaker;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;

import net.fabricmc.classtweaker.api.ClassTweaker;
import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor;

public interface ClassTweakerFactory {
ClassTweakerFactory DEFAULT = new ClassTweakerFactoryImpl();

int readVersion(byte[] data);

void read(ClassTweakerVisitor visitor, byte[] data, String modId);

ClassTweaker readEntries(List<ClassTweakerEntry> entries) throws IOException;

void transformJar(Path jar, ClassTweaker classTweaker) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.classtweaker;

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;

import net.fabricmc.classtweaker.api.ClassTweaker;
import net.fabricmc.classtweaker.api.ClassTweakerReader;
import net.fabricmc.classtweaker.api.visitor.ClassTweakerVisitor;

final class ClassTweakerFactoryImpl implements ClassTweakerFactory {
@Override
public int readVersion(byte[] data) {
Objects.requireNonNull(data);
return ClassTweakerReader.readVersion(data);
}

@Override
public void read(ClassTweakerVisitor visitor, byte[] data, String modId) {
Objects.requireNonNull(visitor);
Objects.requireNonNull(data);
Objects.requireNonNull(modId);

ClassTweakerReader.create(visitor).read(data, modId);
}

@Override
public ClassTweaker readEntries(List<ClassTweakerEntry> entries) throws IOException {
final ClassTweaker classTweaker = ClassTweaker.newInstance();

for (ClassTweakerEntry entry : entries) {
ClassTweakerVisitor visitor = classTweaker;

if (!entry.local()) {
visitor = ClassTweakerVisitor.transitiveOnly(visitor);
}

entry.read(this, visitor);
}

return classTweaker;
}

@Override
public void transformJar(Path jar, ClassTweaker classTweaker) throws IOException {
new ClassTweakerJarTransformer(classTweaker).transform(jar);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.classtweaker;

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jetbrains.annotations.Nullable;

import net.fabricmc.classtweaker.api.ClassTweaker;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.ModEnvironment;

public abstract class ClassTweakerJarProcessor implements MinecraftJarProcessor<ClassTweakerJarProcessor.Spec> {
private final ClassTweakerFactory classTweakerFactory;

public ClassTweakerJarProcessor(ClassTweakerFactory classTweakerFactory) {
this.classTweakerFactory = classTweakerFactory;
}

@Override
public @Nullable ClassTweakerJarProcessor.Spec buildSpec(SpecContext context) {
var classTweakers = new ArrayList<ClassTweakerEntry>();

for (FabricModJson localMod : context.localMods()) {
classTweakers.addAll(ClassTweakerEntry.readAll(localMod, true));
}

for (FabricModJson dependentMod : context.modDependencies()) {
// TODO check and cache if it has any transitive CTs
classTweakers.addAll(ClassTweakerEntry.readAll(dependentMod, false));
}

if (classTweakers.isEmpty()) {
return null;
}

return new Spec(Collections.unmodifiableList(classTweakers));
}

public record Spec(List<ClassTweakerEntry> classTweakers) implements MinecraftJarProcessor.Spec {
List<ClassTweakerEntry> getSupportedClassTweakers(ProcessorContext context) {
return classTweakers.stream()
.filter(entry -> isSupported(entry.environment(), context))
.toList();
}

private static boolean isSupported(ModEnvironment modEnvironment, ProcessorContext context) {
if (context.isMerged()) {
// All envs are supported wth a merged jar
return true;
}

if (context.includesClient() && modEnvironment.isClient()) {
return true;
}

if (context.includesServer() && modEnvironment.isServer()) {
return true;
}

// Universal supports all jars
return modEnvironment == ModEnvironment.UNIVERSAL;
}
}

@Override
public void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException {
assert !spec.classTweakers.isEmpty();

final List<ClassTweakerEntry> classTweakers = spec.getSupportedClassTweakers(context);
final ClassTweaker classTweaker = classTweakerFactory.readEntries(classTweakers);
classTweakerFactory.transformJar(jar, classTweaker);
}
}
Loading