Skip to content

Commit

Permalink
Adds a kt_plugin rule (bazelbuild#308)
Browse files Browse the repository at this point in the history
Creates a `kt_compiler_plugin` rule to allow kotlin compiler plugins to be specified, and then included with a `plugins=` attribute on kt_jvm_* jobs.  This should support arbitrary kotlin compiler plugins, such as the android extensions, open-for-testing, and others. 

This does _not_ add an exported_plugins infrastructure (such as you get with java_plugins), and it does not change the special-case handling of kapt. It also doesn't do anything to resolve plugin inter-0compatibilities (e.g. the ABI plugin possibly needing information from the parcelizable, or other such interactions) The latter are issues yet to be figured out int he kotlinc plugin infrastructure, and we'll adapt this rule as appropriate.

Also adds docs and examples.
  • Loading branch information
jongerrish authored Apr 6, 2020
1 parent 7b205f1 commit d89d6a3
Show file tree
Hide file tree
Showing 27 changed files with 376 additions and 22 deletions.
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,42 @@ kotlin_repositories() # if you want the default. Otherwise see custom kotlinc di
kt_register_toolchains() # to use the default toolchain, otherwise see toolchains below
```

# Kotlin compiler plugins

The `kt_compiler_plugin` rule allows running Kotlin compiler plugins, such as no-arg, sam-with-receiver and allopen.

For example, you can add allopen to your project like this:
```python
load("//kotlin:kotlin.bzl", "kt_compiler_plugin", "kt_jvm_library")

kt_compiler_plugin(
name = "open_for_testing_plugin",
id = "org.jetbrains.kotlin.allopen",
options = {
"annotation": "plugin.allopen.OpenForTesting",
},
deps = [
"@com_github_jetbrains_kotlin//:allopen-compiler-plugin",
],
)

kt_jvm_library(
name = "user",
srcs = ["User.kt"], # The User class is annotated with OpenForTesting
plugins = [
":open_for_testing_plugin",
],
deps = [
":open_for_testing", # This contains the annotation (plugin.allopen.OpenForTesting)
],
)
```

Full examples of using compiler plugins can be found [here](examples/plugin).

## Examples

Examples can be found in the [examples directory](https://github.com/bazelbuild/rules_kotlin/tree/master/examples), including usage with Android, Dagger, Node-JS, etc.
Examples can be found in the [examples directory](https://github.com/bazelbuild/rules_kotlin/tree/master/examples), including usage with Android, Dagger, Node-JS, Kotlin compiler plugins, etc.

# History

Expand Down
36 changes: 36 additions & 0 deletions examples/plugin/src/allopen/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("//kotlin:kotlin.bzl", "kt_compiler_plugin", "kt_jvm_library")

kt_compiler_plugin(
name = "open_for_testing_plugin",
id = "org.jetbrains.kotlin.allopen",
options = {
"annotation": "plugin.allopen.OpenForTesting",
},
deps = [
"@com_github_jetbrains_kotlin//:allopen-compiler-plugin",
],
)

kt_jvm_library(
name = "open_for_testing",
srcs = ["OpenForTesting.kt"],
)

kt_jvm_library(
name = "user",
srcs = ["User.kt"],
plugins = [
":open_for_testing_plugin",
],
deps = [
":open_for_testing",
],
)

kt_jvm_library(
name = "user_is_open_test",
srcs = ["UserIsOpenTest.kt"],
deps = [
":user",
],
)
3 changes: 3 additions & 0 deletions examples/plugin/src/allopen/OpenForTesting.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package plugin.allopen

annotation class OpenForTesting
9 changes: 9 additions & 0 deletions examples/plugin/src/allopen/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package plugin.allopen;

import java.util.*

@OpenForTesting
data class User(
val userId: UUID,
val emails: String
)
5 changes: 5 additions & 0 deletions examples/plugin/src/allopen/UserIsOpenTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package plugin.allopen

import java.util.*

class Subclass : User(UUID.randomUUID(), "[email protected]")
38 changes: 38 additions & 0 deletions examples/plugin/src/noarg/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
load("//kotlin:kotlin.bzl", "kt_compiler_plugin", "kt_jvm_library", "kt_jvm_test")

kt_compiler_plugin(
name = "no_arg_plugin",
id = "org.jetbrains.kotlin.noarg",
options = {
"annotation": "plugin.noarg.NoArgConstructor",
},
deps = [
"@com_github_jetbrains_kotlin//:noarg-compiler-plugin",
],
)

kt_jvm_library(
name = "no_arg_constructor",
srcs = ["NoArgConstructor.kt"],
)

kt_jvm_library(
name = "user",
srcs = ["User.kt"],
plugins = [":no_arg_plugin"],
deps = [
":no_arg_constructor",
],
)

# The no-arg constructor that is generated cannot be compiled against, but should be discoverable at runtime.
kt_jvm_test(
name = "user_has_noarg_constructor_test",
srcs = ["UserHasNoargConstructorTest.kt"],
test_class = "plugin.noarg.UserHasNoargConstructorTest",
deps = [
":user",
"@com_github_jetbrains_kotlin//:kotlin-reflect",
"@kotlin_rules_maven//:junit_junit",
],
)
3 changes: 3 additions & 0 deletions examples/plugin/src/noarg/NoArgConstructor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package plugin.noarg

annotation class NoArgConstructor
9 changes: 9 additions & 0 deletions examples/plugin/src/noarg/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package plugin.noarg;

import java.util.*

@NoArgConstructor
data class User(
val userId: UUID,
val emails: String
)
13 changes: 13 additions & 0 deletions examples/plugin/src/noarg/UserHasNoargConstructorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package plugin.noarg

import org.junit.*
import java.lang.Exception

class UserHasNoargConstructorTest {
@Test
fun userShouldHaveNoargConstructor() {
if (User::class.java.constructors.none { it.parameters.isEmpty() }) {
throw Exception("Expected an empty constructor to exist")
}
}
}
31 changes: 31 additions & 0 deletions examples/plugin/src/sam_with_receiver/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
load("//kotlin:kotlin.bzl", "kt_compiler_plugin", "kt_jvm_library")
load("@rules_java//java:defs.bzl", "java_library")

kt_compiler_plugin(
name = "sam_with_receiver_plugin",
id = "org.jetbrains.kotlin.samWithReceiver",
options = {
"annotation": "plugin.sam_with_receiver.SamWithReceiver",
},
deps = [
"@com_github_jetbrains_kotlin//:sam-with-receiver-compiler-plugin",
],
)

kt_jvm_library(
name = "sam_with_receiver",
srcs = ["SamWithReceiver.kt"],
)

java_library(
name = "runner",
srcs = ["Runner.java"],
deps = [":sam_with_receiver"],
)

kt_jvm_library(
name = "runner_test",
srcs = ["RunnerTest.kt"],
plugins = [":sam_with_receiver_plugin"],
deps = [":runner"],
)
6 changes: 6 additions & 0 deletions examples/plugin/src/sam_with_receiver/Runner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package plugin.sam_with_receiver;

@SamWithReceiver
public interface Runner {
void run(Double shouldBecomeThis);
}
5 changes: 5 additions & 0 deletions examples/plugin/src/sam_with_receiver/RunnerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package plugin.sam_with_receiver

val thisShouldWork = Runner {
println(this.isFinite())
}
3 changes: 3 additions & 0 deletions examples/plugin/src/sam_with_receiver/SamWithReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package plugin.sam_with_receiver

annotation class SamWithReceiver
21 changes: 21 additions & 0 deletions kotlin/internal/compiler_plugins.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load(
"//kotlin/internal:defs.bzl",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
)

def plugins_to_classpaths(providers_list):
flattened_files = []
for providers in providers_list:
if _KtCompilerPluginInfo in providers:
provider = providers[_KtCompilerPluginInfo]
for e in provider.classpath:
flattened_files.append(e)
return flattened_files

def plugins_to_options(providers_list):
kt_compiler_plugin_providers = [providers[_KtCompilerPluginInfo] for providers in providers_list if _KtCompilerPluginInfo in providers]
flattened_options = []
for provider in kt_compiler_plugin_providers:
for option in provider.options:
flattened_options.append("%s:%s" % (option.id, option.value))
return flattened_options
7 changes: 7 additions & 0 deletions kotlin/internal/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ KtJsInfo = provider(
"srcjar": "The jar containing the sources of the library",
},
)

KtCompilerPluginInfo = provider(
fields = {
"classpath": "The kotlin compiler plugin classpath",
"options": "List of plugin options, represented as structs with an id and a value field, to be passed to the compiler",
},
)
29 changes: 24 additions & 5 deletions kotlin/internal/jvm/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ load(
_merge_plugin_infos = "merge_plugin_infos",
_plugin_mappers = "mappers",
)
load(
"//kotlin/internal:compiler_plugins.bzl",
_plugins_to_classpaths = "plugins_to_classpaths",
_plugins_to_options = "plugins_to_options",
)
load(
"//kotlin/internal/utils:utils.bzl",
_utils = "utils",
Expand Down Expand Up @@ -218,8 +223,10 @@ def kt_jvm_compile_action(ctx, rule_kind, output_jar):
dirs = _compiler_directories(ctx)
srcs = _partitioned_srcs(ctx.files.srcs)
friend = _compiler_friends(ctx, friends = getattr(ctx.attr, "friends", []))
compile_deps = _compiler_deps(toolchains, friend, deps = ctx.attr.deps)
plugins = _plugin_mappers.targets_to_kt_plugins(ctx.attr.plugins + ctx.attr.deps)
compile_deps = _compiler_deps(toolchains, friend, deps = ctx.attr.deps + ctx.attr.plugins)
annotation_processors = _plugin_mappers.targets_to_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
plugins = ctx.attr.plugins

_run_kt_builder_action(
ctx = ctx,
rule_kind = rule_kind,
Expand All @@ -228,6 +235,7 @@ def kt_jvm_compile_action(ctx, rule_kind, output_jar):
srcs = srcs,
friend = friend,
compile_deps = compile_deps,
annotation_processors = annotation_processors,
plugins = plugins,
outputs = {
"output": output_jar,
Expand Down Expand Up @@ -263,7 +271,7 @@ def kt_jvm_compile_action(ctx, rule_kind, output_jar):
),
)

def _run_kt_builder_action(ctx, rule_kind, toolchains, dirs, srcs, friend, compile_deps, plugins, outputs):
def _run_kt_builder_action(ctx, rule_kind, toolchains, dirs, srcs, friend, compile_deps, annotation_processors, plugins, outputs):
"""Creates a KotlinBuilder action invocation."""
args = _utils.init_args(ctx, rule_kind, friend.module_name)

Expand All @@ -279,17 +287,28 @@ def _run_kt_builder_action(ctx, rule_kind, toolchains, dirs, srcs, friend, compi
# Collect and prepare plugin descriptor for the worker.
args.add_all(
"--processors",
plugins,
annotation_processors,
map_each = _plugin_mappers.kt_plugin_to_processor,
omit_if_empty = True,
)
args.add_all(
"--processorpath",
plugins,
annotation_processors,
map_each = _plugin_mappers.kt_plugin_to_processorpath,
omit_if_empty = True,
)

args.add_all(
"--pluginpath",
_plugins_to_classpaths(plugins),
omit_if_empty = True,
)
args.add_all(
"--plugin_options",
_plugins_to_options(plugins),
omit_if_empty = True,
)

progress_message = "Compiling Kotlin to JVM %s { kt: %d, java: %d, srcjars: %d }" % (
ctx.label,
len(srcs.kt),
Expand Down
19 changes: 19 additions & 0 deletions kotlin/internal/jvm/impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ load(
)
load(
"//kotlin/internal:defs.bzl",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
_KtJvmInfo = "KtJvmInfo",
)
load(
Expand Down Expand Up @@ -192,6 +193,7 @@ def kt_jvm_junit_test_impl(ctx):
main_class = ctx.attr.main_class,
jvm_flags = ["-ea", "-Dbazel.test_suite=%s" % test_class] + ctx.attr.jvm_flags,
)

return _make_providers(
ctx,
providers,
Expand All @@ -201,3 +203,20 @@ def kt_jvm_junit_test_impl(ctx):
direct = ctx.files._java_runtime,
),
)

def kt_compiler_plugin_impl(ctx):
merged_deps = java_common.merge([j[JavaInfo] for j in ctx.attr.deps])
plugin_id = ctx.attr.id
options = []
for (k, v) in ctx.attr.options.items():
if "=" in k:
fail("kt_compiler_plugin options keys cannot contain the = symbol")
options.append(struct(id = plugin_id, value = "%s=%s" % (k, v)))

return [
merged_deps,
_KtCompilerPluginInfo(
classpath = merged_deps.transitive_runtime_jars.to_list(),
options = options,
),
]
Loading

0 comments on commit d89d6a3

Please sign in to comment.