bazelbuild / bazelbuild/rules_android
OSS dynamic feature support
- Dominant language
- Java
- Stars
- 203
- Forks
- 95
- PR merge metrics
- No merged PRs in 30d
Description
# Abstract
This issue describes the current state of dynamic features in Bazel, the differences between Bazel and Gradle as well as the required work to bring them to parity with Gradle to support Open Source builds. This document focuses on the two main issues with the current version of the rules: native library support and dex support. Finally, an appendix with all the issues we encountered can be seen at the end. These issues are minor and are only listed for documentation purposes.
## Current support in Bazel:
Currently Bazel supports dynamic features via the `android_feature_module` and `android_application` rules. In order to create a feature module, you declare a new `android_feature_module` target and pass it to an `android_application` target via the `feature_modules` attribute. The `android_feature_module` can’t be used on its own. It must always be used through `android_application`.
The `android_feature_module` rule supports creating dynamic features that contain native libraries and/or assets only. There is no support for dynamic features that contain Java/Kotlin code or Android resources.
In order to specify the dependencies of a dynamic feature, one can either pass an `android_library` target through the `library` attribute or/and an `android_binary` target through the `binary` attribute of the rule. The full list of attributes the rule takes can be found [here](https://github.com/bazelbuild/rules_android/blob/pre-alpha/rules/android_application/attrs.bzl#L82).
The assets for the dynamic feature are collected from the set of transitive dependencies of the dynamic feature (via `library `and `binary` attrs).
The native libraries the dynamic feature are collected from the APK of the Android Binary target passed via the `binary` attr of the feature module rule. The `android_application` rule [extracts all the libraries](https://github.com/bazelbuild/rules_android/blob/pre-alpha/rules/android_application/android_application_rule.bzl#L132) in the binary apk and packages them with the module. I.e. the module will include all the libraries in the transitive dependencies of the binary.
## Dynamic feature support in Gradle {#dynamic-feature-support-in-gradle}
Gradle supports dynamic features via the [dynamicFeatures](https://developer.android.com/guide/playcore/feature-delivery) attribute. The base module is added as dependency of every feature module by default in the _build.gradle_ file (not this is fundamentally not possible in Bazel due to the cyclic dependency). The module will contain the direct dependencies specified in the _build.gradle_. Only artifacts that are explicitly specified in the _build.gradle_ file are considered to be part of the feature module. The dependency on the base module is only used for compilation purposes. This means that multiple feature modules can have common dependencies without ending up with multiple duplicate dependencies in different modules.
Assets, native libraries, Java/Kotlin code and resources are supported.
## Issues with current Bazel approach
The current approach used by Bazel to build feature modules, while simple, has issues when building complex feature modules that include more than just assets.
The native libraries support relies on building an intermediate APK which is only used to extract the ._so_ files. This binary can’t be reused across feature modules, since doing so would mean duplicating the native binaries included in each feature module. In addition to that, this future is implemented by inspecting the `IdeInfo` provider in order to detect if the target includes native binaries, which seems like a hacky use of the provider.
The same approach could be used in order to support Dex files (create a binary target and extract the dex files from the APK) but this would also fall into duplicate dependencies being included in each feature module in any case where different feature modules share common dependencies. If we wanted to use this approach, we’d need to take care into crafting targets with a disjoint set of dependencies (which is infeasible if we want to share code) or craft custom targets marked as _neverlink_ in order to be used by the feature module. Doing this would mean writing a lot of boiler plate targets and could prove to be very error prone.
## Changes to **android_application** and **android_feature_module** rule
In order to enable dynamic features with Bazel in Gradle repos with minimal modifications to the current builds working with Gradle, we propose flattening the `native_feature_module` rule and allow users to explicitly specify the dependencies that need to be included (android_library and native library deps). Concretely we propose replacing the `library` and `binary` attributes for a `deps` attribute.
The deps attribute would take `kt_android_library`, android_library and aar_import targets. For dependencies with code it will just dex the code just in that dependency (it will not dex the set of transitive deps) and will collect any native library included in that dependency (no transitive libraries as well). In order to enable dexing, the code from [DexArchiveAspect](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java) will need to be lifted into starlark and applied at the library level instead of the binary level (most of the code can be simplified since there is significant legacy code that can be ignored).
One remaining issue with this approach is that R8/proguarding feature modules requires the full set of binary dexes and feature module dexes. Currently the R8 code lives in the native android binary so any support for R8 with feature modules is TBD until there is more clarity on the future of the `android_binary` rule.
Contributor guide
Assessment
This issue has not been assessed yet.