Isolate KSP2 processor classpath from KAPT processor JARs (#1670)
When a target has both KAPT and KSP plugins, the KSP2 action was receiving all processor JARs — including KAPT-only processors — on its --processor_classpath. If any of those KAPT JARs contained a META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider registration (e.g. dagger-compiler 2.48+), KSP2 would auto-discover and run them, causing spurious type resolution failures.
- Add `_targets_to_ksp_transitive_runtime_jars` in plugins.bzl that collects processor JARs only from KspPluginInfo targets, excluding JavaPluginInfo/JavaInfo (KAPT) processors
- Pass this KSP-only depset to `_run_ksp_builder_actions` for `--processor_classpath`, while KAPT and KotlinCompile actions continue to receive the full `transitive_runtime_jars`
- Fix `_targets_to_annotation_processors` checking `_KspPluginInfo in targets` (the list) instead of `_KspPluginInfo in t` (the loop variable)
- Add analysis test verifying KSP2 processor classpath excludes KAPT JARs when both plugin types are present
diff --git a/kotlin/internal/jvm/compile.bzl b/kotlin/internal/jvm/compile.bzl
index 7991b73..8c40e0a 100644
--- a/kotlin/internal/jvm/compile.bzl
+++ b/kotlin/internal/jvm/compile.bzl
@@ -778,6 +778,7 @@
ksp_annotation_processors = _plugin_mappers.targets_to_ksp_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
ksp_options = _plugin_mappers.targets_to_ksp_options(ctx.attr.plugins + ctx.attr.deps)
transitive_runtime_jars = _plugin_mappers.targets_to_transitive_runtime_jars(ctx.attr.plugins + ctx.attr.deps)
+ ksp_transitive_runtime_jars = _plugin_mappers.targets_to_ksp_transitive_runtime_jars(ctx.attr.plugins + ctx.attr.deps)
plugins = _new_plugins_from(ctx.attr.plugins + _exported_plugins(deps = ctx.attr.deps))
deps_artifacts = _deps_artifacts(toolchains, ctx.attr.deps + ctx.attr.associates)
@@ -802,6 +803,7 @@
ksp_annotation_processors = ksp_annotation_processors,
ksp_options = ksp_options,
transitive_runtime_jars = transitive_runtime_jars,
+ ksp_transitive_runtime_jars = ksp_transitive_runtime_jars,
plugins = plugins,
compile_jar = compile_jar,
output_jdeps = output_jdeps,
@@ -907,6 +909,7 @@
ksp_annotation_processors,
ksp_options,
transitive_runtime_jars,
+ ksp_transitive_runtime_jars,
plugins,
compile_jar,
output_jdeps):
@@ -952,7 +955,7 @@
toolchains = toolchains,
srcs = srcs,
compile_deps = compile_deps,
- transitive_runtime_jars = transitive_runtime_jars,
+ transitive_runtime_jars = ksp_transitive_runtime_jars,
ksp_options = ksp_options,
)
ksp_generated_class_jar = ksp_outputs.ksp_generated_class_jar
diff --git a/kotlin/internal/jvm/plugins.bzl b/kotlin/internal/jvm/plugins.bzl
index ab949a0..9591414 100644
--- a/kotlin/internal/jvm/plugins.bzl
+++ b/kotlin/internal/jvm/plugins.bzl
@@ -35,7 +35,7 @@
def _targets_to_annotation_processors(targets):
plugins = []
for t in targets:
- if _KspPluginInfo in targets:
+ if _KspPluginInfo in t:
# KSP plugins are handled by the KSP Kotlinc compiler plugin
pass
elif JavaPluginInfo in t:
@@ -81,12 +81,20 @@
transitive.extend([plugin.plugins.processor_jars for plugin in t[_KspPluginInfo].plugins])
return depset(transitive = transitive)
+def _targets_to_ksp_transitive_runtime_jars(targets):
+ transitive = []
+ for t in targets:
+ if _KspPluginInfo in t:
+ transitive.extend([plugin.plugins.processor_jars for plugin in t[_KspPluginInfo].plugins])
+ return depset(transitive = transitive)
+
mappers = struct(
targets_to_annotation_processors = _targets_to_annotation_processors,
targets_to_ksp_annotation_processors = _targets_to_ksp_annotation_processors,
targets_to_ksp_options = _targets_to_ksp_options,
targets_to_annotation_processors_java_plugin_info = _targets_to_annotation_processors_java_plugin_info,
targets_to_transitive_runtime_jars = _targets_to_transitive_runtime_jars,
+ targets_to_ksp_transitive_runtime_jars = _targets_to_ksp_transitive_runtime_jars,
kt_plugin_to_processor = _kt_plugin_to_processor,
kt_plugin_to_processorpath = _kt_plugin_to_processorpath,
)
diff --git a/src/test/starlark/ksp/BUILD.bazel b/src/test/starlark/ksp/BUILD.bazel
index fcf0cc0..67e2273 100644
--- a/src/test/starlark/ksp/BUILD.bazel
+++ b/src/test/starlark/ksp/BUILD.bazel
@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+load("@rules_java//java:defs.bzl", "java_plugin")
load("//kotlin:core.bzl", "kt_ksp_plugin")
load("//kotlin:jvm.bzl", "kt_jvm_library")
-load(":ksp_test.bzl", "ksp_action_test", "ksp_conflicting_options_test", "ksp_javac_excludes_srcjars_test", "ksp_javac_includes_srcjars_test", "ksp_options_action_test", "ksp_outputs_test", "ksp_plugin_empty_options_provider_test", "ksp_plugin_options_provider_test", "ksp_single_action_test")
+load(":ksp_test.bzl", "ksp_action_test", "ksp_conflicting_options_test", "ksp_javac_excludes_srcjars_test", "ksp_javac_includes_srcjars_test", "ksp_options_action_test", "ksp_outputs_test", "ksp_plugin_empty_options_provider_test", "ksp_plugin_options_provider_test", "ksp_processor_classpath_isolation_test", "ksp_single_action_test")
# Simple KSP plugin for testing (uses moshi which generates Kotlin code)
kt_ksp_plugin(
@@ -158,6 +159,14 @@
],
)
+# KAPT processor plugin for testing classpath isolation
+java_plugin(
+ name = "autovalue_plugin",
+ generates_api = 1,
+ processor_class = "com.google.auto.value.processor.AutoValueProcessor",
+ deps = ["@kotlin_rules_maven//:com_google_auto_value_auto_value"],
+)
+
# Mixed Java+Kotlin with KSP (generates_java=False) — KSP srcjars must NOT go to javac
kt_jvm_library(
name = "ksp_mixed_no_java_gen",
@@ -186,6 +195,22 @@
],
)
+# Target with both KAPT (autovalue) and KSP (moshi) plugins — verifies
+# that KAPT processor JARs do not leak onto the KSP2 processor classpath
+kt_jvm_library(
+ name = "ksp_test_lib_with_kapt",
+ srcs = ["TestModel.kt"],
+ plugins = [
+ ":autovalue_plugin",
+ ":moshi_plugin",
+ ],
+ deps = [
+ "@kotlin_rules_maven//:com_google_auto_value_auto_value_annotations",
+ "@kotlin_rules_maven//:com_squareup_moshi_moshi",
+ "@kotlin_rules_maven//:com_squareup_moshi_moshi_kotlin",
+ ],
+)
+
# Test that KSP srcjars are excluded from javac when generates_java=False
ksp_javac_excludes_srcjars_test(
name = "ksp_javac_excludes_srcjars_test",
@@ -198,6 +223,12 @@
target_under_test = ":ksp_mixed_with_java_gen",
)
+# Test that KSP2 processor classpath only contains KSP processor JARs
+ksp_processor_classpath_isolation_test(
+ name = "ksp_processor_classpath_isolation_test",
+ target_under_test = ":ksp_test_lib_with_kapt",
+)
+
test_suite(
name = "ksp_tests",
tests = [
@@ -209,6 +240,7 @@
":ksp_outputs_test",
":ksp_plugin_empty_options_provider_test",
":ksp_plugin_options_provider_test",
+ ":ksp_processor_classpath_isolation_test",
":ksp_single_action_test",
],
)
diff --git a/src/test/starlark/ksp/ksp_test.bzl b/src/test/starlark/ksp/ksp_test.bzl
index 724eba6..9c6199f 100644
--- a/src/test/starlark/ksp/ksp_test.bzl
+++ b/src/test/starlark/ksp/ksp_test.bzl
@@ -256,6 +256,49 @@
_ksp_javac_includes_srcjars_when_generating_java_test_impl,
)
+def _ksp_processor_classpath_isolation_test_impl(ctx):
+ """Verify KSP2 processor classpath excludes KAPT processor JARs."""
+ env = analysistest.begin(ctx)
+
+ actions = analysistest.target_actions(env)
+ ksp2_actions = [a for a in actions if a.mnemonic == "KotlinKsp2"]
+
+ asserts.equals(env, 1, len(ksp2_actions), "Should have exactly one KotlinKsp2 action")
+
+ argv = ksp2_actions[0].argv
+
+ # Collect all --processor_classpath values from argv
+ processor_classpath_entries = []
+ collecting = False
+ for arg in argv:
+ if arg == "--processor_classpath":
+ collecting = True
+ elif collecting and arg.startswith("--"):
+ collecting = False
+ elif collecting:
+ processor_classpath_entries.append(arg)
+
+ # KAPT processor JARs (autovalue) must NOT be on KSP2 processor classpath
+ kapt_jars_on_ksp = [e for e in processor_classpath_entries if "auto_value" in e or "auto-value" in e]
+ asserts.equals(
+ env,
+ 0,
+ len(kapt_jars_on_ksp),
+ "KAPT processor JARs should not leak onto KSP2 --processor_classpath, found: %s" % kapt_jars_on_ksp,
+ )
+
+ # KSP processor JARs (moshi) SHOULD be present
+ ksp_jars = [e for e in processor_classpath_entries if "moshi" in e]
+ asserts.true(
+ env,
+ len(ksp_jars) > 0,
+ "KSP processor JARs (moshi) should be on KSP2 --processor_classpath",
+ )
+
+ return analysistest.end(env)
+
+ksp_processor_classpath_isolation_test = analysistest.make(_ksp_processor_classpath_isolation_test_impl)
+
def ksp_test_suite(name):
"""Create test suite for KSP2 integration tests.
@@ -276,5 +319,6 @@
":ksp_options_action_test",
":ksp_javac_excludes_srcjars_test",
":ksp_javac_includes_srcjars_test",
+ ":ksp_processor_classpath_isolation_test",
],
)