Skip to content

Commit

Permalink
Add support for generated Java code with KSP (#1139)
Browse files Browse the repository at this point in the history
* Add support for generating Java code with KSP

* Add dagger to ksp sample project

* Run java compilation action only when ksp generates java

* Update ksp tests
  • Loading branch information
zalewskise authored Apr 30, 2024
1 parent d0c96cc commit 60670b3
Show file tree
Hide file tree
Showing 31 changed files with 839 additions and 248 deletions.
6 changes: 3 additions & 3 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ maven.install(
"com.google.auto.service:auto-service-annotations:1.0.1",
"com.google.auto.value:auto-value:1.10.1",
"com.google.auto.value:auto-value-annotations:1.10.1",
"com.google.dagger:dagger:2.43.2",
"com.google.dagger:dagger-compiler:2.43.2",
"com.google.dagger:dagger-producers:2.43.2",
"com.google.dagger:dagger:2.51",
"com.google.dagger:dagger-compiler:2.51",
"com.google.dagger:dagger-producers:2.51",
"javax.annotation:javax.annotation-api:1.3.2",
"javax.inject:javax.inject:1",
"org.pantsbuild:jarjar:1.7.2",
Expand Down
4 changes: 3 additions & 1 deletion docs/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ kt_kotlinc_options(<a href="#kt_kotlinc_options-name">name</a>, <a href="#kt_kot

## kt_ksp_plugin

kt_ksp_plugin(<a href="#kt_ksp_plugin-name">name</a>, <a href="#kt_ksp_plugin-deps">deps</a>, <a href="#kt_ksp_plugin-processor_class">processor_class</a>)
kt_ksp_plugin(<a href="#kt_ksp_plugin-name">name</a>, <a href="#kt_ksp_plugin-deps">deps</a>, <a href="#kt_ksp_plugin-generates_java">generates_java</a>, <a href="#kt_ksp_plugin-processor_class">processor_class</a>, <a href="#kt_ksp_plugin-target_embedded_compiler">target_embedded_compiler</a>)


Define a KSP plugin for the Kotlin compiler to run. The plugin can then be referenced in the `plugins` attribute
Expand Down Expand Up @@ -481,7 +481,9 @@ kt_ksp_plugin(<a href="#kt_ksp_plugin-name">name</a>, <a href="#kt_ksp_plugin-de
| :------------- | :------------- | :------------- | :------------- | :------------- |
|<a id="kt_ksp_plugin-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
|<a id="kt_ksp_plugin-deps"></a>deps | The list of libraries to be added to the compiler's plugin classpath | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | [] |
|<a id="kt_ksp_plugin-generates_java"></a>generates_java | Runs Java compilation action for plugin generating Java output. | Boolean | optional | False |
|<a id="kt_ksp_plugin-processor_class"></a>processor_class | The fully qualified class name that the Java compiler uses as an entry point to the annotation processor. | String | required | |
|<a id="kt_ksp_plugin-target_embedded_compiler"></a>target_embedded_compiler | Plugin was compiled against the embeddable kotlin compiler. These plugins expect shaded kotlinc dependencies, and will fail when running against a non-embeddable compiler. | Boolean | optional | False |


<a id="#kt_plugin_cfg"></a>
Expand Down
148 changes: 72 additions & 76 deletions examples/ksp/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@rules_java//java:defs.bzl", "java_binary", "java_plugin")
load("@rules_java//java:defs.bzl", "java_binary")

# Copyright 2018 The Bazel Authors. All rights reserved.
#
Expand All @@ -14,55 +14,99 @@ load("@rules_java//java:defs.bzl", "java_binary", "java_plugin")
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("@rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_compiler_plugin", "kt_ksp_plugin", "kt_plugin_cfg")
load("@rules_kotlin//kotlin:core.bzl", "define_kt_toolchain")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

package(default_visibility = ["//visibility:public"])

define_kt_toolchain(name = "kotlin_toolchain")

java_plugin(
name = "autovalue",
generates_api = True,
processor_class = "com.google.auto.value.processor.AutoValueProcessor",
deps = ["@maven//:com_google_auto_value_auto_value"],
# Generate a srcjar to validate intellij plugin correctly attaches it.
genrule(
name = "tea_lib_src",
outs = ["tea_lib_src.srcjar"],
cmd = """
cat << EOF > TeaPot.kt
package tea
object TeaPot {
fun isEmpty() = true
}
EOF
$(JAVABASE)/bin/jar -cf $@ TeaPot.kt
rm TeaPot.kt
""",
toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"],
)

kt_ksp_plugin(
name = "moshi-kotlin-codegen",
processor_class = "com.squareup.moshi.kotlin.codegen.ksp.JsonClassSymbolProcessorProvider",
deps = [
"@maven//:com_squareup_moshi_moshi",
"@maven//:com_squareup_moshi_moshi_kotlin",
"@maven//:com_squareup_moshi_moshi_kotlin_codegen",
],
genrule(
name = "chai_lib_src",
outs = ["chai_lib_src.srcjar"],
cmd = """
cat << EOF > ChaiCup.kt
package chai
object ChaiCup {
fun isEmpty() = true
}
EOF
$(JAVABASE)/bin/jar -cf $@ ChaiCup.kt
rm ChaiCup.kt
""",
toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"],
)

kt_ksp_plugin(
name = "autoservice",
processor_class = "dev.zacsweers.autoservice.ksp.AutoServiceSymbolProcessor$Provider",
deps = [
"@maven//:com_google_auto_service_auto_service_annotations",
"@maven//:dev_zacsweers_autoservice_auto_service_ksp",
],
genrule(
name = "genereated_module_src",
outs = ["genarated_module_src.srcjar"],
cmd = """
cat << EOF > GeneratedModule.kt
package generated
import dagger.Provides
import dagger.Module
@Module
object GeneratedModule {
@Provides
fun provideString() = "Hello Coffee"
}
EOF
$(JAVABASE)/bin/jar -cf $@ GeneratedModule.kt
rm GeneratedModule.kt
""",
toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"],
)

kt_jvm_library(
name = "generated_lib",
srcs = [":genereated_module_src"],
plugins = ["//third_party:dagger_ksp_plugin"],
deps = ["@maven//:com_google_dagger_dagger"],
)

kt_jvm_library(
name = "coffee_lib",
srcs = glob([
"*.kt",
"*.java",
]),
]) + [
# Adding a file ending with .srcjar is how code generation patterns are implemented.
":tea_lib_src",
":chai_lib_src",
],
plugins = [
"//:moshi-kotlin-codegen",
"//:autovalue",
"//:autoservice",
"//third_party:dagger_ksp_plugin",
"//third_party:moshi-kotlin-codegen",
"//third_party:autovalue",
"//third_party:autoservice",
],
deps = [
":generated_lib",
"@maven//:com_google_auto_service_auto_service_annotations",
"@maven//:com_google_auto_value_auto_value_annotations",
"@maven//:com_google_dagger_dagger",
"@maven//:com_squareup_moshi_moshi",
"@maven//:com_squareup_moshi_moshi_kotlin",
"@maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core",
],
)

Expand All @@ -82,60 +126,12 @@ build_test(
],
)

kt_compiler_plugin(
name = "ksp",
compile_phase = True,
id = "com.google.devtools.ksp.symbol-processing",
options = {
"apclasspath": "{classpath}",
# projectBaseDir shouldn't matter because incremental is disabled
"projectBaseDir": "{temp}",
# Disable incremental mode
"incremental": "false",
# Directory where class files are written to. Files written to this directory are class
# files being written directly from the annotation processor, not Kotlinc
"classOutputDir": "{generatedClasses}",
# Directory where generated Java sources files are written to
"javaOutputDir": "{generatedSources}",
# Directory where generated Kotlin sources files are written to
"kotlinOutputDir": "{generatedSources}",
# Directory where META-INF data is written to. This might not be the most ideal place to
# write this. Maybe just directly to the classes directory?
"resourceOutputDir": "{generatedSources}",
# TODO(bencodes) Not sure what this directory is yet.
"kspOutputDir": "{temp}",
# Directory to write KSP caches. Shouldn't matter because incremental is disabled
"cachesDir": "{temp}",
# Include in compilation as an example. This should be processed in the stubs phase.
"withCompilation": "true",
# Set returnOkOnError to false because we want to fail the build if there are any errors
"returnOkOnError": "false",
"allWarningsAsErrors": "false",
},
deps = [
"@rules_kotlin//kotlin/compiler:symbol-processing-api",
"@rules_kotlin//kotlin/compiler:symbol-processing-cmdline",
],
)

kt_plugin_cfg(
name = "ksp_moshi",
options = {
},
plugin = ":ksp",
deps = [
"@maven//:com_squareup_moshi_moshi",
"@maven//:com_squareup_moshi_moshi_kotlin",
"@maven//:com_squareup_moshi_moshi_kotlin_codegen",
],
)

kt_jvm_library(
name = "raw_ksp_coffee_app_lib",
srcs = ["CoffeeAppModel.kt"],
plugins = [
"//:ksp",
"//:ksp_moshi",
"//third_party:ksp",
"//third_party:ksp_moshi",
],
deps = [
"@maven//:com_google_auto_service_auto_service_annotations",
Expand Down
23 changes: 20 additions & 3 deletions examples/ksp/CoffeeApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,21 @@
package coffee

import com.squareup.moshi.Moshi
import generated.GeneratedModule
import dagger.Component
import kotlinx.coroutines.runBlocking
import tea.TeaPot
import chai.ChaiCup
import javax.inject.Singleton

class CoffeeApp {

@Singleton
@Component(modules = [DripCoffeeModule::class, GeneratedModule::class])
interface CoffeeShop {
fun maker(): CoffeeMaker
}

companion object {

private val adapter = CoffeeAppModelJsonAdapter(Moshi.Builder().build())
Expand All @@ -28,9 +40,14 @@ class CoffeeApp {

@JvmStatic
fun main(args: Array<String>) {
println(
adapter.toJson(d.coffeeAppModel())
)
println("Coffee model: ${adapter.toJson(d.coffeeAppModel())}")

if (TeaPot.isEmpty() && ChaiCup.isEmpty()) {
val coffeeShop = DaggerCoffeeApp_CoffeeShop.builder().build()
runBlocking {
coffeeShop.maker().brew()
}
}
}
}
}
40 changes: 40 additions & 0 deletions examples/ksp/CoffeeMaker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2018 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee

import dagger.Lazy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class CoffeeMaker @Inject internal constructor(
// Create a possibly costly heater only when we use it.
private val heater: Lazy<Heater>,
private val pump: Pump,
private val string: String
) {

suspend fun brew() {
// this function is async to verify intellij support for coroutines.
withContext(Dispatchers.Default) {
heater.get().on()
pump.pump()
println(" [_]P coffee! [_]P ")
println(string)
heater.get().off()
}
}
}
29 changes: 29 additions & 0 deletions examples/ksp/DripCoffeeModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2018 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee

import dagger.Module
import dagger.Provides
import javax.inject.Singleton

@Module(includes = arrayOf(PumpModule::class))
internal class DripCoffeeModule {
@Provides
@Singleton
fun provideHeater(): Heater {
return ElectricHeater()
}
}
29 changes: 29 additions & 0 deletions examples/ksp/ElectricHeater.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2018 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee

internal class ElectricHeater : Heater {
override var isHot: Boolean = false

override fun on() {
println("~ ~ ~ heating ~ ~ ~")
this.isHot = true
}

override fun off() {
this.isHot = false
}
}
22 changes: 22 additions & 0 deletions examples/ksp/Heater.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2018 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package coffee

internal interface Heater {
val isHot: Boolean
fun on()
fun off()
}
Loading

0 comments on commit 60670b3

Please sign in to comment.