Skip to content

Commit df25ba1

Browse files
minkuan88claude
authored andcommitted
Add complete KSP plugin support with auto-detection and configuration
- Add KspExtension DSL with nested compiler and processor configuration - Support version catalog references for processor configuration - Auto-detect processor_class from JAR's META-INF/services file - Configure generates_java and target_embedded_compiler per processor - Generate kt_ksp_plugin rules in root BUILD.bazel with all attributes - Add KspProcessorClassExtractor for reading processor classes from JARs - Fix Groovy closure delegation for nested DSL blocks - Add kspConfiguration support to Variant interface for extracting KSP deps - Emit plugins attribute on android_library/android_binary targets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent edbbc82 commit df25ba1

File tree

21 files changed

+558
-33
lines changed

21 files changed

+558
-33
lines changed

BUILD.bazel

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
load("@dagger//:workspace_defs.bzl", "dagger_rules")
22
load("@grab_bazel_common//toolchains:toolchains.bzl", "configure_common_toolchains")
33
load("@grab_bazel_common//tools/parcelize:parcelize.bzl", "parcelize_rules")
4-
load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_javac_options", "kt_kotlinc_options")
4+
load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_javac_options", "kt_kotlinc_options", "kt_ksp_plugin")
55

66
kt_kotlinc_options(
77
name = "kt_kotlinc_options",
@@ -28,6 +28,19 @@ dagger_rules()
2828

2929
parcelize_rules()
3030

31+
kt_ksp_plugin(
32+
name = "dagger-compiler-ksp",
33+
generates_java = True,
34+
processor_class = "dagger.internal.codegen.KspComponentProcessor$Provider",
35+
target_embedded_compiler = False,
36+
visibility = [
37+
"//visibility:public",
38+
],
39+
deps = [
40+
"@maven//:com_google_dagger_dagger_compiler",
41+
],
42+
)
43+
3144
configure_common_toolchains()
3245

3346
exports_files(["lint.xml"])

WORKSPACE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
44

55
http_archive(
66
name = "io_bazel_rules_kotlin",
7-
sha256 = "34e8c0351764b71d78f76c8746e98063979ce08dcf1a91666f3f3bc2949a533d",
8-
url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.5/rules_kotlin-v1.9.5.tar.gz",
7+
sha256 = "3b772976fec7bdcda1d84b9d39b176589424c047eb2175bed09aac630e50af43",
8+
url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.6/rules_kotlin-v1.9.6.tar.gz",
99
)
1010

1111
KOTLIN_VERSION = "1.9.25"
1212

1313
KOTLINC_RELEASE_SHA = "6ab72d6144e71cbbc380b770c2ad380972548c63ab6ed4c79f11c88f2967332e"
1414

15-
KSP_VERSION = "1.8.10-1.0.9"
15+
KSP_VERSION = "1.9.25-1.0.20"
1616

17-
KSP_COMPILER_RELEASE_SHA = "2f60c27956e4033c4c94355624e3fe88f255df42d8b67af44c1f2cdcbd513a13"
17+
KSP_COMPILER_RELEASE_SHA = "3a2d24623409ac5904c87a7e130f5b39ce9fd67ca8b44e4fe5b784a6ec102b81"
1818

1919
load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories", "kotlinc_version", "ksp_version")
2020

build.gradle

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,21 @@ grazel {
122122
}
123123
kotlin {
124124
httpArchiveRepository {
125-
url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.5/rules_kotlin-v1.9.5.tar.gz"
126-
sha256 = "34e8c0351764b71d78f76c8746e98063979ce08dcf1a91666f3f3bc2949a533d"
125+
url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.9.6/rules_kotlin-v1.9.6.tar.gz"
126+
sha256 = "3b772976fec7bdcda1d84b9d39b176589424c047eb2175bed09aac630e50af43"
127127
}
128128
compiler {
129129
tag = "1.9.25"
130130
sha = "6ab72d6144e71cbbc380b770c2ad380972548c63ab6ed4c79f11c88f2967332e"
131131
}
132-
kspCompiler {
133-
tag = "1.8.10-1.0.9"
134-
sha = "2f60c27956e4033c4c94355624e3fe88f255df42d8b67af44c1f2cdcbd513a13"
132+
ksp {
133+
compiler {
134+
tag = "1.9.25-1.0.20"
135+
sha = "3a2d24623409ac5904c87a7e130f5b39ce9fd67ca8b44e4fe5b784a6ec102b81"
136+
}
137+
processor("com.google.dagger:dagger-compiler") {
138+
generatesJava = true
139+
}
135140
}
136141
toolchain {
137142
enabled = true

grazel-gradle-plugin/src/main/kotlin/com/grab/grazel/bazel/rules/AndroidRules.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ internal fun StatementsBuilder.androidBinary(
137137
resourceSets: Assignee? = null,
138138
resValuesData: ResValuesData,
139139
deps: List<BazelDependency>,
140+
plugins: List<BazelDependency> = emptyList(),
140141
assetsGlob: List<String> = emptyList(),
141142
assetsDir: String? = null,
142143
buildConfigData: BuildConfigData,
@@ -178,6 +179,9 @@ internal fun StatementsBuilder.androidBinary(
178179
deps.notEmpty {
179180
"deps" `=` array(deps.map(BazelDependency::toString).quote)
180181
}
182+
plugins.notEmpty {
183+
"plugins" `=` array(plugins.map(BazelDependency::toString).map(String::quote))
184+
}
181185
assetsDir?.let {
182186
"assets" `=` glob(assetsGlob.quote)
183187
"assets_dir" `=` assetsDir.quote
@@ -210,6 +214,7 @@ internal fun StatementsBuilder.androidLibrary(
210214
enableDataBinding: Boolean = false,
211215
enableCompose: Boolean = false,
212216
deps: List<BazelDependency>,
217+
plugins: List<BazelDependency> = emptyList(),
213218
tags: List<String> = emptyList(),
214219
assetsGlob: List<String> = emptyList(),
215220
assetsDir: String? = null,
@@ -236,6 +241,9 @@ internal fun StatementsBuilder.androidLibrary(
236241
deps.notEmpty {
237242
"deps" `=` array(deps.map(BazelDependency::toString).map(String::quote))
238243
}
244+
plugins.notEmpty {
245+
"plugins" `=` array(plugins.map(BazelDependency::toString).map(String::quote))
246+
}
239247
if (enableDataBinding) {
240248
"enable_data_binding" `=` enableDataBinding.toString().capitalize()
241249
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2022 Grabtaxi Holdings PTE LTD (GRAB)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.grab.grazel.bazel.rules
18+
19+
import com.grab.grazel.bazel.starlark.BazelDependency
20+
import com.grab.grazel.bazel.starlark.StatementsBuilder
21+
import com.grab.grazel.bazel.starlark.array
22+
import com.grab.grazel.bazel.starlark.load
23+
import com.grab.grazel.bazel.starlark.quote
24+
import com.grab.grazel.gradle.KspProcessor
25+
import com.grab.grazel.gradle.hasKsp
26+
import org.gradle.api.Project
27+
import org.gradle.api.artifacts.ExternalDependency
28+
29+
/**
30+
* Generates the target name for a KSP plugin rule.
31+
* Converts maven coordinates to a valid Bazel target name.
32+
* Example: "com.squareup.moshi:moshi-kotlin-codegen" -> "moshi-kotlin-codegen-ksp"
33+
*/
34+
fun kspTargetName(processor: KspProcessor): String {
35+
return "${processor.name}-ksp"
36+
}
37+
38+
/**
39+
* Creates a BazelDependency reference to a KSP plugin target in root BUILD.bazel.
40+
* Example: //:room-compiler-ksp
41+
*/
42+
fun kspPluginTarget(processor: KspProcessor): BazelDependency =
43+
BazelDependency.StringDependency("//:" + kspTargetName(processor))
44+
45+
/**
46+
* Converts a KSP processor to its maven label format.
47+
* Example: "@maven//:androidx_room_room_compiler"
48+
*/
49+
private fun KspProcessor.toMavenLabel(): String {
50+
val groupPart = group.replace(".", "_").replace("-", "_")
51+
val namePart = name.replace(".", "_").replace("-", "_")
52+
return "@maven//:${groupPart}_$namePart"
53+
}
54+
55+
/**
56+
* Generates `kt_ksp_plugin` rules in root BUILD.bazel for all KSP processors.
57+
* These consolidated targets can be referenced by modules using KSP.
58+
*
59+
* Example output:
60+
* ```starlark
61+
* load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_ksp_plugin")
62+
*
63+
* kt_ksp_plugin(
64+
* name = "dagger-compiler-ksp",
65+
* deps = ["@maven//:com_google_dagger_dagger_compiler"],
66+
* processor_class = "dagger.internal.codegen.KspComponentProcessor$Provider",
67+
* generates_java = True,
68+
* visibility = ["//visibility:public"],
69+
* )
70+
* ```
71+
*/
72+
fun StatementsBuilder.kspPluginRules(kspProcessors: Set<KspProcessor>) {
73+
if (kspProcessors.isEmpty()) return
74+
75+
load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_ksp_plugin")
76+
77+
kspProcessors.forEach { processor ->
78+
rule("kt_ksp_plugin") {
79+
"name" `=` kspTargetName(processor).quote
80+
"deps" `=` array(listOf(processor.toMavenLabel()).map(String::quote))
81+
"processor_class" `=` processor.processorClass.quote
82+
"generates_java" `=` processor.generatesJava.toString().replaceFirstChar { it.titlecase() }
83+
"target_embedded_compiler" `=` processor.targetEmbeddedCompiler.toString().replaceFirstChar { it.titlecase() }
84+
"visibility" `=` array(listOf("//visibility:public".quote))
85+
}
86+
}
87+
}
88+
89+
/**
90+
* Extracts KSP plugin targets for a module based on its KSP configurations.
91+
* Returns references to root-level kt_ksp_plugin targets (e.g., //:room-compiler-ksp).
92+
*
93+
* @param project The project to extract KSP plugin targets from
94+
* @return List of BazelDependency references to root-level KSP plugin targets
95+
*/
96+
internal fun kspPluginDeps(project: Project): List<BazelDependency> {
97+
if (!project.hasKsp) return emptyList()
98+
99+
// Filter out KSP's own internal dependencies (symbol-processing-*)
100+
val kspInternalGroup = "com.google.devtools.ksp"
101+
102+
return project.configurations
103+
.filter { config ->
104+
val name = config.name
105+
name.startsWith("ksp") &&
106+
!name.contains("Test", ignoreCase = true) &&
107+
!name.contains("AndroidTest", ignoreCase = true)
108+
}
109+
.flatMap { config -> config.allDependencies.filterIsInstance<ExternalDependency>() }
110+
.filter { dep -> dep.group != kspInternalGroup }
111+
.distinctBy { "${it.group}:${it.name}" }
112+
.map { dep ->
113+
val processor = KspProcessor(
114+
group = dep.group ?: "",
115+
name = dep.name,
116+
version = dep.version
117+
)
118+
kspPluginTarget(processor)
119+
}
120+
}

grazel-gradle-plugin/src/main/kotlin/com/grab/grazel/extension/KotlinExtension.kt

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import com.grab.grazel.bazel.rules.BazelRepositoryRule
2020
import com.grab.grazel.bazel.rules.GitRepositoryRule
2121
import com.grab.grazel.bazel.rules.HttpArchiveRule
2222
import groovy.lang.Closure
23+
import org.gradle.api.artifacts.MinimalExternalModuleDependency
24+
import org.gradle.api.provider.Provider
2325

2426
internal const val RULE_KOTLIN_NAME = "io_bazel_rules_kotlin"
2527
internal const val RULES_KOTLIN_SHA =
@@ -82,6 +84,91 @@ data class KspCompiler(
8284
var sha: String? = null
8385
)
8486

87+
/**
88+
* Configuration for a specific KSP processor.
89+
* Used to specify attributes that cannot be auto-detected from JAR metadata.
90+
*
91+
* @param generatesJava Whether this processor generates Java code (default: false)
92+
* @param targetEmbeddedCompiler Whether to use embedded Kotlin compiler (default: false)
93+
*/
94+
data class KspProcessorConfig(
95+
var generatesJava: Boolean = false,
96+
var targetEmbeddedCompiler: Boolean = false
97+
)
98+
99+
/**
100+
* Configuration for KSP (Kotlin Symbol Processing).
101+
* Groups compiler settings and processor-specific options.
102+
*
103+
* Usage:
104+
* ```kotlin
105+
* kotlin {
106+
* ksp {
107+
* compiler {
108+
* tag = "1.9.25-1.0.20"
109+
* sha = "..."
110+
* }
111+
* // String coordinate
112+
* processor("com.google.dagger:dagger-compiler") {
113+
* generatesJava = true
114+
* }
115+
* // Or version catalog reference
116+
* processor(libs.google.dagger.compiler) {
117+
* generatesJava = true
118+
* }
119+
* }
120+
* }
121+
* ```
122+
*/
123+
data class KspExtension(
124+
val compiler: KspCompiler = KspCompiler(),
125+
val processors: MutableMap<String, KspProcessorConfig> = mutableMapOf()
126+
) {
127+
fun compiler(block: KspCompiler.() -> Unit) {
128+
block(compiler)
129+
}
130+
131+
fun compiler(closure: Closure<*>) {
132+
closure.delegate = compiler
133+
closure.resolveStrategy = Closure.DELEGATE_FIRST
134+
closure.call()
135+
}
136+
137+
/**
138+
* Configure a KSP processor by maven coordinate string.
139+
* @param coordinate Maven coordinate in format "group:artifact"
140+
*/
141+
fun processor(coordinate: String, block: KspProcessorConfig.() -> Unit) {
142+
processors.getOrPut(coordinate) { KspProcessorConfig() }.apply(block)
143+
}
144+
145+
fun processor(coordinate: String, closure: Closure<*>) {
146+
val config = processors.getOrPut(coordinate) { KspProcessorConfig() }
147+
closure.delegate = config
148+
closure.resolveStrategy = Closure.DELEGATE_FIRST
149+
closure.call()
150+
}
151+
152+
/**
153+
* Configure a KSP processor using version catalog reference.
154+
* @param dependency Version catalog dependency (e.g., libs.google.dagger.compiler)
155+
*/
156+
fun processor(dependency: Provider<MinimalExternalModuleDependency>, block: KspProcessorConfig.() -> Unit) {
157+
val dep = dependency.get()
158+
val coordinate = "${dep.module.group}:${dep.module.name}"
159+
processors.getOrPut(coordinate) { KspProcessorConfig() }.apply(block)
160+
}
161+
162+
fun processor(dependency: Provider<MinimalExternalModuleDependency>, closure: Closure<*>) {
163+
val dep = dependency.get()
164+
val coordinate = "${dep.module.group}:${dep.module.name}"
165+
val config = processors.getOrPut(coordinate) { KspProcessorConfig() }
166+
closure.delegate = config
167+
closure.resolveStrategy = Closure.DELEGATE_FIRST
168+
closure.call()
169+
}
170+
}
171+
85172
/**
86173
* Configuration for Kotlin compiler and toolchains. Options configured will be used in root `BUILD.bazel`, `WORKSPACE`
87174
* respectively.
@@ -102,12 +189,21 @@ data class KspCompiler(
102189
* abiJars = true
103190
* languageVersion = "1.4"
104191
* }
192+
* ksp {
193+
* compiler {
194+
* tag = "1.9.25-1.0.20"
195+
* sha = "..."
196+
* }
197+
* processor("com.google.dagger:dagger-compiler") {
198+
* generatesJava = true
199+
* }
200+
* }
105201
* }
106202
* ```
107203
*/
108204
data class KotlinExtension(
109205
val compiler: KotlinCompiler = KotlinCompiler(),
110-
val kspCompiler: KspCompiler = KspCompiler(),
206+
val ksp: KspExtension = KspExtension(),
111207
val kotlinCOptions: KotlinCOptions = KotlinCOptions(),
112208
val javaCOptions: JavaCOptions = JavaCOptions(),
113209
val toolchain: KotlinToolChain = KotlinToolChain(),
@@ -150,12 +246,13 @@ data class KotlinExtension(
150246
closure.call()
151247
}
152248

153-
fun kspCompiler(block: KspCompiler.() -> Unit) {
154-
block(kspCompiler)
249+
fun ksp(block: KspExtension.() -> Unit) {
250+
block(ksp)
155251
}
156252

157-
fun kspCompiler(closure: Closure<*>) {
158-
closure.delegate = kspCompiler
253+
fun ksp(closure: Closure<*>) {
254+
closure.delegate = ksp
255+
closure.resolveStrategy = Closure.DELEGATE_FIRST
159256
closure.call()
160257
}
161258

0 commit comments

Comments
 (0)