Support Starlark rules_android (#1333)

* Support Starlark rules_android

* Revert

* Formatting

* Add missing _lcov_merger attr

* More clean up

* More clean up and fixes

* More cleanup

* More cleanup

* More cleanup

* More cleanup and fixes

* More

* Fixes

* More fixes

* More fixes

* Fixes
diff --git a/examples/android/MODULE.bazel b/examples/android/MODULE.bazel
index b024c23..91f7dc8 100644
--- a/examples/android/MODULE.bazel
+++ b/examples/android/MODULE.bazel
@@ -7,6 +7,11 @@
 bazel_dep(name = "rules_kotlin", version = "1.9.5")
 bazel_dep(name = "rules_jvm_external", version = "6.6")
 
+android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
+use_repo(android_sdk_repository_extension, "androidsdk")
+
+register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")
+
 maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
 maven.install(
     name = "maven_rules_kotlin_example",
diff --git a/examples/android/libKtAndroid/src/test/java/examples/android/lib/BUILD.bazel b/examples/android/libKtAndroid/src/test/java/examples/android/lib/BUILD.bazel
index b4d9843..c26317f 100644
--- a/examples/android/libKtAndroid/src/test/java/examples/android/lib/BUILD.bazel
+++ b/examples/android/libKtAndroid/src/test/java/examples/android/lib/BUILD.bazel
@@ -3,7 +3,7 @@
 kt_android_local_test(
     name = "SomeTest",
     srcs = ["SomeTest.kt"],
-    associates = ["//libKtAndroid:my_kt_kt"],
+    associates = ["//libKtAndroid:my_kt"],
     custom_package = "examples.android.lib",
     jvm_flags = [
         "-Djava.security.manager=allow",
diff --git a/kotlin/android.bzl b/kotlin/android.bzl
index 6095ad6..f9c2443 100644
--- a/kotlin/android.bzl
+++ b/kotlin/android.bzl
@@ -1,6 +1,9 @@
 load(
-    "//kotlin/internal/jvm:android.bzl",
+    "//kotlin/internal/jvm:kt_android_library.bzl",
     _kt_android_library = "kt_android_library",
+)
+load(
+    "//kotlin/internal/jvm:kt_android_local_test.bzl",
     _kt_android_local_test = "kt_android_local_test",
 )
 
diff --git a/kotlin/internal/jvm/android.bzl b/kotlin/internal/jvm/android.bzl
index 53eafe4..80a7d15 100644
--- a/kotlin/internal/jvm/android.bzl
+++ b/kotlin/internal/jvm/android.bzl
@@ -11,123 +11,8 @@
 # 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.
-load(
-    "@rules_android//android:rules.bzl",
-    _android_library = "android_library",
-    _android_local_test = "android_local_test",
-)
-load(
-    "//kotlin/internal/jvm:jvm.bzl",
-    _kt_jvm_library = "kt_jvm_library",
-)
+load(":kt_android_library.bzl", _kt_android_library = "kt_android_library")
+load(":kt_android_local_test_impl.bzl", _kt_android_local_test = "kt_android_local_test")
 
-_ANDROID_SDK_JAR = "%s" % Label("//third_party:android_sdk")
-
-def _kt_android_artifact(
-        name,
-        srcs = [],
-        deps = [],
-        resources = [],
-        plugins = [],
-        associates = [],
-        module_name = "",
-        kotlinc_opts = None,
-        javac_opts = None,
-        enable_data_binding = False,
-        tags = [],
-        exec_properties = None,
-        **kwargs):
-    """Delegates Android related build attributes to the native rules but uses the Kotlin builder to compile Java and
-    Kotlin srcs. Returns a sequence of labels that a wrapping macro should export.
-    """
-    base_name = name + "_base"
-    kt_name = name + "_kt"
-
-    # TODO(bazelbuild/rules_kotlin/issues/273): This should be retrieved from a provider.
-    base_deps = [_ANDROID_SDK_JAR] + deps
-
-    _android_library(
-        name = base_name,
-        visibility = ["//visibility:private"],
-        exports = base_deps,
-        deps = deps if enable_data_binding else [],
-        enable_data_binding = enable_data_binding,
-        tags = tags,
-        exec_properties = exec_properties,
-        **kwargs
-    )
-    _kt_jvm_library(
-        name = kt_name,
-        srcs = srcs,
-        deps = [base_name] + base_deps,
-        resources = resources,
-        plugins = plugins,
-        associates = associates,
-        module_name = module_name,
-        testonly = kwargs.get("testonly", default = False),
-        visibility = ["//visibility:public"],
-        kotlinc_opts = kotlinc_opts,
-        javac_opts = javac_opts,
-        tags = tags,
-        exec_properties = exec_properties,
-    )
-    return [base_name, kt_name]
-
-def kt_android_library(name, exports = [], visibility = None, exec_properties = None, **kwargs):
-    """Creates an Android sandwich library.
-
-    `srcs`, `deps`, `plugins` are routed to `kt_jvm_library` the other android
-    related attributes are handled by the native `android_library` rule.
-    """
-
-    _android_library(
-        name = name,
-        exports = exports + _kt_android_artifact(name, exec_properties = exec_properties, **kwargs),
-        visibility = visibility,
-        tags = kwargs.get("tags", default = None),
-        testonly = kwargs.get("testonly", default = 0),
-        exec_properties = exec_properties,
-    )
-
-def kt_android_local_test(
-        name,
-        jvm_flags = None,
-        manifest = None,
-        manifest_values = None,
-        test_class = None,
-        size = None,
-        data = None,
-        timeout = None,
-        flaky = False,
-        shard_count = None,
-        visibility = None,
-        testonly = True,
-        exec_properties = None,
-        nocompress_extensions = None,
-        **kwargs):
-    """Creates a testable Android sandwich library.
-
-    `srcs`, `deps`, `plugins`, `associates` are routed to `kt_jvm_library` the other android
-    related attributes are handled by the native `android_library` rule while the test attributes
-    are picked out and handled by the `android_local_test` rule.
-    """
-
-    _android_local_test(
-        name = name,
-        deps = kwargs.get("deps", []) + _kt_android_artifact(name = name, testonly = testonly, exec_properties = exec_properties, **kwargs),
-        jvm_flags = jvm_flags,
-        test_class = test_class,
-        visibility = visibility,
-        size = size,
-        data = data,
-        timeout = timeout,
-        flaky = flaky,
-        shard_count = shard_count,
-        custom_package = kwargs.get("custom_package", default = None),
-        manifest = manifest,
-        manifest_values = manifest_values,
-        tags = kwargs.get("tags", default = None),
-        testonly = testonly,
-        exec_properties = exec_properties,
-        nocompress_extensions = nocompress_extensions,
-    )
+kt_android_library = _kt_android_library
+kt_android_local_test = _kt_android_local_test
diff --git a/kotlin/internal/jvm/compile.bzl b/kotlin/internal/jvm/compile.bzl
index b501bbc..2015e3a 100644
--- a/kotlin/internal/jvm/compile.bzl
+++ b/kotlin/internal/jvm/compile.bzl
@@ -272,7 +272,7 @@
         toolchain = _TOOLCHAIN_TYPE,
     )
 
-def _resourcejar_args_action(ctx):
+def _resourcejar_args_action(ctx, extra_resources = {}):
     res_cmd = []
     for f in ctx.files.resources:
         target_path = _adjust_resources_path(f.short_path, ctx.attr.resource_strip_prefix)
@@ -283,20 +283,31 @@
             f_path = f.path,
         )
         res_cmd.extend([line])
+
+    for key, value in extra_resources.items():
+        target_path = _adjust_resources_path(value.short_path, ctx.label.package)
+        if target_path[0] == "/":
+            target_path = target_path[1:]
+        line = "{target_path}={res_path}\n".format(
+            res_path = value.path,
+            target_path = key,
+        )
+        res_cmd.extend([line])
+
     zipper_args_file = ctx.actions.declare_file("%s_resources_zipper_args" % ctx.label.name)
     ctx.actions.write(zipper_args_file, "".join(res_cmd))
     return zipper_args_file
 
-def _build_resourcejar_action(ctx):
+def _build_resourcejar_action(ctx, extra_resources = {}):
     """sets up an action to build a resource jar for the target being compiled.
     Returns:
         The file resource jar file.
     """
     resources_jar_output = ctx.actions.declare_file(ctx.label.name + "-resources.jar")
-    zipper_args = _resourcejar_args_action(ctx)
+    zipper_args = _resourcejar_args_action(ctx, extra_resources)
     ctx.actions.run_shell(
         mnemonic = "KotlinZipResourceJar",
-        inputs = ctx.files.resources + [zipper_args],
+        inputs = ctx.files.resources + extra_resources.values() + [zipper_args],
         tools = [ctx.executable._zipper],
         outputs = [resources_jar_output],
         command = "{zipper} c {resources_jar_output} @{path}".format(
@@ -570,27 +581,57 @@
 
 # MAIN ACTIONS #########################################################################################################
 
-def kt_jvm_produce_jar_actions(ctx, rule_kind):
+def _kt_jvm_produce_jar_actions(ctx, rule_kind, extra_resources = {}):
+    """Setup The actions to compile a jar and if any resources or resource_jars were provided to merge these in with the
+    compilation output.
+
+    Returns:
+        see `kt_jvm_compile_action`.
+    """
+    deps = getattr(ctx.attr, "deps", [])
+    associates = getattr(ctx.attr, "associates", [])
+    _fail_if_invalid_associate_deps(associates, deps)
+    compile_deps = _jvm_deps_utils.jvm_deps(
+        ctx,
+        toolchains = _compiler_toolchains(ctx),
+        associate_deps = associates,
+        deps = deps,
+        exports = getattr(ctx.attr, "exports", []),
+        runtime_deps = getattr(ctx.attr, "runtime_deps", []),
+    )
+
+    outputs = struct(
+        jar = ctx.outputs.jar,
+        srcjar = ctx.outputs.srcjar,
+    )
+
+    # Setup the compile action.
+    return _kt_jvm_produce_output_jar_actions(
+        ctx,
+        rule_kind = rule_kind,
+        compile_deps = compile_deps,
+        outputs = outputs,
+        extra_resources = extra_resources,
+    )
+
+def _kt_jvm_produce_output_jar_actions(
+        ctx,
+        rule_kind,
+        compile_deps,
+        outputs,
+        extra_resources = {}):
     """This macro sets up a compile action for a Kotlin jar.
 
     Args:
         ctx: Invoking rule ctx, used for attr, actions, and label.
         rule_kind: The rule kind --e.g., `kt_jvm_library`.
+        compile_deps: The rule kind --e.g., `kt_jvm_library`.
     Returns:
         A struct containing the providers JavaInfo (`java`) and `kt` (KtJvmInfo). This struct is not intended to be
         used as a legacy provider -- rather the caller should transform the result.
     """
     toolchains = _compiler_toolchains(ctx)
     srcs = _partitioned_srcs(ctx.files.srcs)
-    _fail_if_invalid_associate_deps(ctx.attr.associates, ctx.attr.deps)
-    compile_deps = _jvm_deps_utils.jvm_deps(
-        ctx,
-        toolchains = toolchains,
-        associate_deps = ctx.attr.associates,
-        deps = ctx.attr.deps,
-        exports = getattr(ctx.attr, "exports", []),
-        runtime_deps = getattr(ctx.attr, "runtime_deps", []),
-    )
 
     annotation_processors = _plugin_mappers.targets_to_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
     ksp_annotation_processors = _plugin_mappers.targets_to_ksp_annotation_processors(ctx.attr.plugins + ctx.attr.deps)
@@ -627,12 +668,12 @@
     annotation_processing = outputs_struct.annotation_processing
 
     # If this rule has any resources declared setup a zipper action to turn them into a jar.
-    if len(ctx.files.resources) > 0:
-        output_jars.append(_build_resourcejar_action(ctx))
+    if len(ctx.files.resources) + len(extra_resources) > 0:
+        output_jars.append(_build_resourcejar_action(ctx, extra_resources))
     output_jars.extend(ctx.files.resource_jars)
 
     # Merge outputs into final runtime jar.
-    output_jar = ctx.actions.declare_file(ctx.label.name + ".jar")
+    output_jar = outputs.jar
     _fold_jars_action(
         ctx,
         rule_kind = rule_kind,
@@ -644,7 +685,7 @@
 
     source_jar = java_common.pack_sources(
         ctx.actions,
-        output_source_jar = ctx.outputs.srcjar,
+        output_source_jar = outputs.srcjar,
         sources = srcs.kt + srcs.java,
         source_jars = srcs.src_jars + generated_src_jars,
         java_toolchain = toolchains.java,
@@ -928,7 +969,7 @@
         )
     return None
 
-def export_only_providers(ctx, actions, attr, outputs):
+def _export_only_providers(ctx, actions, attr, outputs):
     """_export_only_providers creates a series of forwarding providers without compilation overhead.
 
     Args:
@@ -936,6 +977,7 @@
         actions: invoking rule actions,
         attr: kt_compiler_attributes,
         outputs: kt_compiler_outputs
+
     Returns:
         kt_compiler_result
     """
@@ -987,3 +1029,11 @@
             extensions = ["kt", "java"],
         ),
     )
+
+compile = struct(
+    compiler_toolchains = _compiler_toolchains,
+    verify_associates_not_duplicated_in_deps = _fail_if_invalid_associate_deps,
+    export_only_providers = _export_only_providers,
+    kt_jvm_produce_output_jar_actions = _kt_jvm_produce_output_jar_actions,
+    kt_jvm_produce_jar_actions = _kt_jvm_produce_jar_actions,
+)
diff --git a/kotlin/internal/jvm/impl.bzl b/kotlin/internal/jvm/impl.bzl
index cb3ba18..2136946 100644
--- a/kotlin/internal/jvm/impl.bzl
+++ b/kotlin/internal/jvm/impl.bzl
@@ -24,8 +24,7 @@
 )
 load(
     "//kotlin/internal/jvm:compile.bzl",
-    "export_only_providers",
-    _kt_jvm_produce_jar_actions = "kt_jvm_produce_jar_actions",
+    _compile = "compile",
 )
 load(
     "//kotlin/internal/utils:utils.bzl",
@@ -41,6 +40,7 @@
     files = [ctx.outputs.jar]
     if providers.java.outputs.jdeps:
         files.append(providers.java.outputs.jdeps)
+
     return [
         providers.java,
         providers.kt,
@@ -224,7 +224,7 @@
         )
     return _make_providers(
         ctx,
-        _kt_jvm_produce_jar_actions(ctx, "kt_jvm_library") if ctx.attr.srcs or ctx.attr.resources else export_only_providers(
+        _compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_library") if ctx.attr.srcs or ctx.attr.resources else _compile.export_only_providers(
             ctx = ctx,
             actions = ctx.actions,
             outputs = ctx.outputs,
@@ -234,7 +234,7 @@
     )
 
 def kt_jvm_binary_impl(ctx):
-    providers = _kt_jvm_produce_jar_actions(ctx, "kt_jvm_binary")
+    providers = _compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_binary")
     jvm_flags = []
     if hasattr(ctx.fragments.java, "default_jvm_opts"):
         jvm_flags = ctx.fragments.java.default_jvm_opts
@@ -269,7 +269,7 @@
 ]
 
 def kt_jvm_junit_test_impl(ctx):
-    providers = _kt_jvm_produce_jar_actions(ctx, "kt_jvm_test")
+    providers = _compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_test")
     runtime_jars = depset(ctx.files._bazel_test_runner, transitive = [providers.java.transitive_runtime_jars])
 
     coverage_runfiles = []
diff --git a/kotlin/internal/jvm/jvm.bzl b/kotlin/internal/jvm/jvm.bzl
index d4a2c15..7ffeb7f 100644
--- a/kotlin/internal/jvm/jvm.bzl
+++ b/kotlin/internal/jvm/jvm.bzl
@@ -249,6 +249,7 @@
             providers = [_JavacOptions],
             mandatory = False,
         ),
+        "_use_auto_exec_groups": attr.bool(default = False),
     },
 )
 
@@ -659,3 +660,8 @@
         ),
     },
 )
+
+attrs = struct(
+    lib_common_attr = _lib_common_attr,
+    runnable_common_attr = _runnable_common_attr,
+)
diff --git a/kotlin/internal/jvm/jvm_deps.bzl b/kotlin/internal/jvm/jvm_deps.bzl
index 6e2957a..9c5637f 100644
--- a/kotlin/internal/jvm/jvm_deps.bzl
+++ b/kotlin/internal/jvm/jvm_deps.bzl
@@ -21,9 +21,9 @@
 def _java_info(target):
     return target[JavaInfo] if JavaInfo in target else None
 
-def _jvm_deps(ctx, toolchains, associate_deps, deps, exports = [], runtime_deps = []):
+def _jvm_deps(ctx, toolchains, associate_deps, deps = [], deps_java_infos = [], exports = [], runtime_deps = []):
     """Encapsulates jvm dependency metadata."""
-    dep_infos = [_java_info(d) for d in deps] + [toolchains.kt.jvm_stdlibs]
+    dep_infos = deps_java_infos + [_java_info(d) for d in deps] + [toolchains.kt.jvm_stdlibs]
 
     associates = _associate_utils.get_associates(ctx, toolchains = toolchains, associates = associate_deps)
 
diff --git a/kotlin/internal/jvm/kt_android_library.bzl b/kotlin/internal/jvm/kt_android_library.bzl
new file mode 100644
index 0000000..5f112c2
--- /dev/null
+++ b/kotlin/internal/jvm/kt_android_library.bzl
@@ -0,0 +1,53 @@
+# 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.
+load(
+    "@rules_android//rules/android_library:attrs.bzl",
+    _BASE_ATTRS = "ATTRS",
+)
+load(
+    "@rules_android//rules/android_library:rule.bzl",
+    _make_rule = "make_rule",
+)
+load(
+    "//kotlin/internal:defs.bzl",
+    _KtJvmInfo = "KtJvmInfo",
+    _TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
+)
+load(
+    "//kotlin/internal/jvm:jvm.bzl",
+    _attrs = "attrs",
+)
+load(
+    "//kotlin/internal/jvm:kt_android_library_impl.bzl",
+    _kt_android_library_impl = "kt_android_library_impl",
+)
+load(
+    "//kotlin/internal/utils:utils.bzl",
+    _utils = "utils",
+)
+
+_ATTRS = _utils.add_dicts(_BASE_ATTRS, _attrs.lib_common_attr, {
+    # Pass or override any attributes needed here
+})
+
+kt_android_library = _make_rule(
+    implementation = _kt_android_library_impl,
+    attrs = _ATTRS,
+    additional_toolchains = [
+        _TOOLCHAIN_TYPE,
+    ],
+    additional_providers = [
+        _KtJvmInfo,
+    ],
+)
diff --git a/kotlin/internal/jvm/kt_android_library_impl.bzl b/kotlin/internal/jvm/kt_android_library_impl.bzl
new file mode 100644
index 0000000..3a22b85
--- /dev/null
+++ b/kotlin/internal/jvm/kt_android_library_impl.bzl
@@ -0,0 +1,155 @@
+# 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.
+load(
+    "@rules_android//providers:providers.bzl",
+    _AndroidLibraryResourceClassJarProvider = "AndroidLibraryResourceClassJarProvider",
+)
+load(
+    "@rules_android//rules:java.bzl",
+    _java = "java",
+)
+load(
+    "@rules_android//rules:processing_pipeline.bzl",
+    _ProviderInfo = "ProviderInfo",
+    _processing_pipeline = "processing_pipeline",
+)
+load(
+    "@rules_android//rules:utils.bzl",
+    _get_android_sdk = "get_android_sdk",
+    _utils = "utils",
+)
+load(
+    "@rules_android//rules/android_library:impl.bzl",
+    _BASE_PROCESSORS = "PROCESSORS",
+    _finalize = "finalize",
+)
+load(
+    "@rules_java//java:defs.bzl",
+    "JavaInfo",
+)
+load(
+    "//kotlin/internal/jvm:compile.bzl",
+    _compile = "compile",
+)
+load(
+    "//kotlin/internal/jvm:jvm_deps.bzl",
+    _jvm_deps_utils = "jvm_deps_utils",
+)
+
+def _process_jvm(ctx, resources_ctx, **_unused_sub_ctxs):
+    """Custom JvmProcessor that handles Kotlin compilation
+    """
+    r_java = resources_ctx.r_java
+    outputs = struct(jar = ctx.outputs.lib_jar, srcjar = ctx.outputs.lib_src_jar, deploy_jar = None)
+    providers = _kt_android_produce_jar_actions(ctx, "kt_android_library", outputs, r_java)
+
+    return _ProviderInfo(
+        name = "jvm_ctx",
+        value = struct(
+            java_info = providers.java,
+            kt_info = providers.kt,
+            providers = [
+                providers.kt,
+                providers.java,
+            ],
+        ),
+    )
+
+PROCESSORS = _processing_pipeline.replace(
+    _BASE_PROCESSORS,
+    JvmProcessor = _process_jvm,
+)
+
+_PROCESSING_PIPELINE = _processing_pipeline.make_processing_pipeline(
+    processors = PROCESSORS,
+    finalize = _finalize,
+)
+
+def kt_android_library_impl(ctx):
+    """The rule implementation.
+
+    Args:
+      ctx: The context.
+
+    Returns:
+      A list of providers.
+    """
+    java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.custom_package)
+    return _processing_pipeline.run(ctx, java_package, _PROCESSING_PIPELINE)
+
+def _get_android_resource_class_jars(targets):
+    android_compile_dependencies = []
+
+    # Collect R.class jar files from direct dependencies
+    for d in targets:
+        if _AndroidLibraryResourceClassJarProvider in d:
+            jars = d[_AndroidLibraryResourceClassJarProvider].jars
+            if jars:
+                android_compile_dependencies.extend([
+                    JavaInfo(output_jar = jar, compile_jar = jar, neverlink = True)
+                    for jar in _utils.list_or_depset_to_list(jars)
+                ])
+
+    return android_compile_dependencies
+
+def _kt_android_produce_jar_actions(
+        ctx,
+        rule_kind,
+        outputs,
+        rClass = None,
+        extra_resources = {}):
+    """Setup The actions to compile a jar and if any resources or resource_jars were provided to merge these in with the
+    compilation output.
+    """
+    deps = getattr(ctx.attr, "deps", [])
+    associates = getattr(ctx.attr, "associates", [])
+    exports = getattr(ctx.attr, "exports", [])
+    runtime_deps = getattr(ctx.attr, "runtime_deps", [])
+    _compile.verify_associates_not_duplicated_in_deps(deps = deps, associate_deps = associates)
+
+    # Collect the android compile dependencies
+    android_java_infos = [
+        JavaInfo(
+            output_jar = _get_android_sdk(ctx).android_jar,
+            compile_jar = _get_android_sdk(ctx).android_jar,
+            neverlink = True,
+        ),
+    ]
+    if rClass:
+        android_java_infos.append(rClass)
+    android_java_infos.extend(_get_android_resource_class_jars(deps + associates + runtime_deps))
+
+    compile_deps = _jvm_deps_utils.jvm_deps(
+        ctx,
+        toolchains = _compile.compiler_toolchains(ctx),
+        associate_deps = associates,
+        deps = deps,
+        deps_java_infos = android_java_infos,
+        exports = exports,
+        runtime_deps = runtime_deps,
+    )
+
+    # Setup the compile action.
+    return _compile.kt_jvm_produce_output_jar_actions(
+        ctx,
+        rule_kind = rule_kind,
+        compile_deps = compile_deps,
+        outputs = outputs,
+        extra_resources = extra_resources,
+    ) if ctx.attr.srcs else _compile.compile.export_only_providers(
+        ctx = ctx,
+        actions = ctx.actions,
+        outputs = outputs,
+        attr = ctx.attr,
+    )
diff --git a/kotlin/internal/jvm/kt_android_local_test.bzl b/kotlin/internal/jvm/kt_android_local_test.bzl
new file mode 100644
index 0000000..0bdaf5a
--- /dev/null
+++ b/kotlin/internal/jvm/kt_android_local_test.bzl
@@ -0,0 +1,63 @@
+# 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.
+load(
+    "@rules_android//rules/android_local_test:attrs.bzl",
+    _BASE_ATTRS = "ATTRS",
+)
+load(
+    "@rules_android//rules/android_local_test:rule.bzl",
+    _make_rule = "make_rule",
+)
+load(
+    "//kotlin/internal:defs.bzl",
+    _JAVA_RUNTIME_TOOLCHAIN_TYPE = "JAVA_RUNTIME_TOOLCHAIN_TYPE",
+    _TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
+)
+load(
+    "//kotlin/internal/jvm:jvm.bzl",
+    _attrs = "attrs",
+)
+load(
+    "//kotlin/internal/jvm:kt_android_local_test_impl.bzl",
+    _kt_android_local_test_impl = "kt_android_local_test_impl",
+)
+load(
+    "//kotlin/internal/utils:utils.bzl",
+    _utils = "utils",
+)
+
+_ATTRS = _utils.add_dicts(_BASE_ATTRS, _attrs.runnable_common_attr, {
+    "main_class": attr.string(
+        default = "com.google.testing.junit.runner.BazelTestRunner",
+    ),
+    "jacocorunner": attr.label(
+        default = Label("@bazel_tools//tools/jdk:JacocoCoverage"),
+    ),
+    "_lcov_merger": attr.label(
+        cfg = "exec",
+        default = configuration_field(
+            fragment = "coverage",
+            name = "output_generator",
+        ),
+    ),
+})
+
+kt_android_local_test = _make_rule(
+    implementation = _kt_android_local_test_impl,
+    attrs = _ATTRS,
+    additional_toolchains = [
+        _TOOLCHAIN_TYPE,
+        _JAVA_RUNTIME_TOOLCHAIN_TYPE,
+    ],
+)
diff --git a/kotlin/internal/jvm/kt_android_local_test_impl.bzl b/kotlin/internal/jvm/kt_android_local_test_impl.bzl
new file mode 100644
index 0000000..46d163c
--- /dev/null
+++ b/kotlin/internal/jvm/kt_android_local_test_impl.bzl
@@ -0,0 +1,214 @@
+# 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.
+load(
+    "@bazel_skylib//rules:common_settings.bzl",
+    _BuildSettingInfo = "BuildSettingInfo",
+)
+load(
+    "@rules_android//providers:providers.bzl",
+    _AndroidFilteredJdepsInfo = "AndroidFilteredJdepsInfo",
+)
+load(
+    "@rules_android//rules:attrs.bzl",
+    _attrs = "attrs",
+)
+load(
+    "@rules_android//rules:java.bzl",
+    _java = "java",
+)
+load(
+    "@rules_android//rules:processing_pipeline.bzl",
+    _ProviderInfo = "ProviderInfo",
+    _processing_pipeline = "processing_pipeline",
+)
+load(
+    "@rules_android//rules:resources.bzl",
+    _resources = "resources",
+)
+load(
+    "@rules_android//rules:utils.bzl",
+    _compilation_mode = "compilation_mode",
+    _get_android_sdk = "get_android_sdk",
+    _get_android_toolchain = "get_android_toolchain",
+    _utils = "utils",
+)
+load(
+    "@rules_android//rules/android_local_test:impl.bzl",
+    _BASE_PROCESSORS = "PROCESSORS",
+    _filter_jdeps = "filter_jdeps",
+    _finalize = "finalize",
+)
+load(
+    "@rules_java//java:defs.bzl",
+    "JavaInfo",
+    "java_common",
+)
+load(
+    "//kotlin/internal:defs.bzl",
+    _JAVA_RUNTIME_TOOLCHAIN_TYPE = "JAVA_RUNTIME_TOOLCHAIN_TYPE",
+    _TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
+)
+load(
+    "//kotlin/internal/jvm:compile.bzl",
+    _compile = "compile",
+)
+load(
+    "//kotlin/internal/jvm:jvm_deps.bzl",
+    _jvm_deps_utils = "jvm_deps_utils",
+)
+
+_JACOCOCO_CLASS = "com.google.testing.coverage.JacocoCoverageRunner"
+
+def _process_resources(ctx, java_package, manifest_ctx, **_unused_sub_ctxs):
+    # Note: This needs to be kept in sync with.
+    # The main difference between this and the upstream macro is that both ctx.attr.associates and ctx.attr.deps needs to
+    # be passed to `_resources.package(` in order for ALL of the resource references to get merged into a single R
+    # class file.
+    # https://github.com/bazelbuild/rules_android/blob/e98ee9eb79c9398a9866d073a43ecd5e97aaf896/rules/android_local_test/impl.bzl#L94-L122
+    resources_ctx = _resources.package(
+        ctx,
+        # This entire section is being overridden so that we can pass the associates into the deps section.
+        # Without this tests won't be able to reference resources of the associate targets
+        deps = ctx.attr.associates + ctx.attr.deps,
+        manifest = manifest_ctx.processed_manifest,
+        manifest_values = manifest_ctx.processed_manifest_values,
+        manifest_merge_order = ctx.attr._manifest_merge_order[_BuildSettingInfo].value,
+        resource_files = ctx.files.resource_files,
+        assets = ctx.files.assets,
+        assets_dir = ctx.attr.assets_dir,
+        resource_configs = ctx.attr.resource_configuration_filters,
+        densities = ctx.attr.densities,
+        nocompress_extensions = ctx.attr.nocompress_extensions,
+        compilation_mode = _compilation_mode.get(ctx),
+        java_package = java_package,
+        shrink_resources = _attrs.tristate.no,
+        build_java_with_final_resources = True,
+        aapt = _get_android_toolchain(ctx).aapt2.files_to_run,
+        android_jar = _get_android_sdk(ctx).android_jar,
+        busybox = _get_android_toolchain(ctx).android_resources_busybox.files_to_run,
+        host_javabase = ctx.attr._host_javabase,
+        # TODO(b/140582167): Throwing on resource conflict need to be rolled
+        # out to android_local_test.
+        should_throw_on_conflict = False,
+    )
+
+    return _ProviderInfo(
+        name = "resources_ctx",
+        value = resources_ctx,
+    )
+
+def _process_jvm(ctx, resources_ctx, **_unused_sub_ctxs):
+    """Custom JvmProcessor that handles Kotlin compilation
+    """
+    _compile.verify_associates_not_duplicated_in_deps(deps = getattr(ctx.attr, "deps", []), associate_deps = getattr(ctx.attr, "associates", []))
+
+    outputs = struct(jar = ctx.outputs.jar, srcjar = ctx.actions.declare_file(ctx.label.name + "-src.jar"))
+
+    deps = (
+        [_get_android_toolchain(ctx).testsupport] +
+        getattr(ctx.attr, "associates", []) +
+        getattr(ctx.attr, "deps", [])
+    )
+
+    if ctx.configuration.coverage_enabled:
+        deps.append(ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner)
+        java_start_class = _JACOCOCO_CLASS
+        coverage_start_class = ctx.attr.main_class
+    else:
+        java_start_class = ctx.attr.main_class
+        coverage_start_class = None
+
+    # Setup the compile action.
+    providers = _compile.kt_jvm_produce_output_jar_actions(
+        ctx,
+        rule_kind = "kt_android_local_test",
+        compile_deps = _jvm_deps_utils.jvm_deps(
+            ctx,
+            toolchains = _compile.compiler_toolchains(ctx),
+            deps = deps,
+            deps_java_infos = (
+                ([resources_ctx.r_java] if resources_ctx.r_java else []) +
+                [
+                    JavaInfo(
+                        output_jar = _get_android_sdk(ctx).android_jar,
+                        compile_jar = _get_android_sdk(ctx).android_jar,
+                        # The android_jar must not be compiled into the test, it
+                        # will bloat the Jar with no benefit.
+                        neverlink = True,
+                    ),
+                ]
+            ),
+            associate_deps = getattr(ctx.attr, "associates", []),
+            runtime_deps = getattr(ctx.attr, "runtime_deps", []),
+        ),
+        outputs = outputs,
+    )
+
+    java_info = providers.java
+    if getattr(java_common, "add_constraints", None):
+        java_info = java_common.add_constraints(java_info, constraints = ["android"])
+
+    # Create test run action
+    providers = [providers.kt, java_info]
+    runfiles = []
+
+    # Create a filtered jdeps with no resources jar. See b/129011477 for more context.
+    if java_info.outputs.jdeps != None:
+        filtered_jdeps = ctx.actions.declare_file(ctx.label.name + ".filtered.jdeps")
+        _filter_jdeps(ctx, java_info.outputs.jdeps, filtered_jdeps, _utils.only(resources_ctx.r_java.compile_jars.to_list()))
+        providers.append(_AndroidFilteredJdepsInfo(jdeps = filtered_jdeps))
+        runfiles.append(filtered_jdeps)
+
+    # Append the security manager override
+    jvm_flags = []
+    java_runtime = ctx.toolchains[_JAVA_RUNTIME_TOOLCHAIN_TYPE].java_runtime
+    if java_runtime.version >= 17:
+        jvm_flags.append("-Djava.security.manager=allow")
+
+    return _ProviderInfo(
+        name = "jvm_ctx",
+        value = struct(
+            java_info = java_info,
+            providers = providers,
+            deps = deps,
+            java_start_class = java_start_class,
+            coverage_start_class = coverage_start_class,
+            android_properties_file = ctx.file.robolectric_properties_file.short_path,
+            additional_jvm_flags = jvm_flags,
+        ),
+        runfiles = ctx.runfiles(files = runfiles),
+    )
+
+PROCESSORS = _processing_pipeline.replace(
+    _BASE_PROCESSORS,
+    ResourceProcessor = _process_resources,
+    JvmProcessor = _process_jvm,
+)
+
+_PROCESSING_PIPELINE = _processing_pipeline.make_processing_pipeline(
+    processors = PROCESSORS,
+    finalize = _finalize,
+)
+
+def kt_android_local_test_impl(ctx):
+    """The rule implementation.
+
+    Args:
+      ctx: The context.
+
+    Returns:
+      A list of providers.
+    """
+    java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.custom_package)
+    return _processing_pipeline.run(ctx, java_package, _PROCESSING_PIPELINE)
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt
index e8bb033..305172b 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt
+++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/KotlinBuilder.kt
@@ -101,7 +101,9 @@
       try {
         @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
         when (compileContext.info.platform) {
-          Platform.JVM -> executeJvmTask(compileContext, taskContext.directory, argMap)
+          Platform.JVM,
+          Platform.ANDROID,
+          -> executeJvmTask(compileContext, taskContext.directory, argMap)
           Platform.UNRECOGNIZED -> throw IllegalStateException(
             "unrecognized platform: ${compileContext.info}",
           )
@@ -140,16 +142,11 @@
         addAllDebug(argMap.mandatory(KotlinBuilderFlags.DEBUG))
 
         label = argMap.mandatorySingle(KotlinBuilderFlags.TARGET_LABEL)
-        argMap.mandatorySingle(KotlinBuilderFlags.RULE_KIND).split("_").also {
-          check(it.size == 3 && it[0] == "kt") { "invalid rule kind $it" }
-          platform =
-            checkNotNull(Platform.valueOf(it[1].uppercase())) {
-              "unrecognized platform ${it[1]}"
-            }
-          ruleKind =
-            checkNotNull(RuleKind.valueOf(it[2].uppercase())) {
-              "unrecognized rule kind ${it[2]}"
-            }
+        argMap.mandatorySingle(KotlinBuilderFlags.RULE_KIND).also {
+          val splitRuleKind = it.split("_")
+          require(splitRuleKind[0] == "kt") { "Invalid rule kind $it" }
+          platform = Platform.valueOf(splitRuleKind[1].uppercase())
+          ruleKind = RuleKind.valueOf(splitRuleKind.last().uppercase())
         }
         moduleName =
           argMap.mandatorySingle(KotlinBuilderFlags.MODULE_NAME).also {
diff --git a/src/main/protobuf/kotlin_model.proto b/src/main/protobuf/kotlin_model.proto
index 60399bf..183c573 100644
--- a/src/main/protobuf/kotlin_model.proto
+++ b/src/main/protobuf/kotlin_model.proto
@@ -54,6 +54,7 @@
 
 enum Platform {
     JVM = 0;
+    ANDROID = 1;
 }
 
 // Common info about a Kotlin compilation task, this message is shared by all compilation tasks.
diff --git a/third_party/BUILD b/third_party/BUILD
index 53b1494..6517349 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -1,5 +1,5 @@
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-load("@rules_java//java:defs.bzl", "java_binary", "java_import", "java_library", "java_plugin")
+load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin")
 load("//src/main/starlark/release:packager.bzl", "release_archive")
 
 # Copyright 2018 The Bazel Authors. All rights reserved.
@@ -74,14 +74,6 @@
     runtime_deps = ["@kotlin_rules_maven//:org_pantsbuild_jarjar"],
 )
 
-# TODO(bazelbuild/rules_kotlin/issues/273): Remove android_sdk import.
-java_import(
-    name = "android_sdk",
-    jars = ["@rules_android//tools/android:android_jar"],
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-)
-
 bzl_library(
     name = "bzl",
     srcs = [
diff --git a/third_party/BUILD.release.bazel b/third_party/BUILD.release.bazel
index fb1536f..ba19780 100644
--- a/third_party/BUILD.release.bazel
+++ b/third_party/BUILD.release.bazel
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-load("@rules_java//java:defs.bzl", "java_binary", "java_import")
+load("@rules_java//java:defs.bzl", "java_binary")
 
 exports_files([
     "empty.jar",
@@ -28,13 +28,6 @@
     runtime_deps = [":jarjar.jar"],
 )
 
-java_import(
-    name = "android_sdk",
-    jars = ["@rules_android//tools/android:android_jar"],
-    neverlink = True,
-    visibility = ["//visibility:public"],
-)
-
 bzl_library(
     name = "bzl",
     srcs = [