diff --git a/examples/bundle/.bazelrc b/examples/bundle/.bazelrc
new file mode 100644
index 000000000..254e2d178
--- /dev/null
+++ b/examples/bundle/.bazelrc
@@ -0,0 +1,14 @@
+# Flags needed while the Android rules are being migrated to Starlark.
+common --experimental_google_legacy_api
+common --experimental_enable_android_migration_apis
+common --android_sdk=@androidsdk//:sdk
+common:core_library_desugaring --desugar_java8_libs
+
+# Flags to enable mobile-install v3
+mobile-install --mode=skylark --mobile_install_aspect=@rules_android//mobile_install:mi.bzl --mobile_install_supported_rules=android_binary
+# Required to invoke the Studio deployer jar
+mobile-install --tool_java_runtime_version=17
+common --java_language_version=17
+common --java_runtime_version=17
+common --tool_java_language_version=17
+common --tool_java_runtime_version=17
diff --git a/examples/bundle/.gitignore b/examples/bundle/.gitignore
new file mode 100644
index 000000000..63f1fef0e
--- /dev/null
+++ b/examples/bundle/.gitignore
@@ -0,0 +1 @@
+*.lock
diff --git a/examples/bundle/BUILD b/examples/bundle/BUILD
new file mode 100644
index 000000000..a09fce916
--- /dev/null
+++ b/examples/bundle/BUILD
@@ -0,0 +1 @@
+# Empty build file to satisfy gazelle for rules_go.
\ No newline at end of file
diff --git a/examples/bundle/MODULE.bazel b/examples/bundle/MODULE.bazel
new file mode 100644
index 000000000..988e813a3
--- /dev/null
+++ b/examples/bundle/MODULE.bazel
@@ -0,0 +1,55 @@
+module(
+ name = "bundle",
+)
+
+bazel_dep(name = "rules_java", version = "7.4.0")
+bazel_dep(name = "bazel_skylib", version = "1.3.0")
+
+bazel_dep(
+ name = "rules_android",
+ version = "0.5.1",
+)
+
+local_path_override(
+ module_name = "rules_android",
+ path = "../../",
+)
+
+remote_android_extensions = use_extension(
+ "@rules_android//bzlmod_extensions:android_extensions.bzl",
+ "remote_android_tools_extensions")
+use_repo(remote_android_extensions, "android_tools")
+
+register_toolchains(
+ "@rules_android//toolchains/android:android_default_toolchain",
+ "@rules_android//toolchains/android_sdk:android_sdk_tools",
+)
+
+android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
+use_repo(android_sdk_repository_extension, "androidsdk")
+
+register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")
+
+bazel_dep(name = "rules_jvm_external", version = "5.3")
+
+# Load the maven extension from rules_jvm_external
+maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
+
+maven.install(
+ name = "maven",
+ aar_import_bzl_label = "@rules_android//rules:rules.bzl",
+ artifacts = [
+ "com.google.guava:guava:32.1.2-android",
+ "com.facebook.soloader:soloader:0.12.1",
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+ use_starlark_android_rules = True,
+)
+use_repo(maven, "maven")
+
+
+
+
diff --git a/examples/bundle/README.md b/examples/bundle/README.md
new file mode 100644
index 000000000..0d923e32d
--- /dev/null
+++ b/examples/bundle/README.md
@@ -0,0 +1,12 @@
+To build, ensure that the `ANDROID_HOME` environment variable is set to the path
+to an Android SDK, and run:
+
+```
+bazel build app:assets
+```
+
+This will build application bundle containing a dynamic feature containing assets (named assets.txt). Verify with :
+
+```
+jar -tf bazel-bin/app/assets_unsigned.aab | grep assets.txt
+```
diff --git a/examples/bundle/WORKSPACE b/examples/bundle/WORKSPACE
new file mode 100644
index 000000000..a23815374
--- /dev/null
+++ b/examples/bundle/WORKSPACE
@@ -0,0 +1,58 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+maybe(
+ http_archive,
+ name = "rules_jvm_external",
+ strip_prefix = "rules_jvm_external-fa73b1a8e4846cee88240d0019b8f80d39feb1c3",
+ sha256 = "7e13e48b50f9505e8a99cc5a16c557cbe826e9b68d733050cd1e318d69f94bb5",
+ url = "https://github.com/bazelbuild/rules_jvm_external/archive/fa73b1a8e4846cee88240d0019b8f80d39feb1c3.zip",
+)
+
+maybe(
+ http_archive,
+ name = "bazel_skylib",
+ urls = [
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz",
+ ],
+ sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c",
+)
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+bazel_skylib_workspace()
+
+local_repository(
+ name = "rules_android",
+ path = "../..", # rules_android's WORKSPACE relative to this inner workspace
+)
+
+load("@rules_android//:prereqs.bzl", "rules_android_prereqs")
+rules_android_prereqs()
+load("@rules_android//:defs.bzl", "rules_android_workspace")
+rules_android_workspace()
+
+load("@rules_android//rules:rules.bzl", "android_sdk_repository")
+android_sdk_repository(
+ name = "androidsdk",
+)
+
+register_toolchains(
+ "@rules_android//toolchains/android:android_default_toolchain",
+ "@rules_android//toolchains/android_sdk:android_sdk_tools",
+)
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+
+maven_install(
+ name = "maven",
+ aar_import_bzl_label = "@rules_android//rules:rules.bzl",
+ artifacts = [
+ "com.google.guava:guava:32.1.2-android",
+ "com.arthenica:ffmpeg-kit-https:4.4.LTS",
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+ use_starlark_android_rules = True,
+)
diff --git a/examples/bundle/WORKSPACE.bzlmod b/examples/bundle/WORKSPACE.bzlmod
new file mode 100644
index 000000000..df9ed0ec9
--- /dev/null
+++ b/examples/bundle/WORKSPACE.bzlmod
@@ -0,0 +1 @@
+workspace(name = "bundle")
diff --git a/examples/bundle/app/AndroidManifest.xml b/examples/bundle/app/AndroidManifest.xml
new file mode 100644
index 000000000..1a4a7a99d
--- /dev/null
+++ b/examples/bundle/app/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/app/BUILD b/examples/bundle/app/BUILD
new file mode 100644
index 000000000..aff62ba6a
--- /dev/null
+++ b/examples/bundle/app/BUILD
@@ -0,0 +1,31 @@
+load("@rules_android//android:rules.bzl", "android_application", "android_library")
+
+android_application(
+ name = "assets",
+ manifest_values = {
+ "applicationId" : "com.examples.bundle.app",
+ "versionCode": "0",
+ },
+ feature_modules = ["//features/assets:feature_module"],
+ manifest = "AndroidManifest.xml",
+ deps = [":lib"],
+)
+
+android_application(
+ name = "native",
+ manifest_values = {
+ "applicationId" : "com.examples.bundle.app",
+ "versionCode": "0",
+ },
+ feature_modules = ["//features/native:feature_module"],
+ manifest = "AndroidManifest.xml",
+ deps = [":lib"],
+)
+
+android_library(
+ name = "lib",
+ srcs = ["BasicActivity.java"],
+ manifest = "AndroidManifest.xml",
+ resource_files = glob(["res/**"]),
+ deps = ["@maven//:com_google_guava_guava",]
+)
diff --git a/examples/bundle/app/BasicActivity.java b/examples/bundle/app/BasicActivity.java
new file mode 100644
index 000000000..f051263ba
--- /dev/null
+++ b/examples/bundle/app/BasicActivity.java
@@ -0,0 +1,59 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.examples.bundle.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * The main activity of the Basic Sample App.
+ */
+public class BasicActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.basic_activity);
+
+ final Button buttons[] = {
+ findViewById(R.id.button_id_fizz), findViewById(R.id.button_id_buzz),
+ };
+
+ for (Button b : buttons) {
+ b.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ TextView tv = findViewById(R.id.text_hello);
+ if (v.getId() == R.id.button_id_fizz) {
+ tv.setText("fizz");
+ } else if (v.getId() == R.id.button_id_buzz) {
+ tv.setText("buzz ");
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu, menu);
+ return true;
+ }
+}
diff --git a/examples/bundle/app/res/drawable-hdpi/ic_launcher.png b/examples/bundle/app/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 000000000..6ab2adde2
Binary files /dev/null and b/examples/bundle/app/res/drawable-hdpi/ic_launcher.png differ
diff --git a/examples/bundle/app/res/drawable-mdpi/ic_launcher.png b/examples/bundle/app/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 000000000..c0a73c33d
Binary files /dev/null and b/examples/bundle/app/res/drawable-mdpi/ic_launcher.png differ
diff --git a/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png b/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..014b0f106
Binary files /dev/null and b/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png b/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..20703a15c
Binary files /dev/null and b/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/examples/bundle/app/res/layout/basic_activity.xml b/examples/bundle/app/res/layout/basic_activity.xml
new file mode 100644
index 000000000..f84199cb5
--- /dev/null
+++ b/examples/bundle/app/res/layout/basic_activity.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/app/res/menu/menu.xml b/examples/bundle/app/res/menu/menu.xml
new file mode 100644
index 000000000..478f557fa
--- /dev/null
+++ b/examples/bundle/app/res/menu/menu.xml
@@ -0,0 +1,8 @@
+
diff --git a/examples/bundle/app/res/values/dimens.xml b/examples/bundle/app/res/values/dimens.xml
new file mode 100644
index 000000000..47c822467
--- /dev/null
+++ b/examples/bundle/app/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/examples/bundle/app/res/values/strings.xml b/examples/bundle/app/res/values/strings.xml
new file mode 100644
index 000000000..451d3739f
--- /dev/null
+++ b/examples/bundle/app/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+
+ basicbundle
+ Hello world!
+ Settings
+
+
diff --git a/examples/bundle/features/assets/BUILD b/examples/bundle/features/assets/BUILD
new file mode 100644
index 000000000..245a904ee
--- /dev/null
+++ b/examples/bundle/features/assets/BUILD
@@ -0,0 +1,18 @@
+load("@rules_android//android:rules.bzl", "android_library")
+load("@rules_android//rules:rules.bzl", "android_feature_module")
+
+android_library(
+ name = "lib",
+ manifest = "src/AndroidManifest.xml",
+ assets = ["src/assets.txt"],
+)
+
+android_feature_module(
+ name = "feature_module",
+ custom_package = "com.example.bundle.features.assets",
+ manifest = "src/AndroidManifest.xml",
+ title = "asset_feature",
+ library = ":lib",
+ feature_name = "asset_feature",
+ visibility = ["//visibility:public"],
+)
\ No newline at end of file
diff --git a/examples/bundle/features/assets/src/AndroidManifest.xml b/examples/bundle/features/assets/src/AndroidManifest.xml
new file mode 100644
index 000000000..c9556abd1
--- /dev/null
+++ b/examples/bundle/features/assets/src/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/features/assets/src/assets.txt b/examples/bundle/features/assets/src/assets.txt
new file mode 100644
index 000000000..08c4a31e7
--- /dev/null
+++ b/examples/bundle/features/assets/src/assets.txt
@@ -0,0 +1 @@
+This text originates from a dynamically loaded feature.
\ No newline at end of file
diff --git a/examples/bundle/features/dex/BUILD b/examples/bundle/features/dex/BUILD
new file mode 100644
index 000000000..fac2ba489
--- /dev/null
+++ b/examples/bundle/features/dex/BUILD
@@ -0,0 +1,22 @@
+load("@rules_android//android:rules.bzl", "android_library")
+load("@rules_android//rules:rules.bzl", "android_feature_module")
+
+android_library(
+ name = "lib",
+ srcs = glob(["src/main/*.java"]),
+ custom_package = "com.examples.bundle.features.dex",
+ manifest = "src/AndroidManifest.xml",
+ deps = [
+ "@maven//:com_google_guava_guava",
+ ]
+)
+
+android_feature_module(
+ name = "feature_module",
+ custom_package = "com.examples.bundle.features.dex",
+ manifest = "src/AndroidManifest.xml",
+ title = "dex_feature",
+ library = ":lib",
+ feature_name = "dex_feature",
+ visibility = ["//visibility:public"],
+)
\ No newline at end of file
diff --git a/examples/bundle/features/dex/src/AndroidManifest.xml b/examples/bundle/features/dex/src/AndroidManifest.xml
new file mode 100644
index 000000000..56f053ceb
--- /dev/null
+++ b/examples/bundle/features/dex/src/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/features/dex/src/main/JavaSampleActivity.java b/examples/bundle/features/dex/src/main/JavaSampleActivity.java
new file mode 100644
index 000000000..405e04c9d
--- /dev/null
+++ b/examples/bundle/features/dex/src/main/JavaSampleActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.examples.bundle.dex;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.google.common.collect.ImmutableList;
+
+/** A simple activity displaying text written in Java. */
+public class JavaSampleActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+}
diff --git a/examples/bundle/features/dex/src/main/Unused.java b/examples/bundle/features/dex/src/main/Unused.java
new file mode 100644
index 000000000..1c72f8b12
--- /dev/null
+++ b/examples/bundle/features/dex/src/main/Unused.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.examples.bundle.dex;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Unused {
+
+ private void foo() {
+ }
+
+}
diff --git a/examples/bundle/features/native/BUILD b/examples/bundle/features/native/BUILD
new file mode 100644
index 000000000..4222b8bc6
--- /dev/null
+++ b/examples/bundle/features/native/BUILD
@@ -0,0 +1,21 @@
+load("@rules_android//android:rules.bzl", "android_library")
+load("@rules_android//rules:rules.bzl", "android_feature_module")
+
+android_library(
+ name = "lib",
+ custom_package = "com.examples.bundle.native",
+ manifest = "src/AndroidManifest.xml",
+ deps = [
+ "@maven//:com_facebook_soloader_soloader",
+ ]
+)
+
+android_feature_module(
+ name = "feature_module",
+ custom_package = "com.examples.bundle.features.native",
+ manifest = "src/AndroidManifest.xml",
+ title = "native_feature",
+ library = ":lib",
+ feature_name = "native_feature",
+ visibility = ["//visibility:public"],
+)
\ No newline at end of file
diff --git a/examples/bundle/features/native/src/AndroidManifest.xml b/examples/bundle/features/native/src/AndroidManifest.xml
new file mode 100644
index 000000000..56f053ceb
--- /dev/null
+++ b/examples/bundle/features/native/src/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/features/resources/BUILD b/examples/bundle/features/resources/BUILD
new file mode 100644
index 000000000..f92225985
--- /dev/null
+++ b/examples/bundle/features/resources/BUILD
@@ -0,0 +1,19 @@
+load("@rules_android//android:rules.bzl", "android_library")
+load("@rules_android//rules:rules.bzl", "android_feature_module")
+
+android_library(
+ name = "lib",
+ custom_package = "com.examples.bundle.resources",
+ resource_files = glob(["src/res/**"]),
+ manifest = "src/AndroidManifest.xml",
+)
+
+android_feature_module(
+ name = "feature_module",
+ custom_package = "com.examples.bundle.features.resources",
+ manifest = "src/AndroidManifest.xml",
+ title = "resources_feature",
+ library = ":lib",
+ feature_name = "resources_feature",
+ visibility = ["//visibility:public"],
+)
\ No newline at end of file
diff --git a/examples/bundle/features/resources/src/AndroidManifest.xml b/examples/bundle/features/resources/src/AndroidManifest.xml
new file mode 100644
index 000000000..2b16757a6
--- /dev/null
+++ b/examples/bundle/features/resources/src/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/examples/bundle/features/resources/src/res/layout/activity_feature_java.xml b/examples/bundle/features/resources/src/res/layout/activity_feature_java.xml
new file mode 100644
index 000000000..6d8d1f604
--- /dev/null
+++ b/examples/bundle/features/resources/src/res/layout/activity_feature_java.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/examples/bundle/features/resources/src/res/values/strings.xml b/examples/bundle/features/resources/src/res/values/strings.xml
new file mode 100644
index 000000000..ab62d104d
--- /dev/null
+++ b/examples/bundle/features/resources/src/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ This is a dynamic feature, written in \nJava\n which can be loaded on demand.
+
diff --git a/providers/providers.bzl b/providers/providers.bzl
index 0317a539e..5ca844b1d 100644
--- a/providers/providers.bzl
+++ b/providers/providers.bzl
@@ -110,6 +110,7 @@ AndroidFeatureModuleInfo = provider(
min_sdk_version = "String, the min SDK version for this feature.",
title_id = "String, resource identifier for the split title.",
title_lib = "String, target of the split title android_library.",
+ library_resources_only_lib = "String, target of the library resource-only android_library.",
is_asset_pack = "Boolean, whether this feature module is an asset pack. AI packs are a type of asset pack.",
),
)
@@ -200,6 +201,14 @@ AndroidLibraryAarInfo = provider(
),
)
+AndroidApplicationResourceInfo = provider(
+ doc = "Contains application resource data from an android_binary target.",
+ fields = dict(
+ resource_apk = "The resource APK file.",
+ resource_proguard_config = "The resource proguard config file.",
+ ),
+)
+
AndroidBinaryNativeLibsInfo = provider(
doc = "AndroidBinaryNativeLibsInfo",
fields = dict(
diff --git a/rules/android_application/BUILD b/rules/android_application/BUILD
index 92cde8e7e..5ee1326e8 100644
--- a/rules/android_application/BUILD
+++ b/rules/android_application/BUILD
@@ -8,6 +8,7 @@ licenses(["notice"])
exports_files([
"bundle_deploy.sh_template",
"feature_module_validation.sh",
+ "filter_feature_classes.sh",
"gen_android_feature_manifest.sh",
"gen_priority_android_feature_manifest.sh",
"rule.bzl",
@@ -40,10 +41,3 @@ py_binary(
"@py_absl//absl/flags",
],
)
-
-filegroup(
- name = "merge_feature_manifests.par",
- srcs = [":merge_feature_manifests"],
- output_group = "python_zip_file",
- visibility = ["//visibility:public"],
-)
diff --git a/rules/android_application/android_application_rule.bzl b/rules/android_application/android_application_rule.bzl
index a450426d7..c5c5c4f6c 100644
--- a/rules/android_application/android_application_rule.bzl
+++ b/rules/android_application/android_application_rule.bzl
@@ -13,9 +13,14 @@
# limitations under the License.
"""android_application rule."""
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load("@rules_java//java/common:java_info.bzl", "JavaInfo")
+load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
load(
"//providers:providers.bzl",
+ "AndroidApplicationResourceInfo",
"AndroidArchivedSandboxedSdkInfo",
+ "AndroidBinaryNativeLibsInfo",
"AndroidBundleInfo",
"AndroidFeatureModuleInfo",
"AndroidIdeInfo",
@@ -58,12 +63,14 @@ load(
"ANDROID_SDK_TOOLCHAIN_TYPE",
"get_android_sdk",
"get_android_toolchain",
+ "utils",
_log = "log",
)
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
load("@rules_java//java/common:java_common.bzl", "java_common")
load(":android_feature_module_rule.bzl", "get_feature_module_paths")
load(":attrs.bzl", "ANDROID_APPLICATION_ATTRS")
+load("//rules:r8.bzl", _r8 = "r8")
visibility(PROJECT_VISIBILITY)
@@ -71,6 +78,8 @@ UNSUPPORTED_ATTRS = [
"srcs",
]
+_EMPTY_ZIP = "UEsFBgAAAAAAAAAAAAAAAAAAAAAAAA=="
+
def _verify_attrs(attrs, fqn):
for attr in UNSUPPORTED_ATTRS:
if hasattr(attrs, attr):
@@ -86,12 +95,74 @@ def _process_feature_module(
base_apk = None,
feature_target = None,
java_package = None,
- application_id = None):
+ application_id = None,
+ r8_feature_map = None,
+ base_module_paths = []):
+
+ dex_archives = []
+ apk_info = feature_target[AndroidFeatureModuleInfo].binary[ApkInfo]
+ optimized_dex = r8_feature_map.get(apk_info.deploy_jar)
+ feature_name = optimized_dex.path.split("/")[-1].lower() if optimized_dex else feature_target.label.name
+ dex_zip = ctx.actions.declare_file(ctx.label.name + "/" + feature_name + "/classes.dex.zip")
+ zip_tool = get_android_toolchain(ctx).zip_tool.files_to_run
+
+ if optimized_dex:
+ ctx.actions.run_shell(
+ tools = [zip_tool],
+ inputs = [optimized_dex],
+ outputs = [dex_zip],
+ command = """#!/bin/sh
+if [ ! -f "{dex_dir}/classes.dex" ]; then
+ echo "{empty_zip}" | base64 -d > "{dex_zip}"
+else
+ find {dex_dir} -exec touch -t 199609240000 {{}} \\;
+ {zip_tool} -X -j -r -q {dex_zip} {dex_dir}
+fi
+ """.format(
+ empty_zip = _EMPTY_ZIP,
+ zip_tool = zip_tool.executable.path,
+ dex_zip = dex_zip.path,
+ dex_dir = optimized_dex.path,
+ ),
+ mnemonic = "ZipDex",
+ progress_message = "Zipping optimized dex %s" % optimized_dex.path,
+ )
+ dex_archives = [dex_zip]
+ else:
+ # extract dex files from the feature module apk or create an empty zip if there are no dex files
+ unzip_tool = get_android_toolchain(ctx).unzip_tool.files_to_run
+ ctx.actions.run_shell(
+ tools = [unzip_tool, zip_tool],
+ inputs = [apk_info.unsigned_apk],
+ outputs = [dex_zip],
+ command = """#!/bin/sh
+ {unzip_tool} -l {unsigned_apk} "classes*.dex"
+ unzip=$?
+ if [[ "${{unzip}}" != 0 ]]; then
+ echo "{empty_zip}" | base64 -d > "{dex_zip}"
+ else
+ {zip_tool} -q {unsigned_apk} "classes*.dex" --copy --out {dex_zip}
+ fi
+ """.format(
+ empty_zip = _EMPTY_ZIP,
+ zip_tool = zip_tool.executable.path,
+ unzip_tool = unzip_tool.executable.path,
+ dex_zip = dex_zip.path,
+ unsigned_apk = apk_info.unsigned_apk.path,
+ ),
+ mnemonic = "ZipDex",
+ progress_message = "Zipping dex %s" % dex_zip.path,
+ )
+ dex_archives = [dex_zip]
+
manifest = _create_feature_manifest(
ctx,
base_apk,
java_package,
feature_target,
+ feature_name,
+ dex_zip,
+ base_module_paths,
get_android_sdk(ctx).aapt2,
ctx.executable._feature_manifest_script,
ctx.executable._priority_feature_manifest_script,
@@ -99,27 +170,12 @@ def _process_feature_module(
_common.get_host_javabase(ctx),
)
- # Remove all dexes from the feature module apk. jvm / resources are not
- # supported in feature modules. The android_feature_module rule has
- # already validated that there are no transitive sources / resources, but
- # we may get dexes via e.g. the legacy dex or the record globals.
- binary = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "_filtered.apk")
- _common.filter_zip_exclude(
- ctx,
- output = binary,
- input = feature_target[AndroidFeatureModuleInfo].binary[ApkInfo].unsigned_apk,
- filter_types = [".dex"],
- )
res = feature_target[AndroidFeatureModuleInfo].library[StarlarkAndroidResourcesInfo]
- has_native_libs = bool(feature_target[AndroidFeatureModuleInfo].binary[AndroidIdeInfo].native_libs)
- is_asset_pack = bool(feature_target[AndroidFeatureModuleInfo].is_asset_pack)
+ binary = feature_target[AndroidFeatureModuleInfo].binary[ApkInfo].unsigned_apk
+ _feature_unique_name = feature_target[AndroidFeatureModuleInfo].feature_name
- # Create res .proto-apk_, output depending on whether further manipulations
- # are required after busybox. This prevents action conflicts.
- if has_native_libs or is_asset_pack:
- res_apk = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/res.proto-ap_")
- else:
- res_apk = out
+ # Create res .proto-apk_ (always as a separate file to avoid bundletool entry clash)
+ res_apk = ctx.actions.declare_file(ctx.label.name + "/" + _feature_unique_name + "/res.proto-ap_")
_busybox.package(
ctx,
out_r_src_jar = ctx.actions.declare_file("R.srcjar", sibling = manifest),
@@ -149,49 +205,93 @@ def _process_feature_module(
aapt = get_android_toolchain(ctx).aapt2.files_to_run,
busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run,
host_javabase = _common.get_host_javabase(ctx),
- should_throw_on_conflict = True,
- # We only support native libraries and assets in feature modules. This likely needs to be
- # conditionally set if/when we support jvm / resources.
+ should_throw_on_conflict = False,
debug = False,
application_id = application_id,
)
- if not is_asset_pack and not has_native_libs:
- return
+ # Extract libs/ from split binary (two-step: include then exclude manifest)
+ native_lib_tmp = ctx.actions.declare_file(ctx.label.name + "/" + _feature_unique_name + "/native_libs_tmp.zip")
+ _common.filter_zip_include(ctx, binary, native_lib_tmp, ["lib/*", "AndroidManifest.xml"])
+ native_lib = ctx.actions.declare_file(ctx.label.name + "/" + _feature_unique_name + "/native_libs.zip")
+ _common.filter_zip_exclude(ctx, native_lib, native_lib_tmp, filters = ["AndroidManifest.xml"])
+ native_libs = [native_lib]
- if is_asset_pack:
- # Return AndroidManifest.xml and assets from res-ap_
- _common.filter_zip_include(ctx, res_apk, out, ["AndroidManifest.xml", "assets/*"])
- else:
- # Extract libs/ from split binary
- native_libs = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/native_libs.zip")
- _common.filter_zip_include(ctx, binary, native_libs, ["lib/*"])
+ # Extract only AndroidManifest.xml and assets from res-ap_ (no res/ files to avoid bundletool clash)
+ filtered_res = ctx.actions.declare_file(ctx.label.name + "/" + _feature_unique_name + "/filtered_res.zip")
+ _common.filter_zip_include(ctx, res_apk, filtered_res, ["AndroidManifest.xml", "assets/*"])
- # Extract AndroidManifest.xml and assets from res-ap_
- filtered_res = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/filtered_res.zip")
- _common.filter_zip_include(ctx, res_apk, filtered_res, ["AndroidManifest.xml", "assets/*"])
+ # Merge dex + native libs + manifest/assets into one zip
+ merged_jar = ctx.actions.declare_file(ctx.label.name + "/" + _feature_unique_name + "/merged.zip")
+ _java.singlejar(
+ ctx,
+ inputs = dex_archives + native_libs + [filtered_res],
+ output = merged_jar,
+ java_toolchain = _common.get_java_toolchain(ctx),
+ )
- # Merge into output
- _java.singlejar(
- ctx,
- inputs = [filtered_res, native_libs],
- output = out,
- java_toolchain = _common.get_java_toolchain(ctx),
+ # Filter out classes/resources already present in the base to avoid duplication
+ inputs = [base_apk, merged_jar]
+ args = ctx.actions.args()
+ args.add(out.path)
+ args.add(base_apk.path)
+ args.add(merged_jar.path)
+ args.add("^lib/|^assets/|META-INF/MANIFEST.MF")
+ ctx.actions.run(
+ executable = ctx.executable._filter_feature_classes_script,
+ inputs = inputs,
+ outputs = [out],
+ arguments = [args],
+ mnemonic = "FilterResFeatureModule",
+ progress_message = "Filtering resource jar for feature module '%s'" % feature_name,
+ toolchain = None,
+ )
+
+def _create_r8_output_directories(ctx):
+ jar_to_dir = dict()
+ for module in ctx.attr.feature_modules:
+ name = module[AndroidFeatureModuleInfo].feature_name
+ output_dir = ctx.actions.declare_directory(
+ ctx.label.name + "/proguarded_modules/" + name
)
+ deploy_jar = module[AndroidFeatureModuleInfo].binary[ApkInfo].deploy_jar
+ jar_to_dir[deploy_jar] = output_dir
+ return jar_to_dir
+
+def _module_path(artifact):
+ path = artifact.short_path.replace("/_migrated", "")
+ if path.startswith("../"):
+ path = path.replace("_processed_manifest/AndroidManifest.xml", "").replace("_resources.jar", "")
+ else:
+ path = path.replace("/_migrated", "").replace("/AndroidManifest.xml", "")
+ path, _, _ = path.rpartition("/")
+ return path
+
def _create_feature_manifest(
ctx,
base_apk,
java_package,
feature_target,
+ feature_name,
+ dex_zip,
+ base_module_paths,
aapt2,
feature_manifest_script,
priority_feature_manifest_script,
android_resources_busybox,
host_javabase):
info = feature_target[AndroidFeatureModuleInfo]
- manifest = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/AndroidManifest.xml")
+ manifest = ctx.actions.declare_file(ctx.label.name + "/" + feature_name + "/AndroidManifest.xml")
+ # Only include manifest not already present in base module
+ transitive_manifests = []
+ for transitive_manifest in info.library[StarlarkAndroidResourcesInfo].transitive_manifests.to_list():
+ manifest_module_path = _module_path(transitive_manifest)
+ if manifest_module_path not in base_module_paths:
+ transitive_manifests.append(transitive_manifest)
+
+ manifest_to_merge = None
# Rule has not specified a manifest. Populate the default manifest template.
if not info.manifest:
args = ctx.actions.args()
@@ -202,6 +302,7 @@ def _create_feature_manifest(
args.add(info.title_id)
args.add(info.fused)
args.add(aapt2.executable)
+ args.add(dex_zip)
ctx.actions.run(
executable = feature_manifest_script,
@@ -212,54 +313,65 @@ def _create_feature_manifest(
aapt2,
],
mnemonic = "GenFeatureManifest",
- progress_message = "Generating AndroidManifest.xml for " + feature_target.label.name,
+ progress_message = "Generating AndroidManifest.xml for " + feature_name,
+ toolchain = None,
+ )
+ manifest_to_merge = manifest
+ else:
+ # Rule has a manifest (already validated by android_feature_module).
+ # Generate a priority manifest and then merge the user supplied manifest.
+ priority_manifest = ctx.actions.declare_file(
+ ctx.label.name + "/" + feature_name + "/Prioriy_AndroidManifest.xml",
+ )
+ args = ctx.actions.args()
+ args.add(priority_manifest.path)
+ args.add(base_apk.path)
+ args.add(info.manifest.path)
+ args.add(info.feature_name)
+ args.add(aapt2.executable)
+ args.add(dex_zip)
+ ctx.actions.run(
+ executable = priority_feature_manifest_script,
+ inputs = [info.manifest, base_apk, dex_zip],
+ outputs = [priority_manifest],
+ arguments = [args],
+ tools = [
+ aapt2,
+ ],
+ mnemonic = "GenPriorityFeatureManifest",
+ progress_message = "Generating Priority AndroidManifest.xml for " + feature_name,
toolchain = None,
)
- return manifest
-
- # Rule has a manifest (already validated by android_feature_module).
- # Generate a priority manifest and then merge the user supplied manifest.
- is_asset_pack = feature_target[AndroidFeatureModuleInfo].is_asset_pack
- priority_manifest = ctx.actions.declare_file(
- ctx.label.name + "/" + feature_target.label.name + "/Priority_AndroidManifest.xml",
- )
- args = ctx.actions.args()
- args.add(priority_manifest.path)
- args.add(base_apk.path)
- args.add(java_package)
- args.add(info.feature_name)
- args.add(aapt2.executable)
- args.add(info.manifest)
- args.add(is_asset_pack)
- ctx.actions.run(
- executable = priority_feature_manifest_script,
- inputs = [base_apk, info.manifest],
- outputs = [priority_manifest],
- arguments = [args],
- tools = [
- aapt2,
- ],
- mnemonic = "GenPriorityFeatureManifest",
- progress_message = "Generating Priority AndroidManifest.xml for " + feature_target.label.name,
- toolchain = None,
- )
+ manifest_to_merge = ctx.actions.declare_file(ctx.label.name + "/" + feature_name + "/feature_AndroidManifest.xml")
+ args = ctx.actions.args()
+ args.add("--main_manifest", priority_manifest.path)
+ args.add("--feature_manifest", info.manifest.path)
+ args.add("--feature_title", "@string/" + info.title_id)
+ args.add("--out", manifest_to_merge.path)
+ ctx.actions.run(
+ executable = ctx.attr._merge_manifests.files_to_run,
+ inputs = [priority_manifest, info.manifest],
+ outputs = [manifest_to_merge],
+ arguments = [args],
+ toolchain = None,
+ )
- args = ctx.actions.args()
- args.add("--main_manifest", priority_manifest.path)
- args.add("--feature_manifest", info.manifest.path)
- args.add("--feature_title", "@string/" + info.title_id)
- args.add("--out", manifest.path)
- if is_asset_pack:
- args.add("--is_asset_pack")
- ctx.actions.run(
- executable = ctx.attr._merge_manifests.files_to_run,
- inputs = [priority_manifest, info.manifest],
- outputs = [manifest],
- arguments = [args],
- toolchain = None,
+ _busybox.merge_manifests(
+ ctx,
+ out_file = manifest,
+ out_log_file = ctx.actions.declare_file(
+ ctx.label.name + "/%s_feature_manifest_merger_log.txt" % info.feature_name,
+ ),
+ manifest = manifest_to_merge,
+ mergee_manifests = depset(transitive_manifests),
+ manifest_values = {"MODULE_TITLE": "@string/" + info.title_id},
+ manifest_merge_order = ctx.attr._manifest_merge_order[BuildSettingInfo].value,
+ merge_type = "APPLICATION",
+ java_package = java_package,
+ busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run,
+ host_javabase = _common.get_host_javabase(ctx),
)
-
return manifest
def _generate_runtime_enabled_sdk_config(ctx, base_proto_apk):
@@ -306,10 +418,77 @@ def _impl(ctx):
# Convert base apk to .proto_ap_
base_apk = ctx.attr.base_module[ApkInfo].unsigned_apk
base_proto_apk = ctx.actions.declare_file(ctx.label.name + "/modules/base.proto-ap_")
+
+ r8_feature_map = dict()
+ android_dex_info = None
+ optimization_info = None
+ baseline_profile_info = None
+ if ctx.attr.proguard_specs:
+ r8_feature_map = _create_r8_output_directories(ctx)
+ main_deploy_jar = ctx.attr.base_module[ApkInfo].deploy_jar
+
+ proguard_specs = []
+ for specs in ctx.files.proguard_specs:
+ proguard_specs.append(specs)
+
+ # Include base module's resource proguard config (keeps Android components from manifest)
+ base_resource_info = ctx.attr.base_module[AndroidApplicationResourceInfo]
+ if base_resource_info.resource_proguard_config:
+ proguard_specs.append(base_resource_info.resource_proguard_config)
+
+ all_modules = [ctx.attr.base_module]
+ for feature in ctx.attr.feature_modules:
+ binary = feature[AndroidFeatureModuleInfo].binary
+ if AndroidApplicationResourceInfo in binary:
+ proguard_specs.append(binary[AndroidApplicationResourceInfo].resource_proguard_config)
+ all_modules.append(feature[AndroidFeatureModuleInfo].library)
+
+ spec_providers = utils.collect_providers(
+ ProguardSpecInfo,
+ all_modules
+ )
+
+ for sp in spec_providers:
+ for spec in sp.specs.to_list():
+ if spec not in proguard_specs:
+ proguard_specs.append(spec)
+
+ resource_apk = ctx.attr.base_module[AndroidApplicationResourceInfo].resource_apk
+ app_resource_info = ctx.attr.base_module[AndroidApplicationResourceInfo]
+ android_dex_info, optimization_info = _r8.process(
+ ctx,
+ main_deploy_jar,
+ app_resource_info,
+ proguard_specs,
+ startup_profile = ctx.file.startup_profile,
+ feature_split_jars = r8_feature_map,
+ )
+
+ base_apk = ctx.actions.declare_file(ctx.label.name + "_base_proguarded_unsigned.apk")
+ native_libs = depset(transitive = ctx.attr.base_module[AndroidBinaryNativeLibsInfo].transitive_native_libs_by_cpu_architecture.values()).to_list()
+ _java.singlejar(
+ ctx,
+ inputs = [resource_apk, android_dex_info.final_classes_dex_zip] + native_libs,
+ output = base_apk,
+ include_build_data = False,
+ java_toolchain = _common.get_java_toolchain(ctx),
+ )
+
+ if optimization_info:
+ optimized_base_apk = ctx.actions.declare_file(ctx.label.name + "_base_proguarded_optimized_unsigned.apk")
+ native_libs = depset(transitive = ctx.attr.base_module[AndroidBinaryNativeLibsInfo].transitive_native_libs_by_cpu_architecture.values()).to_list()
+ _java.singlejar(
+ ctx,
+ inputs = [optimization_info.optimized_resource_apk, android_dex_info.final_classes_dex_zip] + native_libs,
+ output = optimized_base_apk,
+ include_build_data = False,
+ java_toolchain = _common.get_java_toolchain(ctx),
+ )
+
_aapt.convert(
ctx,
out = base_proto_apk,
- input = base_apk,
+ input = optimized_base_apk if optimization_info else base_apk,
to_proto = True,
aapt = get_android_toolchain(ctx).aapt2.files_to_run,
)
@@ -330,10 +509,15 @@ def _impl(ctx):
get_android_toolchain(ctx).bundletool_module_builder.files_to_run,
)
+ base_module_paths = []
+ for dep in ctx.attr.deps:
+ for jar in dep[JavaInfo].transitive_runtime_jars.to_list():
+ base_module_paths.append(_module_path(jar))
+
# Convert each feature to module zip.
for feature in ctx.attr.feature_modules:
proto_apk = ctx.actions.declare_file(
- "%s.proto-ap_" % feature.label.name,
+ "%s.proto-ap_" % feature[AndroidFeatureModuleInfo].feature_name,
sibling = base_proto_apk,
)
_process_feature_module(
@@ -343,6 +527,8 @@ def _impl(ctx):
feature_target = feature,
java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.custom_package),
application_id = ctx.attr.manifest_values.get("applicationId"),
+ r8_feature_map = r8_feature_map,
+ base_module_paths = base_module_paths,
)
module = ctx.actions.declare_file(
proto_apk.basename + ".zip",
@@ -358,8 +544,12 @@ def _impl(ctx):
)
metadata = dict()
- if ProguardMappingInfo in ctx.attr.base_module:
- metadata["com.android.tools.build.obfuscation/proguard.map"] = ctx.attr.base_module[ProguardMappingInfo].proguard_mapping
+ if ctx.attr.proguard_include_mapping:
+ if android_dex_info:
+ metadata["com.android.tools.build.obfuscation/proguard.map"] = android_dex_info.final_proguard_output_map
+
+ if ProguardMappingInfo in ctx.attr.base_module:
+ metadata["com.android.tools.build.obfuscation/proguard.map"] = ctx.attr.base_module[ProguardMappingInfo].proguard_mapping
if ctx.file.device_group_config:
metadata["com.android.tools.build.bundletool/DeviceGroupConfig.json"] = ctx.file.device_group_config
@@ -395,11 +585,13 @@ def _impl(ctx):
java_runtime = _common.get_host_javabase(ctx)[java_common.JavaRuntimeInfo]
base_apk_info = ctx.attr.base_module[ApkInfo]
deploy_script_files = [base_apk_info.signing_keys[-1]]
+
subs = {
"%java_executable%": java_runtime.java_executable_exec_path,
"%bundletool_path%": get_android_toolchain(ctx).bundletool.files_to_run.executable.short_path,
"%aab%": ctx.outputs.unsigned_aab.short_path,
"%newest_key%": base_apk_info.signing_keys[-1].short_path,
+ "%aapt2_path%" : get_android_sdk(ctx).aapt2.executable.short_path,
}
if base_apk_info.signing_lineage:
signer_properties = _common.create_signer_properties(ctx, base_apk_info.signing_keys[0])
@@ -420,7 +612,7 @@ def _impl(ctx):
is_executable = True,
)
- return [
+ providers = [
ctx.attr.base_module[ApkInfo],
ctx.attr.base_module[AndroidPreDexJarInfo],
AndroidBundleInfo(unsigned_aab = ctx.outputs.unsigned_aab),
@@ -429,9 +621,13 @@ def _impl(ctx):
runfiles = ctx.runfiles([
ctx.outputs.unsigned_aab,
get_android_toolchain(ctx).bundletool.files_to_run.executable,
+ get_android_sdk(ctx).aapt2.executable,
] + deploy_script_files, transitive_files = java_runtime.files),
),
]
+ if android_dex_info:
+ providers.append(android_dex_info)
+ return providers
android_application = rule(
attrs = ANDROID_APPLICATION_ATTRS,
@@ -505,34 +701,59 @@ def android_application_macro(_android_binary, **attrs):
# In the future bundle_config will accept a build rule rather than a raw file.
bundle_config_file = bundle_config_file or bundle_config
+ modules_titles = []
deps = attrs.pop("deps", [])
+ original_deps = deps
for feature_module in feature_modules:
if not feature_module.startswith("//") or ":" not in feature_module:
_log.error("feature_modules expects fully qualified paths, i.e. //some/path:target")
module_targets = get_feature_module_paths(feature_module)
- deps = deps + [str(module_targets.title_lib)]
+ deps = deps + [module_targets.title_lib, module_targets.library_resources_only_lib]
+ modules_titles.append(str(module_targets.title_strings_xml))
+
+ # we dont want to proguard the base module. It needs to be proguarded with all the splits.
+ proguard_specs = attrs.get("proguard_specs", [])
+ startup_profile = attrs.pop("startup_profile", None)
+
+ tags = attrs.pop("tags", [])
+ if proguard_specs:
+ tags += ["has_proguard_specs"]
+
+ # obfuscate_resources is an android_application attr, not android_binary.
+ obfuscate_resources = attrs.pop("obfuscate_resources", False)
+
+ base_name = "%s_internal" % name
+ base_module = ":%s" % base_name
_android_binary(
- name = base_split_name,
+ name = base_name,
deps = deps,
+ tags = tags,
**attrs
)
-
+
android_application(
name = name,
- base_module = ":%s" % base_split_name,
+ base_module = base_module,
bundle_config_file = bundle_config_file,
app_integrity_config = app_integrity_config,
device_group_config = device_group_config,
rotation_config = rotation_config,
+ proguard_specs = proguard_specs,
custom_package = attrs.get("custom_package", None),
testonly = attrs.get("testonly"),
transitive_configs = attrs.get("transitive_configs", []),
feature_modules = feature_modules,
+ feature_modules_title_files = modules_titles,
sdk_archives = sdk_archives,
sdk_bundles = sdk_bundles,
+ startup_profile = startup_profile,
manifest_values = attrs.get("manifest_values"),
+ min_sdk_version = attrs.get("min_sdk_version", None),
visibility = attrs.get("visibility", None),
tags = attrs.get("tags", []),
exec_properties = attrs.get("exec_properties", None),
+ deps = original_deps,
+ shrink_resources = attrs.get("shrink_resources", False),
+ obfuscate_resources = obfuscate_resources,
)
diff --git a/rules/android_application/android_feature_module_rule.bzl b/rules/android_application/android_feature_module_rule.bzl
index f4b240571..55a26d118 100644
--- a/rules/android_application/android_feature_module_rule.bzl
+++ b/rules/android_application/android_feature_module_rule.bzl
@@ -18,6 +18,8 @@ load(
"AndroidFeatureModuleInfo",
"AndroidIdeInfo",
"ApkInfo",
+ "ResourcesNodeInfo",
+ "StarlarkAndroidResourcesInfo",
)
load("//rules:acls.bzl", "acls")
load("//rules:java.bzl", _java = "java")
@@ -29,6 +31,8 @@ load(
)
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
load(":attrs.bzl", "ANDROID_FEATURE_MODULE_ATTRS")
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
visibility(PROJECT_VISIBILITY)
@@ -64,19 +68,26 @@ def _impl(ctx):
toolchain = None,
)
+ proguard_provider = []
+ if ctx.attr.proguard_specs:
+ proguard_provider = [
+ ProguardSpecInfo(depset(ctx.files.proguard_specs))
+ ]
+
return [
AndroidFeatureModuleInfo(
binary = ctx.attr.binary,
library = utils.dedupe_split_attr(ctx.split_attr.library),
title_id = ctx.attr.title_id,
title_lib = ctx.attr.title_lib,
+ library_resources_only_lib = ctx.attr.library_resources_only_lib,
feature_name = ctx.attr.feature_name,
fused = ctx.attr.fused,
manifest = ctx.file.manifest,
is_asset_pack = ctx.attr.is_asset_pack,
),
OutputGroupInfo(_validation = depset([validation])),
- ]
+ ] + proguard_provider
android_feature_module = rule(
attrs = ANDROID_FEATURE_MODULE_ATTRS,
@@ -95,10 +106,11 @@ def get_feature_module_paths(fqn):
# Given a fqn to an android_feature_module, returns the absolute paths to
# all implicitly generated targets
return struct(
- binary = Label("%s_bin" % fqn),
- manifest_lib = Label("%s_AndroidManifest" % fqn),
- title_strings_xml = Label("%s_title_strings_xml" % fqn),
- title_lib = Label("%s_title_lib" % fqn),
+ binary = native.package_relative_label("%s_bin" % fqn),
+ manifest_lib = native.package_relative_label("%s_AndroidManifest" % fqn),
+ title_strings_xml = native.package_relative_label("%s_title_strings_xml" % fqn),
+ title_lib = native.package_relative_label("%s_title_lib" % fqn),
+ library_resources_only_lib = native.package_relative_label("%s_resources_only_lib" % fqn),
)
def android_feature_module_macro(_android_binary, _android_library, **attrs):
@@ -129,6 +141,8 @@ def android_feature_module_macro(_android_binary, _android_library, **attrs):
targets = get_feature_module_paths(fqn)
tags = getattr(attrs, "tags", [])
+ tags += ["has_proguard_specs"]
+
transitive_configs = getattr(attrs, "transitive_configs", [])
visibility = getattr(attrs, "visibility", None)
testonly = getattr(attrs, "testonly", None)
@@ -147,6 +161,7 @@ tools:keep="@string/{title_id}">
EOF
""".format(title = attrs.title, title_id = title_id),
+ visibility = ["//visibility:public"],
)
# Create AndroidManifest.xml
@@ -178,6 +193,19 @@ EOF
testonly = testonly,
)
+ # Create a resource-only android library so that base module
+ # includes android resources of feature modules, similarly to Buck.
+ resource_only_lib_internal_name = targets.library_resources_only_lib.name + "_internal"
+ android_resources_only(
+ name = resource_only_lib_internal_name,
+ deps = [attrs.library],
+ )
+ _android_library(
+ name = targets.library_resources_only_lib.name,
+ exports = [":" + resource_only_lib_internal_name],
+ visibility = visibility,
+ )
+
# Wrap any deps in an android_binary. Will be validated to ensure does not contain any dexes
binary_attrs = {
"name": targets.binary.name,
@@ -188,7 +216,7 @@ EOF
"transitive_configs": transitive_configs,
"visibility": visibility,
"feature_flags": getattr(attrs, "feature_flags", None),
- "$enable_manifest_merging": False,
+ "$enable_manifest_merging": True,
"testonly": testonly,
}
_android_binary(**binary_attrs)
@@ -202,9 +230,82 @@ EOF
feature_name = getattr(attrs, "feature_name", attrs.name),
fused = getattr(attrs, "fused", True),
manifest = getattr(attrs, "manifest", None),
+ proguard_specs = getattr(attrs, "proguard_specs", []),
tags = tags,
transitive_configs = transitive_configs,
visibility = visibility,
testonly = testonly,
is_asset_pack = getattr(attrs, "is_asset_pack", False),
)
+
+# Dedicated rules to implement a resource-only android library from all transitive deps.
+def _android_resources_only_impl(ctx):
+ direct_resources_nodes = []
+ direct_compiled_resources = []
+ transitive_compiled_resources = []
+ transitive_r_txts = []
+ transitive_resource_files = []
+ packages_to_r_txts_depset = dict()
+ transitive_resource_apks = []
+ transitive_manifests = []
+ node_manifests = []
+
+ for dep in ctx.attr.deps:
+ if StarlarkAndroidResourcesInfo in dep:
+ info = dep[StarlarkAndroidResourcesInfo]
+ for resources_node in info.direct_resources_nodes.to_list():
+ filtered_resources_node = ResourcesNodeInfo(
+ label = resources_node.label,
+ assets = depset(),
+ assets_dir = None,
+ assets_symbols = None,
+ compiled_assets = None,
+ resource_apks = resources_node.resource_apks,
+ resource_files = resources_node.resource_files,
+ compiled_resources = resources_node.compiled_resources,
+ r_txt = resources_node.r_txt,
+ manifest = resources_node.manifest,
+ exports_manifest = False,
+ )
+ direct_resources_nodes.append(filtered_resources_node)
+ if resources_node.manifest:
+ node_manifests.append(resources_node.manifest)
+ direct_compiled_resources.append(info.direct_compiled_resources)
+ transitive_compiled_resources.append(info.transitive_compiled_resources)
+ transitive_r_txts.append(info.transitive_r_txts)
+ transitive_resource_files.append(info.transitive_resource_files)
+ transitive_resource_apks.append(info.transitive_resource_apks)
+ transitive_manifests.append(info.transitive_manifests)
+ for pkg, r_txts in info.packages_to_r_txts.items():
+ packages_to_r_txts_depset.setdefault(pkg, []).append(r_txts)
+
+ packages_to_r_txts = dict()
+ for pkg, depsets in packages_to_r_txts_depset.items():
+ packages_to_r_txts[pkg] = depset(transitive = depsets)
+
+ provider = StarlarkAndroidResourcesInfo(
+ direct_resources_nodes = depset(direct_resources_nodes),
+ transitive_resources_nodes = depset(),
+ direct_compiled_resources = depset(transitive=direct_compiled_resources),
+ transitive_compiled_resources = depset(transitive=transitive_compiled_resources),
+ transitive_r_txts = depset(transitive=transitive_r_txts),
+ transitive_resource_files = depset(transitive=transitive_resource_files),
+ packages_to_r_txts = packages_to_r_txts,
+ transitive_resource_apks = depset(transitive=transitive_resource_apks),
+ transitive_assets = depset(),
+ transitive_assets_symbols = depset(),
+ transitive_compiled_assets = depset(),
+ transitive_manifests = depset(node_manifests, transitive = transitive_manifests),
+ )
+
+ return [provider, CcInfo()]
+
+android_resources_only = rule(
+ implementation = _android_resources_only_impl,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "Android libraries containing both resources and Java code",
+ providers = [StarlarkAndroidResourcesInfo], # Ensure only android_library targets are allowed
+ ),
+ },
+)
diff --git a/rules/android_application/attrs.bzl b/rules/android_application/attrs.bzl
index 88fb2ff4c..ebbd14367 100644
--- a/rules/android_application/attrs.bzl
+++ b/rules/android_application/attrs.bzl
@@ -33,7 +33,7 @@ ANDROID_APPLICATION_ATTRS = _attrs.add(
manifest_values = attr.string_dict(),
base_module = attr.label(allow_files = False),
bundle_config_file = attr.label(
- allow_single_file = [".pb.json"],
+ allow_single_file = [".json"],
doc = ("Path to config.pb.json file, see " +
"https://github.com/google/bundletool/blob/master/src/main/proto/config.proto " +
"for definition.\n\nNote: this attribute is subject to changes which may " +
@@ -55,8 +55,40 @@ ANDROID_APPLICATION_ATTRS = _attrs.add(
allow_single_file = [".textproto"],
default = None,
),
+ proguard_specs = attr.label_list(allow_empty = True, allow_files = True),
+ proguard_generate_mapping = attr.bool(
+ default = False,
+ doc = "Whether to output the proguard mapping file.",
+ ),
+ proguard_include_mapping = attr.bool(
+ default = False,
+ doc = "Whether to include proguard mapping into final bundle as METADATA file.",
+ ),
+ startup_profile = attr.label(
+ mandatory = False,
+ allow_single_file = True,
+ default = None,
+ doc = "ART Startup profile to provide to R8 for PGO",
+ ),
+ feature_modules_title_files = attr.label_list(
+ default = [],
+ allow_files = True,
+ doc = "Feature modules XML string resources files. " +
+ "Generated by the feature module macro and passed by the android application macro. " +
+ "Each file contains a single resource definition with the feature's title. " +
+ "These files are provided to the resource shrinker in order to keep them.",
+ ),
custom_package = attr.string(),
feature_modules = attr.label_list(allow_files = False),
+ shrink_resources = attr.bool(
+ default = False,
+ doc = "Whether to enable R8 unused resource prunning.",
+ ),
+ obfuscate_resources = attr.bool(
+ default = False,
+ doc = "Whether to enable AAPT2 resource obfuscation, which shorten resource names.",
+ ),
+ min_sdk_version = attr.int(),
sdk_archives = attr.label_list(
providers = [
[AndroidArchivedSandboxedSdkInfo],
@@ -77,15 +109,23 @@ ANDROID_APPLICATION_ATTRS = _attrs.add(
executable = True,
default = ":gen_android_feature_manifest.sh",
),
+ _filter_feature_classes_script = attr.label(
+ allow_single_file = True,
+ cfg = "exec",
+ executable = True,
+ default = ":filter_feature_classes.sh",
+ ),
_java_toolchain = attr.label(
default = Label("//tools/jdk:toolchain_android_only"),
),
_merge_manifests = attr.label(
- default = ":merge_feature_manifests.par",
- allow_single_file = True,
+ default = ":merge_feature_manifests",
cfg = "exec",
executable = True,
),
+ _manifest_merge_order = attr.label(
+ default = "//rules/flags:manifest_merge_order",
+ ),
_priority_feature_manifest_script = attr.label(
allow_single_file = True,
cfg = "exec",
@@ -100,15 +140,21 @@ ANDROID_APPLICATION_ATTRS = _attrs.add(
allow_single_file = True,
default = Label("//tools/android:debug_keystore"),
),
+ deps = attr.label_list(
+ default = [],
+ ),
),
_attrs.ANDROID_SDK,
)
ANDROID_FEATURE_MODULE_ATTRS = dict(
- binary = attr.label(aspects = [android_feature_module_validation_aspect]),
+ binary = attr.label(),
feature_name = attr.string(),
library = attr.label(
- allow_rules = ["android_library"],
+ allow_rules = [
+ "android_library",
+ "kt_android_library",
+ ],
cfg = android_split_transition,
mandatory = True,
doc = "android_library target to include as a feature split.",
@@ -116,6 +162,9 @@ ANDROID_FEATURE_MODULE_ATTRS = dict(
manifest = attr.label(allow_single_file = True),
title_id = attr.string(),
title_lib = attr.string(),
+ library_resources_only_lib = attr.string(),
+ proguard_specs = attr.label_list(allow_empty = True, allow_files = True),
+ fused = attr.bool(),
_feature_module_validation_script = attr.label(
allow_single_file = True,
cfg = "exec",
diff --git a/rules/android_application/bundle_deploy.sh_template b/rules/android_application/bundle_deploy.sh_template
index c941a463a..1bafab05e 100644
--- a/rules/android_application/bundle_deploy.sh_template
+++ b/rules/android_application/bundle_deploy.sh_template
@@ -7,16 +7,21 @@ oldest_signer_properties="%oldest_signer_properties%"
newest_key="%newest_key%"
lineage="%lineage%"
min_rotation_api="%min_rotation_api%"
-tmp="$(mktemp /tmp/XXXXbundle.apks)"
+aapt2_path="%aapt2_path%"
+tmp="$(mktemp -t bundleXXXXXX).apks"
function cleanup {
- rm -r "$tmp"
+ rm -rf "$tmp"
}
trap cleanup EXIT
+# bundletool doesn't support symlink for aapt2 paramater
+aapt2_path=$(readlink $aapt2_path)
+
args=(
--bundle="$aab"
--output="$tmp"
+ --aapt2="$aapt2_path"
--overwrite
--local-testing
--ks="$newest_key"
diff --git a/rules/android_application/feature_module_validation.sh b/rules/android_application/feature_module_validation.sh
index 4c5c67e90..17fc31e06 100755
--- a/rules/android_application/feature_module_validation.sh
+++ b/rules/android_application/feature_module_validation.sh
@@ -22,41 +22,12 @@ unzip="${6}"
is_asset_pack="${7}"
if [[ -n "$manifest" ]]; then
- node_count=$("$xmllint" --xpath "count(//manifest/*)" "$manifest")
- module_count=$("$xmllint" --xpath "count(//manifest/*[local-name()='module'])" "$manifest")
- application_count=$("$xmllint" --xpath "count(//manifest/*[local-name()='application'])" "$manifest")
- application_attr_count=$("$xmllint" --xpath "count(//manifest/application/@*)" "$manifest")
- application_content_count=$("$xmllint" --xpath "count(//manifest/application/*)" "$manifest")
module_title=$("$xmllint" --xpath "string(//manifest/*[local-name()='module'][1]/@*[local-name()='title'])" "$manifest")
- valid=0
-
- # Valid manifest, containing a dist:module and an empty
- if [[ "$node_count" == "2" &&
- "$module_count" == "1" &&
- "$application_count" == "1" &&
- "$application_attr_count" == "0" &&
- "$application_content_count" == "0" ]]; then
- valid=1
- fi
-
- # Valid manifest, containing a dist:module
- if [[ "$node_count" == "1" && "$module_count" == "1" ]]; then
- valid=1
- fi
-
- if [[ "$valid" == "0" ]]; then
- echo ""
- echo "$manifest should only contain a single element (and optional empty ), nothing else"
- echo "Manifest contents: "
- cat "$manifest"
- exit 1
- fi
if [[ "$is_asset_pack" = false && "$module_title" != "\${MODULE_TITLE}" ]]; then
echo ""
- echo "$manifest dist:title should be \${MODULE_TITLE} placeholder"
+ echo "$manifest dist:title should be \${MODULE_TITLE} placeholder. Found $module_title"
echo ""
- exit 1
fi
fi
diff --git a/rules/android_application/filter_feature_classes.sh b/rules/android_application/filter_feature_classes.sh
new file mode 100755
index 000000000..aa70b15d8
--- /dev/null
+++ b/rules/android_application/filter_feature_classes.sh
@@ -0,0 +1,43 @@
+#!/bin/bash --posix
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+out="${1}"
+base_jar="${2}"
+feature_jar="${3}"
+filter_regex="${4:-""}"
+tmp="$(mktemp -t featureXXXXXX).jar"
+
+tmp_dir=$(mktemp -d)
+
+# Compute the list of files to keep in the feature jar.
+base_jar_lines=$(zip -sf "${base_jar}" | wc -l)
+feature_jar_lines=$(zip -sf "${feature_jar}" | wc -l)
+
+zip -sf "${base_jar}" | awk -v NR_last="${base_jar_lines}" -v filter="${filter_regex}" 'NR>1 && NR<(NR_last) && $1 !~ /\/$/ && $1 ~ filter {print $1}' | sort >> "${tmp_dir}/base_files" &
+zip -sf "${feature_jar}" | awk -v NR_last="${feature_jar_lines}" 'NR>1 && NR<(NR_last) && $1 !~ /\/$/ {print $1}' | sort >> "${tmp_dir}/feature_files" &
+wait
+comm -23 "${tmp_dir}/feature_files" "${tmp_dir}/base_files" >> "${tmp_dir}/files_to_keep"
+
+content_tmp_dir=$(mktemp -d)
+if [[ ! -s "${tmp_dir}/files_to_keep" ]]; then
+ touch "${content_tmp_dir}/.empty"
+ (cd "${content_tmp_dir}" && zip -q "${tmp}" .empty && zip -q -d "${tmp}" .empty)
+else
+ abs_feature_jar="$(cd "$(dirname "${feature_jar}")" && pwd)/$(basename "${feature_jar}")"
+ (cd "${content_tmp_dir}" && unzip -q "${abs_feature_jar}" $(cat "${tmp_dir}/files_to_keep" | tr '\n' ' '))
+ (cd "${content_tmp_dir}" && zip -q -r "${tmp}" . )
+fi
+
+cp ${tmp} ${out}
diff --git a/rules/android_application/gen_android_feature_manifest.sh b/rules/android_application/gen_android_feature_manifest.sh
index 5b5552e0e..beaa3a020 100755
--- a/rules/android_application/gen_android_feature_manifest.sh
+++ b/rules/android_application/gen_android_feature_manifest.sh
@@ -20,6 +20,7 @@ split="${4}"
title_id="${5}"
fused="${6}"
aapt="${7}"
+dex_zip="${8}"
aapt_cmd="$aapt dump xmltree $base_apk --file AndroidManifest.xml"
version_code=$(${aapt_cmd} | grep "http://schemas.android.com/apk/res/android:versionCode" | cut -d "=" -f2 | head -n 1 )
@@ -29,6 +30,11 @@ then
exit 1
fi
+has_code="false"
+if unzip -l "${dex_zip}" classes.dex &> /dev/null; then
+ has_code="true"
+fi
+
cat >$out_manifest <
$out_manifest <
-
+
EOF
diff --git a/rules/android_application/gen_priority_android_feature_manifest.sh b/rules/android_application/gen_priority_android_feature_manifest.sh
index 066bfc783..566a7be49 100755
--- a/rules/android_application/gen_priority_android_feature_manifest.sh
+++ b/rules/android_application/gen_priority_android_feature_manifest.sh
@@ -15,15 +15,21 @@
out_manifest="${1}"
base_apk="${2}"
-package="${3}"
+feature_manifest="${3}"
split="${4}"
aapt="${5}"
-in_manifest="${6}" # Developer-provided manifest for the feature module
-is_asset_pack="${7}"
+dex_zip="${6}"
aapt_cmd="$aapt dump xmltree $base_apk --file AndroidManifest.xml"
version_code=$(${aapt_cmd} | grep "http://schemas.android.com/apk/res/android:versionCode" | cut -d "=" -f2 | head -n 1)
min_sdk=$(${aapt_cmd} | grep "http://schemas.android.com/apk/res/android:minSdkVersion" | cut -d "=" -f2 | head -n 1)
+package=$(awk -F 'package="' '{if ($2 != "") print $2}' $feature_manifest | cut -d '"' -f1)
+
+has_code="false"
+if unzip -l "${dex_zip}" classes.dex > /dev/null; then
+ has_code="true"
+fi
+
if [[ -z "$version_code" ]]
then
echo "Base app missing versionCode in AndroidManifest.xml"
@@ -58,8 +64,8 @@ else
android:versionCode="$version_code"
android:isFeatureSplit="true">
-
-
-
- EOF
+
+
+
+EOF
fi
diff --git a/rules/android_binary/impl.bzl b/rules/android_binary/impl.bzl
index 7e3fcedc4..32bbaacc6 100644
--- a/rules/android_binary/impl.bzl
+++ b/rules/android_binary/impl.bzl
@@ -13,7 +13,7 @@
# limitations under the License.
"""Implementation."""
-load("//providers:providers.bzl", "AndroidDexInfo", "AndroidFeatureFlagSet", "AndroidIdlInfo", "AndroidInstrumentationInfo", "AndroidLibraryResourceClassJarProvider", "AndroidOptimizationInfo", "AndroidPreDexJarInfo", "ApkInfo", "BaselineProfileProvider", "DataBindingV2Info", "ProguardMappingInfo", "StarlarkAndroidDexInfo", "StarlarkAndroidResourcesInfo", "StarlarkApkInfo")
+load("//providers:providers.bzl", "AndroidApplicationResourceInfo", "AndroidDexInfo", "AndroidFeatureFlagSet", "AndroidIdlInfo", "AndroidInstrumentationInfo", "AndroidLibraryResourceClassJarProvider", "AndroidOptimizationInfo", "AndroidPreDexJarInfo", "ApkInfo", "BaselineProfileProvider", "DataBindingV2Info", "ProguardMappingInfo", "StarlarkAndroidDexInfo", "StarlarkAndroidResourcesInfo", "StarlarkApkInfo")
load("//rules:acls.bzl", "acls")
load("//rules:add_constraints.bzl", "add_constraints")
load("//rules:apk_packaging.bzl", _apk_packaging = "apk_packaging")
@@ -64,7 +64,7 @@ def _base_validations_processor(ctx, **_unused_ctxs):
if StarlarkAndroidResourcesInfo in src:
fail("srcs should not contain label with resources %s" % str(src.label))
- use_r8 = acls.use_r8(str(ctx.label)) and bool(ctx.files.proguard_specs)
+ use_r8 = acls.use_r8(str(ctx.label)) and (bool(ctx.files.proguard_specs) or "has_proguard_specs" in ctx.attr.tags)
return ProviderInfo(
name = "validation_ctx",
value = struct(
@@ -929,7 +929,7 @@ def _process_apk_packaging(ctx, packaged_resources_ctx, native_libs_ctx, dex_ctx
elif ctx.file.debug_key:
signing_keys.append(ctx.file.debug_key)
- use_r8 = acls.use_r8(str(ctx.label)) and ctx.files.proguard_specs
+ use_r8 = ctx.files.proguard_specs or "has_proguard_specs" in ctx.attr.tags
if getattr(r8_ctx, "dex_info", None) and getattr(dex_ctx, "dex_info", None):
fail("Either R8 or Dex should be used, but not both!")
dex_info = r8_ctx.dex_info if use_r8 else dex_ctx.dex_info
@@ -1055,6 +1055,18 @@ def _process_coverage(ctx, **_unused_ctxs):
),
)
+def _process_application_resource_info(ctx, packaged_resources_ctx, optimize_ctx, resource_shrinking_r8_ctx = None, **_unused_ctxs):
+ resources_apk = get_final_resources(packaged_resources_ctx, optimize_ctx, resource_shrinking_r8_ctx)
+ return ProviderInfo(
+ name = "application_resource_info_ctx",
+ value = struct(
+ providers = [AndroidApplicationResourceInfo(
+ resource_apk = resources_apk,
+ resource_proguard_config = packaged_resources_ctx.resource_proguard_config,
+ )],
+ ),
+ )
+
# Order dependent, as providers will not be available to downstream processors
# that may depend on the provider. Iteration order for a dictionary is based on
# insertion.
@@ -1081,6 +1093,7 @@ PROCESSORS = dict(
ApkPackagingProcessor = _process_apk_packaging,
IntellijProcessor = _process_intellij,
CoverageProcessor = _process_coverage,
+ ApplicationResourceInfoProcessor = _process_application_resource_info,
)
_PROCESSING_PIPELINE = processing_pipeline.make_processing_pipeline(
diff --git a/rules/android_neverlink_aspect.bzl b/rules/android_neverlink_aspect.bzl
index c6c145d58..17307253c 100644
--- a/rules/android_neverlink_aspect.bzl
+++ b/rules/android_neverlink_aspect.bzl
@@ -49,7 +49,7 @@ def _android_neverlink_aspect_impl(target, ctx):
deps.extend(getattr(ctx.rule.attr, attr))
direct_runtime_jars = depset(
- target[JavaInfo].runtime_output_jars,
+ target[JavaInfo].runtime_output_jars if JavaInfo in target else [],
transitive = [target[AndroidLibraryResourceClassJarProvider].jars] if AndroidLibraryResourceClassJarProvider in target else [],
)
diff --git a/rules/busybox.bzl b/rules/busybox.bzl
index 64f5c8a96..dd00213de 100644
--- a/rules/busybox.bzl
+++ b/rules/busybox.bzl
@@ -1097,6 +1097,10 @@ def _generate_binary_r(
outputs = [out_class_jar],
mnemonic = "StarlarkRClassGenerator",
progress_message = "Generating R classes",
+ jvm_flags = [
+ "-Xms8g",
+ "-Xmx8g",
+ ],
)
def _make_aar(
@@ -1369,8 +1373,6 @@ def _optimize(
args.add("-o", out_apk)
args.add(in_apk)
- _set_warning_level(ctx, args)
-
_java_run(
ctx = ctx,
host_javabase = host_javabase,
diff --git a/rules/r8.bzl b/rules/r8.bzl
new file mode 100644
index 000000000..d94713ad3
--- /dev/null
+++ b/rules/r8.bzl
@@ -0,0 +1,193 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""R8 processor steps for android_application/android_features."""
+
+load(
+ "//rules:busybox.bzl",
+ _busybox = "busybox",
+)
+load(
+ "//rules:utils.bzl",
+ "ANDROID_TOOLCHAIN_TYPE",
+ "get_android_sdk",
+ "get_android_toolchain",
+)
+load("//rules:common.bzl", "common")
+load("//rules:java.bzl", "java")
+load(
+ "//providers:providers.bzl",
+ "AndroidDexInfo",
+ "AndroidOptimizationInfo",
+)
+
+def filter_feature_jar(ctx, feature_name, feature_jar, base_jar):
+ """Filter out feature jar by removing duplicated classes found in base jar.
+ Args:
+ ctx: Rule contxt.
+ feature_name: The name of the feature, used to compute output file path
+ feature_jar: The deploy jar of the feature module
+ base_jar: The deploy jar of the base application
+
+ Returns:
+ A output file corresponding to the filtered jar.
+ """
+
+ output = ctx.actions.declare_file(ctx.label.name + "/filtered_modules/" + feature_name + ".jar")
+ inputs = [base_jar, feature_jar]
+ args = ctx.actions.args()
+ args.add(output.path)
+ args.add(base_jar.path)
+ args.add(feature_jar.path)
+
+ ctx.actions.run(
+ executable = ctx.executable._filter_feature_classes_script,
+ inputs = inputs,
+ outputs = [output],
+ arguments = [args],
+ mnemonic = "FilterFeatureModule",
+ progress_message = "Filtering jar for feature module '%s'" % feature_name,
+ toolchain = None,
+ )
+ return output
+
+def _process(ctx, deploy_jar, resource_info, proguard_specs, startup_profile = None, feature_split_jars = {}):
+ """Runs R8 for desugaring, optimization, and dexing.
+ Args:
+ ctx: Rule contxt.
+ deploy_jar: The deploy jar from java compilation..
+ resource_info: [optional] The application resource info containing resource binary apk to shrink, i.e remove unsued resources.
+ feature_split_jars: [optional] Dictionary of feature module jars to outputs.
+
+ Returns:
+ A AndroidDexInfo provider.
+ """
+ dexes_zip = ctx.actions.declare_file(ctx.label.name + "_dexes.zip")
+ optimisation_info = None
+ resource_apk = resource_info.resource_apk
+
+ android_jar = get_android_sdk(ctx).android_jar
+
+ inputs = [android_jar, deploy_jar] + proguard_specs
+ outputs = [dexes_zip]
+
+ min_sdk_version = getattr(ctx.attr, "min_sdk_version")
+ if not min_sdk_version:
+ min_sdk_version = 21
+ args = ctx.actions.args()
+ args.add("--release")
+ args.add("--min-api", min_sdk_version)
+ args.add("--output", dexes_zip)
+ args.add_all(proguard_specs, before_each = "--pg-conf")
+ args.add("--lib", android_jar)
+ args.add("--pg-compat")
+
+ # Convert resource APK to proto format, as expected by R8 tool
+ if ctx.attr.shrink_resources:
+ proto_resource_apk = ctx.actions.declare_file("proto_" + resource_apk.basename, sibling = resource_apk)
+ ctx.actions.run(
+ arguments = [ctx.actions.args()
+ .add("convert")
+ .add(resource_apk)
+ .add("-o", proto_resource_apk)
+ .add("--output-format", "proto")],
+ executable = get_android_toolchain(ctx).aapt2.files_to_run,
+ inputs = [resource_apk],
+ mnemonic = "Aapt2ConvertToProtoForResourceShrinkerR8",
+ outputs = [proto_resource_apk],
+ toolchain = ANDROID_TOOLCHAIN_TYPE,
+ )
+
+ # Declare optimized proto APK R8 output, and include it on R8 command line parameters
+ optimized_proto_apk = ctx.actions.declare_file("optimized_" + proto_resource_apk.basename, sibling=proto_resource_apk)
+ args.add("--android-resources")
+ args.add(proto_resource_apk)
+ args.add(optimized_proto_apk)
+ inputs.append(proto_resource_apk)
+ outputs.append(optimized_proto_apk)
+
+ for feature_jar, optimized_dex in feature_split_jars.items():
+ feature_name = optimized_dex.path.split("/")[-1].lower()
+ filtered_feature_jar = filter_feature_jar(ctx, feature_name, feature_jar, deploy_jar)
+ args.add("--feature", filtered_feature_jar)
+ args.add(optimized_dex.path)
+ inputs.append(filtered_feature_jar)
+ outputs.append(optimized_dex)
+
+ args.add(deploy_jar) # jar to optimize + desugar + dex
+
+ proguard_output_map = ctx.actions.declare_file(ctx.label.name + "_proguard.map")
+ merged_config_file = ctx.actions.declare_file(ctx.label.name + "_merged-config.pro")
+ args.add("--pg-map-output", proguard_output_map)
+ args.add("--pg-conf-output", merged_config_file)
+ outputs.append(proguard_output_map)
+ outputs.append(merged_config_file)
+
+ if startup_profile:
+ args.add("--startup-profile", startup_profile)
+ inputs.append(startup_profile)
+
+ java.run(
+ ctx = ctx,
+ host_javabase = common.get_host_javabase(ctx),
+ executable = get_android_toolchain(ctx).r8.files_to_run,
+ arguments = [args],
+ inputs = inputs,
+ outputs = outputs,
+ jvm_flags = [
+ "-Xms20g",
+ "-Xmx20g",
+ ],
+ mnemonic = "AndroidR8Bundle",
+ progress_message = "R8 Optimizing, Desugaring, and Dexing Bundle %{label}",
+ )
+
+ android_dex_info = AndroidDexInfo(
+ deploy_jar = deploy_jar,
+ final_classes_dex_zip = dexes_zip,
+ final_proguard_output_map = proguard_output_map,
+ # R8 preserves the Java resources (i.e. non-Java-class files) in its output zip, so no need
+ # to provide a Java resources zip.
+ java_resource_jar = None,
+ )
+
+ if ctx.attr.shrink_resources:
+
+ # Run AAPT2 optimize on binary proto APK to obfuscate and shorten resources
+ if ctx.attr.obfuscate_resources:
+ obfuscated_resource_apk = ctx.actions.declare_file("obfuscated_" + resource_apk.basename, sibling = resource_apk)
+ resource_path_shortening_map = ctx.actions.declare_file(ctx.label.name + "_resource_paths.map", sibling = resource_apk)
+ _busybox.optimize(
+ ctx,
+ out_apk = obfuscated_resource_apk,
+ in_apk = optimized_proto_apk,
+ resource_path_shortening_map = resource_path_shortening_map,
+ aapt = get_android_toolchain(ctx).aapt2.files_to_run,
+ busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run,
+ host_javabase = common.get_host_javabase(ctx),
+ )
+ optimized_proto_apk = obfuscated_resource_apk
+
+ optimisation_info = AndroidOptimizationInfo(
+ optimized_resource_apk = optimized_proto_apk,
+ mapping = proguard_output_map,
+ config = merged_config_file,
+ resource_path_shortening_map = resource_path_shortening_map,
+ )
+
+ return android_dex_info, optimisation_info
+
+r8 = struct(
+ process = _process,
+)
diff --git a/rules/rules.bzl b/rules/rules.bzl
index af06e80e2..afe215768 100644
--- a/rules/rules.bzl
+++ b/rules/rules.bzl
@@ -49,6 +49,10 @@ load(
"//rules/android_library:rule.bzl",
_android_library = "android_library_macro",
)
+load(
+ "//rules/android_application:android_feature_module.bzl",
+ _android_feature_module = "android_feature_module",
+)
load(
"//rules/android_local_test:rule.bzl",
_android_local_test = "android_local_test",
@@ -77,6 +81,7 @@ aar_import = _aar_import
android_application = _android_application
android_binary = _android_binary
android_library = _android_library
+android_feature_module = _android_feature_module
android_local_test = _android_local_test
android_sandboxed_sdk = _android_sandboxed_sdk
android_sandboxed_sdk_bundle = _android_sandboxed_sdk_bundle
diff --git a/toolchains/android/BUILD b/toolchains/android/BUILD
index 941fbe17f..68e805a2f 100644
--- a/toolchains/android/BUILD
+++ b/toolchains/android/BUILD
@@ -56,3 +56,19 @@ sh_binary(
srcs = [":unzip.sh"],
visibility = ["//visibility:public"],
)
+
+genrule(
+ name = "gen_xmllint",
+ outs = ["xmllint.sh"],
+ cmd = """cat > $@ <