Skip to content

Commit f6c7b31

Browse files
committed
Merge remote-tracking branch 'origin/1.16' into 1.18
2 parents 6f0d6e4 + e301d7d commit f6c7b31

File tree

28 files changed

+538
-88
lines changed

28 files changed

+538
-88
lines changed

annotation-processor/build.gradle

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
plugins {
2+
id 'com.github.johnrengelman.shadow'
3+
id 'java-library'
4+
id 'com.diffplug.spotless'
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
maven { url uri("https://maven.fabricmc.net") }
10+
maven { url "https://maven.neoforged.net/releases" }
11+
}
12+
13+
dependencies {
14+
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
15+
compileOnly 'com.google.auto.service:auto-service:1.1.1'
16+
17+
implementation 'com.google.code.gson:gson:2.10.1'
18+
shadow 'com.google.code.gson:gson:2.10.1'
19+
implementation 'com.google.auto:auto-common:1.2.1'
20+
shadow 'com.google.auto:auto-common:1.2.1'
21+
implementation 'com.google.guava:guava:21.0'
22+
shadow 'com.google.guava:guava:21.0'
23+
24+
implementation project(":annotations")
25+
shadow project(":annotations")
26+
// Shadow annotations
27+
implementation 'net.fabricmc:sponge-mixin:0.12.5+'
28+
implementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
29+
implementation 'net.minecraftforge:mergetool:1.1.7'
30+
implementation 'net.neoforged:mergetool:2.0.2'
31+
}
32+
33+
tasks.withType(JavaCompile) {
34+
options.compilerArgs += '--enable-preview'
35+
options.release = 17
36+
}
37+
38+
shadowJar {
39+
dependencies {
40+
include(dependency('net.fabricmc:sponge-mixin:'))
41+
include(dependency('net.fabricmc:fabric-loader:'))
42+
include(dependency(':mergetool:'))
43+
}
44+
// shadowJar bug
45+
include '*.jar'
46+
include 'META-INF/services/javax.annotation.processing.Processor'
47+
include 'org/spongepowered/asm/mixin/Mixin.class'
48+
include 'org/fury_phoenix/**/*'
49+
include {it.getName() == 'OnlyIn.class'}
50+
include {it.getName() == 'Dist.class'}
51+
include {it.getName() == 'Environment.class'}
52+
include {it.getName() == 'EnvType.class'}
53+
}
54+
55+
spotless {
56+
java {
57+
removeUnusedImports()
58+
}
59+
}
60+
version = '1.1.4'
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package org.fury_phoenix.mixinAp.annotation;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.util.Collection;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.Set;
9+
import java.util.function.Function;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.Stream;
12+
13+
import javax.annotation.processing.Messager;
14+
import javax.annotation.processing.ProcessingEnvironment;
15+
import javax.lang.model.element.AnnotationValue;
16+
import javax.lang.model.element.TypeElement;
17+
import javax.lang.model.type.TypeMirror;
18+
import javax.lang.model.util.Elements;
19+
import javax.lang.model.util.Types;
20+
import javax.tools.Diagnostic;
21+
22+
import net.fabricmc.api.Environment;
23+
24+
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
25+
import org.embeddedt.modernfix.annotation.IgnoreMixin;
26+
import org.fury_phoenix.mixinAp.util.TypedAccessorMap;
27+
import org.spongepowered.asm.mixin.Mixin;
28+
29+
import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
30+
import static java.util.AbstractMap.SimpleImmutableEntry;
31+
32+
public class ClientMixinValidator {
33+
34+
private final Messager messager;
35+
36+
private final Elements elemUtils;
37+
38+
private final Types types;
39+
40+
private final boolean debug;
41+
42+
private static final TypedAccessorMap<Annotation> markers = new TypedAccessorMap<>();
43+
44+
private static final Map.Entry<Class<Environment>, Function<? super Environment, ?>>
45+
FabricAccessor = new SimpleImmutableEntry<>(Environment.class, Environment::value);
46+
47+
private static final Map.Entry<
48+
Class<net.minecraftforge.api.distmarker.OnlyIn>,
49+
Function<? super net.minecraftforge.api.distmarker.OnlyIn, ?>>
50+
ForgeAccessor = new SimpleImmutableEntry<>(
51+
net.minecraftforge.api.distmarker.OnlyIn.class,
52+
net.minecraftforge.api.distmarker.OnlyIn::value
53+
);
54+
55+
private static final Map.Entry<
56+
Class<net.neoforged.api.distmarker.OnlyIn>,
57+
Function<? super net.neoforged.api.distmarker.OnlyIn, ?>>
58+
NeoForgeAccessor = new SimpleImmutableEntry<>(
59+
net.neoforged.api.distmarker.OnlyIn.class,
60+
net.neoforged.api.distmarker.OnlyIn::value
61+
);
62+
63+
static {
64+
markers.put(FabricAccessor);
65+
markers.put(ForgeAccessor);
66+
markers.put(NeoForgeAccessor);
67+
}
68+
69+
private static final Collection<String> unannotatedClasses = new HashSet<>();
70+
71+
public ClientMixinValidator(ProcessingEnvironment env) {
72+
debug = Boolean.valueOf(env.getOptions().get("org.fury_phoenix.mixinAp.validator.debug"));
73+
messager = env.getMessager();
74+
elemUtils = env.getElementUtils();
75+
types = env.getTypeUtils();
76+
}
77+
78+
public boolean validateMixin(TypeElement annotatedMixinClass) {
79+
return targetsClient(annotatedMixinClass) &&
80+
(annotatedMixinClass.getAnnotation(ClientOnlyMixin.class) == null);
81+
}
82+
83+
public boolean targetsClient(TypeElement annotatedMixinClass) {
84+
return targetsClient(getTargets(annotatedMixinClass)) &&
85+
!isIgnored(annotatedMixinClass);
86+
}
87+
88+
private boolean targetsClient(Collection<?> classTargets) {
89+
return classTargets.stream().anyMatch(this::targetsClient);
90+
}
91+
92+
private boolean targetsClient(Object classTarget) {
93+
return switch (classTarget) {
94+
case TypeElement te ->
95+
isClientMarked(te);
96+
case TypeMirror tm -> {
97+
var el = types.asElement(tm);
98+
yield el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
99+
}
100+
// If you're using a dollar sign in class names you are insane
101+
case String s -> {
102+
var te =
103+
elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
104+
yield te != null ? targetsClient(te) : warn(s);
105+
}
106+
default ->
107+
throw new IllegalArgumentException("Unhandled type: "
108+
+ classTarget.getClass() + "\n" + "Stringified contents: "
109+
+ classTarget.toString());
110+
};
111+
}
112+
113+
private boolean isClientMarked(TypeElement te) {
114+
for (var entry : markers.entrySet()) {
115+
var marker = te.getAnnotation(entry.getKey());
116+
if(marker == null) continue;
117+
118+
return entry.getValue().apply(marker).toString().equals("CLIENT");
119+
}
120+
if(debug && unannotatedClasses.add(te.toString())) {
121+
messager.printMessage(Diagnostic.Kind.WARNING,
122+
"No marker annotations present on " + te + "!");
123+
}
124+
return false;
125+
}
126+
127+
private boolean isIgnored(TypeElement te) {
128+
if(te.getAnnotation(IgnoreMixin.class) != null) {
129+
messager.printMessage(Diagnostic.Kind.WARNING,
130+
toSourceString(te.toString()) + " is ignored!");
131+
return true;
132+
}
133+
return false;
134+
}
135+
136+
private boolean warn(Object o) {
137+
messager.printMessage(Diagnostic.Kind.WARNING,
138+
toSourceString(o.toString()) + " can't be loaded, so it is skipped!");
139+
return false;
140+
}
141+
142+
public Map.Entry<? extends CharSequence, ? extends CharSequence>
143+
getClientMixinEntry(TypeElement annotatedMixinClass) {
144+
return new SimpleImmutableEntry<>(
145+
annotatedMixinClass.getQualifiedName(),
146+
getTargets(annotatedMixinClass)
147+
.stream()
148+
.filter(this::targetsClient)
149+
.map(Object::toString)
150+
.map(ClientMixinValidator::toSourceString)
151+
.collect(Collectors.joining(", "))
152+
);
153+
}
154+
155+
private Collection<Object> getTargets(TypeElement annotatedMixinClass) {
156+
Collection<? extends TypeMirror> clzsses = Set.of();
157+
Collection<? extends String> imaginaries = Set.of();
158+
TypeMirror MixinElement = elemUtils.getTypeElement(Mixin.class.getName()).asType();
159+
for (var mirror : annotatedMixinClass.getAnnotationMirrors()) {
160+
if(!types.isSameType(mirror.getAnnotationType(), MixinElement))
161+
continue;
162+
163+
@SuppressWarnings("unchecked")
164+
var wrappedClzss = (List<? extends AnnotationValue>)
165+
getAnnotationValue(mirror, "value").getValue();
166+
167+
clzsses = wrappedClzss.stream()
168+
.map(AnnotationValue::getValue)
169+
.map(TypeMirror.class::cast)
170+
.collect(Collectors.toSet());
171+
172+
@SuppressWarnings("unchecked")
173+
var wrappedStrings = (List<? extends AnnotationValue>)
174+
getAnnotationValue(mirror, "targets").getValue();
175+
176+
imaginaries = wrappedStrings.stream()
177+
.map(AnnotationValue::getValue)
178+
.map(String.class::cast)
179+
.collect(Collectors.toSet());
180+
}
181+
return Stream.of(clzsses, imaginaries)
182+
.flatMap(Collection::stream)
183+
.collect(Collectors.toSet());
184+
}
185+
186+
public static String toSourceString(String bytecodeName) {
187+
return bytecodeName.replaceAll("\\/", ".");
188+
}
189+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.fury_phoenix.mixinAp.annotation;
2+
3+
import com.google.auto.service.AutoService;
4+
import com.google.common.base.Throwables;
5+
6+
import java.util.List;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
12+
import java.util.stream.Stream;
13+
14+
import javax.annotation.processing.AbstractProcessor;
15+
import javax.annotation.processing.Processor;
16+
import javax.annotation.processing.RoundEnvironment;
17+
import javax.annotation.processing.SupportedAnnotationTypes;
18+
import javax.annotation.processing.SupportedOptions;
19+
import javax.annotation.processing.SupportedSourceVersion;
20+
import javax.lang.model.SourceVersion;
21+
import javax.lang.model.element.Element;
22+
import javax.lang.model.element.TypeElement;
23+
import javax.tools.Diagnostic;
24+
25+
import org.fury_phoenix.mixinAp.config.MixinConfig;
26+
27+
@SupportedAnnotationTypes({"org.spongepowered.asm.mixin.Mixin", "org.embeddedt.modernfix.annotation.ClientOnlyMixin"})
28+
@SupportedOptions({"rootProject.name", "project.name", "org.fury_phoenix.mixinAp.validator.debug"})
29+
@SupportedSourceVersion(SourceVersion.RELEASE_17)
30+
@AutoService(Processor.class)
31+
public class MixinProcessor extends AbstractProcessor {
32+
33+
// Remember to call toString when using aliases
34+
private static final Map<String, String> aliases = Map.of(
35+
"Mixin", "mixins",
36+
"ClientOnlyMixin", "client"
37+
);
38+
39+
private final Map<String, List<String>> mixinConfigList = new HashMap<>();
40+
41+
@Override
42+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
43+
try {
44+
if(roundEnv.processingOver()){
45+
filterMixinSets();
46+
// create record for serialization, compute package name
47+
String packageName = Optional.ofNullable(mixinConfigList.get("mixins"))
48+
.orElse(mixinConfigList.get("client"))
49+
.get(0).split("(?<=mixin)")[0];
50+
finalizeMixinConfig();
51+
new MixinConfig(packageName,
52+
mixinConfigList.get("mixins"),
53+
mixinConfigList.get("client")
54+
).generateMixinConfig(processingEnv);
55+
} else {
56+
processMixins(annotations, roundEnv);
57+
}
58+
} catch (Exception e) {
59+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
60+
Throwables.getStackTraceAsString(e));
61+
throw new RuntimeException(e);
62+
// Halt the AP to prevent nonsense errors
63+
}
64+
return false;
65+
}
66+
67+
private void processMixins(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
68+
for (TypeElement annotation : annotations) {
69+
Set<? extends Element> annotatedMixins = roundEnv.getElementsAnnotatedWith(annotation);
70+
71+
Stream<TypeElement> mixinStream =
72+
annotatedMixins.stream()
73+
.map(TypeElement.class::cast);
74+
75+
validateCommonMixins(annotation, mixinStream);
76+
77+
List<String> mixins =
78+
annotatedMixins.stream()
79+
.map(TypeElement.class::cast)
80+
.map(TypeElement::toString)
81+
.collect(Collectors.toList());
82+
83+
mixinConfigList.putIfAbsent(aliases.get(annotation.getSimpleName().toString()), mixins);
84+
}
85+
}
86+
87+
private void filterMixinSets() {
88+
List<String> commonSet = mixinConfigList.get("mixins");
89+
if(commonSet == null) return;
90+
commonSet.removeAll(mixinConfigList.get("client"));
91+
}
92+
93+
private void validateCommonMixins(TypeElement annotation, Stream<TypeElement> mixins) {
94+
if(!annotation.getSimpleName().toString().equals("Mixin"))
95+
return;
96+
ClientMixinValidator validator = new ClientMixinValidator(processingEnv);
97+
// The implementation may throw a CME
98+
mixins.sequential()
99+
.filter(validator::validateMixin)
100+
.map(validator::getClientMixinEntry)
101+
.forEach(this::logClientClassTarget);
102+
}
103+
104+
private void logClientClassTarget(Map.Entry<? extends CharSequence, ? extends CharSequence> mixin) {
105+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
106+
"Mixin " + mixin.getKey() + " targets client-side classes: " + mixin.getValue());
107+
}
108+
109+
private void finalizeMixinConfig() {
110+
// relativize class names
111+
for(var list : mixinConfigList.values()) {
112+
list.replaceAll(className -> className.split("(?<=mixin.)")[1]);
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)