Skip to content

Commit

Permalink
[kt_compiler_plugin] Add kt_plugin_cfg for reusable and composable op…
Browse files Browse the repository at this point in the history
…tions (#1105)

* Fix non-bzlmod, format

* Centralize plugin providers, add plugin option provider

* Add rules testing

* First pass, tests fail

* [kt_compiler_plugin] Add kt_plugin_cfg for reusuable and composable options

* Example of using ksp without leveraging kapt architecture

* Remove MODULE.bazel.lock
  • Loading branch information
restingbull authored Mar 10, 2024
1 parent 775625d commit b9380cd
Show file tree
Hide file tree
Showing 22 changed files with 875 additions and 102 deletions.
5 changes: 5 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ rules_kotlin_bootstrap_extensions = use_extension(
"//src/main/starlark/core/repositories:bzlmod_bootstrap.bzl",
"rules_kotlin_bootstrap_extensions",
)

use_repo(
rules_kotlin_bootstrap_extensions,
"kt_java_stub_template",
Expand All @@ -41,6 +42,7 @@ register_toolchains("//kotlin/internal:default_toolchain")

# TODO(bencodes) We should be able to remove this once rules_android has rolled out official Bzlmod support
remote_android_extensions = use_extension("@bazel_tools//tools/android:android_extensions.bzl", "remote_android_tools_extensions")

use_repo(remote_android_extensions, "android_gmaven_r8", "android_tools")

# Development dependencies
Expand Down Expand Up @@ -84,8 +86,11 @@ maven.install(
"https://repo1.maven.org/maven2",
],
)

use_repo(maven, "kotlin_rules_maven")

bazel_dep(name = "rules_pkg", version = "0.7.0")
bazel_dep(name = "stardoc", version = "0.5.6", repo_name = "io_bazel_stardoc")
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")

bazel_dep(name = "rules_testing", version = "0.5.0", dev_dependency = True)
72 changes: 71 additions & 1 deletion examples/ksp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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_ksp_plugin")
load("@rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_compiler_plugin", "kt_ksp_plugin", "kt_plugin_cfg")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

package(default_visibility = ["//visibility:public"])
Expand Down Expand Up @@ -81,3 +81,73 @@ build_test(
"//:coffee_app_deploy.jar",
],
)

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",
],
deps = [
"@maven//:com_google_auto_service_auto_service_annotations",
"@maven//:com_google_auto_value_auto_value_annotations",
"@maven//:com_squareup_moshi_moshi",
"@maven//:com_squareup_moshi_moshi_kotlin",
],
)

build_test(
name = "raw_ksp_lib_test",
targets = [
"//:raw_ksp_coffee_app_lib",
],
)
6 changes: 6 additions & 0 deletions examples/ksp/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################
2 changes: 2 additions & 0 deletions kotlin/core.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ load(
"//kotlin/internal/jvm:jvm.bzl",
_kt_compiler_plugin = "kt_compiler_plugin",
_kt_ksp_plugin = "kt_ksp_plugin",
_kt_plugin_cfg = "kt_plugin_cfg",
)

define_kt_toolchain = _define_kt_toolchain
Expand All @@ -20,3 +21,4 @@ kt_javac_options = _kt_javac_options
kt_kotlinc_options = _kt_kotlinc_options
kt_compiler_plugin = _kt_compiler_plugin
kt_ksp_plugin = _kt_ksp_plugin
kt_plugin_cfg = _kt_plugin_cfg
21 changes: 0 additions & 21 deletions kotlin/internal/compiler_plugins.bzl

This file was deleted.

27 changes: 13 additions & 14 deletions kotlin/internal/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
# 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(
"//src/main/starlark/core/plugin:providers.bzl",
_KspPluginInfo = "KspPluginInfo",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
_KtCompilerPluginOption = "KtCompilerPluginOption",
_KtPluginConfiguration = "KtPluginConfiguration",
)

# The Kotlin Toolchain type.
TOOLCHAIN_TYPE = "%s" % Label("//kotlin/internal:kt_toolchain_type")
Expand Down Expand Up @@ -50,18 +57,10 @@ KtJsInfo = provider(
},
)

KtCompilerPluginInfo = provider(
fields = {
"plugin_jars": "List of plugin jars.",
"classpath": "The kotlin compiler plugin classpath.",
"stubs": "Run this plugin during kapt stub generation.",
"compile": "Run this plugin during koltinc compilation.",
"options": "List of plugin options, represented as structs with an id and a value field, to be passed to the compiler",
},
)
KtCompilerPluginInfo = _KtCompilerPluginInfo

KspPluginInfo = provider(
fields = {
"plugins": "List of JavaPLuginInfo providers for the plugins to run with KSP",
},
)
KspPluginInfo = _KspPluginInfo

KtCompilerPluginOption = _KtCompilerPluginOption

KtPluginConfiguration = _KtPluginConfiguration
139 changes: 102 additions & 37 deletions kotlin/internal/jvm/compile.bzl
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
load(
"@bazel_tools//tools/jdk:toolchain_utils.bzl",
"find_java_runtime_toolchain",
"find_java_toolchain",
)

# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -17,11 +11,21 @@ load(
# 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_java//java:defs.bzl", "JavaInfo", "java_common")
load(
"@bazel_tools//tools/jdk:toolchain_utils.bzl",
"find_java_runtime_toolchain",
"find_java_toolchain",
)
load(
"@rules_java//java:defs.bzl",
"JavaInfo",
"java_common",
)
load(
"//kotlin/internal:defs.bzl",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
_KtJvmInfo = "KtJvmInfo",
_KtPluginConfiguration = "KtPluginConfiguration",
_TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
)
load(
Expand Down Expand Up @@ -178,9 +182,83 @@ def _adjust_resources_path(path, resource_strip_prefix):
else:
return _adjust_resources_path_by_default_prefixes(path)

def _format_compile_plugin_options(options):
"""Format options into id:value for cmd line."""
return ["%s:%s" % (o.id, o.value) for o in options]
def _format_compile_plugin_options(o):
"""Format compiler option into id:value for cmd line."""
return [
"%s:%s" % (o.id, o.value),
]

def _new_plugins_from(targets):
"""Returns a struct containing the plugin metadata for the given targets.
Args:
targets: A list of targets.
Returns:
A struct containing the plugins for the given targets in the format:
{
stubs_phase = {
classpath = depset,
options= List[KtCompilerPluginOption],
),
compile = {
classpath = depset,
options = List[KtCompilerPluginOption],
},
}
"""

all_plugins = {}
plugins_without_phase = []
for t in targets:
if _KtCompilerPluginInfo not in t:
continue
plugin = t[_KtCompilerPluginInfo]
if not (plugin.stubs or plugin.compile):
plugins_without_phase.append("%s: %s" % (t.label, plugin.id))
if plugin.id in all_plugins:
# This need a more robust error messaging.
fail("has multiple plugins with the same id: %s." % plugin.id)
all_plugins[plugin.id] = plugin

if plugins_without_phase:
fail("has plugin without a phase defined: %s" % cfgs_without_plugin)

all_plugin_cfgs = {}
cfgs_without_plugin = []
for t in targets:
if _KtPluginConfiguration not in t:
continue
cfg = t[_KtPluginConfiguration]
if cfg.id not in all_plugins:
cfgs_without_plugin.append("%s: %s" % (t.label, cfg.id))
all_plugin_cfgs[cfg.id] = cfg

if cfgs_without_plugin:
fail("has plugin configurations without corresponding plugins: %s" % cfgs_without_plugin)

return struct(
stubs_phase = _new_plugin_from(all_plugin_cfgs, [p for p in all_plugins.values() if p.stubs]),
compile_phase = _new_plugin_from(all_plugin_cfgs, [p for p in all_plugins.values() if p.compile]),
)

def _new_plugin_from(all_cfgs, plugins_for_phase):
classpath = []
data = []
options = []
for p in plugins_for_phase:
classpath.append(p.classpath)
options.extend(p.options)
if p.id in all_cfgs:
cfg = all_cfgs[p.id]
classpath.append(cfg.classpath)
data.append(cfg.data)
options.extend(cfg.options)

return struct(
classpath = depset(transitive = classpath),
data = depset(transitive = data),
options = options,
)

# INTERNAL ACTIONS #####################################################################################################
def _fold_jars_action(ctx, rule_kind, toolchains, output_jar, input_jars, action_type = ""):
Expand Down Expand Up @@ -425,49 +503,28 @@ def _run_kt_builder_action(
uniquify = True,
)

compiler_plugins = [
p[_KtCompilerPluginInfo]
for p in plugins
if _KtCompilerPluginInfo in p and p[_KtCompilerPluginInfo]
]

stubs_compiler_plugins = [
kcp
for kcp in compiler_plugins
if kcp.stubs
]

compiler_compiler_plugins = [
ccp
for ccp in compiler_plugins
if ccp.compile
]

if compiler_plugins and not (stubs_compiler_plugins or compiler_compiler_plugins):
fail("plugins but no phase plugins: %s" % compiler_plugins)

args.add_all(
"--stubs_plugin_classpath",
depset(transitive = [p.classpath for p in stubs_compiler_plugins]),
plugins.stubs_phase.classpath,
omit_if_empty = True,
)

args.add_all(
"--stubs_plugin_options",
[p.options for p in stubs_compiler_plugins],
plugins.stubs_phase.options,
map_each = _format_compile_plugin_options,
omit_if_empty = True,
)

args.add_all(
"--compiler_plugin_classpath",
depset(transitive = [p.classpath for p in compiler_compiler_plugins]),
plugins.compile_phase.classpath,
omit_if_empty = True,
)

args.add_all(
"--compiler_plugin_options",
[p.options for p in compiler_compiler_plugins],
plugins.compile_phase.options,
map_each = _format_compile_plugin_options,
omit_if_empty = True,
)
Expand All @@ -493,7 +550,13 @@ def _run_kt_builder_action(
mnemonic = mnemonic,
inputs = depset(
srcs.all_srcs + srcs.src_jars + generated_src_jars,
transitive = [compile_deps.compile_jars, transitive_runtime_jars, deps_artifacts] + [p.classpath for p in compiler_plugins],
transitive = [
compile_deps.compile_jars,
transitive_runtime_jars,
deps_artifacts,
plugins.stubs_phase.classpath,
plugins.compile_phase.classpath,
],
),
tools = tools,
input_manifests = input_manifests,
Expand Down Expand Up @@ -533,10 +596,12 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
deps = ctx.attr.deps,
runtime_deps = ctx.attr.runtime_deps,
)

annotation_processors = _plugin_mappers.targets_to_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
ksp_annotation_processors = _plugin_mappers.targets_to_ksp_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
transitive_runtime_jars = _plugin_mappers.targets_to_transitive_runtime_jars(ctx.attr.plugins + ctx.attr.deps)
plugins = ctx.attr.plugins + _exported_plugins(deps = ctx.attr.deps)
plugins = _new_plugins_from(ctx.attr.plugins + _exported_plugins(deps = ctx.attr.deps))

deps_artifacts = _deps_artifacts(toolchains, ctx.attr.deps + associates.targets)

generated_src_jars = []
Expand Down
Loading

0 comments on commit b9380cd

Please sign in to comment.