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.
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 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 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 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.