@@ -89,12 +89,58 @@ public static String sanitize(String mixinClassName) {
8989 private final Set <String > mixinOptions = new ObjectOpenHashSet <>();
9090 private final Map <String , String > mixinsMissingMods = new Object2ObjectOpenHashMap <>();
9191
92+ private static class PackageMetadata {
93+ String requiredModId ;
94+ FeatureLevel requiredLevel ;
95+ }
96+
97+ private final Map <String , PackageMetadata > packageMetadataCache = new HashMap <>();
98+
9299 public static boolean isFabric = ModernFixEarlyConfig .class .getClassLoader ().getResourceAsStream ("modernfix-fabric.mixins.json" ) != null ;
93100
94101 public Map <String , String > getPermanentlyDisabledMixins () {
95102 return mixinsMissingMods ;
96103 }
97104
105+ @ SuppressWarnings ("unchecked" )
106+ private static <T > T getAnnotationValue (AnnotationNode ann , String key ) {
107+ if (ann .values == null ) return null ;
108+ for (int i = 0 ; i < ann .values .size (); i += 2 ) {
109+ if (ann .values .get (i ).equals (key )) return (T ) ann .values .get (i + 1 );
110+ }
111+ return null ;
112+ }
113+
114+ private PackageMetadata loadPackageMetadata (String packageResourcePath ) {
115+ String classPath = packageResourcePath + "/package-info.class" ;
116+ try (InputStream stream = ModernFixEarlyConfig .class .getClassLoader ().getResourceAsStream (classPath )) {
117+ if (stream == null ) return new PackageMetadata ();
118+ ClassReader reader = new ClassReader (stream );
119+ ClassNode node = new ClassNode ();
120+ reader .accept (node , ClassReader .SKIP_CODE | ClassReader .SKIP_FRAMES | ClassReader .SKIP_DEBUG );
121+ PackageMetadata meta = new PackageMetadata ();
122+ List <AnnotationNode > annotations = new ArrayList <>();
123+ if (node .invisibleAnnotations != null ) annotations .addAll (node .invisibleAnnotations );
124+ if (node .visibleAnnotations != null ) annotations .addAll (node .visibleAnnotations );
125+ for (AnnotationNode annotation : annotations ) {
126+ if (Objects .equals (annotation .desc , MIXIN_REQUIRES_MOD_DESC )) {
127+ meta .requiredModId = getAnnotationValue (annotation , "value" );
128+ } else if (Objects .equals (annotation .desc , FEATURE_LEVEL_ANNOTATION_DESC )) {
129+ String [] enumVal = getAnnotationValue (annotation , "value" );
130+ meta .requiredLevel = FeatureLevel .valueOf (enumVal [1 ]);
131+ }
132+ }
133+ return meta ;
134+ } catch (IOException e ) {
135+ LOGGER .error ("Error scanning package-info " + classPath , e );
136+ return new PackageMetadata ();
137+ }
138+ }
139+
140+ private PackageMetadata getOrLoadPackageMetadata (String packageResourcePath ) {
141+ return packageMetadataCache .computeIfAbsent (packageResourcePath , this ::loadPackageMetadata );
142+ }
143+
98144 private void scanForAndBuildMixinOptions () {
99145 List <String > configFiles = ImmutableList .of ("modernfix-modernfix.mixins.json" );
100146 List <String > mixinPaths = new ArrayList <>();
@@ -133,27 +179,41 @@ private void scanForAndBuildMixinOptions() {
133179 } else if (Objects .equals (annotation .desc , MIXIN_CLIENT_ONLY_DESC )) {
134180 isClientOnly = true ;
135181 } else if (Objects .equals (annotation .desc , MIXIN_REQUIRES_MOD_DESC )) {
136- for (int i = 0 ; i < annotation .values .size (); i += 2 ) {
137- if (annotation .values .get (i ).equals ("value" )) {
138- String modId = (String )annotation .values .get (i + 1 );
139- if (modId != null ) {
140- requiredModPresent = modId .startsWith ("!" ) ? !modPresent (modId .substring (1 )) : modPresent (modId );
141- requiredModId = modId ;
142- }
143- break ;
144- }
182+ String modId = getAnnotationValue (annotation , "value" );
183+ if (modId != null ) {
184+ requiredModPresent = modId .startsWith ("!" ) ? !modPresent (modId .substring (1 )) : modPresent (modId );
185+ requiredModId = modId ;
145186 }
146187 } else if (Objects .equals (annotation .desc , MIXIN_DEV_ONLY_DESC )) {
147188 isDevOnly = true ;
148189 } else if (Objects .equals (annotation .desc , FEATURE_LEVEL_ANNOTATION_DESC )) {
149- for (int i = 0 ; i < annotation .values .size (); i += 2 ) {
150- if (annotation .values .get (i ).equals ("value" )) {
151- // ASM stores enum annotation values as String[]{typeDescriptor, constantName}
152- String [] enumVal = (String []) annotation .values .get (i + 1 );
153- requiredLevel = FeatureLevel .valueOf (enumVal [1 ]);
154- break ;
190+ // ASM stores enum annotation values as String[]{typeDescriptor, constantName}
191+ String [] enumVal = getAnnotationValue (annotation , "value" );
192+ requiredLevel = FeatureLevel .valueOf (enumVal [1 ]);
193+ }
194+ }
195+ // Merge constraints from ancestor package-info files (up to the mixin root)
196+ String classPackagePath = mixinPath .substring (0 , mixinPath .lastIndexOf ('/' ));
197+ int mixinRootEnd = classPackagePath .indexOf ("/mixin" );
198+ if (mixinRootEnd >= 0 ) {
199+ String mixinRoot = classPackagePath .substring (0 , mixinRootEnd + "/mixin" .length ());
200+ String walkPkg = mixinRoot ;
201+ while (walkPkg .length () < classPackagePath .length ()) {
202+ int nextSlash = classPackagePath .indexOf ('/' , walkPkg .length () + 1 );
203+ walkPkg = (nextSlash == -1 ) ? classPackagePath : classPackagePath .substring (0 , nextSlash );
204+ PackageMetadata pkgMeta = getOrLoadPackageMetadata (walkPkg );
205+ if (requiredModPresent && pkgMeta .requiredModId != null ) {
206+ boolean present = pkgMeta .requiredModId .startsWith ("!" )
207+ ? !modPresent (pkgMeta .requiredModId .substring (1 ))
208+ : modPresent (pkgMeta .requiredModId );
209+ if (!present ) {
210+ requiredModPresent = false ;
211+ requiredModId = pkgMeta .requiredModId ;
155212 }
156213 }
214+ if (pkgMeta .requiredLevel != null && pkgMeta .requiredLevel .ordinal () > requiredLevel .ordinal ()) {
215+ requiredLevel = pkgMeta .requiredLevel ;
216+ }
157217 }
158218 }
159219 if (isMixin && (!isDevOnly || ModernFixPlatformHooks .INSTANCE .isDevEnv ())) {
0 commit comments