Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
64348c0
Create mixin config AP
Phoenix-Starlight Nov 23, 2023
08078c8
Integrate AP
Phoenix-Starlight Nov 23, 2023
73fdafd
Spotless
Phoenix-Starlight Nov 23, 2023
631913c
Cleanup
Phoenix-Starlight Nov 25, 2023
4645c26
Split annotations to separate project
Phoenix-Starlight Dec 3, 2023
2660df6
Emit compile-time errors for mislabeled mixins that targets client si…
Phoenix-Starlight Dec 3, 2023
ecfc4eb
Fix NPEs
Phoenix-Starlight Dec 3, 2023
cff63f8
Cleanup
Phoenix-Starlight Dec 3, 2023
fe7532a
Fix compile-time errors
Phoenix-Starlight Dec 5, 2023
1d88e1b
Fix file naming
Phoenix-Starlight Dec 5, 2023
587fb08
Exclude tests
Phoenix-Starlight Dec 5, 2023
06a02dc
Add platform support and rework detection logic
Phoenix-Starlight Dec 8, 2023
077f726
Fix platform support
Phoenix-Starlight Dec 10, 2023
0396d43
Fix platform detection again
Phoenix-Starlight Dec 10, 2023
35880b3
Enable debug
Phoenix-Starlight Dec 10, 2023
f6cba43
Revert debug
Phoenix-Starlight Dec 10, 2023
52293cb
Brute force all markers
Phoenix-Starlight Dec 11, 2023
e190a71
Fix overloads
Phoenix-Starlight Dec 11, 2023
85dbaed
Debug again
Phoenix-Starlight Dec 11, 2023
96b84af
Bring in platform classes
Phoenix-Starlight Dec 11, 2023
1936dea
Retry
Phoenix-Starlight Dec 11, 2023
81d30d5
Add in neoforge maven
Phoenix-Starlight Dec 11, 2023
47b614e
Rework platform detection logic again
Phoenix-Starlight Dec 11, 2023
6f76906
Don't rely on enums being present
Phoenix-Starlight Dec 11, 2023
050bb43
Runtime only classes
Phoenix-Starlight Dec 11, 2023
c6d8303
Disable debug
Phoenix-Starlight Dec 11, 2023
6876f37
Prevent CME
Phoenix-Starlight Dec 11, 2023
244fb8b
Fix Forge mixins
Phoenix-Starlight Dec 11, 2023
53c96d3
Remove brute force debug
Phoenix-Starlight Dec 11, 2023
da5e642
Shadow relocate Fabric Mixin and intentionally override Mixin AP
Phoenix-Starlight Dec 13, 2023
8a349ce
Don't assume common mixins is always present
Phoenix-Starlight Dec 13, 2023
7319e08
Rework shadowing
Phoenix-Starlight Dec 13, 2023
94e9c63
Unify shadow version
Phoenix-Starlight Dec 14, 2023
542ae3e
Fix property substitution
Phoenix-Starlight Dec 14, 2023
0c4912b
Unshadow mixin processor
Phoenix-Starlight Dec 14, 2023
68952d8
Depend on shadow config
Phoenix-Starlight Dec 14, 2023
2870a42
Manually shadow Mixin
Phoenix-Starlight Dec 15, 2023
6a12f72
Unshadow mixin again, wean off dependency
Phoenix-Starlight Dec 17, 2023
83625bd
Reunify AP
Phoenix-Starlight Dec 17, 2023
e69ab1f
Customize reshadowing of dependencies
Phoenix-Starlight Dec 17, 2023
d3968e0
Tidy up
Phoenix-Starlight Dec 17, 2023
0836ac6
In the name of type-safety
Phoenix-Starlight Dec 19, 2023
65ecdd6
Add annotation to ignore mixins
Phoenix-Starlight Dec 23, 2023
cfae648
Remove throws declaration
Phoenix-Starlight Dec 24, 2023
bf9d45f
Spotless
Phoenix-Starlight Dec 24, 2023
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
60 changes: 60 additions & 0 deletions annotation-processor/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
plugins {
id 'com.github.johnrengelman.shadow'
id 'java-library'
id 'com.diffplug.spotless'
}

repositories {
mavenCentral()
maven { url uri("https://maven.fabricmc.net") }
maven { url "https://maven.neoforged.net/releases" }
}

dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
compileOnly 'com.google.auto.service:auto-service:1.1.1'

implementation 'com.google.code.gson:gson:2.10.1'
shadow 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.auto:auto-common:1.2.1'
shadow 'com.google.auto:auto-common:1.2.1'
implementation 'com.google.guava:guava:21.0'
shadow 'com.google.guava:guava:21.0'

implementation project(":annotations")
shadow project(":annotations")
// Shadow annotations
implementation 'net.fabricmc:sponge-mixin:0.12.5+'
implementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
implementation 'net.minecraftforge:mergetool:1.1.7'
implementation 'net.neoforged:mergetool:2.0.2'
}

tasks.withType(JavaCompile) {
options.compilerArgs += '--enable-preview'
options.release = 17
}

shadowJar {
dependencies {
include(dependency('net.fabricmc:sponge-mixin:'))
include(dependency('net.fabricmc:fabric-loader:'))
include(dependency(':mergetool:'))
}
// shadowJar bug
include '*.jar'
include 'META-INF/services/javax.annotation.processing.Processor'
include 'org/spongepowered/asm/mixin/Mixin.class'
include 'org/fury_phoenix/**/*'
include {it.getName() == 'OnlyIn.class'}
include {it.getName() == 'Dist.class'}
include {it.getName() == 'Environment.class'}
include {it.getName() == 'EnvType.class'}
}

spotless {
java {
removeUnusedImports()
}
}
version = '1.1.4'
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.fury_phoenix.mixinAp.annotation;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import net.fabricmc.api.Environment;

import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.IgnoreMixin;
import org.fury_phoenix.mixinAp.util.TypedAccessorMap;
import org.spongepowered.asm.mixin.Mixin;

import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static java.util.AbstractMap.SimpleImmutableEntry;

public class ClientMixinValidator {

private final Messager messager;

private final Elements elemUtils;

private final Types types;

private final boolean debug;

private static final TypedAccessorMap<Annotation> markers = new TypedAccessorMap<>();

private static final Map.Entry<Class<Environment>, Function<? super Environment, ?>>
FabricAccessor = new SimpleImmutableEntry<>(Environment.class, Environment::value);

private static final Map.Entry<
Class<net.minecraftforge.api.distmarker.OnlyIn>,
Function<? super net.minecraftforge.api.distmarker.OnlyIn, ?>>
ForgeAccessor = new SimpleImmutableEntry<>(
net.minecraftforge.api.distmarker.OnlyIn.class,
net.minecraftforge.api.distmarker.OnlyIn::value
);

private static final Map.Entry<
Class<net.neoforged.api.distmarker.OnlyIn>,
Function<? super net.neoforged.api.distmarker.OnlyIn, ?>>
NeoForgeAccessor = new SimpleImmutableEntry<>(
net.neoforged.api.distmarker.OnlyIn.class,
net.neoforged.api.distmarker.OnlyIn::value
);

static {
markers.put(FabricAccessor);
markers.put(ForgeAccessor);
markers.put(NeoForgeAccessor);
}

private static final Collection<String> unannotatedClasses = new HashSet<>();

public ClientMixinValidator(ProcessingEnvironment env) {
debug = Boolean.valueOf(env.getOptions().get("org.fury_phoenix.mixinAp.validator.debug"));
messager = env.getMessager();
elemUtils = env.getElementUtils();
types = env.getTypeUtils();
}

public boolean validateMixin(TypeElement annotatedMixinClass) {
return targetsClient(annotatedMixinClass) &&
(annotatedMixinClass.getAnnotation(ClientOnlyMixin.class) == null);
}

public boolean targetsClient(TypeElement annotatedMixinClass) {
return targetsClient(getTargets(annotatedMixinClass)) &&
!isIgnored(annotatedMixinClass);
}

private boolean targetsClient(Collection<?> classTargets) {
return classTargets.stream().anyMatch(this::targetsClient);
}

private boolean targetsClient(Object classTarget) {
return switch (classTarget) {
case TypeElement te ->
isClientMarked(te);
case TypeMirror tm -> {
var el = types.asElement(tm);
yield el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
}
// If you're using a dollar sign in class names you are insane
case String s -> {
var te =
elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
yield te != null ? targetsClient(te) : warn(s);
}
default ->
throw new IllegalArgumentException("Unhandled type: "
+ classTarget.getClass() + "\n" + "Stringified contents: "
+ classTarget.toString());
};
}

private boolean isClientMarked(TypeElement te) {
for (var entry : markers.entrySet()) {
var marker = te.getAnnotation(entry.getKey());
if(marker == null) continue;

return entry.getValue().apply(marker).toString().equals("CLIENT");
}
if(debug && unannotatedClasses.add(te.toString())) {
messager.printMessage(Diagnostic.Kind.WARNING,
"No marker annotations present on " + te + "!");
}
return false;
}

private boolean isIgnored(TypeElement te) {
if(te.getAnnotation(IgnoreMixin.class) != null) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(te.toString()) + " is ignored!");
return true;
}
return false;
}

private boolean warn(Object o) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(o.toString()) + " can't be loaded, so it is skipped!");
return false;
}

public Map.Entry<? extends CharSequence, ? extends CharSequence>
getClientMixinEntry(TypeElement annotatedMixinClass) {
return new SimpleImmutableEntry<>(
annotatedMixinClass.getQualifiedName(),
getTargets(annotatedMixinClass)
.stream()
.filter(this::targetsClient)
.map(Object::toString)
.map(ClientMixinValidator::toSourceString)
.collect(Collectors.joining(", "))
);
}

private Collection<Object> getTargets(TypeElement annotatedMixinClass) {
Collection<? extends TypeMirror> clzsses = Set.of();
Collection<? extends String> imaginaries = Set.of();
TypeMirror MixinElement = elemUtils.getTypeElement(Mixin.class.getName()).asType();
for (var mirror : annotatedMixinClass.getAnnotationMirrors()) {
if(!types.isSameType(mirror.getAnnotationType(), MixinElement))
continue;

@SuppressWarnings("unchecked")
var wrappedClzss = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "value").getValue();

clzsses = wrappedClzss.stream()
.map(AnnotationValue::getValue)
.map(TypeMirror.class::cast)
.collect(Collectors.toSet());

@SuppressWarnings("unchecked")
var wrappedStrings = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "targets").getValue();

imaginaries = wrappedStrings.stream()
.map(AnnotationValue::getValue)
.map(String.class::cast)
.collect(Collectors.toSet());
}
return Stream.of(clzsses, imaginaries)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}

public static String toSourceString(String bytecodeName) {
return bytecodeName.replaceAll("\\/", ".");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.fury_phoenix.mixinAp.annotation;

import com.google.auto.service.AutoService;
import com.google.common.base.Throwables;

import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

import org.fury_phoenix.mixinAp.config.MixinConfig;

@SupportedAnnotationTypes({"org.spongepowered.asm.mixin.Mixin", "org.embeddedt.modernfix.annotation.ClientOnlyMixin"})
@SupportedOptions({"rootProject.name", "project.name", "org.fury_phoenix.mixinAp.validator.debug"})
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class MixinProcessor extends AbstractProcessor {

// Remember to call toString when using aliases
private static final Map<String, String> aliases = Map.of(
"Mixin", "mixins",
"ClientOnlyMixin", "client"
);

private final Map<String, List<String>> mixinConfigList = new HashMap<>();

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
if(roundEnv.processingOver()){
filterMixinSets();
// create record for serialization, compute package name
String packageName = Optional.ofNullable(mixinConfigList.get("mixins"))
.orElse(mixinConfigList.get("client"))
.get(0).split("(?<=mixin)")[0];
finalizeMixinConfig();
new MixinConfig(packageName,
mixinConfigList.get("mixins"),
mixinConfigList.get("client")
).generateMixinConfig(processingEnv);
} else {
processMixins(annotations, roundEnv);
}
} catch (Exception e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
Throwables.getStackTraceAsString(e));
throw new RuntimeException(e);
// Halt the AP to prevent nonsense errors
}
return false;
}

private void processMixins(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedMixins = roundEnv.getElementsAnnotatedWith(annotation);

Stream<TypeElement> mixinStream =
annotatedMixins.stream()
.map(TypeElement.class::cast);

validateCommonMixins(annotation, mixinStream);

List<String> mixins =
annotatedMixins.stream()
.map(TypeElement.class::cast)
.map(TypeElement::toString)
.collect(Collectors.toList());

mixinConfigList.putIfAbsent(aliases.get(annotation.getSimpleName().toString()), mixins);
}
}

private void filterMixinSets() {
List<String> commonSet = mixinConfigList.get("mixins");
if(commonSet == null) return;
commonSet.removeAll(mixinConfigList.get("client"));
}

private void validateCommonMixins(TypeElement annotation, Stream<TypeElement> mixins) {
if(!annotation.getSimpleName().toString().equals("Mixin"))
return;
ClientMixinValidator validator = new ClientMixinValidator(processingEnv);
// The implementation may throw a CME
mixins.sequential()
.filter(validator::validateMixin)
.map(validator::getClientMixinEntry)
.forEach(this::logClientClassTarget);
}

private void logClientClassTarget(Map.Entry<? extends CharSequence, ? extends CharSequence> mixin) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Mixin " + mixin.getKey() + " targets client-side classes: " + mixin.getValue());
}

private void finalizeMixinConfig() {
// relativize class names
for(var list : mixinConfigList.values()) {
list.replaceAll(className -> className.split("(?<=mixin.)")[1]);
}
}
}
Loading