Skip to content

Commit 6e231e4

Browse files
committed
Add support for class-level indexing too
1 parent 427dd3d commit 6e231e4

3 files changed

Lines changed: 387 additions & 1 deletion

File tree

java/gazelle/javaconfig/config.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,37 @@ const (
2727
//
2828
JavaExtensionLibraryKindsKey = "java_extension_library_kinds"
2929

30+
// JavaGazelleProvidedPackagesAttr is a well-known private attribute key that other
31+
// Gazelle plugins can set on their rules to declare Java packages they provide.
32+
// The value should be []string where each string is a Java package name
33+
// (e.g., "com.example.proto").
34+
//
35+
// When set, the Java plugin will register these packages in Gazelle's RuleIndex,
36+
// enabling package-level dependency resolution for the external rule.
37+
//
38+
// Example usage in another plugin's GenerateRules:
39+
//
40+
// r.SetPrivateAttr(javaconfig.JavaGazelleProvidedPackagesAttr, []string{"com.example"})
41+
//
42+
JavaGazelleProvidedPackagesAttr = "_java_gazelle_provided_packages"
43+
44+
// JavaGazelleProvidedClassesAttr is a well-known private attribute key that other
45+
// Gazelle plugins can set on their rules to declare fully-qualified Java class names
46+
// they provide. The value should be []string where each string is a fully-qualified
47+
// class name (e.g., "com.example.Person").
48+
//
49+
// When set, the Java plugin will add these classes to its class export cache,
50+
// enabling class-level dependency resolution for split packages.
51+
//
52+
// Example usage in another plugin's GenerateRules:
53+
//
54+
// r.SetPrivateAttr(javaconfig.JavaGazelleProvidedClassesAttr, []string{
55+
// "com.example.Person",
56+
// "com.example.Address",
57+
// })
58+
//
59+
JavaGazelleProvidedClassesAttr = "_java_gazelle_provided_classes"
60+
3061
// JavaExcludeArtifact tells the resolver to disregard a given maven artifact.
3162
// Can be repeated.
3263
JavaExcludeArtifact = "java_exclude_artifact"

java/gazelle/resolve.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)