Merge remote-tracking branch 'aosp/upstream-main' into HEAD

* aosp/upstream-main: (27 commits)
  Migrate AppendJava8LegacyDex and BuildLegacyDex action from native to Starlark
  Migrate DexArchiveAspect from native Blaze to Starlark, and also add 2 new processors in android_binary_internal: DexProcessor and DeployJarProcessor
  Internal Change
  Make default_applicable_licenses usage consistent
  N/a
  Internal Change
  Add java toolchain type to all rules which are using java_common
  Add com.android.tools.build:gradle to MODULE.bazel
  Update .gitignore
  Add `com.android.tools.build:gradle:8.0.1` to `rules_android_maven` for the resource shrinker to be used with R8.
  Don't use stamping if stamping is not available.
  Internal Change
  Deleting ACL
  Migrate CreateDeployJar from native Blaze to Starlark
  Disable windows for rules_android since it doesn't supported it
  Add abseil-py to MODULE.bazel in rules_android
  Add Android revision checking.
  Migrate ZipFilter action to Starlark used in proguard and android_instrumentation test.
  Add a flag to the android kit manifest command that allows enabling debugging of the application.
  Fix bazel_gazelle error inside MODULE.bazel file
  ...

Change-Id: If4884a9815cbe08e6b19ac6b86f0ca9ff7ebc9ae
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 3e42be3..6df2bf1 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -27,8 +27,6 @@
     <<: *common
   macos_arm64:
     <<: *common
-  windows:
-    <<: *common
   ubuntu1604_bzlmod:
     name: Bzlmod ubuntu1604
     platform: ubuntu1604
@@ -52,10 +50,4 @@
     platform: macos_arm64
     build_flags:
     - "--enable_bzlmod"
-    <<: *common
-  windows_bzlmods:
-    name: Bzlmod windows
-    platform: windows
-    build_flags:
-    - "--enable_bzlmod"
     <<: *common
\ No newline at end of file
diff --git a/.bazelignore b/.bazelignore
new file mode 100644
index 0000000..1e107f5
--- /dev/null
+++ b/.bazelignore
@@ -0,0 +1 @@
+examples
diff --git a/.gitignore b/.gitignore
index ac51a05..3fd8560 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
 bazel-*
+# Intellij Bazel Plugin
+.ijwb
+# Android Studio Bazel Plugin
+.aswb
diff --git a/BUILD b/BUILD
index 91b76b1..c0770f0 100644
--- a/BUILD
+++ b/BUILD
@@ -1,4 +1,4 @@
-load("@gazelle//:def.bzl", "gazelle")
+load("@bazel_gazelle//:def.bzl", "gazelle")
 load("@rules_license//rules:license.bzl", "license")
 
 package(
diff --git a/METADATA b/METADATA
index bfafc2c..1a0dec4 100644
--- a/METADATA
+++ b/METADATA
@@ -12,7 +12,7 @@
     type: GIT
     value: "https://github.com/bazelbuild/rules_android"
   }
-  version: "88ac3334cab0bbff2ce10e8b76eaf988c27b966b"
-  last_upgrade_date { year: 2023 month: 5 day: 11 }
+  version: "0ba045398138d641fd8895bb08a6bd4bfa51d0f9"
+  last_upgrade_date { year: 2023 month: 5 day: 31 }
   license_type: NOTICE
 }
diff --git a/MODULE.bazel b/MODULE.bazel
index 8d94790..0987192 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -20,9 +20,10 @@
 
 # go-related dependency setup
 bazel_dep(name = "rules_go", version = "0.39.1", repo_name = "io_bazel_rules_go")
-bazel_dep(name = "gazelle", version = "0.28.0")
+bazel_dep(name = "gazelle", version = "0.28.0", repo_name = "bazel_gazelle")
+bazel_dep(name = "abseil-py", version = "1.4.0", repo_name = "py_absl")
 go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
-go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
+go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
 go_deps.from_file(go_mod = "//:go.mod")
 use_repo(
     go_deps,
@@ -35,6 +36,7 @@
     name = "rules_android_maven",
     artifacts = [
         "com.android.tools.build:bundletool:1.6.1",
+        "com.android.tools.build:gradle:8.0.1",
     ],
     repositories = [
         "https://maven.google.com",
diff --git a/android/rules.bzl b/android/rules.bzl
index 556968d..ecaef19 100644
--- a/android/rules.bzl
+++ b/android/rules.bzl
@@ -1,4 +1,4 @@
-# Copyright 2023 The Bazel Authors. All rights reserved.
+# Copyright 2019 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.
@@ -12,53 +12,51 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Redirecting starlark rules to //rules/rules.bzl for easier migration to a new branch."""
+"""Starlark rules for building Android apps."""
+
+# Don't use relative paths since this file is coppied to //android/rules.bzl.
 
 load(
-    "//rules/rules.bzl",
+    "//rules/aar_import:rule.bzl",
     _aar_import = "aar_import",
-    _android_archive = "android_archive",
+)
+load(
+    "//rules/android_application:android_application.bzl",
+    _android_application = "android_application",
+)
+load(
+    "//rules:android_binary.bzl",
     _android_binary = "android_binary",
-    _android_bundle_to_apks = "android_bundle_to_apks",
-    _android_device = "android_device",
-    _android_device_script_fixture = "android_device_script_fixture",
-    _android_host_service_fixture = "android_host_service_fixture",
-    _android_instrumentation_test = "android_instrumentation_test_macro",
+)
+load(
+    "//rules/android_library:rule.bzl",
     _android_library = "android_library_macro",
-    _android_local_test = "android_local_test",
+)
+load(
+    "//rules:android_ndk_repository.bzl",
     _android_ndk_repository = "android_ndk_repository",
+)
+load(
+    "//rules:android_sdk.bzl",
     _android_sdk = "android_sdk",
+)
+load(
+    "//rules:android_sdk_repository.bzl",
     _android_sdk_repository = "android_sdk_repository",
+)
+load(
+    "//rules:android_tools_defaults_jar.bzl",
     _android_tools_defaults_jar = "android_tools_defaults_jar",
-    _apk_import = "apk_import",
 )
 
+# Current version. Tools may check this to determine compatibility.
+RULES_ANDROID_VERSION = "0.1.0"
+
 aar_import = _aar_import
-
-android_archive = _android_archive
-
+android_application = _android_application
 android_binary = _android_binary
-
-android_bundle_to_apks = _android_bundle_to_apks
-
-android_device = _android_device
-
-android_device_script_fixture = _android_device_script_fixture
-
-android_host_service_fixture = _android_host_service_fixture
-
-android_instrumentation_test = _android_instrumentation_test
-
 android_library = _android_library
-
-android_local_test = _android_local_test
-
 android_ndk_repository = _android_ndk_repository
-
 android_sdk = _android_sdk
-
 android_sdk_repository = _android_sdk_repository
-
 android_tools_defaults_jar = _android_tools_defaults_jar
-
-apk_import = _apk_import
diff --git a/defs.bzl b/defs.bzl
index 8abd100..9b40587 100644
--- a/defs.bzl
+++ b/defs.bzl
@@ -14,6 +14,7 @@
 
 """Workspace setup macro for rules_android."""
 
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
 load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
 load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
 load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
@@ -22,12 +23,15 @@
 
 def rules_android_workspace():
     """ Sets up workspace dependencies for rules_android."""
+    bazel_skylib_workspace()
+
     protobuf_deps()
 
     maven_install(
         name = "rules_android_maven",
         artifacts = [
             "com.android.tools.build:bundletool:1.6.1",
+            "com.android.tools.build:gradle:8.0.1",
         ],
         repositories = [
             "https://maven.google.com",
diff --git a/examples/basicapp/java/com/basicapp/BUILD b/examples/basicapp/java/com/basicapp/BUILD
index adcb05b..a8fb99d 100644
--- a/examples/basicapp/java/com/basicapp/BUILD
+++ b/examples/basicapp/java/com/basicapp/BUILD
@@ -1,4 +1,4 @@
-load("@rules_android//rules:rules.bzl", "android_binary", "android_library")
+load("@rules_android//android:rules.bzl", "android_binary", "android_library")
 
 android_binary(
     name = "basic_app",
diff --git a/kokoro/presubmit/kokoro_presubmit.sh b/kokoro/presubmit/kokoro_presubmit.sh
index 45a1c7a..b90b12c 100644
--- a/kokoro/presubmit/kokoro_presubmit.sh
+++ b/kokoro/presubmit/kokoro_presubmit.sh
@@ -61,12 +61,19 @@
   "--verbose_failures"
   "--experimental_google_legacy_api"
   "--experimental_enable_android_migration_apis"
+  "--build_tests_only"
 )
 
 # Go to rules_android workspace and run relevant tests.
 cd "${KOKORO_ARTIFACTS_DIR}/git/rules_android"
+
+# Fetch all external deps; should reveal any bugs related to external dep
+# references.
+"$bazel" aquery 'deps(...)' 2>&1 > /dev/null
+
 "$bazel" test "${COMMON_ARGS[@]}" //src/common/golang/... \
-  //src/tools/ak/...
+  //src/tools/ak/... \
+  //test/...
 
 # Go to basic app workspace in the source tree
 cd "${KOKORO_ARTIFACTS_DIR}/git/rules_android/examples/basicapp"
diff --git a/mobile_install/adapters/android_instrumentation_test.bzl b/mobile_install/adapters/android_instrumentation_test.bzl
index ffd9c4d..22964a3 100644
--- a/mobile_install/adapters/android_instrumentation_test.bzl
+++ b/mobile_install/adapters/android_instrumentation_test.bzl
@@ -25,7 +25,11 @@
     return ["test_app", "support_apps"]
 
 def _adapt(target, ctx):
-    if not hasattr(ctx.attr, "_android_test_runner"):
+    is_mac = select({
+        "//conditions:default": "no",
+        "@platforms//os:macos": "yes",
+    })
+    if is_mac == "yes":
         fail("mobile-install does not support running tests on mac, check b/134172473 for more details")
 
     # TODO(b/): Tests have yet to be optimized so, this is an irrelevant error.
diff --git a/mobile_install/adapters/android_library.bzl b/mobile_install/adapters/android_library.bzl
index 698bf27..be30a32 100644
--- a/mobile_install/adapters/android_library.bzl
+++ b/mobile_install/adapters/android_library.bzl
@@ -31,9 +31,7 @@
     """Attrs of the rule requiring traversal by the aspect."""
     return [
         "_android_sdk",
-
-        # For the Google-internal kotlin rule to access the toolchain to
-        # get kotlin std and runtime libs.
+        # Access the kt toolchain to get kotlin std and runtime libs.
         "_toolchain",
         "deps",
         "exports",
diff --git a/rules/BUILD b/rules/BUILD
index 33ce74a..3648349 100644
--- a/rules/BUILD
+++ b/rules/BUILD
@@ -14,6 +14,14 @@
 )
 
 bzl_library(
+    name = "android_revision_bzl",
+    srcs = [
+        "android_revision.bzl",
+    ],
+    visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
     name = "common_bzl",
     srcs = [
         "aapt.bzl",
@@ -36,10 +44,10 @@
         "resources.bzl",
         "utils.bzl",
     ],
+    visibility = ["//:__subpackages__"],
     deps = [
         "//rules/acls:bzl",
         "//rules/android_common:bzl",
         "//rules/flags:bzl",
     ],
-    visibility = ["//:__subpackages__"],
 )
diff --git a/rules/aar_import/rule.bzl b/rules/aar_import/rule.bzl
index bf82fe1..606a7f0 100644
--- a/rules/aar_import/rule.bzl
+++ b/rules/aar_import/rule.bzl
@@ -46,5 +46,8 @@
         AndroidNativeLibsInfo,
         JavaInfo,
     ],
-    toolchains = ["//toolchains/android:toolchain_type"],
+    toolchains = [
+        "//toolchains/android:toolchain_type",
+        "@bazel_tools//tools/jdk:toolchain_type",
+    ],
 )
diff --git a/rules/acls.bzl b/rules/acls.bzl
index 376c7eb..419cf03 100644
--- a/rules/acls.bzl
+++ b/rules/acls.bzl
@@ -62,7 +62,6 @@
 load("//rules/acls:local_test_rollout.bzl", "LOCAL_TEST_FALLBACK", "LOCAL_TEST_ROLLOUT")
 load("//rules/acls:local_test_starlark_resources.bzl", "LOCAL_TEST_STARLARK_RESOURCES_FALLBACK", "LOCAL_TEST_STARLARK_RESOURCES_ROLLOUT")
 load("//rules/acls:android_test_platform_rollout.bzl", "ANDROID_TEST_PLATFORM_FALLBACK", "ANDROID_TEST_PLATFORM_ROLLOUT")
-load("//rules/acls:sourceless_binary_rollout.bzl", "SOURCELESS_BINARY_FALLBACK", "SOURCELESS_BINARY_ROLLOUT")
 load("//rules/acls:test_to_instrument_test_rollout.bzl", "TEST_TO_INSTRUMENT_TEST_FALLBACK", "TEST_TO_INSTRUMENT_TEST_ROLLOUT")
 load(
     "//rules/acls:partial_jetification_targets.bzl",
@@ -77,6 +76,7 @@
 load("//rules/acls:android_local_test_jdk_sts_rollout.bzl", "ANDROID_LOCAL_TEST_JDK_STS_FALLBACK", "ANDROID_LOCAL_TEST_JDK_STS_ROLLOUT")
 load("//rules/acls:shared_library_resource_linking.bzl", "SHARED_LIBRARY_RESOURCE_LINKING_ALLOWLIST")
 load("//rules/acls:android_binary_starlark_dex_desugar_proguard.bzl", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT")
+load("//rules/acls:android_binary_min_sdk_version_attribute.bzl", "ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_ALLOWLIST")
 
 def _in_aar_import_deps_checker(fqn):
     return not matches(fqn, AAR_IMPORT_DEPS_CHECKER_FALLBACK_DICT) and matches(fqn, AAR_IMPORT_DEPS_CHECKER_ROLLOUT_DICT)
@@ -179,9 +179,6 @@
 def _in_android_test_platform_rollout(fqn):
     return not matches(fqn, ANDROID_TEST_PLATFORM_FALLBACK_DICT) and matches(fqn, ANDROID_TEST_PLATFORM_ROLLOUT_DICT)
 
-def _in_sourceless_binary_rollout(fqn):
-    return not matches(fqn, SOURCELESS_BINARY_FALLBACK_DICT) and matches(fqn, SOURCELESS_BINARY_ROLLOUT_DICT)
-
 def _in_test_to_instrument_test_rollout(fqn):
     return not matches(fqn, TEST_TO_INSTRUMENT_TEST_FALLBACK_DICT) and matches(fqn, TEST_TO_INSTRUMENT_TEST_ROLLOUT_DICT)
 
@@ -221,6 +218,9 @@
 def _in_android_binary_starlark_dex_desugar_proguard(fqn):
     return not matches(fqn, ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK_DICT) and matches(fqn, ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT_DICT)
 
+def _in_android_binary_min_sdk_version_attribute_allowlist(fqn):
+    return matches(fqn, ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_DICT)
+
 def make_dict(lst):
     """Do not use this method outside of acls directory."""
     return {t: True for t in lst}
@@ -276,8 +276,6 @@
 LOCAL_TEST_STARLARK_RESOURCES_ROLLOUT_DICT = make_dict(LOCAL_TEST_STARLARK_RESOURCES_ROLLOUT)
 ANDROID_TEST_PLATFORM_FALLBACK_DICT = make_dict(ANDROID_TEST_PLATFORM_FALLBACK)
 ANDROID_TEST_PLATFORM_ROLLOUT_DICT = make_dict(ANDROID_TEST_PLATFORM_ROLLOUT)
-SOURCELESS_BINARY_FALLBACK_DICT = make_dict(SOURCELESS_BINARY_FALLBACK)
-SOURCELESS_BINARY_ROLLOUT_DICT = make_dict(SOURCELESS_BINARY_ROLLOUT)
 TEST_TO_INSTRUMENT_TEST_FALLBACK_DICT = make_dict(TEST_TO_INSTRUMENT_TEST_FALLBACK)
 TEST_TO_INSTRUMENT_TEST_ROLLOUT_DICT = make_dict(TEST_TO_INSTRUMENT_TEST_ROLLOUT)
 ALLOW_RESOURCE_CONFLICTS_DICT = make_dict(ALLOW_RESOURCE_CONFLICTS)
@@ -299,6 +297,7 @@
 SHARED_LIBRARY_RESOURCE_LINKING_DICT = make_dict(SHARED_LIBRARY_RESOURCE_LINKING_ALLOWLIST)
 ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT_DICT = make_dict(ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT)
 ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK_DICT = make_dict(ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK)
+ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_DICT = make_dict(ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_ALLOWLIST)
 
 def matches(fqn, dct):
     # Labels with workspace names ("@workspace//pkg:target") are not supported.
@@ -374,7 +373,6 @@
     in_local_test_rollout = _in_local_test_rollout,
     in_local_test_starlark_resources = _in_local_test_starlark_resources,
     in_android_test_platform_rollout = _in_android_test_platform_rollout,
-    in_sourceless_binary_rollout = _in_sourceless_binary_rollout,
     in_test_to_instrument_test_rollout = _in_test_to_instrument_test_rollout,
     in_allow_resource_conflicts = _in_allow_resource_conflicts,
     in_partial_jetification_targets = _in_partial_jetification_targets,
@@ -386,6 +384,7 @@
     in_android_local_test_jdk_sts_rollout = _in_android_local_test_jdk_sts_rollout,
     in_shared_library_resource_linking_allowlist = _in_shared_library_resource_linking_allowlist,
     in_android_binary_starlark_dex_desugar_proguard = _in_android_binary_starlark_dex_desugar_proguard,
+    in_android_binary_min_sdk_version_attribute_allowlist = _in_android_binary_min_sdk_version_attribute_allowlist,
 )
 
 # Visible for testing
diff --git a/rules/acls/sourceless_binary_rollout.bzl b/rules/acls/android_binary_min_sdk_version_attribute.bzl
similarity index 72%
rename from rules/acls/sourceless_binary_rollout.bzl
rename to rules/acls/android_binary_min_sdk_version_attribute.bzl
index 132ba10..9e058d4 100644
--- a/rules/acls/sourceless_binary_rollout.bzl
+++ b/rules/acls/android_binary_min_sdk_version_attribute.bzl
@@ -1,4 +1,4 @@
-# Copyright 2020 The Bazel Authors. All rights reserved.
+# 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.
@@ -12,10 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Allow list for sourceless android_binary rollout"""
+"""Allowlist for setting new min_sdk_version on android_binary targets."""
 
-SOURCELESS_BINARY_ROLLOUT = [
-]
-
-SOURCELESS_BINARY_FALLBACK = [
+# keep sorted.
+ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_ALLOWLIST = [
 ]
diff --git a/rules/acls/android_binary_starlark_dex_desugar_proguard.bzl b/rules/acls/android_binary_starlark_dex_desugar_proguard.bzl
index 9a212c2..1f7ec8b 100644
--- a/rules/acls/android_binary_starlark_dex_desugar_proguard.bzl
+++ b/rules/acls/android_binary_starlark_dex_desugar_proguard.bzl
@@ -15,7 +15,9 @@
 """Allow list for rollout of Starlark dex, desugar and proguard in android_binary_internal."""
 
 # keep sorted
-ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT = []
+ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT = [
+    "//test/rules/android_binary_internal:__subpackages__",
+]
 
 # keep sorted
 ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK = []
diff --git a/rules/android_application/android_application_rule.bzl b/rules/android_application/android_application_rule.bzl
index ce3f321..0a931da 100644
--- a/rules/android_application/android_application_rule.bzl
+++ b/rules/android_application/android_application_rule.bzl
@@ -129,11 +129,11 @@
 
     # Extract libs/ from split binary
     native_libs = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/native_libs.zip")
-    _common.filter_zip(ctx, binary, native_libs, ["lib/*"])
+    _common.filter_zip_include(ctx, binary, native_libs, ["lib/*"])
 
     # 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(ctx, res_apk, filtered_res, ["AndroidManifest.xml", "assets/*"])
+    _common.filter_zip_include(ctx, res_apk, filtered_res, ["AndroidManifest.xml", "assets/*"])
 
     # Merge into output
     _java.singlejar(
diff --git a/rules/android_binary_internal/attrs.bzl b/rules/android_binary_internal/attrs.bzl
index 5a6f807..ed14647 100644
--- a/rules/android_binary_internal/attrs.bzl
+++ b/rules/android_binary_internal/attrs.bzl
@@ -23,12 +23,14 @@
     "split_config_aspect",
 )
 load("//rules:providers.bzl", "StarlarkApkInfo")
+load("//rules:dex_desugar_aspect.bzl", "dex_desugar_aspect")
 
-def make_deps(allow_rules, providers):
+def make_deps(allow_rules, providers, aspects):
     return attr.label_list(
         allow_files = True,
         allow_rules = allow_rules,
         providers = providers,
+        aspects = aspects,
         cfg = android_common.multi_cpu_configuration,
     )
 
@@ -47,6 +49,10 @@
     ["AndroidResourcesInfo", "AndroidAssetsInfo"],
 ]
 
+DEPS_ASPECTS = [
+    dex_desugar_aspect,
+]
+
 ATTRS = _attrs.replace(
     _attrs.add(
         dict(
@@ -54,7 +60,7 @@
                 # TODO(timpeut): Set PropertyFlag direct_compile_time_input
                 allow_files = [".java", ".srcjar"],
             ),
-            deps = make_deps(DEPS_ALLOW_RULES, DEPS_PROVIDERS),
+            deps = make_deps(DEPS_ALLOW_RULES, DEPS_PROVIDERS, DEPS_ASPECTS),
             enable_data_binding = attr.bool(),
             instruments = attr.label(),
             manifest_values = attr.string_dict(),
@@ -82,6 +88,12 @@
             shrink_resources = _attrs.tristate.create(
                 default = _attrs.tristate.auto,
             ),
+            dexopts = attr.string_list(),
+            main_dex_list = attr.label(allow_single_file = True),
+            min_sdk_version = attr.int(),
+            incremental_dexing = _attrs.tristate.create(
+                default = _attrs.tristate.auto,
+            ),
             _java_toolchain = attr.label(
                 default = Label("//tools/jdk:toolchain_android_only"),
             ),
diff --git a/rules/android_binary_internal/impl.bzl b/rules/android_binary_internal/impl.bzl
index 660f91f..72d2b7d 100644
--- a/rules/android_binary_internal/impl.bzl
+++ b/rules/android_binary_internal/impl.bzl
@@ -29,7 +29,9 @@
     "//rules:native_deps.bzl",
     _process_native_deps = "process",
 )
-load("//rules:providers.bzl", "StarlarkApkInfo")
+load("//rules:providers.bzl", "StarlarkAndroidDexInfo", "StarlarkApkInfo")
+load("//rules:dex.bzl", _dex = "dex")
+load("//rules:desugar.bzl", _desugar = "desugar")
 
 def _process_manifest(ctx, **unused_ctxs):
     manifest_ctx = _resources.bump_min_sdk(
@@ -181,6 +183,143 @@
         ),
     )
 
+def _process_build_info(_unused_ctx, **unused_ctxs):
+    return ProviderInfo(
+        name = "build_info_ctx",
+        value = struct(
+            build_info_files = depset(),
+            deploy_manifest_lines = [],
+            extra_build_info = "",
+            providers = [],
+        ),
+    )
+
+def _process_dex(ctx, packaged_resources_ctx, jvm_ctx, deploy_ctx, **_unused_ctxs):
+    providers = []
+    classes_dex_zip = None
+    final_classes_dex_zip = None
+    deploy_jar = deploy_ctx.deploy_jar
+    is_binary_optimized = len(ctx.attr.proguard_specs) > 0
+
+    if acls.in_android_binary_starlark_dex_desugar_proguard(str(ctx.label)):
+        runtime_jars = jvm_ctx.java_info.runtime_output_jars + [packaged_resources_ctx.class_jar]
+        forbidden_dexopts = ctx.fragments.android.get_target_dexopts_that_prevent_incremental_dexing
+        java8_legacy_dex, java8_legacy_dex_map = _dex.get_java8_legacy_dex_and_map(
+            ctx,
+            android_jar = ctx.attr._android_sdk[AndroidSdkInfo].android_jar,
+            binary_jar = deploy_jar,
+            build_customized_files = is_binary_optimized,
+        )
+
+        incremental_dexing = _dex.get_effective_incremental_dexing(
+            force_incremental_dexing = ctx.attr.incremental_dexing,
+            has_forbidden_dexopts = len([d for d in ctx.attr.dexopts if d in forbidden_dexopts]) > 0,
+            is_binary_optimized = is_binary_optimized,
+            incremental_dexing_after_proguard_by_default = ctx.fragments.android.incremental_dexing_after_proguard_by_default,
+            incremental_dexing_shards_after_proguard = ctx.fragments.android.incremental_dexing_shards_after_proguard,
+            use_incremental_dexing = ctx.fragments.android.use_incremental_dexing,
+        )
+
+        # TODO(b/263473668): Implement dexing after optimization
+        if incremental_dexing:
+            classes_dex_zip = _dex.process_incremental_dexing(
+                ctx,
+                deps = ctx.attr.deps,
+                dexopts = ctx.attr.dexopts,
+                runtime_jars = runtime_jars,
+                main_dex_list = ctx.file.main_dex_list,
+                min_sdk_version = ctx.attr.min_sdk_version,
+                java_info = jvm_ctx.java_info,
+                desugar_dict = deploy_ctx.desugar_dict,
+                dexbuilder = get_android_toolchain(ctx).dexbuilder.files_to_run,
+                dexmerger = get_android_toolchain(ctx).dexmerger.files_to_run,
+            )
+
+        if ctx.fragments.android.desugar_java8_libs and classes_dex_zip.extension == "zip":
+            final_classes_dex_zip = _dex.get_dx_artifact(ctx, "final_classes_dex_zip")
+            _dex.append_java8_legacy_dex(
+                ctx,
+                output = final_classes_dex_zip,
+                input = classes_dex_zip,
+                java8_legacy_dex = java8_legacy_dex,
+                dex_zips_merger = get_android_toolchain(ctx).dex_zips_merger.files_to_run,
+            )
+        else:
+            final_classes_dex_zip = classes_dex_zip
+
+        providers.append(
+            AndroidDexInfo(
+                deploy_jar = deploy_jar,
+                final_classes_dex_zip = final_classes_dex_zip,
+                java_resource_jar = deploy_jar,
+            ),
+        )
+
+    return ProviderInfo(
+        name = "dex_ctx",
+        value = struct(providers = providers),
+    )
+
+def _process_deploy_jar(ctx, packaged_resources_ctx, jvm_ctx, build_info_ctx, **unused_ctx):
+    deploy_jar, desugar_dict = None, {}
+
+    if acls.in_android_binary_starlark_dex_desugar_proguard(str(ctx.label)):
+        java_toolchain = common.get_java_toolchain(ctx)
+        java_info = jvm_ctx.java_info
+        info = _dex.merge_infos(utils.collect_providers(StarlarkAndroidDexInfo, ctx.attr.deps))
+        incremental_dexopts = _dex.incremental_dexopts(ctx.attr.dexopts, ctx.fragments.android.get_dexopts_supported_in_incremental_dexing)
+        dex_archives = info.dex_archives_dict.get("".join(incremental_dexopts), depset()).to_list()
+
+        if ctx.fragments.android.desugar_java8:
+            desugared_jars = []
+            desugar_dict = {d.jar: d.desugared_jar for d in dex_archives}
+
+            for jar in java_info.runtime_output_jars + [packaged_resources_ctx.class_jar]:
+                desugared_jar = ctx.actions.declare_file(ctx.label.name + "/" + jar.basename + "_desugared.jar")
+                _desugar.desugar(
+                    ctx,
+                    input = jar,
+                    output = desugared_jar,
+                    classpath = java_info.transitive_compile_time_jars,
+                    bootclasspath = java_toolchain[java_common.JavaToolchainInfo].bootclasspath.to_list(),
+                    min_sdk_version = ctx.attr.min_sdk_version,
+                    desugar_exec = get_android_toolchain(ctx).desugar.files_to_run,
+                )
+                desugared_jars.append(desugared_jar)
+                desugar_dict[jar] = desugared_jar
+
+            for jar in java_info.transitive_runtime_jars.to_list():
+                if jar in desugar_dict and desugar_dict[jar]:
+                    desugared_jars.append(desugar_dict[jar])
+
+            runtime_jars = depset(desugared_jars)
+        else:
+            runtime_jars = depset(transitive = [
+                java_info.runtime_output_jars,
+                java_info.transitive_runtime_jars,
+            ])
+
+        output = ctx.actions.declare_file(ctx.label.name + "_migrated_deploy.jar")
+        deploy_jar = java.create_deploy_jar(
+            ctx,
+            output = output,
+            runtime_jars = runtime_jars,
+            java_toolchain = java_toolchain,
+            target_name = ctx.label.name,
+            build_info_files = build_info_ctx.build_info_files,
+            deploy_manifest_lines = build_info_ctx.deploy_manifest_lines,
+            extra_build_info = build_info_ctx.extra_build_info,
+        )
+
+    return ProviderInfo(
+        name = "deploy_ctx",
+        value = struct(
+            deploy_jar = deploy_jar,
+            desugar_dict = desugar_dict,
+            providers = [],
+        ),
+    )
+
 def use_legacy_manifest_merger(ctx):
     """Whether legacy manifest merging is enabled.
 
@@ -231,6 +370,9 @@
     NativeLibsProcessor = _process_native_libs,
     DataBindingProcessor = _process_data_binding,
     JvmProcessor = _process_jvm,
+    BuildInfoProcessor = _process_build_info,
+    DeployJarProcessor = _process_deploy_jar,
+    DexProcessor = _process_dex,
 )
 
 _PROCESSING_PIPELINE = processing_pipeline.make_processing_pipeline(
diff --git a/rules/android_binary_internal/rule.bzl b/rules/android_binary_internal/rule.bzl
index b08a39e..949cca1 100644
--- a/rules/android_binary_internal/rule.bzl
+++ b/rules/android_binary_internal/rule.bzl
@@ -43,7 +43,10 @@
         attrs = attrs,
         implementation = implementation,
         provides = provides,
-        toolchains = ["//toolchains/android:toolchain_type"],
+        toolchains = [
+            "//toolchains/android:toolchain_type",
+            "@bazel_tools//tools/jdk:toolchain_type",
+        ],
         _skylark_testable = True,
         fragments = [
             "android",
diff --git a/rules/android_common/BUILD b/rules/android_common/BUILD
index 241cf20..cd6c06f 100644
--- a/rules/android_common/BUILD
+++ b/rules/android_common/BUILD
@@ -1,6 +1,7 @@
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
 
 package(
+    default_applicable_licenses = ["//:license"],
     default_visibility =
         ["//:__subpackages__"],
 )
diff --git a/rules/android_library/rule.bzl b/rules/android_library/rule.bzl
index a1a69d9..e36bdd3 100644
--- a/rules/android_library/rule.bzl
+++ b/rules/android_library/rule.bzl
@@ -172,6 +172,7 @@
         toolchains = [
             "//toolchains/android:toolchain_type",
             "//toolchains/android_sdk:toolchain_type",
+            "@bazel_tools//tools/jdk:toolchain_type",
         ] + additional_toolchains,
         _skylark_testable = True,
     )
diff --git a/rules/android_revision.bzl b/rules/android_revision.bzl
new file mode 100644
index 0000000..28b45dd
--- /dev/null
+++ b/rules/android_revision.bzl
@@ -0,0 +1,88 @@
+# 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.
+
+"""Parse and compare Android revision strings."""
+
+# TODO(katre): support preview versions.
+AndroidRevisionInfo = provider(
+    "Information about Android revision specifications.",
+    fields = {
+        "major": "The major version number",
+        "minor": "The minor version number, or 0 if unset.",
+        "micro": "The micro version number, or 0 if unset.",
+        "version": "The version string.",
+        "dir": "The directory where the revision would exist in an Android SDK.",
+    },
+)
+
+def parse_android_revision(input):
+    """Parse and Android revision string and return an AndroidRevisionInfo.
+
+    Args:
+      input: The raw revision string to parse.
+
+    Returns:
+      An AndroidRevisionInfo provider representing the input.
+    """
+    input = input.strip()
+    parts = input.split(".")
+    if len(parts) < 1:
+        fail("Invalid Android revision %s" % input)
+    major = int(parts[0]) if len(parts) >= 1 else 0
+    minor = int(parts[1]) if len(parts) >= 2 else 0
+    micro = int(parts[2]) if len(parts) >= 3 else 0
+
+    return AndroidRevisionInfo(
+        version = input,
+        dir = input,
+        major = major,
+        minor = minor,
+        micro = micro,
+    )
+
+def _compare_android_revision_field(first, second, name):
+    first_val = getattr(first, name)
+    second_val = getattr(second, name)
+    if first_val > second_val:
+        return first
+    elif first_val < second_val:
+        return second
+    return None
+
+def compare_android_revisions(first, second):
+    """Compares two AndroidRevisionInfo providers and returns the one with the highest version.
+
+    Args:
+      first: The first revision to compare.
+      second: The first revision to compare.
+
+    Returns:
+      The revision with the higher version number, or the first if they are equal.
+    """
+    if first == None and second == None:
+        return None
+    if first != None and second == None:
+        return first
+    if first == None and second != None:
+        return second
+    highest = _compare_android_revision_field(first, second, "major")
+    if highest != None:
+        return highest
+    highest = _compare_android_revision_field(first, second, "minor")
+    if highest != None:
+        return highest
+    highest = _compare_android_revision_field(first, second, "micro")
+    if highest != None:
+        return highest
+    return first
diff --git a/rules/common.bzl b/rules/common.bzl
index 508413f..a951328 100644
--- a/rules/common.bzl
+++ b/rules/common.bzl
@@ -43,7 +43,7 @@
         _log.error("Missing _host_javabase attr")
     return ctx.attr._host_javabase
 
-def _filter_zip(ctx, in_zip, out_zip, filters = []):
+def _filter_zip_include(ctx, in_zip, out_zip, filters = []):
     """Creates a copy of a zip file with files that match filters."""
     args = ctx.actions.args()
     args.add("-q")
@@ -57,10 +57,62 @@
         arguments = [args],
         inputs = [in_zip],
         outputs = [out_zip],
-        mnemonic = "FilterZip",
+        mnemonic = "FilterZipInclude",
         progress_message = "Filtering %s" % in_zip.short_path,
     )
 
+def _filter_zip_exclude(
+        ctx,
+        output = None,
+        input = None,
+        filter_zips = [],
+        filter_types = [],
+        filters = [],
+        check_hash_mismatch = False,
+        compression_mode = "DONT_CARE"):
+    """Filter out entries from a zip file based on the filter types and filter zips.
+
+    Args:
+        ctx: The Context.
+        output: File. The output zip.
+        input: File. The input zip.
+        filter_zips: List of Files. The zips used as filters. Contents in these files will be omitted from the output zip.
+        filter_types: List of strings. Only contents in the filter Zip files with these extensions will be filtered out.
+        filters: List of strings. The regex to the set of filters to always check for and remove.
+        check_hash_mismatch: Boolean. Whether to enable checking of hash mismatches for files with the same name.
+        compression_mode: String. The compression mode for the output zip. There are 3 modes:
+            * FORCE_DEFLATE: Force the output zip to be compressed.
+            * FORCE_STORED: Force the output zip to be stored.
+            * DONT_CARE: The output zip will have the same compression mode with the input zip.
+    """
+    args = ctx.actions.args()
+
+    args.add("inputZip", input.path)
+    args.add("--outputZip", output.path)
+
+    if filter_zips:
+        args.add("--filterZips", ",".join([z.path for z in filter_zips]))
+    if filter_types:
+        args.add("--filterTypes", ",".join(filter_types))
+    if filters:
+        args.add("--explicitFilters", ",".join(filters))
+
+    if check_hash_mismatch:
+        args.add("--checkHashMismatch", "ERROR")
+    else:
+        args.add("--checkHashMisMatch", "IGNORE")
+
+    args.add("--outputMode", compression_mode)
+
+    ctx.actions.run(
+        executable = get_android_toolchain(ctx).zip_filter.files_to_run,
+        arguments = [args],
+        inputs = [input] + filter_zips,
+        outputs = [output],
+        mnemonic = "FilterZipExclude",
+        progress_message = "Filtering %s" % input.short_path,
+    )
+
 def _create_signer_properties(ctx, oldest_key):
     properties = ctx.actions.declare_file("%s/keystore.properties" % ctx.label.name)
     ctx.actions.expand_template(
@@ -76,7 +128,8 @@
     create_signer_properties = _create_signer_properties,
     get_host_javabase = _get_host_javabase,
     get_java_toolchain = _get_java_toolchain,
-    filter_zip = _filter_zip,
+    filter_zip_include = _filter_zip_include,
+    filter_zip_exclude = _filter_zip_exclude,
 )
 
 android_common = _native_android_common
diff --git a/rules/desugar.bzl b/rules/desugar.bzl
new file mode 100644
index 0000000..c498e00
--- /dev/null
+++ b/rules/desugar.bzl
@@ -0,0 +1,67 @@
+# Copyright 2020 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.
+
+"""Bazel Desugar Commands."""
+
+def _desugar(
+        ctx,
+        input,
+        output = None,
+        classpath = None,
+        bootclasspath = [],
+        min_sdk_version = 0,
+        library_desugaring = True,
+        desugar_exec = None):
+    """Desugars a JAR.
+
+    Args:
+        ctx: The context.
+        input: File. The jar to be desugared.
+        output: File. The desugared jar.
+        classpath: Depset of Files. The transitive classpath needed to resolve symbols in the input jar.
+        bootclasspath: List of Files. Bootclasspaths that was used to compile the input jar with.
+        min_sdk_version: Integer. The minimum targeted sdk version.
+        library_desugaring: Boolean. Whether to enable core library desugaring.
+        desugar_exec: File. The executable desugar file.
+    """
+
+    args = ctx.actions.args()
+    args.add("--input", input)
+    args.add("--output", output)
+    args.add_all(classpath, before_each = "--classpath_entry")
+    args.add_all(bootclasspath, before_each = "--bootclasspath_entry")
+
+    if library_desugaring:
+        if ctx.fragments.android.check_desugar_deps:
+            args.add("--emit_dependency_metadata_as_needed")
+
+        if ctx.fragments.android.desugar_java8_libs and library_desugaring:
+            args.add("--desugar_supported_core_libs")
+
+    if min_sdk_version > 0:
+        args.add("--min_sdk_version", str(min_sdk_version))
+
+    ctx.actions.run(
+        inputs = depset([input] + bootclasspath, transitive = [classpath]),
+        outputs = [output],
+        executable = desugar_exec,
+        arguments = [args],
+        mnemonic = "Desugar",
+        progress_message = "Desugaring " + input.short_path + " for Android",
+        execution_requirements = {"supports-workers": "1"},
+    )
+
+desugar = struct(
+    desugar = _desugar,
+)
diff --git a/rules/dex.bzl b/rules/dex.bzl
index 5d9592d..cc0b44d 100644
--- a/rules/dex.bzl
+++ b/rules/dex.bzl
@@ -14,11 +14,95 @@
 
 """Bazel Dex Commands."""
 
+load(":utils.bzl", "get_android_toolchain", "utils")
+load(":providers.bzl", "StarlarkAndroidDexInfo")
 load("@bazel_skylib//lib:collections.bzl", "collections")
 load("//rules:attrs.bzl", _attrs = "attrs")
 
 _tristate = _attrs.tristate
 
+def _process_incremental_dexing(
+        ctx,
+        deps = [],
+        runtime_jars = [],
+        dexopts = [],
+        main_dex_list = [],
+        min_sdk_version = 0,
+        java_info = None,
+        desugar_dict = {},
+        dexbuilder = None,
+        dexmerger = None):
+    classes_dex_zip = _get_dx_artifact(ctx, "classes.dex.zip")
+    info = _merge_infos(utils.collect_providers(StarlarkAndroidDexInfo, deps))
+
+    incremental_dexopts = _incremental_dexopts(dexopts, ctx.fragments.android.get_dexopts_supported_in_incremental_dexing)
+    dex_archives_list = info.dex_archives_dict.get("".join(incremental_dexopts), depset()).to_list()
+    dex_archives = _to_dexed_classpath(
+        dex_archives_dict = {d.jar: d.dex for d in dex_archives_list},
+        classpath = java_info.transitive_runtime_jars.to_list(),
+        runtime_jars = runtime_jars,
+    )
+
+    for jar in runtime_jars:
+        dex_archive = _get_dx_artifact(ctx, jar.basename + ".dex.zip")
+        _dex(
+            ctx,
+            input = desugar_dict[jar] if jar in desugar_dict else jar,
+            output = dex_archive,
+            incremental_dexopts = incremental_dexopts,
+            min_sdk_version = min_sdk_version,
+            dex_exec = dexbuilder,
+        )
+        dex_archives.append(dex_archive)
+
+    _dex_merge(
+        ctx,
+        output = classes_dex_zip,
+        inputs = dex_archives,
+        multidex_strategy = "minimal",
+        main_dex_list = main_dex_list,
+        dexopts = dexopts,
+        dexmerger = dexmerger,
+    )
+
+    return classes_dex_zip
+
+def _append_java8_legacy_dex(
+        ctx,
+        output = None,
+        input = None,
+        java8_legacy_dex = None,
+        dex_zips_merger = None):
+    args = ctx.actions.args()
+
+    # Order matters here: we want java8_legacy_dex to be the highest-numbered classesN.dex
+    args.add("--input_zip", input)
+    args.add("--input_zip", java8_legacy_dex)
+    args.add("--output_zip", output)
+
+    ctx.actions.run(
+        executable = dex_zips_merger,
+        inputs = [input, java8_legacy_dex],
+        outputs = [output],
+        arguments = [args],
+        mnemonic = "AppendJava8LegacyDex",
+        use_default_shell_env = True,
+        progress_message = "Adding Java8 legacy library for %s" % ctx.label,
+    )
+
+def _to_dexed_classpath(dex_archives_dict = {}, classpath = [], runtime_jars = []):
+    dexed_classpath = []
+    for jar in classpath:
+        if jar not in dex_archives_dict:
+            if jar not in runtime_jars:
+                fail("Dependencies on .jar artifacts are not allowed in Android binaries, please use " +
+                     "a java_import to depend on " + jar.short_path +
+                     ". If this is an implicit dependency then the rule that " +
+                     "introduces it will need to be fixed to account for it correctly.")
+        else:
+            dexed_classpath.append(dex_archives_dict[jar])
+    return dexed_classpath
+
 def _dex(
         ctx,
         input,
@@ -89,6 +173,32 @@
     # use_incremental_dexing config flag will take effect if incremental_dexing attr is not set
     return use_incremental_dexing
 
+def _get_java8_legacy_dex_and_map(ctx, build_customized_files = False, binary_jar = None, android_jar = None):
+    if not build_customized_files:
+        return utils.only(get_android_toolchain(ctx).java8_legacy_dex.files.to_list()), None
+    else:
+        java8_legacy_dex_rules = _get_dx_artifact(ctx, "_java8_legacy.dex.pgcfg")
+        java8_legacy_dex_map = _get_dx_artifact(ctx, "_java8_legacy.dex.map")
+        java8_legacy_dex = _get_dx_artifact(ctx, "_java8_legacy.dex.zip")
+
+        args = ctx.actions.args()
+        args.add("--rules", java8_legacy_dex_rules)
+        args.add("--binary", binary_jar)
+        args.add("--android_jar", android_jar)
+        args.add("--output", java8_legacy_dex)
+        args.add("--output_map", java8_legacy_dex_map)
+
+        ctx.actions.run(
+            executable = get_android_toolchain(ctx).build_java8_legacy_dex.files_to_run,
+            inputs = [binary_jar, android_jar],
+            outputs = [java8_legacy_dex_rules, java8_legacy_dex_map, java8_legacy_dex],
+            arguments = [args],
+            mnemonic = "BuildLegacyDex",
+            progress_message = "Building Java8 legacy library for %s" % ctx.label,
+        )
+
+        return java8_legacy_dex, java8_legacy_dex_map
+
 def _dex_merge(
         ctx,
         output = None,
@@ -101,7 +211,7 @@
     args.add("--multidex", multidex_strategy)
     args.add_all(inputs, before_each = "--input")
     args.add("--output", output)
-    args.add_all(_merger_dexopts(ctx, dexopts))
+    args.add_all(_merger_dexopts(dexopts, ctx.fragments.android.get_dexopts_supported_in_dex_merger))
 
     if main_dex_list:
         inputs.append(main_dex_list)
@@ -122,6 +232,19 @@
 def _incremental_dexopts(tokenized_dexopts, dexopts_supported_in_incremental_dexing):
     return _normalize_dexopts(_filter_dexopts(tokenized_dexopts, dexopts_supported_in_incremental_dexing))
 
+def _merge_infos(infos):
+    dex_archives_dict = {}
+    for info in infos:
+        for dexopts in info.dex_archives_dict:
+            if dexopts not in dex_archives_dict:
+                dex_archives_dict[dexopts] = [info.dex_archives_dict[dexopts]]
+            else:
+                dex_archives_dict[dexopts].append(info.dex_archives_dict[dexopts])
+    return StarlarkAndroidDexInfo(
+        dex_archives_dict =
+            {dexopts: depset(direct = [], transitive = dex_archives) for dexopts, dex_archives in dex_archives_dict.items()},
+    )
+
 def _filter_dexopts(candidates, allowed):
     return [c for c in candidates if c in allowed]
 
@@ -132,10 +255,14 @@
     return collections.uniq(sorted([_dx_to_dexbuilder(token) for token in tokenized_dexopts]))
 
 dex = struct(
+    append_java8_legacy_dex = _append_java8_legacy_dex,
     dex = _dex,
     dex_merge = _dex_merge,
     get_dx_artifact = _get_dx_artifact,
     get_effective_incremental_dexing = _get_effective_incremental_dexing,
+    get_java8_legacy_dex_and_map = _get_java8_legacy_dex_and_map,
     incremental_dexopts = _incremental_dexopts,
+    merge_infos = _merge_infos,
     normalize_dexopts = _normalize_dexopts,
+    process_incremental_dexing = _process_incremental_dexing,
 )
diff --git a/rules/dex_desugar_aspect.bzl b/rules/dex_desugar_aspect.bzl
new file mode 100644
index 0000000..544000c
--- /dev/null
+++ b/rules/dex_desugar_aspect.bzl
@@ -0,0 +1,242 @@
+# 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.
+
+"""Aspect that transitively build .dex archives and desugar jars."""
+
+load(":utils.bzl", _utils = "utils")
+load(":dex.bzl", _dex = "dex")
+load(":desugar.bzl", _desugar = "desugar")
+load(":providers.bzl", "StarlarkAndroidDexInfo")
+load(":attrs.bzl", _attrs = "attrs")
+load("//rules:acls.bzl", "acls")
+
+_tristate = _attrs.tristate
+
+def _aspect_attrs():
+    """Attrs of the rule requiring traversal by the aspect."""
+    return [
+        "aidl_lib",  # for the aidl runtime in the android_sdk rule
+        "deps",
+        "exports",
+        "runtime",
+        "runtime_deps",
+        "_android_sdk",
+        "_aspect_proto_toolchain_for_javalite",
+        "_build_stamp_mergee_manifest_lib",  # To get from proto_library through proto_lang_toolchain rule to proto runtime library.
+        "_toolchain",  # to get Kotlin toolchain component in android_library
+    ]
+
+def _aspect_impl(target, ctx):
+    """Adapts the rule and target data.
+
+    Args:
+      target: The target.
+      ctx: The context.
+
+    Returns:
+      A list of providers.
+    """
+    min_sdk_version = getattr(ctx.rule.attr, "min_sdk_version", 0)
+    if min_sdk_version != 0 and not acls.in_android_binary_min_sdk_version_attribute(str(ctx)):
+        fail("Target is not allowed to set a min_sdk_version value.")
+
+    incremental_dexing = getattr(ctx.rule.attr, "incremental_dexing", _tristate.auto)
+
+    if incremental_dexing == _tristate.no or \
+       (not ctx.fragments.android.use_incremental_dexing and
+        incremental_dexing == _tristate.auto):
+        return []
+
+    # TODO(b/33557068): Desugar protos if needed instead of assuming they don't need desugaring
+    ignore_desugar = not ctx.fragments.android.desugar_java8 or ctx.rule.kind == "proto_library"
+
+    extra_toolchain_jars = _get_platform_based_toolchain_jars(ctx)
+
+    if hasattr(ctx.rule.attr, "neverlink") and ctx.rule.attr.neverlink:
+        return []
+
+    dex_archives_dict = {}
+    runtime_jars = _get_produced_runtime_jars(target, ctx, extra_toolchain_jars)
+    bootclasspath = _get_boot_classpath(target, ctx)
+    compiletime_classpath = target[JavaInfo].transitive_compile_time_jars if JavaInfo in target else None
+    if runtime_jars:
+        basename_clash = _check_basename_clash(runtime_jars)
+        aspect_dexopts = _get_aspect_dexopts(ctx)
+        min_sdk_filename_part = "--min_sdk_version=" + min_sdk_version if min_sdk_version > 0 else ""
+        for jar in runtime_jars:
+            if not ignore_desugar:
+                unique_desugar_filename = (jar.path if basename_clash else jar.basename) + \
+                                          min_sdk_filename_part + "_desugared.jar"
+                desugared_jar = _dex.get_dx_artifact(ctx, unique_desugar_filename)
+                _desugar.desugar(
+                    ctx,
+                    input = jar,
+                    output = desugared_jar,
+                    bootclasspath = bootclasspath,
+                    classpath = compiletime_classpath,
+                    min_sdk_version = min_sdk_version,
+                    desugar_exec = ctx.executable._desugar_java8,
+                )
+            else:
+                desugared_jar = None
+
+            for incremental_dexopts_list in aspect_dexopts:
+                incremental_dexopts = "".join(incremental_dexopts_list)
+
+                unique_dx_filename = (jar.short_path if basename_clash else jar.basename) + \
+                                     incremental_dexopts + min_sdk_filename_part + ".dex.zip"
+                dex = _dex.get_dx_artifact(ctx, unique_dx_filename)
+                _dex.dex(
+                    ctx,
+                    input = desugared_jar if desugared_jar else jar,
+                    output = dex,
+                    incremental_dexopts = incremental_dexopts_list,
+                    min_sdk_version = min_sdk_version,
+                    dex_exec = ctx.executable._dexbuilder,
+                )
+
+                dex_archive = struct(
+                    jar = jar,
+                    desugared_jar = desugared_jar,
+                    dex = dex,
+                )
+
+                if incremental_dexopts not in dex_archives_dict:
+                    dex_archives_dict[incremental_dexopts] = []
+                dex_archives_dict[incremental_dexopts].append(dex_archive)
+
+    infos = _utils.collect_providers(StarlarkAndroidDexInfo, _get_aspect_deps(ctx))
+    merged_info = _dex.merge_infos(infos)
+
+    for dexopts in dex_archives_dict:
+        if dexopts in merged_info.dex_archives_dict:
+            merged_info.dex_archives_dict[dexopts] = depset(dex_archives_dict[dexopts], transitive = [merged_info.dex_archives_dict[dexopts]])
+        else:
+            merged_info.dex_archives_dict[dexopts] = depset(dex_archives_dict[dexopts])
+
+    return [
+        StarlarkAndroidDexInfo(
+            dex_archives_dict = merged_info.dex_archives_dict,
+        ),
+    ]
+
+def _get_aspect_deps(ctx):
+    deps_list = []
+    for deps in [getattr(ctx.rule.attr, attr) for attr in _aspect_attrs() if hasattr(ctx.rule.attr, attr)]:
+        if str(type(deps)) == "list":
+            deps_list += deps
+        else:
+            deps_list.append(deps)
+    return deps_list
+
+def _get_produced_runtime_jars(target, ctx, extra_toolchain_jars):
+    if ctx.rule.kind == "proto_library":
+        if not getattr(ctx.rule.attr, "srcs", []):
+            if JavaInfo in target:
+                return [java_output.class_jar for java_output in target[JavaInfo].java_outputs]
+        return []
+    else:
+        jars = []
+        if JavaInfo in target:
+            jars.extend(target[JavaInfo].runtime_output_jars)
+
+        # TODO(b/124540821): Disable R.jar desugaring (with a flag).
+        if AndroidIdeInfo in target and target[AndroidIdeInfo].resource_jar:
+            jars.append(target[AndroidIdeInfo].resource_jar.class_jar)
+
+        if AndroidApplicationResourceInfo in target and target[AndroidApplicationResourceInfo].build_stamp_jar:
+            jars.append(target[AndroidApplicationResourceInfo].build_stamp_jar)
+
+        jars.extend(extra_toolchain_jars)
+        return jars
+
+def _get_platform_based_toolchain_jars(ctx):
+    if not ctx.fragments.android.incompatible_use_toolchain_resolution:
+        return []
+
+    if not getattr(ctx.rule.attr, "_android_sdk", None):
+        return []
+
+    android_sdk = ctx.rule.attr._android_sdk
+
+    if AndroidSdkInfo in android_sdk and android_sdk[AndroidSdkInfo].aidl_lib:
+        return android_sdk[AndroidSdkInfo].aidl_lib[JavaInfo].runtime_output_jars
+
+    return []
+
+def _get_aspect_dexopts(ctx):
+    return _power_set(_dex.normalize_dexopts(ctx.fragments.android.get_dexopts_supported_in_incremental_dexing))
+
+def _get_boot_classpath(target, ctx):
+    if JavaInfo in target:
+        compilation_info = target[JavaInfo].compilation_info
+        if compilation_info and compilation_info.boot_classpath:
+            return compilation_info.boot_classpath
+    if ctx.attr._android_sdk and ctx.attr._android_sdk[AndroidSdkInfo].android_jar:
+        return [ctx.attr._android_sdk[AndroidSdkInfo].android_jar]
+
+    # This shouldn't ever be reached, but if it is, we should be clear about the error.
+    fail("No compilation info or android jar!")
+
+def _check_basename_clash(artifacts):
+    seen = {}
+    for artifact in artifacts:
+        basename = artifact.basename
+        if basename not in seen:
+            seen[basename] = True
+        else:
+            return True
+    return False
+
+def _power_set(items):
+    """Calculates the power set of the given items.
+    """
+
+    def _exp(base, n):
+        """ Calculates base ** n."""
+        res = 1
+        for _ in range(n):
+            res *= base
+        return res
+
+    power_set = []
+    size = len(items)
+
+    for i in range(_exp(2, size)):
+        element = [items[j] for j in range(size) if (i // _exp(2, j) % 2) != 0]
+        power_set.append(element)
+
+    return power_set
+
+dex_desugar_aspect = aspect(
+    implementation = _aspect_impl,
+    attr_aspects = _aspect_attrs(),
+    attrs = _attrs.add(
+        {
+            "_desugar_java8": attr.label(
+                default = Label("@bazel_tools//tools/android:desugar_java8"),
+                cfg = "exec",
+                executable = True,
+            ),
+            "_dexbuilder": attr.label(
+                default = Label("@bazel_tools//tools/android:dexbuilder"),
+                allow_files = True,
+                cfg = "exec",
+                executable = True,
+            ),
+        },
+        _attrs.ANDROID_SDK,
+    ),
+    fragments = ["android"],
+)
diff --git a/rules/java.bzl b/rules/java.bzl
index 6ad7d40..92b5c0b 100644
--- a/rules/java.bzl
+++ b/rules/java.bzl
@@ -21,6 +21,9 @@
     "A list of constraints provided without the 'android' constraint."
 )
 
+# TODO(b/283499746): Reduce singlejar memory if possible.
+_SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB = 1600
+
 def _segment_idx(path_segments):
     """Finds the index of the segment in the path that preceeds the source root.
 
@@ -360,7 +363,9 @@
         mnemonic = "SingleJar",
         progress_message = "Merge into a single jar.",
         include_build_data = False,
-        java_toolchain = None):
+        java_toolchain = None,
+        extra_args = [],
+        resource_set = None):
     args = ctx.actions.args()
     args.add("--output")
     args.add(output)
@@ -378,11 +383,12 @@
 
     ctx.actions.run(
         executable = java_toolchain[java_common.JavaToolchainInfo].single_jar,
-        arguments = [args],
+        arguments = [args] + extra_args,
         inputs = inputs,
         outputs = [output],
         mnemonic = mnemonic,
         progress_message = progress_message,
+        resource_set = resource_set,
     )
 
 def _run(
@@ -433,6 +439,43 @@
 
     ctx.actions.run(**args)
 
+def _create_deploy_jar(
+        ctx,
+        output = None,
+        runtime_jars = depset(),
+        java_toolchain = None,
+        target_name = "",
+        build_info_files = depset(),
+        deploy_manifest_lines = [],
+        extra_build_info = ""):
+    inputs = depset(transitive = [runtime_jars, build_info_files])
+
+    args = ctx.actions.args()
+    args.add("--build_target", target_name)
+    args.add("--check_desugar_deps")
+
+    if build_info_files:
+        args.add_all(build_info_files, before_each = "--build_info_file")
+
+    args.add_all("--deploy_manifest_lines", deploy_manifest_lines)
+    args.add("--extra_build_info", extra_build_info)
+
+    _singlejar(
+        ctx,
+        inputs = inputs,
+        output = output,
+        mnemonic = "JavaDeployJar",
+        progress_message = "Building deploy jar %s" % output.short_path,
+        java_toolchain = java_toolchain,
+        extra_args = [args],
+        resource_set = _resource_set_for_deploy_jar,
+    )
+    return output
+
+def _resource_set_for_deploy_jar(_os, _inputs_size):
+    # parameters are unused but required by the resource_set API
+    return {"memory": _SINGLEJAR_MEMORY_FOR_DEPLOY_JAR_MB, "cpu": 1}
+
 java = struct(
     compile = _compile,
     compile_android = _compile_android,
@@ -442,4 +485,5 @@
     invalid_java_package = _invalid_java_package,
     run = _run,
     singlejar = _singlejar,
+    create_deploy_jar = _create_deploy_jar,
 )
diff --git a/rules/native_deps.bzl b/rules/native_deps.bzl
index fb4c728..f3537e1 100644
--- a/rules/native_deps.bzl
+++ b/rules/native_deps.bzl
@@ -311,7 +311,7 @@
         feature_configuration = feature_config,
         cc_toolchain = cc_toolchain,
         test_only_target = test_only_target,
-        stamp = ctx.attr.stamp,
+        stamp = getattr(ctx.attr, "stamp", 0),
         grep_includes = ctx.file._grep_includes,
         main_output = linked_lib,
         use_shareable_artifact_factory = True,
diff --git a/rules/providers.bzl b/rules/providers.bzl
index ef138f4..a377afe 100644
--- a/rules/providers.bzl
+++ b/rules/providers.bzl
@@ -151,3 +151,12 @@
         unsigned_aab = "File, the unsigned .aab",
     ),
 )
+
+StarlarkAndroidDexInfo = provider(
+    doc = "Internal provider used to collect transitive dex info.",
+    fields = dict(
+        dex_archives_dict = (
+            "A dictionary of all the transitive dex archives for all dexopts."
+        ),
+    ),
+)
diff --git a/rules/rules.bzl b/rules/rules.bzl
index 315fd4f..ecaef19 100644
--- a/rules/rules.bzl
+++ b/rules/rules.bzl
@@ -14,6 +14,8 @@
 
 """Starlark rules for building Android apps."""
 
+# Don't use relative paths since this file is coppied to //android/rules.bzl.
+
 load(
     "//rules/aar_import:rule.bzl",
     _aar_import = "aar_import",
@@ -23,7 +25,7 @@
     _android_application = "android_application",
 )
 load(
-    ":android_binary.bzl",
+    "//rules:android_binary.bzl",
     _android_binary = "android_binary",
 )
 load(
@@ -31,19 +33,19 @@
     _android_library = "android_library_macro",
 )
 load(
-    ":android_ndk_repository.bzl",
+    "//rules:android_ndk_repository.bzl",
     _android_ndk_repository = "android_ndk_repository",
 )
 load(
-    ":android_sdk.bzl",
+    "//rules:android_sdk.bzl",
     _android_sdk = "android_sdk",
 )
 load(
-    ":android_sdk_repository.bzl",
+    "//rules:android_sdk_repository.bzl",
     _android_sdk_repository = "android_sdk_repository",
 )
 load(
-    ":android_tools_defaults_jar.bzl",
+    "//rules:android_tools_defaults_jar.bzl",
     _android_tools_defaults_jar = "android_tools_defaults_jar",
 )
 
diff --git a/src/common/golang/BUILD b/src/common/golang/BUILD
index 4c1bf0f..16de240 100644
--- a/src/common/golang/BUILD
+++ b/src/common/golang/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Common libraries and utilities.
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/BUILD b/src/tools/ak/BUILD
index 51d524f..6521102 100644
--- a/src/tools/ak/BUILD
+++ b/src/tools/ak/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Top level package for ak, a "busybox" for various minor build-related tools.
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/bucketize/BUILD b/src/tools/ak/bucketize/BUILD
index 750400a..39afbf9 100644
--- a/src/tools/ak/bucketize/BUILD
+++ b/src/tools/ak/bucketize/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for bucketize module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/compile/BUILD b/src/tools/ak/compile/BUILD
index 6b7f246..a97dfeb 100644
--- a/src/tools/ak/compile/BUILD
+++ b/src/tools/ak/compile/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for compile module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/dex/BUILD b/src/tools/ak/dex/BUILD
index 80e4900..eda7073 100644
--- a/src/tools/ak/dex/BUILD
+++ b/src/tools/ak/dex/BUILD
@@ -2,7 +2,10 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
 # Package for dex compilation module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/extractaar/BUILD b/src/tools/ak/extractaar/BUILD
index a4da9ce..403dc84 100644
--- a/src/tools/ak/extractaar/BUILD
+++ b/src/tools/ak/extractaar/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for extractaar module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/finalrjar/BUILD b/src/tools/ak/finalrjar/BUILD
index db487da..1e4e247 100644
--- a/src/tools/ak/finalrjar/BUILD
+++ b/src/tools/ak/finalrjar/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for final R.jar module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/generatemanifest/BUILD b/src/tools/ak/generatemanifest/BUILD
index 2da8e4a..f91ce3e 100644
--- a/src/tools/ak/generatemanifest/BUILD
+++ b/src/tools/ak/generatemanifest/BUILD
@@ -2,7 +2,10 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
 # Package for manifest generation module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/link/BUILD b/src/tools/ak/link/BUILD
index e52fb72..6ef0b73 100644
--- a/src/tools/ak/link/BUILD
+++ b/src/tools/ak/link/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for link module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/liteparse/BUILD b/src/tools/ak/liteparse/BUILD
index 10f1482..80285d8 100644
--- a/src/tools/ak/liteparse/BUILD
+++ b/src/tools/ak/liteparse/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for parse module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/manifest/BUILD b/src/tools/ak/manifest/BUILD
index b8031a8..a114266 100644
--- a/src/tools/ak/manifest/BUILD
+++ b/src/tools/ak/manifest/BUILD
@@ -2,7 +2,10 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
 # Package for manifest compilation module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/manifest/manifest.go b/src/tools/ak/manifest/manifest.go
index c519545..eb0a46a 100644
--- a/src/tools/ak/manifest/manifest.go
+++ b/src/tools/ak/manifest/manifest.go
@@ -62,6 +62,7 @@
 	// Flag variables
 	aapt2, manifest, out, sdkJar, res string
 	attr                              flags.StringList
+	forceDebuggable                   bool
 
 	initOnce sync.Once
 )
@@ -74,6 +75,7 @@
 		flag.StringVar(&out, "out", "", "Path to output")
 		flag.StringVar(&sdkJar, "sdk_jar", "", "Path to sdk jar")
 		flag.StringVar(&res, "res", "", "Path to res")
+		flag.BoolVar(&forceDebuggable, "force_debuggable", false, "Whether to force set android:debuggable=true.")
 		flag.Var(&attr, "attr", "(optional) attr(s) to set. {element}:{attr}:{value}.")
 	})
 }
@@ -103,8 +105,11 @@
 		defer os.Remove(patchedManifest.Name())
 		manifestPath = patchManifest(manifest, patchedManifest, attr)
 	}
-
-	stdoutStderr, err := exec.Command(aapt2, "link", "-o", aaptOut.Name(), "--manifest", manifestPath, "-I", sdkJar, "-I", res).CombinedOutput()
+	args := []string{"link", "-o", aaptOut.Name(), "--manifest", manifestPath, "-I", sdkJar, "-I", res}
+	if forceDebuggable {
+		args = append(args, "--debug-mode")
+	}
+	stdoutStderr, err := exec.Command(aapt2, args...).CombinedOutput()
 	if err != nil {
 		log.Fatalf(errMsg, stdoutStderr)
 	}
diff --git a/src/tools/ak/mindex/BUILD b/src/tools/ak/mindex/BUILD
index aeb40ec..9c26d48 100644
--- a/src/tools/ak/mindex/BUILD
+++ b/src/tools/ak/mindex/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for mindex module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/nativelib/BUILD b/src/tools/ak/nativelib/BUILD
index dc9fe12..0aa8a8f 100644
--- a/src/tools/ak/nativelib/BUILD
+++ b/src/tools/ak/nativelib/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for nativelib module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/nativelib/testdata/BUILD b/src/tools/ak/nativelib/testdata/BUILD
index a0615ed0..b438916 100644
--- a/src/tools/ak/nativelib/testdata/BUILD
+++ b/src/tools/ak/nativelib/testdata/BUILD
@@ -3,7 +3,10 @@
 load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
-package(default_visibility = ["//src/tools/ak/nativelib:__subpackages__"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//src/tools/ak/nativelib:__subpackages__"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/patch/BUILD b/src/tools/ak/patch/BUILD
index 6eee3a9..a1099b8 100644
--- a/src/tools/ak/patch/BUILD
+++ b/src/tools/ak/patch/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for patch module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/proto/BUILD b/src/tools/ak/proto/BUILD
index 5e678df..84d35da 100644
--- a/src/tools/ak/proto/BUILD
+++ b/src/tools/ak/proto/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Protocol buffer definitions used by AK
-package(default_visibility = ["//src/tools/ak/package:__subpackages__"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//src/tools/ak/package:__subpackages__"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/repack/BUILD b/src/tools/ak/repack/BUILD
index 3686368..7651ebb 100644
--- a/src/tools/ak/repack/BUILD
+++ b/src/tools/ak/repack/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for repack module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/res/BUILD b/src/tools/ak/res/BUILD
index 6cdb6e5..26c8a10 100644
--- a/src/tools/ak/res/BUILD
+++ b/src/tools/ak/res/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for res module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/res/proto/BUILD b/src/tools/ak/res/proto/BUILD
index adbdf33..a0bbc3b 100644
--- a/src/tools/ak/res/proto/BUILD
+++ b/src/tools/ak/res/proto/BUILD
@@ -3,7 +3,10 @@
 
 # Description
 #   Android resources proto
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/res/respipe/BUILD b/src/tools/ak/res/respipe/BUILD
index ddc3053..dbfb474 100644
--- a/src/tools/ak/res/respipe/BUILD
+++ b/src/tools/ak/res/respipe/BUILD
@@ -1,6 +1,8 @@
 load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
+package(default_applicable_licenses = ["//:license"])
+
 licenses(["notice"])
 
 go_library(
diff --git a/src/tools/ak/res/resxml/BUILD b/src/tools/ak/res/resxml/BUILD
index c74aa68..e68d7f0 100644
--- a/src/tools/ak/res/resxml/BUILD
+++ b/src/tools/ak/res/resxml/BUILD
@@ -1,6 +1,8 @@
 load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
+package(default_applicable_licenses = ["//:license"])
+
 licenses(["notice"])
 
 go_library(
diff --git a/src/tools/ak/rjar/BUILD b/src/tools/ak/rjar/BUILD
index 3d3b8b1..5eee6d9 100644
--- a/src/tools/ak/rjar/BUILD
+++ b/src/tools/ak/rjar/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for R.jar module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/shellapk/BUILD b/src/tools/ak/shellapk/BUILD
index 9303000..9accaad 100644
--- a/src/tools/ak/shellapk/BUILD
+++ b/src/tools/ak/shellapk/BUILD
@@ -3,7 +3,10 @@
 
 # Description:
 #   Package for shellapk module
-package(default_visibility = ["//visibility:public"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
 
 licenses(["notice"])
 
diff --git a/src/tools/ak/shellapk/testdata/BUILD b/src/tools/ak/shellapk/testdata/BUILD
index 6e9bed2..3bccf50 100644
--- a/src/tools/ak/shellapk/testdata/BUILD
+++ b/src/tools/ak/shellapk/testdata/BUILD
@@ -3,7 +3,10 @@
 load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
 
-package(default_visibility = ["//src/tools/ak/shellapk:__subpackages__"])
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//src/tools/ak/shellapk:__subpackages__"],
+)
 
 licenses(["notice"])
 
diff --git a/test/rules/android_revision/BUILD b/test/rules/android_revision/BUILD
new file mode 100644
index 0000000..bf1b5de
--- /dev/null
+++ b/test/rules/android_revision/BUILD
@@ -0,0 +1,84 @@
+# Description:
+#   Tests for the AndroidRevisionInfo provider.
+
+load(":test.bzl", "android_revision_comparision_test", "android_revision_test")
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+licenses(["notice"])
+
+# Test revision string parsing.
+# buildifier: leave-alone
+android_revision_test(
+    name = "version_1.2.3",
+    input = "1.2.3",
+    expected_major = 1,
+    expected_minor = 2,
+    expected_micro = 3,
+    expected_version = "1.2.3",
+    expected_dir = "1.2.3",
+)
+
+# buildifier: leave-alone
+android_revision_test(
+    name = "micro_missing",
+    input = "1.2",
+    expected_major = 1,
+    expected_minor = 2,
+    expected_micro = 0,
+    expected_version = "1.2",
+    expected_dir = "1.2",
+)
+
+# buildifier: leave-alone
+android_revision_test(
+    name = "minor_missing",
+    input = "1",
+    expected_major = 1,
+    expected_minor = 0,
+    expected_micro = 0,
+    expected_version = "1",
+    expected_dir = "1",
+)
+
+# Test revision comparisions.
+VERSIONS = [
+    ("2.0.0", "1.0.0"),
+    ("12.0.0", "11.0.0"),
+    ("1.1.0", "1.0.0"),
+    ("1.0.1", "1.0.0"),
+    ("1.1.1", "1.0.1"),
+    ("2", "1"),
+    ("2.1", "2"),
+    ("2", "1.0"),
+    # TODO(katre): Re-add when previews are supported.
+    #("1.1.0-rc1", "1.0.0-rc1"),
+    #("1.1.0-alpha1", "1.0.0-rc1"),
+    #("1.0.0", "1.0.0-rc1"),
+    #("1.0.0", "1.0.0-rc2"),
+    #("1.0.0", "1.0.0-alpha1"),
+    #("1.0.0", "1.0.0-alpha2"),
+    #("1.0.0", "1.0.0-beta1"),
+    #("1.0.0", "1.0.0-beta2"),
+    #("1.0.0-rc1", "1.0.0-beta1"),
+    #("1.0.0-beta1", "1.0.0-alpha1"),
+    #("1.0.0-beta1", "1.0.0-alpha2"),
+    #("1.0.0-rc2", "1.0.0-rc1"),
+    #("1.0.0-beta2", "1.0.0-beta1"),
+    #("1.0.0-alpha2", "1.0.0-alpha1"),
+    #("1 rc1", "1 beta1"),
+]
+
+[
+    android_revision_comparision_test(
+        name = "compare_%s_%s" % (higher, lower),
+        higher = higher,
+        lower = lower,
+    )
+    for (higher, lower) in VERSIONS
+]
+
+bzl_library(
+    name = "bzl",
+    srcs = glob(["*.bzl"]),
+    visibility = ["//visibility:private"],
+)
diff --git a/test/rules/android_revision/test.bzl b/test/rules/android_revision/test.bzl
new file mode 100644
index 0000000..714d86c
--- /dev/null
+++ b/test/rules/android_revision/test.bzl
@@ -0,0 +1,103 @@
+# 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.
+
+"""Bazel rules that test the Android revision parsing.
+
+The following are test rules that can be used to test the AndroidRevisionInfo provider.
+
+android_revision_test: Inspect providers with the given set of expected values.
+"""
+
+load(
+    "//rules:android_revision.bzl",
+    "compare_android_revisions",
+    "parse_android_revision",
+)
+load(
+    "//test/utils:lib.bzl",
+    "asserts",
+    "unittest",
+)
+
+def _android_revision_test_impl(ctx):
+    env = unittest.begin(ctx)
+    input = ctx.attr.input
+    revision = parse_android_revision(input)
+
+    asserts.equals(
+        env,
+        ctx.attr.expected_major,
+        revision.major,
+    )
+    asserts.equals(
+        env,
+        ctx.attr.expected_minor,
+        revision.minor,
+    )
+    asserts.equals(
+        env,
+        ctx.attr.expected_micro,
+        revision.micro,
+    )
+    asserts.equals(
+        env,
+        ctx.attr.expected_version,
+        revision.version,
+    )
+    asserts.equals(
+        env,
+        ctx.attr.expected_dir,
+        revision.dir,
+    )
+
+    return unittest.end(env)
+
+android_revision_test = unittest.make(
+    impl = _android_revision_test_impl,
+    attrs = {
+        "input": attr.string(),
+        "expected_major": attr.int(),
+        "expected_minor": attr.int(),
+        "expected_micro": attr.int(),
+        "expected_version": attr.string(),
+        "expected_dir": attr.string(),
+    },
+)
+
+def _assert_revisions_equal(env, expected, value):
+    asserts.equals(env, expected.major, value.major)
+    asserts.equals(env, expected.minor, value.minor)
+    asserts.equals(env, expected.major, value.major)
+
+def _android_revision_comparision_test_impl(ctx):
+    env = unittest.begin(ctx)
+    higher = parse_android_revision(ctx.attr.higher)
+    lower = parse_android_revision(ctx.attr.lower)
+
+    result = compare_android_revisions(higher, lower)
+    _assert_revisions_equal(
+        env,
+        higher,
+        result,
+    )
+
+    return unittest.end(env)
+
+android_revision_comparision_test = unittest.make(
+    impl = _android_revision_comparision_test_impl,
+    attrs = {
+        "higher": attr.string(),
+        "lower": attr.string(),
+    },
+)
diff --git a/test/utils/BUILD b/test/utils/BUILD
new file mode 100644
index 0000000..81cb750
--- /dev/null
+++ b/test/utils/BUILD
@@ -0,0 +1,27 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"])
+
+exports_files(
+    ["lib.bzl"],
+    visibility = [
+        "//test:__subpackages__",
+    ],
+)
+
+filegroup(
+    name = "testing",
+    srcs = glob(["*"]),
+    visibility = ["//test:__pkg__"],
+)
+
+bzl_library(
+    name = "bzl",
+    srcs = glob(["*.bzl"]),
+    visibility = ["//tools/build_defs/android:__subpackages__"],
+)
diff --git a/test/utils/asserts.bzl b/test/utils/asserts.bzl
new file mode 100644
index 0000000..a1e5a1d
--- /dev/null
+++ b/test/utils/asserts.bzl
@@ -0,0 +1,610 @@
+# Copyright 2020 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.
+
+"""Bazel testing library asserts."""
+
+load(
+    "//rules:providers.bzl",
+    "ResourcesNodeInfo",
+    "StarlarkAndroidResourcesInfo",
+)
+
+_ATTRS = dict(
+    expected_default_info = attr.string_list_dict(),
+    expected_java_info = attr.string_list_dict(),
+    expected_proguard_spec_provider = attr.string_list_dict(),
+    expected_starlark_android_resources_info = attr.label(),
+    expected_output_group_info = attr.string_list_dict(),
+    expected_native_libs_info = attr.label(),
+)
+
+def _expected_resources_node_info_impl(ctx):
+    return [
+        ResourcesNodeInfo(
+            label = ctx.attr.label.label,
+            assets = ctx.files.assets,
+            assets_dir = ctx.attr.assets_dir,
+            assets_symbols = ctx.attr.assets_symbols if ctx.attr.assets_symbols else None,
+            compiled_assets = ctx.attr.compiled_assets if ctx.attr.compiled_assets else None,
+            compiled_resources = ctx.attr.compiled_resources if ctx.attr.compiled_resources else None,
+            r_txt = ctx.attr.r_txt if ctx.attr.r_txt else None,
+            manifest = ctx.attr.manifest if ctx.attr.manifest else None,
+            exports_manifest = ctx.attr.exports_manifest,
+        ),
+    ]
+
+_expected_resources_node_info = rule(
+    implementation = _expected_resources_node_info_impl,
+    attrs = dict(
+        label = attr.label(),
+        assets = attr.label_list(allow_files = True),
+        assets_dir = attr.string(),
+        assets_symbols = attr.string(),
+        compiled_assets = attr.string(),
+        compiled_resources = attr.string(),
+        r_txt = attr.string(),
+        manifest = attr.string(),
+        exports_manifest = attr.bool(default = False),
+    ),
+)
+
+def ExpectedResourcesNodeInfo(
+        label,
+        assets = [],
+        assets_dir = "",
+        assets_symbols = None,
+        compiled_assets = None,
+        compiled_resources = None,
+        r_txt = None,
+        manifest = None,
+        exports_manifest = False,
+        name = "unused"):  # appease linter
+    name = label + str(assets) + assets_dir + str(assets_symbols) + str(compiled_resources) + str(exports_manifest)
+    name = ":" + "".join([c for c in name.elems() if c != ":"])
+
+    _expected_resources_node_info(
+        name = name[1:],
+        label = label,
+        assets = assets,
+        assets_dir = assets_dir,
+        assets_symbols = assets_symbols,
+        compiled_assets = compiled_assets,
+        compiled_resources = compiled_resources,
+        r_txt = r_txt,
+        manifest = manifest,
+        exports_manifest = exports_manifest,
+    )
+    return name
+
+def _expected_starlark_android_resources_info_impl(ctx):
+    return [
+        StarlarkAndroidResourcesInfo(
+            direct_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.direct_resources_nodes],
+            transitive_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.transitive_resources_nodes],
+            transitive_assets = ctx.attr.transitive_assets,
+            transitive_assets_symbols = ctx.attr.transitive_assets_symbols,
+            transitive_compiled_resources = ctx.attr.transitive_compiled_resources,
+            packages_to_r_txts = ctx.attr.packages_to_r_txts,
+        ),
+    ]
+
+_expected_starlark_android_resources_info = rule(
+    implementation = _expected_starlark_android_resources_info_impl,
+    attrs = dict(
+        direct_resources_nodes = attr.label_list(
+            providers = [ResourcesNodeInfo],
+        ),
+        transitive_resources_nodes = attr.label_list(
+            providers = [ResourcesNodeInfo],
+        ),
+        transitive_assets = attr.string_list(),
+        transitive_assets_symbols = attr.string_list(),
+        transitive_compiled_resources = attr.string_list(),
+        packages_to_r_txts = attr.string_list_dict(),
+    ),
+)
+
+def ExpectedStarlarkAndroidResourcesInfo(
+        direct_resources_nodes = None,
+        transitive_resources_nodes = [],
+        transitive_assets = [],
+        transitive_assets_symbols = [],
+        transitive_compiled_resources = [],
+        packages_to_r_txts = {},
+        name = "unused"):  # appease linter
+    name = (str(direct_resources_nodes) + str(transitive_resources_nodes) + str(transitive_assets) +
+            str(transitive_assets_symbols) + str(transitive_compiled_resources))
+    name = ":" + "".join([c for c in name.elems() if c not in [":", "\\"]])
+    _expected_starlark_android_resources_info(
+        name = name[1:],
+        direct_resources_nodes = direct_resources_nodes,
+        transitive_resources_nodes = transitive_resources_nodes,
+        transitive_assets = transitive_assets,
+        transitive_assets_symbols = transitive_assets_symbols,
+        transitive_compiled_resources = transitive_compiled_resources,
+        packages_to_r_txts = packages_to_r_txts,
+    )
+    return name
+
+def _build_expected_resources_node_info(string):
+    parts = string.split(":")
+    if len(parts) != 5:
+        fail("Error: malformed resources_node_info string: %s" % string)
+    return dict(
+        label = parts[0],
+        assets = parts[1].split(",") if parts[1] else [],
+        assets_dir = parts[2],
+        assets_symbols = parts[3],
+        compiled_resources = parts[4],
+    )
+
+def _expected_android_binary_native_libs_info_impl(ctx):
+    return _ExpectedAndroidBinaryNativeInfo(
+        transitive_native_libs = ctx.attr.transitive_native_libs,
+        native_libs_name = ctx.attr.native_libs_name,
+        native_libs = ctx.attr.native_libs,
+    )
+
+_expected_android_binary_native_libs_info = rule(
+    implementation = _expected_android_binary_native_libs_info_impl,
+    attrs = {
+        "transitive_native_libs": attr.string_list(),
+        "native_libs_name": attr.string(),
+        "native_libs": attr.string_list_dict(),
+    },
+)
+
+def ExpectedAndroidBinaryNativeLibsInfo(**kwargs):
+    name = "".join([str(kwargs[param]) for param in kwargs])
+    name = ":" + "".join([c for c in name.elems() if c not in [" ", "[", "]", ":", "\\", "{", "\""]])
+    _expected_android_binary_native_libs_info(name = name[1:], **kwargs)
+    return name
+
+_ExpectedAndroidBinaryNativeInfo = provider(
+    "Test provider to compare native deps info",
+    fields = ["native_libs", "native_libs_name", "transitive_native_libs"],
+)
+
+def _assert_native_libs_info(expected, actual):
+    expected = expected[_ExpectedAndroidBinaryNativeInfo]
+    if expected.native_libs_name:
+        _assert_file(
+            expected.native_libs_name,
+            actual.native_libs_name,
+            "AndroidBinaryNativeInfo.native_libs_name",
+        )
+    for config in expected.native_libs:
+        if config not in actual.native_libs:
+            fail("Error for AndroidBinaryNativeInfo.native_libs: expected key %s was not found" % config)
+        _assert_files(
+            expected.native_libs[config],
+            actual.native_libs[config].to_list(),
+            "AndroidBinaryNativeInfo.native_libs." + config,
+        )
+    _assert_files(
+        expected.transitive_native_libs,
+        actual.transitive_native_libs.to_list(),
+        "AndroidBinaryNativeInfo.transitive_native_libs",
+    )
+
+def _assert_files(expected_file_names, actual_files, error_msg_field_name):
+    """Asserts that expected file names and actual list of files is equal.
+
+    Args:
+      expected_file_names: The expected names of file basenames (no path),
+      actual_files: The actual list of files produced.
+      error_msg_field_name: The field the actual list of files is from.
+    """
+    actual_file_names = [f.basename for f in actual_files]
+    if sorted(actual_file_names) == sorted(expected_file_names):
+        return
+    fail("""Error for %s, expected and actual file names are not the same:
+expected file names: %s
+actual files: %s
+""" % (error_msg_field_name, expected_file_names, actual_files))
+
+def _assert_file_objects(expected_files, actual_files, error_msg_field_name):
+    if sorted([f.basename for f in expected_files]) == sorted([f.basename for f in actual_files]):
+        return
+    fail("""Error for %s, expected and actual file names are not the same:
+expected file names: %s
+actual files: %s
+""" % (error_msg_field_name, expected_files, actual_files))
+
+def _assert_file_depset(expected_file_paths, actual_depset, error_msg_field_name, ignore_label_prefix = ""):
+    """Asserts that expected file short_paths and actual depset of files is equal.
+
+    Args:
+      expected_file_paths: The expected file short_paths in depset order.
+      actual_depset: The actual depset produced.
+      error_msg_field_name: The field the actual depset is from.
+      ignore_label_prefix: Path prefix to ignore on actual file short_paths.
+    """
+    actual_paths = []  # = [f.short_path for f in actual_depset.to_list()]
+    for f in actual_depset.to_list():
+        path = f.short_path
+        if path.startswith(ignore_label_prefix):
+            path = path[len(ignore_label_prefix):]
+        actual_paths.append(path)
+
+    if len(expected_file_paths) != len(actual_paths):
+        fail("""Error for %s, expected %d items, got %d items
+expected: %s
+actual: %s""" % (
+            error_msg_field_name,
+            len(expected_file_paths),
+            len(actual_paths),
+            expected_file_paths,
+            actual_paths,
+        ))
+    for i in range(len(expected_file_paths)):
+        if expected_file_paths[i] != actual_paths[i]:
+            fail("""Error for %s, actual file depset ordering does not match expected ordering:
+expected ordering: %s
+actual ordering: %s
+""" % (error_msg_field_name, expected_file_paths, actual_paths))
+
+def _assert_empty(contents, error_msg_field_name):
+    """Asserts that the given is empty."""
+    if len(contents) == 0:
+        return
+    fail("Error %s is not empty: %s" % (error_msg_field_name, contents))
+
+def _assert_none(content, error_msg_field_name):
+    """Asserts that the given is None."""
+    if content == None:
+        return
+    fail("Error %s is not None: %s" % (error_msg_field_name, content))
+
+def _assert_java_info(expected, actual):
+    """Asserts that expected matches actual JavaInfo.
+
+    Args:
+      expected: A dict containing fields of a JavaInfo that are compared against
+        the actual given JavaInfo.
+      actual: A JavaInfo.
+    """
+    for key in expected.keys():
+        if not hasattr(actual, key):
+            fail("Actual JavaInfo does not have attribute %s:\n%s" % (key, actual))
+        actual_attr = getattr(actual, key)
+        expected_attr = expected[key]
+
+        # files based asserts.
+        if key in [
+            "compile_jars",
+            "runtime_output_jars",
+            "source_jars",
+            "transitive_compile_time_jars",
+            "transitive_runtime_jars",
+            "transitive_source_jars",
+        ]:
+            files = \
+                actual_attr if type(actual_attr) == "list" else actual_attr.to_list()
+            _assert_files(expected_attr, files, "JavaInfo.%s" % key)
+        else:
+            fail("Error validation of JavaInfo.%s not implemented." % key)
+
+def _assert_default_info(
+        expected,
+        actual):
+    """Asserts that the DefaultInfo contains the expected values."""
+    if not expected:
+        return
+
+    # DefaultInfo.data_runfiles Assertions
+    _assert_empty(
+        actual.data_runfiles.empty_filenames.to_list(),
+        "DefaultInfo.data_runfiles.empty_filenames",
+    )
+    _assert_files(
+        expected["runfiles"],
+        actual.data_runfiles.files.to_list(),
+        "DefaultInfo.data_runfiles.files",
+    )
+    _assert_empty(
+        actual.data_runfiles.symlinks.to_list(),
+        "DefaultInfo.data_runfiles.symlinks",
+    )
+
+    # DefaultInfo.default_runfile Assertions
+    _assert_empty(
+        actual.default_runfiles.empty_filenames.to_list(),
+        "DefaultInfo.default_runfiles.empty_filenames",
+    )
+    _assert_files(
+        expected["runfiles"],
+        actual.default_runfiles.files.to_list(),
+        "DefaultInfo.default_runfiles.files",
+    )
+    _assert_empty(
+        actual.default_runfiles.symlinks.to_list(),
+        "DefaultInfo.default_runfiles.symlinks",
+    )
+
+    # DefaultInfo.files Assertion
+    _assert_files(
+        expected["files"],
+        actual.files.to_list(),
+        "DefaultInfo.files",
+    )
+
+    # DefaultInfo.files_to_run Assertions
+    _assert_none(
+        actual.files_to_run.executable,
+        "DefaultInfo.files_to_run.executable",
+    )
+    _assert_none(
+        actual.files_to_run.runfiles_manifest,
+        "DefaultInfo.files_to_run.runfiles_manifest",
+    )
+
+def _assert_proguard_spec_provider(expected, actual):
+    """Asserts that expected matches actual ProguardSpecProvider.
+
+    Args:
+      expected: A dict containing fields of a ProguardSpecProvider that are
+        compared against the actual given ProguardSpecProvider.
+      actual: A ProguardSpecProvider.
+    """
+    for key in expected.keys():
+        if not hasattr(actual, key):
+            fail("Actual ProguardSpecProvider does not have attribute %s:\n%s" % (key, actual))
+        actual_attr = getattr(actual, key)
+        expected_attr = expected[key]
+        if key in ["specs"]:
+            _assert_files(
+                expected_attr,
+                actual_attr.to_list(),
+                "ProguardSpecProvider.%s" % key,
+            )
+        else:
+            fail("Error validation of ProguardSpecProvider.%s not implemented." % key)
+
+def _assert_string(expected, actual, error_msg):
+    if type(actual) != "string":
+        fail("Error for %s, actual value not of type string, got %s" % (error_msg, type(actual)))
+    if actual != expected:
+        fail("""Error for %s, expected and actual values are not the same:
+expected value: %s
+actual value: %s
+""" % (error_msg, expected, actual))
+
+def _assert_file(expected, actual, error_msg_field_name):
+    if actual == None and expected == None:
+        return
+
+    if actual == None and expected != None:
+        fail("Error at %s, expected %s but got None" % (error_msg_field_name, expected))
+
+    if type(actual) != "File":
+        fail("Error at %s, expected a File but got %s" % (error_msg_field_name, type(actual)))
+
+    if actual != None and expected == None:
+        fail("Error at %s, expected None but got %s" % (error_msg_field_name, actual.short_path))
+
+    ignore_label_prefix = actual.owner.package + "/"
+    actual_path = actual.short_path
+    if actual_path.startswith(ignore_label_prefix):
+        actual_path = actual_path[len(ignore_label_prefix):]
+    _assert_string(expected, actual_path, error_msg_field_name)
+
+def _assert_resources_node_info(expected, actual):
+    if type(actual.label) != "Label":
+        fail("Error for ResourcesNodeInfo.label, expected type Label, actual type is %s" % type(actual.label))
+    _assert_string(expected.label.name, actual.label.name, "ResourcesNodeInfo.label.name")
+
+    if type(actual.assets) != "depset":
+        fail("Error for ResourcesNodeInfo.assets, expected type depset, actual type is %s" % type(actual.assets))
+
+    # TODO(djwhang): Align _assert_file_objects and _assert_file_depset to work
+    # in a similar manner. For now, we will just call to_list() as this field
+    # was list prior to this change.
+    _assert_file_objects(expected.assets, actual.assets.to_list(), "ResourcesNodeInfo.assets")
+
+    _assert_string(expected.assets_dir, actual.assets_dir, "ResourcesNodeInfo.assets_dir")
+
+    _assert_file(
+        expected.assets_symbols,
+        actual.assets_symbols,
+        "ResourcesNodeInfo.assets_symbols",
+    )
+
+    _assert_file(
+        expected.compiled_assets,
+        actual.compiled_assets,
+        "ResourcesNodeInfo.compiled_assets",
+    )
+
+    _assert_file(
+        expected.compiled_resources,
+        actual.compiled_resources,
+        "ResourcesNodeInfo.compiled_resources",
+    )
+
+    _assert_file(
+        expected.r_txt,
+        actual.r_txt,
+        "ResourcesNodeInfo.r_txt",
+    )
+
+    _assert_file(
+        expected.manifest,
+        actual.manifest,
+        "ResourcesNodeInfo.manifest",
+    )
+
+    if type(actual.exports_manifest) != "bool":
+        fail("Error for ResourcesNodeInfo.exports_manifest, expected type bool, actual type is %s" % type(actual.exports_manifest))
+    if expected.exports_manifest != actual.exports_manifest:
+        fail("""Error for ResourcesNodeInfo.exports_manifest, expected and actual values are not the same:
+expected value: %s
+actual value: %s
+""" % (expected.exports_manifest, actual.exports_manifest))
+
+def _assert_resources_node_info_depset(expected_resources_node_infos, actual_depset, error_msg):
+    actual_resources_node_infos = actual_depset.to_list()
+    if len(expected_resources_node_infos) != len(actual_resources_node_infos):
+        fail(
+            "Error for StarlarkAndroidResourcesInfo.%s, expected size of list to be %d, got %d:\nExpected: %s\nActual: %s" %
+            (
+                error_msg,
+                len(expected_resources_node_infos),
+                len(actual_resources_node_infos),
+                [node.label for node in expected_resources_node_infos],
+                [node.label for node in actual_resources_node_infos],
+            ),
+        )
+    for i in range(len(actual_resources_node_infos)):
+        _assert_resources_node_info(expected_resources_node_infos[i], actual_resources_node_infos[i])
+
+def _assert_starlark_android_resources_info(expected, actual, label_under_test):
+    _assert_resources_node_info_depset(
+        expected.direct_resources_nodes,
+        actual.direct_resources_nodes,
+        "direct_resources_nodes",
+    )
+
+    _assert_resources_node_info_depset(
+        expected.transitive_resources_nodes,
+        actual.transitive_resources_nodes,
+        "transitive_resources_nodes",
+    )
+
+    # Use the package from the target under test to shrink actual paths being compared down to the
+    # name of the target.
+    ignore_label_prefix = label_under_test.package + "/"
+
+    _assert_file_depset(
+        expected.transitive_assets,
+        actual.transitive_assets,
+        "StarlarkAndroidResourcesInfo.transitive_assets",
+        ignore_label_prefix,
+    )
+    _assert_file_depset(
+        expected.transitive_assets_symbols,
+        actual.transitive_assets_symbols,
+        "StarlarkAndroidResourcesInfo.transitive_assets_symbols",
+        ignore_label_prefix,
+    )
+    _assert_file_depset(
+        expected.transitive_compiled_resources,
+        actual.transitive_compiled_resources,
+        "StarlarkAndroidResourcesInfo.transitive_compiled_resources",
+        ignore_label_prefix,
+    )
+    for pkg, value in expected.packages_to_r_txts.items():
+        if pkg in actual.packages_to_r_txts:
+            _assert_file_depset(
+                value,
+                actual.packages_to_r_txts[pkg],
+                "StarlarkAndroidResourcesInfo.packages_to_r_txts[%s]" % pkg,
+                ignore_label_prefix,
+            )
+        else:
+            fail("Error for StarlarkAndroidResourceInfo.packages_to_r_txts, expected key %s was not found" % pkg)
+
+_R_CLASS_ATTRS = dict(
+    _r_class_check = attr.label(
+        default = "//test/utils/java/com/google:RClassChecker_deploy.jar",
+        executable = True,
+        allow_files = True,
+        cfg = "exec",
+    ),
+    expected_r_class_fields = attr.string_list(),
+)
+
+def _assert_output_group_info(expected, actual):
+    for key in expected:
+        actual_attr = getattr(actual, key, None)
+        if actual_attr == None:  # both empty depset and list will fail.
+            fail("%s is not defined in OutputGroupInfo: %s" % (key, actual))
+        _assert_files(
+            expected[key],
+            actual_attr.to_list(),
+            "OutputGroupInfo." + key,
+        )
+
+def _is_suffix_sublist(full, suffixes):
+    """Returns whether suffixes is a sublist of suffixes of full."""
+    for (fi, _) in enumerate(full):
+        sublist_match = True
+        for (si, sv) in enumerate(suffixes):
+            if (fi + si >= len(full)) or not full[fi + si].endswith(sv):
+                sublist_match = False
+                break
+        if sublist_match:
+            return True
+    return False
+
+def _check_actions(inspect, actions):
+    for mnemonic, expected_argvs in inspect.items():
+        # Action mnemonic is not unique, even in the context of a target, hence
+        # it is necessary to find all actions and compare argv. If there are no
+        # matches among the actions that match the mnemonic, fail and present
+        # all the possible actions that could have matched.
+        mnemonic_matching_actions = []
+        mnemonic_match = False
+        for _, value in actions.by_file.items():
+            if mnemonic != value.mnemonic:
+                continue
+            mnemonic_match = True
+
+            if _is_suffix_sublist(value.argv, expected_argvs):
+                # When there is a match, clear the actions stored for displaying
+                # an error messaage.
+                mnemonic_matching_actions = []
+                break
+            else:
+                mnemonic_matching_actions.append(value)
+
+        if not mnemonic_match:
+            fail("%s action not found." % mnemonic)
+        if mnemonic_matching_actions:
+            # If there are mnemonic_matching_actions, then the argvs did not
+            # align. Fail but show the other actions that were created.
+            error_message = (
+                "%s with the following argv not found: %s\nSimilar actions:\n" %
+                (mnemonic, expected_argvs)
+            )
+            for i, action in enumerate(mnemonic_matching_actions):
+                error_message += (
+                    "%d. Progress Message: %s\n   Argv:             %s\n\n" %
+                    (i + 1, action, action.argv)
+                )
+            fail(error_message)
+
+_ACTIONS_ATTRS = dict(
+    inspect_actions = attr.string_list_dict(),
+)
+
+asserts = struct(
+    provider = struct(
+        attrs = _ATTRS,
+        default_info = _assert_default_info,
+        java_info = _assert_java_info,
+        proguard_spec_provider = _assert_proguard_spec_provider,
+        starlark_android_resources_info = _assert_starlark_android_resources_info,
+        output_group_info = _assert_output_group_info,
+        native_libs_info = _assert_native_libs_info,
+    ),
+    files = _assert_files,
+    r_class = struct(
+        attrs = _R_CLASS_ATTRS,
+    ),
+    actions = struct(
+        attrs = _ACTIONS_ATTRS,
+        check_actions = _check_actions,
+    ),
+)
diff --git a/test/utils/file.bzl b/test/utils/file.bzl
new file mode 100644
index 0000000..e019542
--- /dev/null
+++ b/test/utils/file.bzl
@@ -0,0 +1,44 @@
+# Copyright 2018 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.
+
+"""Bazel lib that provides on-the-fly data generation helpers for testing."""
+
+def _create(
+        name = None,
+        contents = "",
+        executable = False):
+    target_name = "gen_" + name.replace(".", "_")
+    native.genrule(
+        name = target_name,
+        cmd = """
+cat > $@ <<MAKE_FILE_EOM
+%s
+MAKE_FILE_EOM
+""" % contents,
+        outs = [name],
+        executable = executable,
+    )
+    return name
+
+def _create_mock_file(path, is_directory = False):
+    return struct(
+        path = path,
+        dirname = path.rpartition("/")[0],
+        is_directory = is_directory,
+    )
+
+file = struct(
+    create = _create,
+    create_mock_file = _create_mock_file,
+)
diff --git a/test/utils/lib.bzl b/test/utils/lib.bzl
new file mode 100644
index 0000000..fef1281
--- /dev/null
+++ b/test/utils/lib.bzl
@@ -0,0 +1,51 @@
+# Copyright 2018 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.
+
+"""Bazel Android testing libs."""
+
+load(
+    ":file.bzl",
+    _file = "file",
+)
+load(
+    ":unittest.bzl",
+    _analysistest = "analysistest",
+    _unittest = "unittest",
+)
+load(
+    "@bazel_skylib//lib:unittest.bzl",
+    _asserts = "asserts",
+)
+
+file = _file
+
+unittest = _unittest
+
+analysistest = _analysistest
+
+asserts = _asserts
+
+def _failure_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    if ctx.attr.expected_error_msg != "":
+        asserts.expect_failure(env, ctx.attr.expected_error_msg)
+    return analysistest.end(env)
+
+failure_test = analysistest.make(
+    _failure_test_impl,
+    expect_failure = True,
+    attrs = dict(
+        expected_error_msg = attr.string(),
+    ),
+)
diff --git a/test/utils/unittest.bzl b/test/utils/unittest.bzl
new file mode 100644
index 0000000..8526902
--- /dev/null
+++ b/test/utils/unittest.bzl
@@ -0,0 +1,108 @@
+# Copyright 2018 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.
+
+"""Bazel lib that provides test helpers for testing."""
+
+load(":file.bzl", _file = "file")
+load(
+    "@bazel_skylib//lib:unittest.bzl",
+    _analysistest = "analysistest",
+    _unittest = "unittest",
+)
+
+TestInfo = provider(
+    doc = "Provides a test a suggested set of attributes.",
+    fields = {
+        "name": "The name of the test.",
+        "prefix": "The prefix used to isolate artifact and target names.",
+    },
+)
+
+def _prefix(prefix, name):
+    """Prepends the given prefix to the given name."""
+    return "%s-%s" % (prefix, name)
+
+def _prefix_from_test_info(test_info, name):
+    """Prepends the prefix of a TestInfo to the given name."""
+    return _prefix(test_info.prefix, name)
+
+def _test_suite(
+        name = None,
+        test_scenarios = None):
+    """Creates a test suite containing the list of test targets.
+
+    Args:
+      name: Name of the test suite, also used as part of a prefix for naming.
+      test_scenarios: A list of methods, that setup and the test. Each scenario
+        method should accept a TestInfo provider.
+    """
+    test_targets = []
+    for scenario_name, make_scenario in test_scenarios.items():
+        test_prefix = _prefix(name, scenario_name)
+        test_info = TestInfo(
+            prefix = test_prefix,
+            name = test_prefix + "_test",
+        )
+        make_scenario(test_info)
+        test_targets.append(test_info.name)
+
+    native.test_suite(
+        name = name,
+        tests = test_targets,
+    )
+
+def _fake_java_library(name):
+    class_name = "".join(
+        [part.title() for part in name.replace("-", "_").split("_")],
+    )
+    native.java_library(
+        name = name,
+        srcs = [_file.create(
+            class_name + ".java",
+            contents = """@SuppressWarnings("DefaultPackage")
+class %s{}""" % class_name,
+        )],
+    )
+
+def _fake_jar(name):
+    if not name.endswith(".jar"):
+        fail("fake_jar method requires name to end with '.jar'")
+    _fake_java_library(name[:-4])
+    return name
+
+def _fake_executable(name):
+    return _file.create(name, contents = "echo %s" % name, executable = True)
+
+def _analysis_test_error(message, *args):
+    return [
+        AnalysisTestResultInfo(
+            success = False,
+            message = message % args,
+        ),
+    ]
+
+analysistest = _analysistest
+
+unittest = struct(
+    # Forward through unittest methods through the current unittest.
+    analysis_test_error = _analysis_test_error,
+    begin = _unittest.begin,
+    end = _unittest.end,
+    fake_executable = _fake_executable,
+    fake_jar = _fake_jar,
+    fake_java_library = _fake_java_library,
+    make = _unittest.make,
+    prefix = _prefix_from_test_info,
+    test_suite = _test_suite,
+)
diff --git a/toolchains/android/toolchain.bzl b/toolchains/android/toolchain.bzl
index d899a17..94c4f85 100644
--- a/toolchains/android/toolchain.bzl
+++ b/toolchains/android/toolchain.bzl
@@ -217,6 +217,26 @@
         default = "//toolchains/android:zip",
         executable = True,
     ),
+    zip_filter = attr.label(
+        cfg = "exec",
+        default = "@bazel_tools//tools/android:zip_filter",
+        executable = True,
+    ),
+    dex_zips_merger = attr.label(
+        cfg = "exec",
+        default = "@bazel_tools//tools/android:merge_dexzips",
+        executable = True,
+    ),
+    java8_legacy_dex = attr.label(
+        allow_single_file = True,
+        cfg = "exec",
+        default = "@bazel_tools//tools/android:java8_legacy_dex",
+    ),
+    build_java8_legacy_dex = attr.label(
+        cfg = "exec",
+        default = "@bazel_tools//tools/android:build_java8_legacy_dex",
+        executable = True,
+    ),
 )
 
 def _impl(ctx):