@@ -70,18 +70,85 @@ func (jr *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []reso
7070 // Cache config for use in Embeds, which doesn't receive config in its interface
7171 jr .lastConfig = c
7272
73- if ! isJvmLibrary (c , r .Kind ()) && r .Kind () != "java_test_suite" && r .Kind () != "java_export" {
73+ // Check for external plugin contributions via well-known private attributes
74+ providedPkgs , hasPkgs := r .PrivateAttr (javaconfig .JavaGazelleProvidedPackagesAttr ).([]string )
75+ providedClasses , hasClasses := r .PrivateAttr (javaconfig .JavaGazelleProvidedClassesAttr ).([]string )
76+
77+ // Early return if this rule is not a provider (neither a recognized JVM library
78+ // nor has external contribution attributes)
79+ if ! isJvmLibrary (c , r .Kind ()) && r .Kind () != "java_test_suite" && r .Kind () != "java_export" && ! hasPkgs && ! hasClasses {
7480 return nil
7581 }
7682
7783 lbl := label .New ("" , f .Pkg , r .Name ())
7884
85+ // Determine testonly status for external contributions
86+ isTestOnly := false
87+ if literalExpr , ok := r .Attr ("testonly" ).(* build.LiteralExpr ); ok {
88+ isTestOnly = literalExpr .Token == "True"
89+ }
90+
7991 var out []resolve.ImportSpec
92+
93+ // Handle packages from internal Java/Kotlin rule generation
8094 if pkgs := r .PrivateAttr (packagesKey ); pkgs != nil {
8195 for _ , pkg := range pkgs .([]types.ResolvableJavaPackage ) {
8296 out = append (out , resolve.ImportSpec {Lang : languageName , Imp : pkg .String ()})
8397 }
8498 }
99+
100+ // Handle packages contributed by external plugins
101+ for _ , pkgName := range providedPkgs {
102+ pkg := types .NewResolvableJavaPackage (types .NewPackageName (pkgName ), isTestOnly , false )
103+ out = append (out , resolve.ImportSpec {Lang : languageName , Imp : pkg .String ()})
104+ }
105+
106+ // Handle classes contributed by external plugins - add to classExportCache
107+ // and infer package registrations if not explicitly provided
108+ if hasClasses && len (providedClasses ) > 0 {
109+ var classes []types.ClassName
110+ inferredPkgs := make (map [string ]bool )
111+
112+ for _ , fqcn := range providedClasses {
113+ cn , err := types .ParseClassName (fqcn )
114+ if err != nil {
115+ log .Debug ().Str ("class" , fqcn ).Err (err ).Msg ("failed to parse contributed class name" )
116+ continue
117+ }
118+ classes = append (classes , * cn )
119+
120+ // Track package names for inference if no explicit packages provided
121+ if ! hasPkgs {
122+ inferredPkgs [cn .PackageName ().Name ] = true
123+ }
124+ }
125+
126+ if len (classes ) > 0 {
127+ jr .lang .classExportCache [lbl .String ()] = classExportInfo {
128+ classes : classes ,
129+ testonly : isTestOnly ,
130+ }
131+ log .Debug ().
132+ Str ("label" , lbl .String ()).
133+ Int ("classes" , len (classes )).
134+ Bool ("testonly" , isTestOnly ).
135+ Msg ("registered external plugin classes in classExportCache" )
136+ }
137+
138+ // If no explicit packages were provided, infer them from the class names
139+ // so that package-level resolution can find this rule
140+ if ! hasPkgs && len (inferredPkgs ) > 0 {
141+ for pkgName := range inferredPkgs {
142+ pkg := types .NewResolvableJavaPackage (types .NewPackageName (pkgName ), isTestOnly , false )
143+ out = append (out , resolve.ImportSpec {Lang : languageName , Imp : pkg .String ()})
144+ }
145+ log .Debug ().
146+ Str ("label" , lbl .String ()).
147+ Int ("inferred_packages" , len (inferredPkgs )).
148+ Msg ("inferred package registrations from contributed classes" )
149+ }
150+ }
151+
85152 // NOTE: We intentionally do NOT register classes in Gazelle's global RuleIndex.
86153 // Class-level resolution uses a lazy, per-package index built only when needed
87154 // (when package-level resolution is ambiguous due to split packages).
0 commit comments