bazelbuild / bazelbuild/rules_android

Support rules_kotlin's `kt_android_library` in aspect traversal

Open
#506 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
203
Forks
95
PR merge metrics
No merged PRs in 30d

Description

## Problem

Several aspects in rules_android have hardcoded attribute traversal lists that predate `rules_kotlin`'s `kt_android_library` rule. They do not traverse `associates`, a `rules_kotlin`-specific attribute that serves as the sole dependency edge for Kotlin `internal` visibility across targets.

This causes two failures:

1. **`dex_desugar_aspect`**: Jars from associate targets are never dexed, producing "Dependencies on .jar artifacts are not allowed in Android binaries" errors.

2. **mobile-install default adapter**: Associate targets' dex shards are absent from the deployed APK, causing `NoClassDefFoundError` at runtime.

Both work fine with `bazel build` — only aspect-driven processing is affected.

## Background

For Kotlin Android targets, the conventional approach has been the "sandwich" pattern: a `kt_jvm_library` for Kotlin compilation wrapped by an `android_library` for Android resource merging. In this pattern, all targets are connected through `deps`, so aspects can discover them.

In https://github.com/bazel-contrib/rules_kotlin/pull/1333 `kt_android_library` collapses the sandwich into a single rule. As projects migrate to it, the dependency graph topology changes. Kotlin `internal` visibility across targets uses the `associates` attribute (implementing Kotlin's `-Xfriend-paths`), and `associates` is often the **only** edge connecting an implementation target to its wiring/DI target:

```starlark
# impl/BUILD.bazel
kt_android_library(
name = "impl",
srcs = ["FooImpl.kt"], # contains: internal class FooImpl
)

# wiring/BUILD.bazel
kt_android_library(
name = "wiring",
associates = ["//path/to/impl"], # sole edge to impl
deps = [...],
)
```

While associate targets' `JavaInfo` is included in the `deps` field of the wiring target's `JavaInfo` provider (so their jars appear in `transitive_runtime_jars` for the final binary), the aspects need to **visit** each target individually to dex its jar. Since `associates` is not in the aspect traversal lists, the associate target is never visited.

Additionally, `kt_android_library` doesn't have a dedicated mobile-install adapter (unlike `android_library` and `java_library`). It falls through to the default adapter, which works but means any future `kt_android_library`-specific attributes would also be missed.

## Affected aspects

| Aspect | File | Missing attribute |
|--------|------|-------------------|
| `dex_desugar_aspect` | `rules/dex_desugar_aspect.bzl` (`_ATTR_ASPECTS`) | `associates` |
| mobile-install | `mobile_install/adapters/default.bzl` (`_aspect_attrs`) | `associates` |

Adding an attribute to an aspect's traversal list that doesn't exist on a given rule is safe — Bazel silently skips it.

## Reproduction

1. Create `kt_android_library` target A with an `internal class`
2. Create `kt_android_library` target B with `associates = [A]` (A not in `deps`)
3. B instantiates A's internal class (e.g. via Dagger `@Provides`)
4. `bazel build //app` succeeds
5. `bazel mobile-install //app` crashes with `NoClassDefFoundError`

## Environment

- rules_android 0.7.1
- rules_kotlin 2.1.8
- Bazel 8.6

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.