Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ use_repo(
"kotlinx_serialization_json_jvm",
)

register_toolchains("//src/test/starlark/compile:abi_stripping_test_toolchain")

register_toolchains("//src/main/starlark/core/compile/cli")

register_toolchains("//kotlin/internal:default_toolchain")
Expand Down
14 changes: 13 additions & 1 deletion kotlin/internal/jvm/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -976,12 +976,24 @@ def _run_kt_java_builder_actions(
# annotation processors in `deps` also.
if len(srcs.kt) > 0:
javac_opts.append("-proc:none")

# When experimental_remove_private_classes_in_abi_jars is enabled, associate targets'
# JavaInfo compile_jars point to ABI jars with internal classes stripped. The Kotlin
# compiler uses compile_deps.compile_jars (which swaps ABI jars for full class jars),
# but java_common.compile derives its classpath from JavaInfo.compile_jars in deps.
# We need to add the associate full jars explicitly so javac can see internal classes
# referenced by generated code (e.g., Dagger components).
associate_java_infos = [
JavaInfo(output_jar = jar, compile_jar = jar, neverlink = True)
for jar in compile_deps.associate_jars.to_list()
]

java_info = java_common.compile(
ctx,
source_files = srcs.java,
source_jars = generated_kapt_src_jars + srcs.src_jars + generated_ksp_src_jars,
output = ctx.actions.declare_file(ctx.label.name + "-java.jar"),
deps = compile_deps.deps + kt_stubs_for_java + [p[JavaInfo] for p in ctx.attr.plugins if JavaInfo in p],
deps = compile_deps.deps + kt_stubs_for_java + [p[JavaInfo] for p in ctx.attr.plugins if JavaInfo in p] + associate_java_infos,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. If I'm not wrong, this undoes the advantages of the ABI jars for associates.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh no! My thinking is that since it's associated I should be able to access the internal symbols as a dependency to the compile task.

And I thought that whatever goes in deps doesn't end up in the final jar which would keep the internal symbols not-exposed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess we could flag this change behind the pair of experimental_treat_internal_as_private_in_abi_jars, experimental_remove_private_classes_in_abi_jars flags, but this concept (a modules java code should have access to its associates internals) is whats for discussion in #1467

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inputs are part of the cache key, so changes would invalidate on any associate change. It's not a whole tree invalidation (just immediate downstream actions), but it will have an impact.

I suspect we'd really want to extend abi here -- a change to a private field shouldn't invalidate. Hm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, one thing I'm not sure I follow. Let me come up with a specific example. I've hit this issue when I was compiling dagger in a bazel target called wiring which was associated with the target that had the implementation - impl.

The way I understand this, is that the dagger generated code placed in wiring might reference internal symbols from impl. shouldn't, in theory, a change to the internal code from impl actually invalidate the build from wiring?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good! thank you

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, so i have tried this out... assuming i could use the skip code gen plugin to avoid a full recompilation for the associates-abi.jar. Looks like that plugin is only good for k1 and i couldnt see any low risk options for having something similar for k2. So I could either do a full compilation and discard the output class jar in order to get an associates-abi.jar or maybe there is some jar processing that could be done to get something that would work, but that dosnt sound too great either

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other thing im trying is to actually use the abigen plugin twice, one with the flags to produce a public only abi jar and the other to make the "associates abi jar" (containing the internal symbols). Given you cant register the plugin twice im trying a thin wrapper that registers it under a different ID in order to configure it differently

Copy link
Contributor

@rbeazleyspot rbeazleyspot Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of #1479 as a starter for ten @restingbull

you might want a cup of tea and a biscuit to take you through it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@restingbull I'm closing this PR. I understand now that the jar I'm providing also include private symbols that once changed shouldn't invalidate the cache.

I've also gave a go at @rbeazleyspot's solution and it works within the Spotify project. I think we would prefer to go with his approach. Thanks for all the help!

java_toolchain = toolchains.java,
plugins = _plugin_mappers.targets_to_annotation_processors_java_plugin_info(ctx.attr.plugins),
javac_opts = javac_opts,
Expand Down
27 changes: 27 additions & 0 deletions src/test/starlark/compile/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("//kotlin:core.bzl", "define_kt_toolchain")
load(":associate_java_compile_tests.bzl", associate_test_suite = "test_suite")
load(":rule_tests.bzl", "test_suite")

test_suite(
name = "rules_tests",
)

associate_test_suite(
name = "associate_java_compile_tests",
)

# Test-only toolchain with experimental_remove_private_classes_in_abi_jars enabled.
# Activated via config_settings in analysis tests to verify that associate class jars
# (not just ABI jars) are passed to java_common.compile().

bool_flag(
name = "use_abi_stripping_toolchain",
build_setting_default = False,
)

config_setting(
name = "abi_stripping_enabled",
flag_values = {":use_abi_stripping_toolchain": "True"},
)

define_kt_toolchain(
name = "abi_stripping_test_toolchain",
experimental_remove_private_classes_in_abi_jars = True,
target_settings = [":abi_stripping_enabled"],
)
79 changes: 79 additions & 0 deletions src/test/starlark/compile/associate_java_compile_tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load("@rules_testing//lib:util.bzl", "util")
load("//kotlin:jvm.bzl", "kt_jvm_library")

_ABI_STRIPPING_FLAG = str(Label("//src/test/starlark/compile:use_abi_stripping_toolchain"))

def _java_compile_has_associate_class_jars_impl(env, target):
"""Verify the Javac action receives associate CLASS jars (not just ABI jars).

When experimental_remove_private_classes_in_abi_jars is enabled:
- The associate's JavaInfo.compile_jars contains ABI jars with internal classes stripped.
- compile_deps.associate_jars contains the full class jars.
- compile.bzl wraps associate_jars in synthetic JavaInfos and adds them to
java_common.compile() deps so javac can resolve internal symbols (e.g. Dagger).

Without the fix, only the stripped ABI jars would reach javac (via compile_deps.deps).
With the fix, the full class jars are also present.
"""
got_target = env.expect.that_target(target)

# java_common.compile() registers an action with mnemonic "Javac"
javac_action = got_target.action_named("Javac")

# With experimental_remove_private_classes_in_abi_jars enabled, associate_jars
# contains the class jars (java_outputs[].class_jar), which are distinct from
# the ABI jars (compile_jars). The fix adds these class jars to java_common.compile()
# deps via synthetic JavaInfos.
associate_target = env.ctx.attr.associate_target
associate_class_jar_paths = [
output.class_jar.short_path
for output in associate_target[JavaInfo].java_outputs
]
javac_action.inputs().contains_at_least(associate_class_jar_paths)

def _test_java_compile_has_associate_class_jars(name):
"""Mixed Kotlin/Java target with associates passes associate class jars to javac."""
util.helper_target(
kt_jvm_library,
name = name + "_associate_lib",
srcs = [util.empty_file(name + "_Internal.kt")],
)

util.helper_target(
kt_jvm_library,
name = name + "_main_lib",
srcs = [
util.empty_file(name + "_Main.kt"),
util.empty_file(name + "_Generated.java"),
],
associates = [name + "_associate_lib"],
)

analysis_test(
name = name,
impl = _java_compile_has_associate_class_jars_impl,
target = name + "_main_lib",
config_settings = {
_ABI_STRIPPING_FLAG: True,
},
attrs = {
"associate_target": attr.label(providers = [JavaInfo]),
},
attr_values = {
"associate_target": ":" + name + "_associate_lib",
},
)

def test_suite(name):
_test_java_compile_has_associate_class_jars(
name = "test_java_compile_has_associate_class_jars",
)

native.test_suite(
name = name,
tests = [
"test_java_compile_has_associate_class_jars",
],
)
104 changes: 104 additions & 0 deletions src/test/starlark/internal/jvm/jvm_deps_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,107 @@ def _transitive_from_exports_test(name):
def _transitive_from_associates_test(name):
_abi_test(name, _transitive_from_associates_test_impl)

def _associate_jars_has_class_jars_with_abi_stripping_test_impl(env, target):
"""associate_jars must contain class jars when ABI stripping is active.

These are the jars that compile.bzl wraps in synthetic JavaInfos and passes
to java_common.compile(), so javac can see internal symbols from associates
(e.g. Dagger components referencing internal classes).
"""
arrangment = _setup(env, target)

strict_abi_configured_toolchains = struct(
kt = struct(
experimental_remove_private_classes_in_abi_jars = True,
experimental_prune_transitive_deps = True,
experimental_strict_associate_dependencies = True,
jvm_stdlibs = JavaInfo(
compile_jar = _file(env.ctx.attr.jvm_jar),
output_jar = _file(env.ctx.attr.jvm_jar),
),
),
)

result = _jvm_deps_utils.jvm_deps(
ctx = arrangment.fake_ctx,
toolchains = strict_abi_configured_toolchains,
associate_deps = arrangment.associate_deps,
deps = arrangment.direct_deps,
)

associate_jars = env.expect.that_depset_of_files(result.associate_jars)

# Class jars (output_jar) SHOULD be in associate_jars
associate_jars.contains(_file(env.ctx.attr.associate_jar).short_path)

# ABI jars (compile_jar) should NOT be in associate_jars
associate_jars.not_contains(_file(env.ctx.attr.associate_abi_jar).short_path)

def _associate_jars_has_class_jars_with_abi_stripping_test(name):
_abi_test(name, _associate_jars_has_class_jars_with_abi_stripping_test_impl)

def _associate_jars_has_compile_jars_without_abi_stripping_test_impl(env, target):
"""Without ABI stripping, associate_jars should contain compile jars (ABI jars)."""
arrangment = _setup(env, target)

strict_no_abi_configured_toolchains = struct(
kt = struct(
experimental_remove_private_classes_in_abi_jars = False,
experimental_prune_transitive_deps = False,
experimental_strict_associate_dependencies = True,
jvm_stdlibs = JavaInfo(
compile_jar = _file(env.ctx.attr.jvm_jar),
output_jar = _file(env.ctx.attr.jvm_jar),
),
),
)

result = _jvm_deps_utils.jvm_deps(
ctx = arrangment.fake_ctx,
toolchains = strict_no_abi_configured_toolchains,
associate_deps = arrangment.associate_deps,
deps = arrangment.direct_deps,
)

associate_jars = env.expect.that_depset_of_files(result.associate_jars)

# Compile jars (ABI jars) should be in associate_jars
associate_jars.contains(_file(env.ctx.attr.associate_abi_jar).short_path)

# Full class jars should NOT be in associate_jars
associate_jars.not_contains(_file(env.ctx.attr.associate_jar).short_path)

def _associate_jars_has_compile_jars_without_abi_stripping_test(name):
_abi_test(name, _associate_jars_has_compile_jars_without_abi_stripping_test_impl)

def _associate_jars_empty_without_associates_test_impl(env, target):
"""With no associates, associate_jars should be empty."""
arrangment = _setup(env, target)

strict_abi_configured_toolchains = struct(
kt = struct(
experimental_remove_private_classes_in_abi_jars = True,
experimental_prune_transitive_deps = False,
experimental_strict_associate_dependencies = False,
jvm_stdlibs = JavaInfo(
compile_jar = _file(env.ctx.attr.jvm_jar),
output_jar = _file(env.ctx.attr.jvm_jar),
),
),
)

result = _jvm_deps_utils.jvm_deps(
ctx = arrangment.fake_ctx,
toolchains = strict_abi_configured_toolchains,
associate_deps = [],
deps = arrangment.direct_deps,
)

env.expect.that_int(len(result.associate_jars.to_list())).equals(0)

def _associate_jars_empty_without_associates_test(name):
_abi_test(name, _associate_jars_empty_without_associates_test_impl)

def _dep_infos_ordering_test_impl(env, target):
"""Test that user deps take precedence over stdlib in dep_infos ordering.

Expand Down Expand Up @@ -443,6 +544,9 @@ def jvm_deps_test_suite(name):
_fat_abi_test,
_transitive_from_exports_test,
_transitive_from_associates_test,
_associate_jars_has_class_jars_with_abi_stripping_test,
_associate_jars_has_compile_jars_without_abi_stripping_test,
_associate_jars_empty_without_associates_test,
_dep_infos_ordering_test,
_sourceless_dep_propagation_test,
],
Expand Down