Snap for 9550355 from bf16c560ee1e97cde64a35348ee50bf580ccac12 to sdk-release

Change-Id: I200bbf7986e3ca8cbe3c1cd320dd70d9187f1b07
diff --git a/METADATA b/METADATA
index 5d59256..9e9632a 100644
--- a/METADATA
+++ b/METADATA
@@ -11,8 +11,8 @@
     type: GIT
     value: "https://team.googlesource.com/kotlin-rules/rules"
   }
-  version: "aebca278376b63c9c07296c9aa052845e0c1fe2a"
-  last_upgrade_date { year: 2022 month: 9 day: 7 }
+  version: "0904db290987a7e48a708369e5093b6d033769e7"
+  last_upgrade_date { year: 2022 month: 12 day: 14}
   license_type: NOTICE
 }
 
diff --git a/WORKSPACE b/WORKSPACE
index 12281f1..5d01cdd 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -70,7 +70,7 @@
 http_archive(
     name = "kotlinc",
     build_file = "@//bazel:kotlinc.BUILD",
-    sha256 = "7683f5451ef308eb773a686ee7779a76a95ed8b143c69ac247937619d7ca3a09",
+    sha256 = "8412b31b808755f0c0d336dbb8c8443fa239bf32ddb3cdb81b305b25f0ad279e",
     strip_prefix = "kotlinc",
     urls = [
         "https://github.com/JetBrains/kotlin/releases/download/v{0}/kotlin-compiler-{0}.zip".format(KT_VERSION[1:].replace("_", ".")),
@@ -86,6 +86,7 @@
         "com.google.auto.value:auto-value:1.9",
         "com.google.testing.compile:compile-testing:0.19",
         "com.google.truth:truth:1.1.3",
+        "info.picocli:picocli:4.6.3",
         "javax.inject:jsr330-api:0.9",
         "junit:junit:4.13.2",
         "org.jacoco:org.jacoco.agent:0.8.8",
diff --git a/bazel/BUILD b/bazel/BUILD
index e10757f..cc77038 100644
--- a/bazel/BUILD
+++ b/bazel/BUILD
@@ -38,5 +38,8 @@
 java_binary(
     name = "jacoco_cli",
     main_class = "org.jacoco.cli.internal.Main",
-    runtime_deps = ["@maven//:org_jacoco_org_jacoco_cli"],
+    runtime_deps = [
+        "@bazel_tools//tools/jdk:JacocoCoverage",
+        "@maven//:org_jacoco_org_jacoco_cli",
+    ],
 )
diff --git a/bazel/deploy_jar_freshness_golden_test.bzl b/bazel/deploy_jar_freshness_golden_test.bzl
new file mode 100644
index 0000000..9e36132
--- /dev/null
+++ b/bazel/deploy_jar_freshness_golden_test.bzl
@@ -0,0 +1,58 @@
+# Copyright 2022 Google LLC. 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.
+
+"""Test on *_deploy.jar freshness"""
+
+def _deploy_jar_freshness_golden_test_impl(ctx):
+    test_command = """
+      if ! cmp $1 $2 ; then
+          echo "$1 needs to be rebuilt"
+          echo "exit 1" > $3
+          exit 0
+      fi
+
+      # Always passes
+      echo "#!/bin/bash" > $3
+    """
+
+    dummy_test_script = ctx.actions.declare_file(ctx.label.name + ".sh")
+    ctx.actions.run_shell(
+        inputs = [ctx.file.current_jar, ctx.file.newly_built_jar],
+        outputs = [dummy_test_script],
+        arguments = [
+            ctx.file.current_jar.path,
+            ctx.file.newly_built_jar.path,
+            dummy_test_script.path,
+        ],
+        command = test_command,
+    )
+
+    return [DefaultInfo(executable = dummy_test_script)]
+
+deploy_jar_freshness_golden_test = rule(
+    implementation = _deploy_jar_freshness_golden_test_impl,
+    attrs = dict(
+        newly_built_jar = attr.label(
+            doc = "Newly built target deploy.jar",
+            mandatory = True,
+            allow_single_file = [".jar"],
+        ),
+        current_jar = attr.label(
+            doc = "Prebuilt jar to verify",
+            allow_single_file = [".jar"],
+            mandatory = True,
+        ),
+    ),
+    test = True,
+)
diff --git a/bazel/stubs.bzl b/bazel/stubs.bzl
index d948cc8..95f5b87 100644
--- a/bazel/stubs.bzl
+++ b/bazel/stubs.bzl
@@ -32,3 +32,6 @@
 ]
 
 BASE_JVMOPTS = []
+
+def select_java_language_version(**_kwargs):
+    return "11"
diff --git a/kokoro/presubmit.sh b/kokoro/presubmit.sh
index 716cb86..c6302a3 100755
--- a/kokoro/presubmit.sh
+++ b/kokoro/presubmit.sh
@@ -37,18 +37,19 @@
 mkdir -p "$hsperfdata_dir"
 
 cd "${workspace_root}"
-"$bazel" test \
+# Run test coverage for all the test targets except excluded by
+# --instrumentation_filter - code coverage doesn't work for them and they
+# would only be tested
+"$bazel" coverage \
     --sandbox_tmpfs_path="$hsperfdata_dir" \
     --verbose_failures \
     --experimental_google_legacy_api \
+    --instrumentation_filter=-//tests/jvm/java/multijarimport[/:],-//tests/jvm/java/functions[/:] \
     //tests/...
 
-# Testing with code coverage
+# For a specific test //tools:source_jar_zipper_freshness_test run test only
 "$bazel" test \
     --sandbox_tmpfs_path="$hsperfdata_dir" \
     --verbose_failures \
     --experimental_google_legacy_api \
-    --collect_code_coverage=1 \
-    //tests/analysis:kt_jvm_compile_using_kt_jvm_compile_with_r_java_test \
-    //tests/analysis:kt_jvm_compile_with_r_java_as_first_dep_test \
-    //tests/analysis:kt_jvm_compile_without_srcs_and_with_exports_test
\ No newline at end of file
+    //tools:source_jar_zipper_freshness_test
\ No newline at end of file
diff --git a/kotlin/common.bzl b/kotlin/common.bzl
index 3ffc660..3789b0c 100644
--- a/kotlin/common.bzl
+++ b/kotlin/common.bzl
@@ -15,9 +15,9 @@
 """Common Kotlin definitions."""
 
 load("@bazel_skylib//lib:sets.bzl", "sets")
-load("@bazel_skylib//lib:structs.bzl", "structs")
 load("//bazel:stubs.bzl", "BASE_JVMOPTS")
 load("//bazel:stubs.bzl", "DEFAULT_BUILTIN_PROCESSORS")
+load(":file_factory.bzl", "FileFactory")
 
 # TODO: Remove the _ALLOWED_*_RULES lists to determine which rules
 # are accepted dependencies to Kotlin rules as the approach does not scale
@@ -42,6 +42,7 @@
     "java_proto_library",
     "java_wrap_cc",  # b/152799927
     "jvm_import",
+    "_kmp_library",
     "kt_grpc_library_helper",
     "kt_jvm_library_helper",
     "kt_jvm_import",
@@ -106,6 +107,7 @@
 # Runs KAPT in two separate actions so annotation processors only rerun when Kotlin stubs changed.
 def _kapt(
         ctx,
+        file_factory,
         kt_srcs = [],
         common_srcs = [],
         java_srcs = [],
@@ -124,7 +126,7 @@
     # just run turbine if there are no .kt sources.
     stub_srcjars = []
     if kt_srcs or common_srcs:
-        stubs_dir = ctx.actions.declare_directory(ctx.label.name + "/kapt/gen/stubs")
+        stubs_dir = file_factory.declare_directory("/kapt/gen/stubs")
         _kapt_stubs(
             ctx,
             stubs_dir,
@@ -142,15 +144,15 @@
         # mostly to filter out non-.java stub outputs, e.g. .kapt_metadata.
         stub_srcjars.append(_create_jar(
             ctx,
-            toolchain.jar_tool,
-            ctx.actions.declare_file("stubs-srcjar.jar", sibling = stubs_dir),
-            [stubs_dir],
-            file_extensions = ["java"],
+            toolchain,
+            file_factory.declare_file("stubs-srcjar.jar"),
+            kt_inputs = [stubs_dir],
+            ignore_not_allowed_files = True,
         ))
 
-    output_jar = ctx.actions.declare_file(ctx.label.name + "-kapt.jar")
-    output_srcjar = ctx.actions.declare_file(ctx.label.name + "-kapt.srcjar")
-    output_manifest = ctx.actions.declare_file(ctx.label.name + "-kapt.jar_manifest_proto")
+    output_jar = file_factory.declare_file("-kapt.jar")
+    output_srcjar = file_factory.declare_file("-kapt.srcjar")
+    output_manifest = file_factory.declare_file("-kapt.jar_manifest_proto")
     _run_turbine(
         ctx,
         toolchain,
@@ -170,8 +172,19 @@
         jar = output_jar,
         manifest = output_manifest,
         srcjar = output_srcjar,
+        java_info = JavaInfo(
+            output_jar = output_jar,
+            compile_jar = output_jar,
+        ),
     )
 
+_EMPTY_KAPT_OUTPUTS = struct(
+    jar = None,
+    manifest = None,
+    srcjar = None,
+    java_info = None,
+)
+
 def _kapt_stubs(
         ctx,
         stubs_dir,
@@ -303,9 +316,12 @@
             progress_message = progress_message,
         )
     else:
+        java_runtime = toolchain.turbine_java_runtime
+        if not java_runtime:
+            java_runtime = toolchain.java_runtime
         _actions_run_deploy_jar(
             ctx = ctx,
-            java_runtime = toolchain.java_runtime,
+            java_runtime = java_runtime,
             deploy_jar = toolchain.turbine,
             deploy_jsa = toolchain.turbine_jsa,
             inputs = inputs,
@@ -332,7 +348,7 @@
 
     # Run GenClass tool to derive gen_class_jar by filtering hand-written sources.
     # cf. Bazel's JavaCompilationHelper#createGenJarAction
-    result = ctx.actions.declare_file(ctx.label.name + "-gen.jar")
+    result = FileFactory(ctx, javac_jar).declare_file("-gen.jar")
 
     genclass_args = ctx.actions.args()
     genclass_args.add("--manifest_proto", manifest_proto)
@@ -368,9 +384,10 @@
 
 def _run_kotlinc(
         ctx,
-        output,
+        file_factory,
         kt_srcs = [],
         common_srcs = [],
+        coverage_srcs = [],
         java_srcs_and_dirs = [],
         kotlincopts = [],
         compile_jdeps = depset(),
@@ -381,9 +398,6 @@
         friend_jars = depset(),
         enforce_strict_deps = False,
         enforce_complete_jdeps = False):
-    if output.extension != "jar":
-        fail("Expect to output a Jar but got %s" % output)
-
     direct_inputs = []
     transitive_inputs = []
     outputs = []
@@ -403,7 +417,7 @@
 
     kotlinc_args.add(toolchain.jvm_abi_gen_plugin, format = "-Xplugin=%s")
     direct_inputs.append(toolchain.jvm_abi_gen_plugin)
-    kt_ijar = ctx.actions.declare_file(output.basename[:-4] + "-ijar.jar", sibling = output)
+    kt_ijar = file_factory.declare_file("-ijar.jar")
     kotlinc_args.add("-P", kt_ijar, format = "plugin:org.jetbrains.kotlin.jvm.abi:outputDir=%s")
     outputs.append(kt_ijar)
 
@@ -419,6 +433,7 @@
         kotlinc_args.add_all(common_srcs, format_each = "-Xcommon-sources=%s")
         direct_inputs.extend(common_srcs)
 
+    output = file_factory.declare_file(".jar")
     kotlinc_args.add("-d", output)
     outputs.append(output)
     kotlinc_args.add_all(kt_srcs)
@@ -451,29 +466,40 @@
         },
     )
 
-    # TODO: Normalize paths to match package declarations in source files.
     srcjar = _create_jar(
         ctx,
-        toolchain.jar_tool,
-        ctx.actions.declare_file(ctx.label.name + "-kt-src.jar"),
-        kt_srcs + common_srcs,
+        toolchain,
+        file_factory.declare_file("-kt-src.jar"),
+        kt_inputs = kt_srcs,
+        common_inputs = common_srcs,
     )
 
-    return struct(
+    if ctx.coverage_instrumented():
+        output = _offline_instrument_jar(
+            ctx,
+            toolchain,
+            output,
+            kt_srcs + common_srcs + coverage_srcs,
+        )
+
+    result = dict(
         output_jar = output,
         compile_jar = kt_ijar,
         source_jar = srcjar,
     )
+    return struct(java_info = JavaInfo(**result), **result)
 
 def _get_original_kt_target_label(ctx):
     label = ctx.label
     if label.name.find("_DO_NOT_DEPEND") > 0:
         # Remove rule suffix added by kt_android_library
         label = label.relative(":%s" % label.name[0:label.name.find("_DO_NOT_DEPEND")])
-
+    elif hasattr(ctx.attr, "_kt_codegen_plugin_build_tool") and label.name.endswith("_processed_srcs"):
+        # Remove rule suffix added by kt_codegen_filegroup. b/259984258
+        label = label.relative(":{}".format(label.name.removesuffix("_processed_srcs")))
     return label
 
-def _check_deps(
+def _run_import_deps_checker(
         ctx,
         jars_to_check = [],
         merged_deps = None,
@@ -481,33 +507,24 @@
         jdeps_output = None,
         deps_checker = None,
         java_toolchain = None):
-    # Direct compile_jars before transitive not to confuse strict_deps (b/149107867)
-    full_classpath = depset(
-        order = "preorder",
-        transitive = [merged_deps.compile_jars, merged_deps.transitive_compile_time_jars],
-    )
+    full_classpath = _create_classpath(java_toolchain, [merged_deps])
     label = _get_original_kt_target_label(ctx)
-    bootclasspath = java_toolchain.bootclasspath
 
     args = ctx.actions.args()
     args.add("--jdeps_output", jdeps_output)
     args.add_all(jars_to_check, before_each = "--input")
-    args.add_all(bootclasspath, before_each = "--bootclasspath_entry")
+    args.add_all(java_toolchain.bootclasspath, before_each = "--bootclasspath_entry")
     args.add_all(full_classpath, before_each = "--classpath_entry")
     if enforce_strict_deps:
         args.add_all(merged_deps.compile_jars, before_each = "--directdep")
-    args.add("--checking_mode=%s" % ("error" if enforce_strict_deps else "silence"))
+    args.add("error" if enforce_strict_deps else "silence", format = "--checking_mode=%s")
     args.add("--nocheck_missing_members")  # compiler was happy so no need
-    args.add("--rule_label")
-    args.add(label)
+    args.add("--rule_label", label)
 
     ctx.actions.run(
         executable = deps_checker,
         arguments = [args],
-        inputs = depset(
-            jars_to_check,
-            transitive = [bootclasspath, full_classpath],
-        ),
+        inputs = depset(jars_to_check, transitive = [full_classpath]),
         outputs = [jdeps_output],
         mnemonic = "KtCheckStrictDeps" if enforce_strict_deps else "KtJdeps",
         progress_message = "%s deps for %s" % (
@@ -517,27 +534,31 @@
     )
 
 def _offline_instrument_jar(ctx, toolchain, jar, srcs = []):
-    paths_for_coverage_file = ctx.actions.declare_file(ctx.label.name + "-kt-paths-for-coverage.txt")
+    if not jar.basename.endswith(".jar"):
+        fail("Expect JAR input but got %s" % jar)
+    file_factory = FileFactory(ctx, jar)
+
+    paths_for_coverage_file = file_factory.declare_file("-kt-paths-for-coverage.txt")
     paths = ctx.actions.args()
     paths.set_param_file_format("multiline")  # don't shell-quote, just list file names
     paths.add_all([src for src in srcs if src.is_source])
     ctx.actions.write(paths_for_coverage_file, paths)
 
-    result = ctx.actions.declare_file(ctx.label.name + "-instrumented.jar")
+    output = file_factory.declare_file("-instrumented.jar")
     args = ctx.actions.args()
     args.add(jar)
-    args.add(result)
+    args.add(output)
     args.add(paths_for_coverage_file)
     ctx.actions.run(
         executable = toolchain.coverage_instrumenter,
         arguments = [args],
         inputs = [jar, paths_for_coverage_file],
-        outputs = [result],
+        outputs = [output],
         mnemonic = "KtJaCoCoInstrument",
         progress_message = "Instrumenting Kotlin for coverage collection: %s" % _get_original_kt_target_label(ctx),
     )
 
-    return result
+    return output
 
 def _singlejar(
         ctx,
@@ -572,8 +593,8 @@
         progress_message = "Merging %s: %s" % (content, label),
     )
 
-def _merge_jdeps(ctx, kt_jvm_toolchain, jdeps_files, output_suffix = ""):
-    merged_jdeps_file = ctx.actions.declare_file(ctx.label.name + output_suffix + ".jdeps")
+def _merge_jdeps(ctx, kt_jvm_toolchain, jdeps_files, file_factory):
+    merged_jdeps_file = file_factory.declare_file("-merged.jdeps")
 
     args = ctx.actions.args()
     args.add("--kind=jdeps")
@@ -592,40 +613,46 @@
 
     return merged_jdeps_file
 
-def _expand_zip(ctx, dir, input, extra_args = []):
-    ctx.actions.run_shell(
-        outputs = [dir],
+def _expand_zip(ctx, toolchain, dir, input):
+    args = ctx.actions.args()
+    args.add("unzip", input)
+    args.add(dir.path)
+
+    _actions_run_deploy_jar(
+        ctx = ctx,
+        java_runtime = toolchain.java_runtime,
+        deploy_jar = toolchain.source_jar_zipper,
         inputs = [input],
-        command = "unzip -q {input} -d {dir} {args} 2> /dev/null || mkdir -p {dir}".format(
-            input = input.path,
-            dir = dir.path,
-            args = " ".join(extra_args),
-        ),
+        outputs = [dir],
+        args = [args],
+        mnemonic = "SrcJarUnzip",
     )
+
     return dir
 
-def _create_jar(ctx, jar_tool, out_jar, inputs, file_extensions = None):
-    def file_filter(file):
-        return file.path if (
-            file_extensions == None or (file.extension in file_extensions)
-        ) else None
-
+def _create_jar(ctx, toolchain, out_jar, kt_inputs = [], common_inputs = [], ignore_not_allowed_files = False):
     args = ctx.actions.args()
-    args.add("cf", out_jar)
-    args.add_all(inputs, map_each = file_filter, allow_closure = True)
+    args.add("zip")
+    args.add(out_jar)
+    args.add_joined("--kotlin_srcs", kt_inputs, join_with = ",")
+    args.add_joined("--common_srcs", common_inputs, join_with = ",")
+    if ignore_not_allowed_files:
+        args.add("-i")
 
-    ctx.actions.run(
-        executable = jar_tool,
-        inputs = inputs,
+    _actions_run_deploy_jar(
+        ctx = ctx,
+        java_runtime = toolchain.java_runtime,
+        deploy_jar = toolchain.source_jar_zipper,
+        inputs = kt_inputs + common_inputs,
         outputs = [out_jar],
-        arguments = [args],
+        args = [args],
         mnemonic = "KtJar",
         progress_message = "Create Jar (kotlin/common.bzl): %{output}",
     )
 
     return out_jar
 
-def _create_jar_from_tree_artifacts(ctx, jar_tool, output_jar, input_dirs):
+def _create_jar_from_tree_artifacts(ctx, toolchain, output_jar, input_dirs):
     """Packs a sequence of tree artifacts into a single jar.
 
     Given the following file directory structure,
@@ -645,11 +672,12 @@
         "/usr/home/b/y",
     ],
     then the blaze action would fail with an error message.
-         "java.util.zip.ZipException: duplicate entry: 1.txt"
+        "java.lang.IllegalStateException: 1.txt has the same path as 1.txt!
+        If it is intended behavior rename one or both of them."
 
     Args:
         ctx: The build rule context.
-        jar_tool: A Unix-API-compatible jar tool.
+        toolchain: Toolchain containing the jar tool.
         output_jar: The jar to be produced by this action.
         input_dirs: A sequence of tree artifacts to be zipped.
 
@@ -658,39 +686,30 @@
     """
 
     args = ctx.actions.args()
+    args.add("zip_resources")
+    args.add(output_jar)
+    args.add_joined(
+        "--input_dirs",
+        input_dirs,
+        join_with = ",",
+        omit_if_empty = False,
+        expand_directories = False,
+    )
 
-    for in_dir in input_dirs:
-        if not in_dir.is_directory:
-            fail("Expected a directory input, but got {}.".format(in_dir))
-        args.add(in_dir.path)
-
-    ctx.actions.run_shell(
-        command = """
-            JAR_TOOL={}
-            OUT_JAR={}
-            OUT_DIR="$(dirname $OUT_JAR)"
-            RES_DIR=$OUT_DIR/META-INF
-            mkdir $RES_DIR
-            $JAR_TOOL cf $OUT_JAR -C $RES_DIR .
-            rmdir $RES_DIR
-            for INPUT_DIR in $@
-            do
-                if [ -d $INPUT_DIR ]
-                then
-                    $JAR_TOOL uf $OUT_JAR -C $INPUT_DIR .
-                fi
-            done
-        """.format(jar_tool.executable.path, output_jar.path),
-        arguments = [args],
+    _actions_run_deploy_jar(
+        ctx = ctx,
+        java_runtime = toolchain.java_runtime,
+        deploy_jar = toolchain.source_jar_zipper,
         inputs = input_dirs,
         outputs = [output_jar],
-        tools = [jar_tool],
+        args = [args],
         mnemonic = "KtJarActionFromTreeArtifacts",
         progress_message = "Create Jar %{output}",
     )
+
     return output_jar
 
-def _DirSrcjarSyncer(ctx, kt_toolchain, name):
+def _DirSrcjarSyncer(ctx, kt_toolchain, file_factory):
     _dirs = []
     _srcjars = []
 
@@ -702,10 +721,8 @@
         _srcjars.append(
             _create_jar_from_tree_artifacts(
                 ctx,
-                kt_toolchain.jar_tool,
-                ctx.actions.declare_file(
-                    "%s/%s%s.srcjar" % (ctx.label.name, name, len(_srcjars)),
-                ),
+                kt_toolchain,
+                file_factory.declare_file("%s.srcjar" % len(_srcjars)),
                 dirs,
             ),
         )
@@ -718,11 +735,9 @@
             _dirs.append(
                 _expand_zip(
                     ctx,
-                    ctx.actions.declare_directory(
-                        "%s/%s%s.expand" % (ctx.label.name, name, len(_dirs)),
-                    ),
+                    kt_toolchain,
+                    file_factory.declare_directory("%s.expand" % len(_dirs)),
                     srcjar,
-                    extra_args = ["*.java", "*.kt"],
                 ),
             )
         _srcjars.extend(srcjars)
@@ -789,6 +804,7 @@
         output = None,
         output_srcjar = None,  # Will derive default filename if not set.
         deps = [],
+        codegen_output_java_infos = [],  # JavaInfo sequence from kt_codegen_plugin.
         exports = [],  # passthrough for JavaInfo constructor
         runtime_deps = [],  # passthrough for JavaInfo constructor
         native_libraries = [],  # passthrough of CcInfo for JavaInfo constructor
@@ -815,12 +831,13 @@
     if not kt_toolchain:
         fail("Missing or invalid kt_toolchain")
 
-    merged_deps = java_common.merge(deps)
+    file_factory = FileFactory(ctx, output)
+    deps = list(deps)  # Defensive copy
 
     # Split sources, as java requires a separate compile step.
     kt_srcs = [s for s in srcs if _is_kt_src(s)]
     java_srcs = [s for s in srcs if s.path.endswith(_EXT.JAVA)]
-    java_syncer = _DirSrcjarSyncer(ctx, kt_toolchain, "java")
+    java_syncer = _DirSrcjarSyncer(ctx, kt_toolchain, file_factory)
     java_syncer.add_dirs([s for s in srcs if _is_dir(s, "java")])
     java_syncer.add_srcjars([s for s in srcs if s.path.endswith(_EXT.SRCJAR)])
 
@@ -829,22 +846,20 @@
     if sets.length(unexpected_srcs) != 0:
         fail("Unexpected srcs: %s" % sets.to_list(unexpected_srcs))
 
+    # TODO: Remove this special case
+    if kt_srcs and ("flogger" in [p.plugin_id for p in plugins.kt_compiler_plugin_infos]):
+        deps.append(kt_toolchain.flogger_runtime)
+
+    if srcs or common_srcs or rule_family != _RULE_FAMILY.ANDROID_LIBRARY:
+        deps.extend(kt_toolchain.kotlin_libs)
+
     # Skip srcs package check for android_library targets with no kotlin sources: b/239725424
     if rule_family != _RULE_FAMILY.ANDROID_LIBRARY or kt_srcs:
         _check_srcs_package(ctx.label.package, srcs, "srcs")
         _check_srcs_package(ctx.label.package, common_srcs, "common_srcs")
         _check_srcs_package(ctx.label.package, coverage_srcs, "coverage_srcs")
 
-    # Complete classpath including bootclasspath. Like for Javac, explicitly place direct
-    # compile_jars before transitive not to confuse strict_deps (b/149107867).
-    full_classpath = depset(
-        order = "preorder",
-        transitive = [
-            java_toolchain.bootclasspath,
-            merged_deps.compile_jars,
-            merged_deps.transitive_compile_time_jars,
-        ],
-    )
+    full_classpath = _create_classpath(java_toolchain, deps + codegen_output_java_infos)
 
     # Collect all plugin data, including processors to run and all plugin classpaths,
     # whether they have processors or not (b/120995492).
@@ -861,16 +876,17 @@
     out_jars = []
     out_srcjars = []
     out_compilejars = []
-    kapt_outputs = struct(jar = None, manifest = None, srcjar = None)
 
     # Kotlin compilation requires two passes when annotation processing is
     # required. The initial pass processes the annotations and generates
     # additional sources and the following pass compiles the Kotlin code.
     # Skip kapt if no plugins have processors (can happen with only
     # go/errorprone plugins, # b/110540324)
+    kapt_outputs = _EMPTY_KAPT_OUTPUTS
     if kt_srcs and plugin_processors:
         kapt_outputs = _kapt(
             ctx,
+            file_factory = file_factory,
             kt_srcs = kt_srcs,
             common_srcs = common_srcs,
             java_srcs = java_srcs,
@@ -890,19 +906,15 @@
         out_jars.append(kapt_outputs.jar)
         java_syncer.add_srcjars([kapt_outputs.srcjar])
 
-        merged_deps = java_common.merge([merged_deps, JavaInfo(
-            output_jar = kapt_outputs.jar,
-            compile_jar = kapt_outputs.jar,
-        )])
-
     kotlinc_result = None
     if kt_srcs or common_srcs:
         kotlinc_result = _run_kotlinc(
             ctx,
             kt_srcs = kt_srcs,
             common_srcs = common_srcs,
+            coverage_srcs = coverage_srcs,
             java_srcs_and_dirs = java_srcs + java_syncer.dirs,
-            output = ctx.actions.declare_file(ctx.label.name + "-kt.jar"),
+            file_factory = file_factory.derive("-kt"),
             kotlincopts = kotlincopts,
             compile_jdeps = compile_jdeps,
             toolchain = kt_toolchain,
@@ -917,21 +929,7 @@
         # (see b/110763361 for the comparable Gradle issue)
         out_compilejars.append(kotlinc_result.compile_jar)
         out_srcjars.append(kotlinc_result.source_jar)
-
-        # Apply coverage instrumentation if requested, and add dep on JaCoCo runtime to merged_deps.
-        # The latter helps jdeps computation (b/130747644) but could be runtime-only if we computed
-        # compile-time Jdeps based using the compile Jar (which doesn't contain instrumentation).
-        # See b/117897097 for why it's still useful to make the (runtime) dep explicit.
-        if ctx.coverage_instrumented():
-            out_jars.append(_offline_instrument_jar(
-                ctx,
-                kt_toolchain,
-                kotlinc_result.output_jar,
-                kt_srcs + common_srcs + coverage_srcs,
-            ))
-            merged_deps = java_common.merge([merged_deps, kt_toolchain.coverage_runtime])
-        else:
-            out_jars.append(kotlinc_result.output_jar)
+        out_jars.append(kotlinc_result.output_jar)
 
     classpath_resources_dirs, classpath_resources_non_dirs = _partition(
         classpath_resources,
@@ -941,8 +939,8 @@
         out_jars.append(
             _create_jar_from_tree_artifacts(
                 ctx,
-                kt_toolchain.jar_tool,
-                ctx.actions.declare_file(ctx.label.name + "-dir-res.jar"),
+                kt_toolchain,
+                file_factory.declare_file("-dir-res.jar"),
                 classpath_resources_dirs,
             ),
         )
@@ -951,15 +949,31 @@
     java_native_headers_jar = None
     java_gensrcjar = None
     java_genjar = None
+    is_android_library_without_kt_srcs = rule_family == _RULE_FAMILY.ANDROID_LIBRARY and not kt_srcs
     if java_srcs or java_syncer.srcjars or classpath_resources:
-        javac_out = ctx.actions.declare_file(ctx.label.name + "-java.jar")
+        javac_deps = deps + codegen_output_java_infos  # Defensive copy
+        if kapt_outputs.java_info:
+            javac_deps.append(kapt_outputs.java_info)
+        if kotlinc_result:
+            javac_deps.append(kotlinc_result.java_info)
+            if ctx.coverage_instrumented():
+                # Including the coverage runtime improves jdeps computation (b/130747644), but it
+                # could be runtime-only if we computed compile-time jdeps using the compile JAR
+                # (which doesn't contain instrumentation). See b/117897097.
+                javac_deps.append(kt_toolchain.coverage_runtime)
+
+        javac_out = output if is_android_library_without_kt_srcs else file_factory.declare_file("-java.jar")
         javac_java_info = java_common.compile(
             ctx,
             source_files = java_srcs,
             source_jars = java_syncer.srcjars,
             resources = classpath_resources_non_dirs,
+            # For targets that are not android_library with java-only srcs, exports will be passed
+            # to the final constructed JavaInfo.
+            exports = exports if is_android_library_without_kt_srcs else [],
             output = javac_out,
-            deps = ([JavaInfo(**structs.to_dict(kotlinc_result))] if kotlinc_result else []) + [merged_deps],
+            exported_plugins = exported_plugins,
+            deps = javac_deps,
             # Include default_javac_flags, which reflect Blaze's --javacopt flag, so they win over
             # all sources of default flags (for Ellipsis builds, see b/125452475).
             # TODO: remove default_javac_flags here once java_common.compile is fixed.
@@ -973,6 +987,15 @@
             annotation_processor_additional_outputs = annotation_processor_additional_outputs,
             annotation_processor_additional_inputs = annotation_processor_additional_inputs,
         )
+
+        # Directly return the JavaInfo from java.compile() for java-only andorid_library targets
+        # to avoid creating a new JavaInfo. See b/239847857 for additional context.
+        if is_android_library_without_kt_srcs:
+            return struct(
+                java_info = javac_java_info,
+                validations = [],
+            )
+
         out_jars.append(javac_out)
         out_srcjars.extend(javac_java_info.source_jars)
         out_compilejars.extend(javac_java_info.compile_jars.to_list())  # unpack singleton depset
@@ -995,8 +1018,8 @@
     blocking_action_outs = []
 
     if output_srcjar == None:
-        output_srcjar = ctx.actions.declare_file("lib%s-src.jar" % ctx.label.name)
-    compile_jar = ctx.actions.declare_file(ctx.label.name + "-compile.jar")
+        output_srcjar = file_factory.declare_file("-src.jar")
+    compile_jar = file_factory.declare_file("-compile.jar")
     single_jar = java_toolchain.single_jar
     _singlejar(ctx, out_srcjars, output_srcjar, single_jar, mnemonic = "KtMergeSrcjar", content = "srcjar", preserve_compression = True)
 
@@ -1043,39 +1066,41 @@
 
 def _kt_jvm_import(
         ctx,
+        kt_toolchain,
         jars = [],
         srcjar = None,
         deps = [],
         runtime_deps = [],
         neverlink = False,
-        java_toolchain = None,
+                java_toolchain = None,
         deps_checker = None):
     if not java_toolchain:
         fail("Missing or invalid java_toolchain")
-    merged_deps = java_common.merge(deps)
+    if not jars:
+        fail("Must import at least one JAR")
+
+    file_factory = FileFactory(ctx, jars[0])
+    deps = java_common.merge(deps + kt_toolchain.kotlin_libs)
 
     # Check that any needed deps are declared unless neverlink, in which case Jars won't be used
     # at runtime so we skip the check, though we'll populate jdeps either way.
-    jdeps_output = ctx.actions.declare_file(ctx.label.name + ".jdeps")
-    _check_deps(
+    jdeps_output = file_factory.declare_file(".jdeps")
+    _run_import_deps_checker(
         ctx,
         jars_to_check = jars,
-        merged_deps = merged_deps,
+        merged_deps = deps,
         enforce_strict_deps = not neverlink,
         jdeps_output = jdeps_output,
         deps_checker = deps_checker,
         java_toolchain = java_toolchain,
     )
 
-    if not jars:
-        fail("Must provide a Jar to use kt_jvm_import")
-
     java_info = java_common.merge([
         JavaInfo(
             output_jar = jar,
             compile_jar = jar,
             source_jar = srcjar,
-            deps = deps,
+            deps = [deps],
             runtime_deps = runtime_deps,
             neverlink = neverlink,
             # TODO: Set compile-time jdeps to help reduce Javac classpaths downstream
@@ -1140,6 +1165,17 @@
     """Collects the requested provider from the given list of deps."""
     return [dep[provider] for dep in deps if provider in dep]
 
+def _create_classpath(java_toolchain, deps):
+    # To not confuse strictdeps, order as boot > direct > transitive JARs (b/149107867).
+    return depset(
+        order = "preorder",
+        transitive = (
+            [java_toolchain.bootclasspath] +
+            [dep.compile_jars for dep in deps] +
+            [dep.transitive_compile_time_jars for dep in deps]
+        ),
+    )
+
 def _partition(sequence, filter):
     pos, neg = [], []
     for element in sequence:
@@ -1161,6 +1197,7 @@
     collect_proguard_specs = _collect_proguard_specs,
     collect_providers = _collect_providers,
     create_jar_from_tree_artifacts = _create_jar_from_tree_artifacts,
+    common_kapt_and_kotlinc_args = _common_kapt_and_kotlinc_args,
     is_kt_src = _is_kt_src,
     kt_jvm_import = _kt_jvm_import,
     kt_jvm_library = _kt_jvm_library,
diff --git a/kotlin/dex_aspects.bzl b/kotlin/dex_aspects.bzl
new file mode 100644
index 0000000..3d2730b
--- /dev/null
+++ b/kotlin/dex_aspects.bzl
@@ -0,0 +1,36 @@
+# Copyright 2022 Google LLC. 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.
+
+"""kt_dex_aspects"""
+
+# (b/259111128) Magic provider to get DexArchiveAspect to traverse into this rule
+_TraverseMeInfo = platform_common.ToolchainInfo
+
+_EXTRA_DEPS_ATTRS = dict(
+    _build_stamp_deps = attr.label_list(
+        doc = """
+            (b/259111128) Magic attr to get DexArchiveAspect to traverse into this rule
+        """,
+    ),
+)
+
+def _set_extra_deps_attrs(deps):
+    return {"$build_stamp_deps": deps}
+
+kt_dex_aspects = struct(
+    TraverseMeInfo = _TraverseMeInfo,
+    TRAVERSE_ME_INFO = _TraverseMeInfo(),
+    extra_deps_attrs = _EXTRA_DEPS_ATTRS,
+    set_extra_deps_attrs = _set_extra_deps_attrs,
+)
diff --git a/kotlin/file_factory.bzl b/kotlin/file_factory.bzl
new file mode 100644
index 0000000..25c2837
--- /dev/null
+++ b/kotlin/file_factory.bzl
@@ -0,0 +1,48 @@
+# Copyright 2022 Google LLC. 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.
+
+"""FileFactory"""
+
+def FileFactory(ctx, base, suffix = None):
+    """Creates files with names derived from some base file
+
+    Including the name of a rule is not always enough to guarantee unique filenames. For example,
+    helper functions that declare their own output files may be called multiple times in the same
+    rule impl.
+
+    Args:
+        ctx: ctx
+        base: [File] The file to derive other filenames from
+        suffix: [Optional[string]] An additional suffix to differentiate declared files
+
+    Returns:
+        FileFactory
+    """
+
+    base_name = base.basename.rsplit(".", 1)[0] + (suffix or "")
+
+    def declare_directory(suffix):
+        return ctx.actions.declare_directory(base_name + suffix, sibling = base)
+
+    def declare_file(suffix):
+        return ctx.actions.declare_file(base_name + suffix, sibling = base)
+
+    def derive(suffix):
+        return FileFactory(ctx, base, suffix)
+
+    return struct(
+        declare_directory = declare_directory,
+        declare_file = declare_file,
+        derive = derive,
+    )
diff --git a/kotlin/friend_jars.bzl b/kotlin/friend_jars.bzl
index 4efb435..1be3f63 100644
--- a/kotlin/friend_jars.bzl
+++ b/kotlin/friend_jars.bzl
@@ -14,29 +14,37 @@
 
 """TODO: Write module docstring."""
 
-def _is_eligible_friend(target, friend):
+def is_eligible_friend(target, friend):
     """
-    Determines if `target` is allowed to call `friend` a friend (and use its `internal` members).
+    Determines if `target` is allowed to use `internal` members of `friend`
 
-    To be eligible, `target` must be one of:
-      - in the parallel `java/` package from a `javatests/` package
-      - in the parallel `main/java` package from a `test/java` package
-      - another target in the same `BUILD` file
+    To be eligible, one of:
+      1. `target` and `friend` in same pkg
+      2. `target` in `testing/` subpkg of `friend` pkg
+      3. `target` in `javatests/` pkg, `friend` in parallel `java/` pkg
+      4. `target` in `test/java/` pkg, `friend` in parallel `main/java/` pkg
 
     Args:
       target: (target) The current target
       friend: (Target) A potential friend of `target`
+
+    Returns:
+      True if `friend` is an eligible friend of `target`.
     """
 
     target_pkg = target.label.package + "/"
     friend_pkg = friend.label.package + "/"
 
     if target_pkg == friend_pkg:
-        # Allow friends on targets in the same package
+        # Case 1
+        return True
+
+    if target_pkg.removesuffix("testing/") == friend_pkg:
+        # Case 2
         return True
 
     if "javatests/" in target_pkg and "java/" in friend_pkg:
-        # Allow friends from javatests/ on the parallel java/ package
+        # Case 3
         target_java_pkg = target_pkg.rsplit("javatests/", 1)[1]
         friend_java_pkg = friend_pkg.rsplit("java/", 1)[1]
         if target_java_pkg == friend_java_pkg:
@@ -44,7 +52,7 @@
 
     if ("test/java/" in target_pkg and "main/java/" in friend_pkg and
         True):
-        # Allow friends from test/java on the parallel main/java package
+        # Case 4
         target_split = target_pkg.rsplit("test/java/", 1)
         friend_split = friend_pkg.rsplit("main/java/", 1)
         if target_split == friend_split:
@@ -60,7 +68,18 @@
 kt_friend_jars_visitor = struct(
     name = "friend_jars",
     visit_target = _get_output_jars,
-    filter_edge = _is_eligible_friend,
+    filter_edge = is_eligible_friend,
+    finish_expansion = None,
+    process_unvisited_target = None,
+)
+
+def _get_output_labels(target, _):
+    return [target.label]
+
+kt_friend_labels_visitor = struct(
+    name = "friend_labels",
+    visit_target = _get_output_labels,
+    filter_edge = is_eligible_friend,
     finish_expansion = None,
     process_unvisited_target = None,
 )
diff --git a/kotlin/java_plugin.internal.bzl b/kotlin/java_plugin.internal.bzl
index 08f5463..69e7bb5 100644
--- a/kotlin/java_plugin.internal.bzl
+++ b/kotlin/java_plugin.internal.bzl
@@ -22,7 +22,7 @@
 def _get_java_plugins(_target, ctx_rule):
     exported_plugins = getattr(ctx_rule.attr, "exported_plugins", [])
     return [
-        t[JavaPluginInfo]
+        t[JavaPluginInfo].plugins
         for t in exported_plugins
         if JavaPluginInfo in t
     ]
diff --git a/kotlin/jvm_compile.bzl b/kotlin/jvm_compile.bzl
index 694a8a4..4139150 100644
--- a/kotlin/jvm_compile.bzl
+++ b/kotlin/jvm_compile.bzl
@@ -15,8 +15,8 @@
 """Compile method that can compile kotlin or java sources"""
 
 load(":common.bzl", "common")
-load(":traverse_exports.bzl", "kt_traverse_exports")
 load(":compiler_plugin.bzl", "KtCompilerPluginInfo")
+load(":traverse_exports.bzl", "kt_traverse_exports")
 load("@bazel_skylib//lib:sets.bzl", "sets")
 
 _RULE_FAMILY = common.RULE_FAMILY
@@ -46,7 +46,6 @@
         disable_lint_checks = [],
         r_java = None,
         output_srcjar = None,
-        flogger_runtime = None,
         rule_family = _RULE_FAMILY.UNKNOWN,
         annotation_processor_additional_outputs = [],
         annotation_processor_additional_inputs = [],
@@ -87,7 +86,6 @@
         NOTE: This field accepts a JavaInfo, but should only be used for the
         Android R.java within an android_library rule.
       output_srcjar: Target output file for generated source jar. Default filename used if None.
-      flogger_runtime: JavaInfo, Flogger runtime. Optional
       rule_family: The family of the rule calling this function. Element of common.RULE_FAMILY.
         May be used to enable/disable some features.
       annotation_processor_additional_outputs: sequence of Files. A list of
@@ -110,6 +108,9 @@
         # dependent libraries.
         fail("Expected one of (srcs, common_srcs, exports) is not empty for kotlin/jvm_compile on target: {}".format(ctx.label))
 
+    if classpath_resources and rule_family != _RULE_FAMILY.JVM_LIBRARY:
+        fail("resources attribute only allowed for jvm libraries")
+
     if type(java_toolchain) != "JavaToolchainInfo":
         # Allow passing either a target or a provider until all callers are updated
         java_toolchain = java_toolchain[java_common.JavaToolchainInfo]
@@ -117,8 +118,21 @@
     srcs = list(srcs)
     classpath_resources = list(classpath_resources)
     java_infos = []
+    codegen_output_java_infos = []
+
+    # The r_java field only support Android resources Jar files. For now, verify
+    # that the name of the jar matches "_resources.jar". This check does not to
+    # prevent malicious use, the intent is to prevent accidental usage.
+    r_java_infos = []
+    if r_java:
+        for jar in r_java.outputs.jars:
+            if not jar.class_jar.path.endswith("_resources.jar"):
+                fail("Error, illegal dependency provided for r_java. This " +
+                     "only supports Android resource Jar files, " +
+                     "'*_resources.jar'.")
+        r_java_infos.append(r_java)
+
     pre_processed_java_plugin_processors = sets.make([])
-    use_flogger = False
 
     # Skip deps validation check for any android_library target with no kotlin sources: b/239721906
     has_kt_srcs = any([common.is_kt_src(src) for src in srcs])
@@ -126,52 +140,29 @@
         kt_traverse_exports.expand_forbidden_deps(deps + runtime_deps + exports)
 
     for dep in deps:
-        # Collect JavaInfo providers and info about plugins (JavaPluginData).
-        if JavaInfo in dep:
+        if False:
+            pass
+        elif JavaInfo in dep:
             java_infos.append(dep[JavaInfo])
-
         else:
             fail("Unexpected dependency (must provide JavaInfo): %s" % dep.label)
 
-    if rule_family != _RULE_FAMILY.ANDROID_LIBRARY or srcs or common_srcs:
-        java_infos.extend(kt_toolchain.kotlin_libs)
-
-    # TODO: Inject the runtime library from the flogger API target
-    if use_flogger:
-        if not flogger_runtime:
-            fail("Dependency on flogger exists, but flogger_runtime not passed")
-        java_infos.append(flogger_runtime)
-
     if kotlincopts != None and "-Werror" in kotlincopts:
         fail("Flag -Werror is not permitted")
 
-    if classpath_resources and rule_family != _RULE_FAMILY.JVM_LIBRARY:
-        fail("resources attribute only allowed for jvm libraries")
-
-    # The r_java field only support Android resources Jar files. For now, verify
-    # that the name of the jar matches "_resources.jar". This check does not to
-    # prevent malicious use, the intent is to prevent accidental usage.
-    r_java_info = []
-    if r_java:
-        for jar in r_java.outputs.jars:
-            if not jar.class_jar.path.endswith("_resources.jar"):
-                fail("Error, illegal dependency provided for r_java. This " +
-                     "only supports Android resource Jar files, " +
-                     "'*_resources.jar'.")
-        r_java_info.append(r_java)
-
     return common.kt_jvm_library(
         ctx,
-        android_lint_plugins = android_lint_plugins,  # List of JavaInfo
+        android_lint_plugins = [p[JavaInfo] for p in android_lint_plugins],
         android_lint_rules_jars = android_lint_rules_jars,
         classpath_resources = classpath_resources,
         common_srcs = common_srcs,
         coverage_srcs = coverage_srcs,
-                deps = r_java_info + java_infos,
+                deps = r_java_infos + java_infos,
+        codegen_output_java_infos = codegen_output_java_infos,
         disable_lint_checks = disable_lint_checks,
         exported_plugins = [e[JavaPluginInfo] for e in exported_plugins if (JavaPluginInfo in e)],
         # Not all exported targets contain a JavaInfo (e.g. some only have CcInfo)
-        exports = r_java_info + [e[JavaInfo] for e in exports if JavaInfo in e],
+        exports = r_java_infos + [e[JavaInfo] for e in exports if JavaInfo in e],
         friend_jars = kt_traverse_exports.expand_friend_jars(deps, root = ctx),
         java_toolchain = java_toolchain,
         javacopts = javacopts,
@@ -202,6 +193,3 @@
         annotation_processor_additional_outputs = annotation_processor_additional_outputs,
         annotation_processor_additional_inputs = annotation_processor_additional_inputs,
     )
-
-# TODO Delete this
-compile = kt_jvm_compile
diff --git a/kotlin/jvm_import.bzl b/kotlin/jvm_import.bzl
index ed740ca..c42bb96 100644
--- a/kotlin/jvm_import.bzl
+++ b/kotlin/jvm_import.bzl
@@ -24,19 +24,8 @@
 def _kt_jvm_import_impl(ctx):
     kt_jvm_toolchain = kt_jvm_toolchains.get(ctx)
 
-    if not ctx.files.jars:
-        fail("List of jars cannot be empty")
-
-    deps_java_infos = []
-    for dep in ctx.attr.deps:
-        deps_java_infos.append(dep[JavaInfo])
-
-    # Put Kotlin's standard libs last to match what kotlinc would do without -no-stdlib
-    deps_java_infos.extend(kt_jvm_toolchain.kotlin_libs)
-
     runtime_deps_java_infos = []
     for runtime_dep in ctx.attr.runtime_deps:
-        # Collect JavaInfo providers
         if JavaInfo in runtime_dep:
             runtime_deps_java_infos.append(runtime_dep[JavaInfo])
         elif CcInfo not in runtime_dep:
@@ -44,15 +33,15 @@
 
     result = common.kt_jvm_import(
         ctx,
+        kt_toolchain = kt_jvm_toolchain,
         jars = ctx.files.jars,
         srcjar = ctx.file.srcjar,
-        deps = deps_java_infos,
+        deps = common.collect_providers(JavaInfo, ctx.attr.deps),
         runtime_deps = runtime_deps_java_infos,
         neverlink = ctx.attr.neverlink,
         java_toolchain = java_toolchains.get(ctx),
         deps_checker = ctx.executable._deps_checker,
     )
-    result_java_info = result.java_info
 
     # Collect runfiles from deps unless neverlink
     runfiles = None
@@ -68,7 +57,7 @@
         )
 
     return [
-        result_java_info,
+        result.java_info,
         ProguardSpecProvider(common.collect_proguard_specs(
             ctx,
             ctx.files.proguard_specs,
diff --git a/kotlin/jvm_library.bzl b/kotlin/jvm_library.bzl
index f2ffe09..916e358 100644
--- a/kotlin/jvm_library.bzl
+++ b/kotlin/jvm_library.bzl
@@ -16,6 +16,7 @@
 
 load(":jvm_library.internal.bzl", "kt_jvm_library_helper")
 load("//bazel:stubs.bzl", "register_extension_info")
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
 
 def kt_jvm_library(
         name,
@@ -30,16 +31,11 @@
         exported_plugins = None,
         resources = None,
         tags = None,
-        testonly = None,  # None to preserve Blaze's defaults, b/112708042
         javacopts = None,
         custom_kotlincopts = None,
         disable_lint_checks = None,
-        compatible_with = None,
-        restricted_to = None,
         transitive_configs = None,
-        visibility = None,
-        deprecation = None,
-        features = []):
+        **kwargs):
     """This rule compiles Kotlin (and Java) sources into a Jar file.
 
     Most Java-like libraries
@@ -67,15 +63,11 @@
       javacopts: Additional flags to pass to javac if used.
       custom_kotlincopts: Additional flags to pass to Kotlin compiler.
       disable_lint_checks: A list of AndroidLint checks to be skipped.
-      compatible_with: Standard attribute, see
-        https://bazel.build/reference/be/common-definitions#common.compatible_with.
-      restricted_to: Standard attribute, see
-        https://bazel.build/reference/be/common-definitions#common.restricted_to.
       transitive_configs:  Blaze feature flags (if any) on which this target depends.
-      visibility: A list of targets allowed to depend on this rule.
       deprecation: Standard attribute, see
         https://bazel.build/reference/be/common-definitions#common.deprecation.
       features: Features enabled.
+      **kwargs: Other keyword arguments.
     """
     srcs = srcs or []
     common_srcs = common_srcs or []
@@ -111,13 +103,10 @@
         data = data,
         disable_lint_checks = disable_lint_checks,
         tags = tags,
-        testonly = testonly,
-        compatible_with = compatible_with,
-        restricted_to = restricted_to,
         transitive_configs = transitive_configs,
-        visibility = visibility,
-        deprecation = deprecation,
-        features = features,
+        **dicts.add(
+            kwargs,
+        )
     )
 
 register_extension_info(
diff --git a/kotlin/jvm_library.internal.bzl b/kotlin/jvm_library.internal.bzl
index 7334f95..85b9e52 100644
--- a/kotlin/jvm_library.internal.bzl
+++ b/kotlin/jvm_library.internal.bzl
@@ -45,9 +45,7 @@
     kt_jvm_toolchain = kt_jvm_toolchains.get(ctx)
 
     for target in ctx.attr.runtime_deps:
-        if JavaInfo in target:
-            pass
-        elif CcInfo not in target:
+        if (JavaInfo not in target) and (CcInfo not in target):
             fail("Unexpected runtime dependency (must provide JavaInfo or CcInfo): " + str(target.label))
 
     if not ctx.files.srcs and not ctx.files.common_srcs and not ctx.attr.exports and not ctx.attr.exported_plugins:
@@ -66,9 +64,9 @@
         exports = ctx.attr.exports,
         javacopts = ctx.attr.javacopts,
         kotlincopts = merge_kotlincopts(ctx),
-        neverlink = False,
+        neverlink = ctx.attr.neverlink,
         testonly = ctx.attr.testonly,
-                android_lint_plugins = [p[JavaInfo] for p in ctx.attr._android_lint_plugins],
+                android_lint_plugins = ctx.attr._android_lint_plugins,
         manifest = None,
         merged_manifest = None,
         resource_files = [],
@@ -189,6 +187,11 @@
                      if `.java` `srcs` are provided or annotation processors generate sources for this
                      rule.""",
     ),
+    neverlink = attr.bool(
+        default = False,
+        doc = """Only use this library for compilation and not at runtime. Useful if the library
+                  will be provided by the runtime environment during execution.""",
+    ),
     plugins = attr.label_list(
         providers = [
             [JavaPluginInfo],
diff --git a/kotlin/jvm_test.bzl b/kotlin/jvm_test.bzl
index b7270e2..1c19902 100644
--- a/kotlin/jvm_test.bzl
+++ b/kotlin/jvm_test.bzl
@@ -36,7 +36,7 @@
     """Wrapper around kt_jvm_library and java_test to conveniently declare tests written in Kotlin.
 
     Use of this rule is discouraged for simple unit tests, which should instead use
-    go/junit-test-suites or other, more efficient ways of compiling and running unit tests.
+     go/junit_test_suites or other, more efficient ways of compiling and running unit tests.
 
     Args:
       name: Name of the target.
@@ -49,6 +49,7 @@
       runtime_deps: A list of runtime dependencies.
       srcs: A list of sources to compile.
       tags: A list of string tags passed to generated targets.
+      resources: A list of data files to include in the jar file.
       **kwargs: Additional parameters to pass on to generated java_test, see go/be-java#java_test.
     """
     if srcs:
diff --git a/kotlin/traverse_exports.bzl b/kotlin/traverse_exports.bzl
index e579080..a8ce86f 100644
--- a/kotlin/traverse_exports.bzl
+++ b/kotlin/traverse_exports.bzl
@@ -17,7 +17,7 @@
 load(":compiler_plugin.bzl", "kt_compiler_plugin_visitor")
 load(":direct_jdeps.bzl", "kt_direct_jdeps_visitor")
 load(":forbidden_deps.bzl", "kt_forbidden_deps_visitor")
-load(":friend_jars.bzl", "kt_friend_jars_visitor")
+load(":friend_jars.bzl", "kt_friend_jars_visitor", "kt_friend_labels_visitor")
 load(":java_plugin.internal.bzl", "java_plugin_visitor")
 
 # java_xxx_proto_library don't populate java_outputs but we can get them through
@@ -29,7 +29,6 @@
 ]
 
 _NO_SRCS_DEPS_AS_EXPORTS_RULES = [
-    "android_library",
     "proto_library",
 ]
 
@@ -45,6 +44,7 @@
     kt_direct_jdeps_visitor,
     kt_compiler_plugin_visitor,
     kt_friend_jars_visitor,
+    kt_friend_labels_visitor,
     java_plugin_visitor,
 ]
 
@@ -65,7 +65,7 @@
     if not (JavaInfo in target):
         # Ignore non-JVM targets. This also chops-up the
         # traversal domain at these targets.
-        # TODO: Supoprt non-JVM targets for KMP
+        # TODO: Support non-JVM targets for KMP
         return _EMPTY_KT_TRAVERSE_EXPORTS_INFO
 
     exports = []
diff --git a/tests/analysis/BUILD b/tests/analysis/BUILD
index b927805..0533e13 100644
--- a/tests/analysis/BUILD
+++ b/tests/analysis/BUILD
@@ -17,6 +17,7 @@
 load("//tests/analysis:jvm_compile_test.bzl", jvm_compile_test_suite = "test_suite")
 load("//tests/analysis:jvm_import_test.bzl", jvm_import_test_suite = "test_suite")
 load("//tests/analysis:jvm_library_test.bzl", jvm_library_test_suite = "test_suite")
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
 
 licenses(["notice"])
 
diff --git a/tests/analysis/for_test.bzl b/tests/analysis/for_test.bzl
new file mode 100644
index 0000000..434c1a4
--- /dev/null
+++ b/tests/analysis/for_test.bzl
@@ -0,0 +1,39 @@
+# Copyright 2022 Google LLC. 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.
+
+"""Rules for test."""
+
+load("//kotlin:jvm_library.bzl", "kt_jvm_library")
+load("//tests/analysis:util.bzl", "ONLY_FOR_ANALYSIS_TEST_TAGS")
+
+def _kt_jvm_library_for_test(name, **kwargs):
+    kt_jvm_library(
+        name = name,
+        tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
+        **kwargs
+    )
+    return name
+
+def _java_library_for_test(name, **kwargs):
+    native.java_library(
+        name = name,
+        tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
+        **kwargs
+    )
+    return name
+
+rules_for_test = struct(
+    kt_jvm_library = _kt_jvm_library_for_test,
+    java_library = _java_library_for_test,
+)
diff --git a/tests/analysis/jvm_library/data/BUILD b/tests/analysis/jvm_library/data/BUILD
new file mode 100644
index 0000000..1809aa1
--- /dev/null
+++ b/tests/analysis/jvm_library/data/BUILD
@@ -0,0 +1,40 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "data_test",
+    expected = dict(
+        data = [
+            "data.txt",
+            # libX.jar is always in data_runfiles as well - just append it.
+            "libdata.jar",
+        ],
+    ),
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "data",
+        srcs = [
+            "Input.kt",
+        ],
+        data = [
+            "data.txt",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/data/Input.kt b/tests/analysis/jvm_library/data/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/data/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/data/data.txt b/tests/analysis/jvm_library/data/data.txt
new file mode 100644
index 0000000..e3ba5e8
--- /dev/null
+++ b/tests/analysis/jvm_library/data/data.txt
@@ -0,0 +1,13 @@
+# Copyright 2022 Google LLC. 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.
\ No newline at end of file
diff --git a/tests/analysis/jvm_library/deps/BUILD b/tests/analysis/jvm_library/deps/BUILD
new file mode 100644
index 0000000..ccb03a0
--- /dev/null
+++ b/tests/analysis/jvm_library/deps/BUILD
@@ -0,0 +1,48 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+rules_for_test.kt_jvm_library(
+    name = "deps_test_kt_dep",
+    srcs = [
+        "Input.kt",
+    ],
+)
+
+rules_for_test.java_library(
+    name = "deps_test_java_dep",
+    srcs = [
+        "Input.java",
+    ],
+)
+
+jvm_library_test(
+    name = "deps_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "deps",
+        srcs = [
+            "Foo.java",
+        ],
+        deps = [
+            ":deps_test_java_dep",
+            ":deps_test_kt_dep",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/deps/Foo.java b/tests/analysis/jvm_library/deps/Foo.java
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/deps/Foo.java
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/deps/Input.java b/tests/analysis/jvm_library/deps/Input.java
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/deps/Input.java
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/deps/Input.kt b/tests/analysis/jvm_library/deps/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/deps/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/friends/BUILD b/tests/analysis/jvm_library/friends/BUILD
index 3c4a7db..538bdce 100644
--- a/tests/analysis/jvm_library/friends/BUILD
+++ b/tests/analysis/jvm_library/friends/BUILD
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load("//kotlin:rules.bzl", "kt_jvm_library")
+load("//tests/analysis:for_test.bzl", "rules_for_test")
 load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
 load("//tests/analysis:util.bzl", "ONLY_FOR_ANALYSIS_TEST_TAGS")
 
@@ -24,35 +25,72 @@
 licenses(["notice"])
 
 jvm_library_test(
-    name = "no_kt_exported_friend_trans_package_test",
+    name = "no_kt_exported_friend_cross_package_test",
     expected_friend_jar_names = [],
-    target_under_test = ":no_kt_exported_friend_trans_package",
-)
-
-kt_jvm_library(
-    name = "no_kt_exported_friend_trans_package",
-    srcs = ["Input.kt"],
-    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = [
-        # Exports :friend from this package
-        "//tests/analysis/jvm_library/friends/sub:kt_exports_friend",
-    ],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_kt_exported_friend_cross_package",
+        srcs = ["Input.kt"],
+        deps = [
+            # Exports :friend from this package
+            "//tests/analysis/jvm_library/friends/sub:kt_exports_friend",
+        ],
+    ),
 )
 
 jvm_library_test(
     name = "has_java_exported_friend_in_package_test",
     expected_friend_jar_names = [
         "libjava_exports_friend-hjar.jar",
-        "friend-compile.jar",
+        "libfriend-compile.jar",
     ],
-    target_under_test = ":has_java_exported_friend_in_package",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "has_java_exported_friend_in_package",
+        srcs = ["Input.kt"],
+        deps = [":java_exports_friend"],
+    ),
 )
 
-kt_jvm_library(
-    name = "has_java_exported_friend_in_package",
-    srcs = ["Input.kt"],
-    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = [":java_exports_friend"],
+jvm_library_test(
+    name = "has_kt_exported_friend_in_package_test",
+    expected_friend_jar_names = [
+        "libkt_exports_friend-compile.jar",
+        "libfriend-compile.jar",
+    ],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "has_kt_exported_friend_in_package",
+        srcs = ["Input.kt"],
+        deps = [":kt_exports_friend"],
+    ),
+)
+
+jvm_library_test(
+    name = "no_direct_friend_testing_package_test",
+    expected_friend_jar_names = [],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_direct_friend_testing_package",
+        srcs = ["Input.kt"],
+        deps = ["//tests/analysis/jvm_library/friends/testing:testingfriend"],
+    ),
+)
+
+jvm_library_test(
+    name = "no_direct_friend_subpackage_test",
+    expected_friend_jar_names = [],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_direct_friend_subpackage",
+        srcs = ["Input.kt"],
+        deps = ["//tests/analysis/jvm_library/friends/sub:subfriend"],
+    ),
+)
+
+jvm_library_test(
+    name = "has_direct_friend_in_package_test",
+    expected_friend_jar_names = ["libfriend-compile.jar"],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "has_direct_friend_in_package",
+        srcs = ["Input.kt"],
+        deps = [":friend"],
+    ),
 )
 
 java_library(
@@ -62,22 +100,6 @@
     exports = [":friend"],
 )
 
-jvm_library_test(
-    name = "has_kt_exported_friend_in_package_test",
-    expected_friend_jar_names = [
-        "kt_exports_friend-compile.jar",
-        "friend-compile.jar",
-    ],
-    target_under_test = ":has_kt_exported_friend_in_package",
-)
-
-kt_jvm_library(
-    name = "has_kt_exported_friend_in_package",
-    srcs = ["Input.kt"],
-    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = [":kt_exports_friend"],
-)
-
 kt_jvm_library(
     name = "kt_exports_friend",
     srcs = ["Input.kt"],
@@ -85,19 +107,6 @@
     exports = [":friend"],
 )
 
-jvm_library_test(
-    name = "has_direct_friend_in_package_test",
-    expected_friend_jar_names = ["friend-compile.jar"],
-    target_under_test = ":has_direct_friend_in_package",
-)
-
-kt_jvm_library(
-    name = "has_direct_friend_in_package",
-    srcs = ["Input.kt"],
-    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = [":friend"],
-)
-
 kt_jvm_library(
     name = "friend",
     srcs = ["Input.kt"],
diff --git a/tests/analysis/jvm_library/friends/sub/BUILD b/tests/analysis/jvm_library/friends/sub/BUILD
index 7c26e59..e7c619f 100644
--- a/tests/analysis/jvm_library/friends/sub/BUILD
+++ b/tests/analysis/jvm_library/friends/sub/BUILD
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load("//kotlin:rules.bzl", "kt_jvm_library")
+load("//tests/analysis:for_test.bzl", "rules_for_test")
 load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
 load("//tests/analysis:util.bzl", "ONLY_FOR_ANALYSIS_TEST_TAGS")
 
@@ -26,17 +27,24 @@
 jvm_library_test(
     name = "no_kt_exported_friend_cross_package_test",
     expected_friend_jar_names = [
-        "kt_exports_friend-compile.jar",
-        # Absent # "friend-compile.jar"
+        "libkt_exports_friend-compile.jar",
+        # Absent # "libfriend-compile.jar"
     ],
-    target_under_test = ":no_kt_exported_friend_cross_package",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_kt_exported_friend_cross_package",
+        srcs = ["Input.kt"],
+        deps = [":kt_exports_friend"],
+    ),
 )
 
-kt_jvm_library(
-    name = "no_kt_exported_friend_cross_package",
-    srcs = ["Input.kt"],
-    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = [":kt_exports_friend"],
+jvm_library_test(
+    name = "no_direct_friend_cross_package_test",
+    expected_friend_jar_names = [],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_direct_friend_cross_package",
+        srcs = ["Input.kt"],
+        deps = ["//tests/analysis/jvm_library/friends:friend"],
+    ),
 )
 
 kt_jvm_library(
@@ -46,15 +54,8 @@
     exports = ["//tests/analysis/jvm_library/friends:friend"],
 )
 
-jvm_library_test(
-    name = "no_direct_friend_cross_package_test",
-    expected_friend_jar_names = [],
-    target_under_test = ":no_direct_friend_cross_package",
-)
-
 kt_jvm_library(
-    name = "no_direct_friend_cross_package",
+    name = "subfriend",
     srcs = ["Input.kt"],
     tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
-    deps = ["//tests/analysis/jvm_library/friends:friend"],
 )
diff --git a/tests/analysis/jvm_library/friends/testing/BUILD b/tests/analysis/jvm_library/friends/testing/BUILD
new file mode 100644
index 0000000..827aa30
--- /dev/null
+++ b/tests/analysis/jvm_library/friends/testing/BUILD
@@ -0,0 +1,93 @@
+# Copyright 2022 Google LLC. 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("//kotlin:rules.bzl", "kt_jvm_library")
+load("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+load("//tests/analysis:util.bzl", "ONLY_FOR_ANALYSIS_TEST_TAGS")
+
+package(
+    default_testonly = True,
+    default_visibility = ["//tests/analysis/jvm_library/friends:__subpackages__"],
+)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "has_kt_exported_friend_impl_package_test",
+    expected_friend_jar_names = [
+        "libkt_exports_friend-compile.jar",
+        "libfriend-compile.jar",
+    ],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "has_kt_exported_friend_impl_package",
+        srcs = ["Input.kt"],
+        deps = [":kt_exports_friend"],
+    ),
+)
+
+jvm_library_test(
+    name = "has_direct_friend_impl_package_test",
+    expected_friend_jar_names = [
+        "libfriend-compile.jar",
+    ],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "has_direct_friend_impl_package",
+        srcs = ["Input.kt"],
+        deps = ["//tests/analysis/jvm_library/friends:friend"],
+    ),
+)
+
+jvm_library_test(
+    name = "no_kt_exported_friend_sibling_package_test",
+    expected_friend_jar_names = [
+        "libkt_exports_subfriend-compile.jar",
+        # Absent # "subfriend-compile.jar"
+    ],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_kt_exported_friend_sibling_package",
+        srcs = ["Input.kt"],
+        deps = [":kt_exports_subfriend"],
+    ),
+)
+
+jvm_library_test(
+    name = "no_direct_friend_sibling_package_test",
+    expected_friend_jar_names = [],
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_direct_friend_sibling_package",
+        srcs = ["Input.kt"],
+        deps = ["//tests/analysis/jvm_library/friends/sub:subfriend"],
+    ),
+)
+
+kt_jvm_library(
+    name = "kt_exports_subfriend",
+    srcs = ["Input.kt"],
+    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
+    exports = ["//tests/analysis/jvm_library/friends/sub:subfriend"],
+)
+
+kt_jvm_library(
+    name = "kt_exports_friend",
+    srcs = ["Input.kt"],
+    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
+    exports = ["//tests/analysis/jvm_library/friends:friend"],
+)
+
+kt_jvm_library(
+    name = "testingfriend",
+    srcs = ["Input.kt"],
+    tags = ONLY_FOR_ANALYSIS_TEST_TAGS,
+)
diff --git a/tests/analysis/jvm_library/friends/testing/Input.kt b/tests/analysis/jvm_library/friends/testing/Input.kt
new file mode 100644
index 0000000..2c65124
--- /dev/null
+++ b/tests/analysis/jvm_library/friends/testing/Input.kt
@@ -0,0 +1,17 @@
+/*
+ * * Copyright 2022 Google LLC. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test.testing
diff --git a/tests/analysis/jvm_library/no_java_srcs/BUILD b/tests/analysis/jvm_library/no_java_srcs/BUILD
new file mode 100644
index 0000000..116dcfd
--- /dev/null
+++ b/tests/analysis/jvm_library/no_java_srcs/BUILD
@@ -0,0 +1,30 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "no_java_srcs_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_java_srcs",
+        srcs = [
+            "Input.kt",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/no_java_srcs/Input.kt b/tests/analysis/jvm_library/no_java_srcs/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/no_java_srcs/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/no_kt_srcs/BUILD b/tests/analysis/jvm_library/no_kt_srcs/BUILD
new file mode 100644
index 0000000..a6e4f18
--- /dev/null
+++ b/tests/analysis/jvm_library/no_kt_srcs/BUILD
@@ -0,0 +1,30 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "no_kt_srcs_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "no_kt_srcs",
+        srcs = [
+            "Input.java",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/no_kt_srcs/Input.java b/tests/analysis/jvm_library/no_kt_srcs/Input.java
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/no_kt_srcs/Input.java
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/nodeps/BUILD b/tests/analysis/jvm_library/nodeps/BUILD
new file mode 100644
index 0000000..fc3e673
--- /dev/null
+++ b/tests/analysis/jvm_library/nodeps/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "nodeps_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "nodeps",
+        srcs = [
+            "Input.java",
+            "Input.kt",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/nodeps/Input.java b/tests/analysis/jvm_library/nodeps/Input.java
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/nodeps/Input.java
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/nodeps/Input.kt b/tests/analysis/jvm_library/nodeps/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/nodeps/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/only_common_srcs/BUILD b/tests/analysis/jvm_library/only_common_srcs/BUILD
new file mode 100644
index 0000000..49930f2
--- /dev/null
+++ b/tests/analysis/jvm_library/only_common_srcs/BUILD
@@ -0,0 +1,30 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "only_common_srcs_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "only_common_srcs",
+        common_srcs = [
+            "Input.kt",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/only_common_srcs/Input.kt b/tests/analysis/jvm_library/only_common_srcs/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/only_common_srcs/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/runtime_deps/BUILD b/tests/analysis/jvm_library/runtime_deps/BUILD
new file mode 100644
index 0000000..17457ba
--- /dev/null
+++ b/tests/analysis/jvm_library/runtime_deps/BUILD
@@ -0,0 +1,38 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+package(default_testonly = True)
+
+licenses(["notice"])
+
+rules_for_test.java_library(
+    name = "runtime_deps_test_dep",
+    srcs = [],
+)
+
+jvm_library_test(
+    name = "runtime_deps_test",
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "runtime_deps",
+        srcs = [
+            "Input.kt",
+        ],
+        runtime_deps = [
+            ":runtime_deps_test_dep",
+        ],
+    ),
+)
diff --git a/tests/analysis/jvm_library/runtime_deps/Input.java b/tests/analysis/jvm_library/runtime_deps/Input.java
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/runtime_deps/Input.java
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library/runtime_deps/Input.kt b/tests/analysis/jvm_library/runtime_deps/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/analysis/jvm_library/runtime_deps/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/tests/analysis/jvm_library_test.bzl b/tests/analysis/jvm_library_test.bzl
index a73bbbc..6435cc5 100644
--- a/tests/analysis/jvm_library_test.bzl
+++ b/tests/analysis/jvm_library_test.bzl
@@ -89,6 +89,13 @@
         friend_jar_names = [p.rsplit("/", 1)[1] for p in friend_paths_arg.split(",")] if friend_paths_arg else []
         asserts.set_equals(env, sets.make(ctx.attr.expected_friend_jar_names), sets.make(friend_jar_names))
 
+    asserts.equals(
+        env,
+        ctx.attr.expect_neverlink,
+        len(actual[JavaInfo].transitive_runtime_jars.to_list()) == 0,
+        "Mismatch: Expected transitive_runtime_jars iff (neverlink == False)",
+    )
+
     return analysistest.end(env)
 
 _test = analysistest.make(
@@ -107,6 +114,7 @@
             default = _DEFAULT_LIST,
         ),
         expect_processor_classpath = attr.bool(),
+        expect_neverlink = attr.bool(),
     ),
 )
 
@@ -139,158 +147,6 @@
 def _extract_data_runfiles(target):
     return [f.basename for f in target[DefaultInfo].data_runfiles.files.to_list()]
 
-def _test_kt_jvm_library_with_deps():
-    test_name = "kt_jvm_library_with_deps_test"
-
-    kt_jvm_library(
-        name = test_name + "_kt_dep",
-        srcs = [create_file(
-            name = test_name + "/Hello.kt",
-            content = """
-package test
-
-fun hello(): String = "Hello!"
-""",
-        )],
-    )
-    native.java_library(
-        name = test_name + "_java_dep",
-        srcs = ["testinputs/Foo.java"],
-    )
-    kt_jvm_library(
-        name = test_name + "_tut",
-        srcs = [
-            create_file(
-                name = test_name + "/Hi.kt",
-                content = """
-package test
-
-fun hi(): String = "Hi!"
-""",
-            ),
-            "testinputs/Bar.java",
-        ],
-        deps = [
-            test_name + "_kt_dep",
-            test_name + "_java_dep",
-        ],
-    )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
-def _test_kt_jvm_library_no_deps():
-    test_name = "kt_jvm_library_no_deps_test"
-    create_file(
-        name = test_name + "/Salutations.kt",
-        content = """
-package test
-
-fun greeting(): String = "Hello World!"
-""",
-    )
-    kt_jvm_library(
-        name = test_name + "_tut",
-        srcs = [
-            "testinputs/Bar.java",
-            test_name + "/Salutations.kt",
-        ],
-    )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
-def _test_kt_jvm_library_with_only_common_srcs():
-    test_name = "kt_jvm_library_only_common_srcs_test"
-    create_file(
-        name = test_name + "/Salutations.kt",
-        content = """
-package test
-
-fun greeting(): String = "Hello World!"
-""",
-    )
-    kt_jvm_library(
-        name = test_name + "_tut",
-        common_srcs = [
-            test_name + "/Salutations.kt",
-        ],
-    )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
-def _test_kt_jvm_library_no_java_srcs():
-    test_name = "kt_jvm_library_no_java_srcs_test"
-    create_file(
-        name = test_name + "/Salutations.kt",
-        content = """
-package test
-
-fun greeting(): String = "Hello World!"
-""",
-    )
-    kt_jvm_library(
-        name = test_name + "_tut",
-        srcs = [
-            test_name + "/Salutations.kt",
-        ],
-            )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
-def _test_kt_jvm_library_no_kt_srcs():
-    test_name = "kt_jvm_library_no_kt_srcs_test"
-    kt_jvm_library(
-        name = test_name + "_tut",
-        srcs = [
-            "testinputs/Bar.java",
-        ],
-    )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
-def _test_kt_jvm_library_with_runtime_deps():
-    test_name = "kt_jvm_library_with_runtime_deps_test"
-    create_file(
-        name = test_name + "/Salutations.kt",
-        content = """
-package test
-
-fun greeting(): String = "Hello World!"
-""",
-    )
-    native.java_library(
-        name = test_name + "_dep",
-        srcs = [],
-    )
-    kt_jvm_library(
-        name = test_name + "_tut",
-        srcs = [
-            test_name + "/Salutations.kt",
-        ],
-        runtime_deps = [
-            test_name + "_dep",
-        ],
-    )
-    _test(
-        name = test_name,
-        target_under_test = test_name + "_tut",
-    )
-    return test_name
-
 def _test_kt_jvm_library_with_proguard_specs():
     test_name = "kt_jvm_library_with_proguard_specs_test"
     create_file(
@@ -356,48 +212,6 @@
     )
     return test_name
 
-def _test_kt_jvm_library_with_data():
-    test_name = "kt_jvm_library_with_data_test"
-    kt_jvm_lib_name = test_name + "_tut"
-
-    data_txt = create_file(
-        name = test_name + "data.txt",
-        content = """
-Hello World!
-""",
-    )
-
-    # Kotlin file
-    muchdata_kt = create_file(
-        name = test_name + "/MuchData.kt",
-        content = """
-package test
-
-import java.io.File
-
-fun greeting(): String = File("data.txt").readText()
-""",
-    )
-
-    kt_jvm_library(
-        name = kt_jvm_lib_name,
-        srcs = [muchdata_kt],
-        data = [data_txt],
-    )
-
-    _test(
-        name = test_name,
-        target_under_test = kt_jvm_lib_name,
-        expected = dict(
-            data = [
-                data_txt,
-                # libX.jar is always in data_runfiles as well - just append it.
-                "lib%s.jar" % kt_jvm_lib_name,
-            ],
-        ),
-    )
-    return test_name
-
 def _test_kt_jvm_library_with_plugin():
     test_name = "kt_jvm_library_with_plugin_test"
     create_file(
@@ -816,23 +630,16 @@
             _test_forbidden_nano_export(),
             _test_kt_jvm_library_dep_on_exported_plugin(),
             _test_kt_jvm_library_java_dep_on_exported_plugin(),
-            _test_kt_jvm_library_no_deps(),
-            _test_kt_jvm_library_no_java_srcs(),
-            _test_kt_jvm_library_no_kt_srcs(),
             _test_kt_jvm_library_no_kt_srcs_with_plugin(),
-            _test_kt_jvm_library_with_data(),
-            _test_kt_jvm_library_with_deps(),
             _test_kt_jvm_library_with_export_that_exports_plugin(),
             _test_kt_jvm_library_with_exported_plugin(),
             _test_kt_jvm_library_with_exports(),
             _test_kt_jvm_library_with_java_export_that_exports_plugin(),
             _test_kt_jvm_library_with_no_sources(),
             _test_kt_jvm_library_with_non_processor_plugin(),
-            _test_kt_jvm_library_with_only_common_srcs(),
             _test_kt_jvm_library_with_plugin(),
             _test_kt_jvm_library_with_proguard_specs(),
             _test_kt_jvm_library_with_resources(),
-            _test_kt_jvm_library_with_runtime_deps(),
             _test_kt_jvm_library_coverage(),
         ],
     )
diff --git a/tests/jvm/java/functions/BUILD b/tests/jvm/java/functions/BUILD
index aa571fe..67c0e9e 100644
--- a/tests/jvm/java/functions/BUILD
+++ b/tests/jvm/java/functions/BUILD
@@ -17,6 +17,12 @@
 
 licenses(["notice"])
 
+# During coverage builds, every library gets a dep on JaCoCo (Java Code Coverage).
+# Libjars, from libraries, only include their direct sources. Together, these behaviours
+# trigger an ImportDepsChecker error for :car-jar and :car-inline-jar. To prevent that, we disable
+# coverage builds on all downstream targets.
+_NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO = ["nozapfhahn"]
+
 kt_jvm_library(
     name = "car_demo_src_lib",
     srcs = ["CarDemo.kt"],
@@ -38,11 +44,13 @@
 java_import(
     name = "car-jar",
     jars = ["//tests/jvm/java/functions/car:car_lib-jar"],
+    tags = _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
 )
 
 kt_jvm_library(
     name = "car_demo_jar_lib",
     srcs = ["CarDemo.kt"],
+    tags = _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
     deps = [
         ":car-jar",
     ],
@@ -53,7 +61,8 @@
 java_test(
     name = "car_jar_demo",
     main_class = "functions.CarDemo",
-    tags = ["darwin_x86_64_compatible"],
+    tags = ["darwin_x86_64_compatible"] +
+           _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
     runtime_deps = [
         ":car_demo_jar_lib",
         "@kotlinc//:kotlin_stdlib",
@@ -66,6 +75,7 @@
         "//tests/jvm/java/functions/car:car_inline_lib-jar",
         "//tests/jvm/java/functions/car:car_extra_lib-jar",
     ],
+    tags = _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
     deps = [
         ":car-jar",
     ],
@@ -74,6 +84,7 @@
 kt_jvm_library(
     name = "car_demo_inline_lib",
     srcs = ["CarInlineDemo.kt"],
+    tags = _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
     deps = [
         ":car-inline-jar",
         ":car-jar",
@@ -85,10 +96,8 @@
 java_test(
     name = "car_inline_demo",
     main_class = "functions.CarInlineDemo",
-    tags = [
-        "darwin_x86_64_compatible",
-        "nozapfhahn",
-    ],
+    tags = ["darwin_x86_64_compatible"] +
+           _NO_ZAPFHAHN_BECAUSE_LIBJARS_EXCLUDE_JACOCO,
     runtime_deps = [
         ":car_demo_inline_lib",
         "@kotlinc//:kotlin_stdlib",
diff --git a/tests/jvm/java/kapt/AP.java b/tests/jvm/java/kapt/AP.java
index 984a444..d8b6d9f 100644
--- a/tests/jvm/java/kapt/AP.java
+++ b/tests/jvm/java/kapt/AP.java
@@ -63,7 +63,7 @@
                 .createSourceFile("kapt." + simpleName, element)
                 .openOutputStream()) {
           os.write(
-              String.format("package kapt; @Count(%d) class %s {}", next, simpleName)
+              String.format("package kapt; @Count(%d) public class %s {}", next, simpleName)
                   .getBytes(UTF_8));
         } catch (IOException e) {
           throw new UncheckedIOException(e);
diff --git a/tests/jvm/java/kapt/BUILD b/tests/jvm/java/kapt/BUILD
index 43b893d..ea6ad66 100644
--- a/tests/jvm/java/kapt/BUILD
+++ b/tests/jvm/java/kapt/BUILD
@@ -21,10 +21,15 @@
 
 licenses(["notice"])
 
+package(default_visibility = [
+    "//tests/android/java/com/google/local_test:__subpackages__",
+])
+
 build_test(
     name = "kapt_regressions",
     tags = ["darwin_x86_64_compatible"],
     targets = [
+        ":test_interface_with_default_impls",
         ":test_javac",
         ":test_kapt",
         ":test_javac",
@@ -52,6 +57,14 @@
 )
 
 kt_jvm_library(
+    name = "test_interface_with_default_impls",
+    srcs = [
+        "InterfaceWithDefaultImpls.kt",
+    ],
+    plugins = [":AP"],  # trigger kapt for regression test (b/121222399)
+)
+
+kt_jvm_library(
     name = "test_kapt",
     srcs = [
         "KTest.kt",
@@ -194,6 +207,11 @@
     srcs = ["Count.java"],
 )
 
+java_library(
+    name = "anno-android",
+    srcs = ["Count.java"],
+)
+
 kt_jvm_library(
     name = "anno_with_plugin",
     srcs = ["Count.java"],
diff --git a/tests/jvm/java/kapt/InterfaceWithDefaultImpls.kt b/tests/jvm/java/kapt/InterfaceWithDefaultImpls.kt
index a9aad4f..0bb1f12 100644
--- a/tests/jvm/java/kapt/InterfaceWithDefaultImpls.kt
+++ b/tests/jvm/java/kapt/InterfaceWithDefaultImpls.kt
@@ -15,7 +15,6 @@
  */
 
 // This file contains the example filed upstream for b/121222399.
-// TODO: Turn this into a regression test once the issue is fixed.
 package kapt
 
 interface Base<T>
diff --git a/tests/jvm/java/neverlink/BUILD b/tests/jvm/java/neverlink/BUILD
new file mode 100644
index 0000000..c6daa08
--- /dev/null
+++ b/tests/jvm/java/neverlink/BUILD
@@ -0,0 +1,28 @@
+# Copyright 2022 Google LLC. 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("//tests/analysis:for_test.bzl", "rules_for_test")
+load("//tests/analysis:jvm_library_test.bzl", "jvm_library_test")
+
+licenses(["notice"])
+
+jvm_library_test(
+    name = "neverlinked_input_library_test",
+    expect_neverlink = True,
+    target_under_test = rules_for_test.kt_jvm_library(
+        name = "neverlinked_input_library",
+        srcs = ["Input.kt"],
+        neverlink = True,
+    ),
+)
diff --git a/tests/jvm/java/neverlink/Input.kt b/tests/jvm/java/neverlink/Input.kt
new file mode 100644
index 0000000..e675bc1
--- /dev/null
+++ b/tests/jvm/java/neverlink/Input.kt
@@ -0,0 +1,16 @@
+/*
+ * * Copyright 2022 Google LLC. 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.
+ */
+
diff --git a/toolchains/kotlin_jvm/kt_jvm_toolchains.bzl b/toolchains/kotlin_jvm/kt_jvm_toolchains.bzl
index 0b9c610..cc73d81 100644
--- a/toolchains/kotlin_jvm/kt_jvm_toolchains.bzl
+++ b/toolchains/kotlin_jvm/kt_jvm_toolchains.bzl
@@ -14,10 +14,12 @@
 
 """Kotlin toolchain."""
 
+load("//bazel:stubs.bzl", "select_java_language_version")
+
 # Work around to toolchains in Google3.
 KtJvmToolchainInfo = provider()
 
-KT_VERSION = "v1_7_10"
+KT_VERSION = "v1_7_21"
 
 KT_LANG_VERSION = "1.7"
 
@@ -102,8 +104,10 @@
         coverage_runtime = ctx.attr.coverage_runtime[JavaInfo] if JavaInfo in ctx.attr.coverage_runtime else None,
         genclass = ctx.file.genclass,
         jar_tool = ctx.attr.jar_tool[DefaultInfo].files_to_run,
+        java_language_version = ctx.attr.java_language_version,
         java_runtime = ctx.attr.java_runtime,
         jvm_abi_gen_plugin = ctx.file.jvm_abi_gen_plugin,
+        jvm_target = ctx.attr.jvm_target,
         kotlin_annotation_processing = ctx.file.kotlin_annotation_processing,
         kotlin_compiler = ctx.attr.kotlin_compiler[DefaultInfo].files_to_run,
         kotlin_language_version = ctx.attr.kotlin_language_version,
@@ -112,17 +116,18 @@
         kotlinc_cli_flags = _kotlinc_cli_flags(ctx),
         kotlinc_ide_flags = _kotlinc_ide_flags(ctx),
         proguard_whitelister = ctx.attr.proguard_whitelister[DefaultInfo].files_to_run,
+        source_jar_zipper = ctx.file.source_jar_zipper,
         turbine = ctx.file.turbine,
         turbine_direct = ctx.file.turbine_direct if ctx.attr.enable_turbine_direct else None,
         turbine_jsa = ctx.file.turbine_jsa,
+        turbine_java_runtime = ctx.attr.turbine_java_runtime,
     )
     return [
         platform_common.ToolchainInfo(**kt_jvm_toolchain),
         KtJvmToolchainInfo(**kt_jvm_toolchain),
     ]
 
-_kt_jvm_toolchain_internal = rule(
-    name = "kt_jvm_toolchain",
+kt_jvm_toolchain = rule(
     attrs = dict(
         build_marker = attr.label(
             default = "//tools:build_marker",
@@ -152,6 +157,9 @@
             allow_files = True,
             cfg = "exec",
         ),
+        java_language_version = attr.string(
+            default = "11",
+        ),
         java_runtime = attr.label(
             default = "@bazel_tools//tools/jdk:current_java_runtime",
             cfg = "exec",
@@ -213,6 +221,11 @@
             cfg = "target",
             doc = "The Kotlin runtime libraries grouped into one attribute.",
         ),
+        source_jar_zipper = attr.label(
+            default = "//tools/bin:source_jar_zipper_binary",
+            cfg = "exec",
+            allow_single_file = [".jar"],
+        ),
         turbine = attr.label(
             default = "@bazel_tools//tools/jdk:turbine_direct",
             cfg = "exec",
@@ -227,16 +240,23 @@
             cfg = "exec",
             allow_single_file = True,
         ),
+        turbine_java_runtime = attr.label(
+            cfg = "exec",
+        ),
     ),
     provides = [platform_common.ToolchainInfo],
     implementation = _kt_jvm_toolchain_impl,
 )
 
-def _kt_jvm_toolchain(**kwargs):
-    _kt_jvm_toolchain_internal(
-        jvm_target = select({
-            "//conditions:default": "11",
-        }),
+def _declare(**kwargs):
+    kt_jvm_toolchain(
+        # TODO: use select_java_language_level() after support for Java 8 is dropped
+        jvm_target = select_java_language_version(
+            # The JVM bytecode version to output
+            java8 = "1.8",
+            java11 = "11",
+            java_head = "18",  # https://kotlinlang.org/docs/compiler-reference.html#jvm-target-version
+        ),
         **kwargs
     )
 
@@ -252,6 +272,6 @@
     name = _TYPE.name,
     get = lambda ctx: ctx.toolchains[_TYPE],
     type = str(_TYPE),
-    declare = _kt_jvm_toolchain,
+    declare = _declare,
     attrs = _ATTRS,
 )
diff --git a/tools/BUILD b/tools/BUILD
index 18f1a9a..02f945c 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 # Description: Kotlin building rules tools
+load("//bazel:deploy_jar_freshness_golden_test.bzl", "deploy_jar_freshness_golden_test")
 
 licenses(["notice"])
 
@@ -25,3 +26,9 @@
     cmd = """$(location @bazel_tools//tools/zip:zipper) c $@ "assets/_kotlin=" """,
     tools = ["@bazel_tools//tools/zip:zipper"],
 )
+
+deploy_jar_freshness_golden_test(
+    name = "source_jar_zipper_freshness_test",
+    current_jar = "//tools/bin:source_jar_zipper_binary",
+    newly_built_jar = "//tools/java/com/google/devtools/kotlin:source_jar_zipper_deploy.jar",
+)
diff --git a/tools/bin/BUILD b/tools/bin/BUILD
new file mode 100644
index 0000000..b5bc641
--- /dev/null
+++ b/tools/bin/BUILD
@@ -0,0 +1,22 @@
+# Copyright 2022 Google LLC. 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.
+
+licenses(["notice"])  # Apache 2.0
+
+package(default_visibility = ["//:internal"])
+
+alias(
+    name = "source_jar_zipper_binary",
+    actual = "source_jar_zipper_deploy.jar",
+)
diff --git a/tools/bin/source_jar_zipper_deploy.jar b/tools/bin/source_jar_zipper_deploy.jar
new file mode 100755
index 0000000..5f283fe
--- /dev/null
+++ b/tools/bin/source_jar_zipper_deploy.jar
Binary files differ
diff --git a/tools/java/com/google/devtools/kotlin/BUILD b/tools/java/com/google/devtools/kotlin/BUILD
new file mode 100644
index 0000000..544536b
--- /dev/null
+++ b/tools/java/com/google/devtools/kotlin/BUILD
@@ -0,0 +1,33 @@
+# Copyright 2022 Google LLC. 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.
+
+# Kotlin building rules tools
+
+load("//kotlin:rules.bzl", "kt_jvm_library")
+
+package(default_visibility = ["//:internal"])
+
+licenses(["notice"])
+
+kt_jvm_library(
+    name = "source_jar_zipper_lib",
+    srcs = ["SourceJarZipper.kt"],
+    deps = ["@maven//:info_picocli_picocli"],
+)
+
+java_binary(
+    name = "source_jar_zipper",
+    main_class = "com.google.devtools.kotlin.SourceJarZipperKt",
+    runtime_deps = [":source_jar_zipper_lib"],
+)
diff --git a/tools/java/com/google/devtools/kotlin/SourceJarZipper.kt b/tools/java/com/google/devtools/kotlin/SourceJarZipper.kt
new file mode 100644
index 0000000..ac57efe
--- /dev/null
+++ b/tools/java/com/google/devtools/kotlin/SourceJarZipper.kt
@@ -0,0 +1,274 @@
+/*
+ * * Copyright 2022 Google LLC. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.kotlin
+
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+import kotlin.system.exitProcess
+import picocli.CommandLine
+import picocli.CommandLine.Command
+import picocli.CommandLine.Model.CommandSpec
+import picocli.CommandLine.Option
+import picocli.CommandLine.ParameterException
+import picocli.CommandLine.Parameters
+import picocli.CommandLine.Spec
+
+@Command(
+  name = "source-jar-zipper",
+  subcommands = [Unzip::class, Zip::class, ZipResources::class],
+  description = ["A tool to pack and unpack srcjar files, and to zip resource files"],
+)
+class SourceJarZipper : Runnable {
+  @Spec private lateinit var spec: CommandSpec
+  override fun run() {
+    throw ParameterException(spec.commandLine(), "Specify a command: zip, zip_resources or unzip")
+  }
+}
+
+fun main(args: Array<String>) {
+  val exitCode = CommandLine(SourceJarZipper()).execute(*args)
+  exitProcess(exitCode)
+}
+
+/**
+ * Checks for duplicates and add an entry into [errors] if found any, otherwise adds a pair
+ * of [zipPath] and [sourcePath] to the receiver
+ * @receiver a mutable map of path to path, where keys are relative paths of files inside the
+ * resulting .jar, and values are full paths of files
+ * @param[zipPath] relative path inside the jar, built either from package name
+ * (e.g. package com.google.foo -> com/google/foo/FileName.kt) or by resolving the file name
+ * relatively the directory it came from (e.g. foo/bar/1/2.txt came from foo/bar -> 1/2.txt)
+ * @param[sourcePath] full path of file into its file system
+ * @param[errors] list of strings describing catched errors
+ */
+fun MutableMap<Path, Path>.checkForDuplicatesAndSetFilePathToPathInsideJar(
+  zipPath: Path,
+  sourcePath: Path,
+  errors: MutableList<String>,
+) {
+  val duplicatedSourcePath: Path? = this[zipPath]
+  if (duplicatedSourcePath == null) {
+    this[zipPath] = sourcePath
+  } else {
+    errors.add(
+      "${sourcePath} has the same path inside .jar as ${duplicatedSourcePath}! " +
+        "If it is intended behavior rename one or both of them."
+    )
+  }
+}
+
+fun MutableMap<Path, Path>.writeToStream(
+  zipper: ZipOutputStream,
+  prefix: String = "",
+) {
+  for ((zipPath, sourcePath) in this) {
+    BufferedInputStream(Files.newInputStream(sourcePath)).use { inputStream ->
+      val entry = ZipEntry(Paths.get(prefix).resolve(zipPath).toString())
+      entry.time = 0
+      zipper.putNextEntry(entry)
+      inputStream.copyTo(zipper, bufferSize = 1024)
+    }
+  }
+}
+
+@Command(name = "zip", description = ["Zip source files into a source jar file"])
+class Zip : Runnable {
+
+  @Parameters(index = "0", paramLabel = "outputJar", description = ["Output jar"])
+  lateinit var outputJar: Path
+
+  @Option(
+    names = ["-i", "--ignore_not_allowed_files"],
+    description = ["Ignore not .kt, .java or invalid file paths without raising an exception"],
+  )
+  var ignoreNotAllowedFiles = false
+
+  @Option(
+    names = ["--kotlin_srcs"],
+    split = ",",
+    description = ["Kotlin source files"],
+  )
+  val kotlinSrcs = mutableListOf<Path>()
+
+  @Option(
+    names = ["--common_srcs"],
+    split = ",",
+    description = ["Common source files"],
+  )
+  val commonSrcs = mutableListOf<Path>()
+
+  companion object {
+    const val PACKAGE_SPACE = "package "
+    val PACKAGE_NAME_REGEX = "[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z0-9_]+)*".toRegex()
+  }
+
+  override fun run() {
+    check(kotlinSrcs.isNotEmpty() or commonSrcs.isNotEmpty()) {
+      "Expected at least one source file."
+    }
+
+    // Validating files and getting paths for resulting .jar in one cycle
+    // for each _srcs list
+    val ktZipPathToSourcePath = mutableMapOf<Path, Path>()
+    val commonZipPathToSourcePath = mutableMapOf<Path, Path>()
+    val errors = mutableListOf<String>()
+
+    fun Path.getPackagePath(): Path {
+      this.toFile().bufferedReader().use { stream ->
+        while (true) {
+          val line = stream.readLine() ?: return this.fileName
+
+          if (line.startsWith(PACKAGE_SPACE)) {
+            // Kotlin allows usage of reserved words in package names framing them
+            // with backquote symbol "`"
+            val packageName =
+              line.substring(PACKAGE_SPACE.length).trim().replace(";", "").replace("`", "")
+            if (!PACKAGE_NAME_REGEX.matches(packageName)) {
+              errors.add("${this} contains an invalid package name")
+              return this.fileName
+            }
+            return Paths.get(packageName.replace(".", "/")).resolve(this.fileName)
+          }
+        }
+      }
+    }
+
+    fun Path.validateFile(): Boolean {
+      when {
+        !Files.isRegularFile(this) -> {
+          if (!ignoreNotAllowedFiles) errors.add("${this} is not a file")
+          return false
+        }
+        !this.toString().endsWith(".kt") && !this.toString().endsWith(".java") -> {
+          if (!ignoreNotAllowedFiles) errors.add("${this} is not a Kotlin file")
+          return false
+        }
+        else -> return true
+      }
+    }
+
+    for (sourcePath in kotlinSrcs) {
+      if (sourcePath.validateFile()) {
+        ktZipPathToSourcePath.checkForDuplicatesAndSetFilePathToPathInsideJar(
+          sourcePath.getPackagePath(),
+          sourcePath,
+          errors,
+        )
+      }
+    }
+
+    for (sourcePath in commonSrcs) {
+      if (sourcePath.validateFile()) {
+        commonZipPathToSourcePath.checkForDuplicatesAndSetFilePathToPathInsideJar(
+          sourcePath.getPackagePath(),
+          sourcePath,
+          errors,
+        )
+      }
+    }
+
+    if (ktZipPathToSourcePath.isEmpty() && commonZipPathToSourcePath.isEmpty()) {
+      errors.add("Expected at least one valid source file .kt or .java")
+    }
+    check(errors.isEmpty()) { errors.joinToString("\n") }
+
+    ZipOutputStream(BufferedOutputStream(Files.newOutputStream(outputJar))).use { zipper ->
+      commonZipPathToSourcePath.writeToStream(zipper, "common-srcs")
+      ktZipPathToSourcePath.writeToStream(zipper)
+    }
+  }
+}
+
+@Command(name = "unzip", description = ["Unzip a jar archive into a specified directory"])
+class Unzip : Runnable {
+
+  @Parameters(index = "0", paramLabel = "inputJar", description = ["Jar archive to unzip"])
+  lateinit var inputJar: Path
+
+  @Parameters(index = "1", paramLabel = "outputDir", description = ["Output directory"])
+  lateinit var outputDir: Path
+
+  override fun run() {
+    ZipInputStream(Files.newInputStream(inputJar)).use { unzipper ->
+      while (true) {
+        val zipEntry: ZipEntry? = unzipper.nextEntry
+        if (zipEntry == null) return
+
+        val entryName = zipEntry.name
+        check(!entryName.contains("./")) { "Cannot unpack srcjar with relative path ${entryName}" }
+
+        if (!entryName.endsWith(".kt") && !entryName.endsWith(".java")) continue
+
+        val entryPath = outputDir.resolve(entryName)
+        if (!Files.exists(entryPath.parent)) Files.createDirectories(entryPath.parent)
+        Files.copy(unzipper, entryPath, StandardCopyOption.REPLACE_EXISTING)
+      }
+    }
+  }
+}
+
+@Command(name = "zip_resources", description = ["Zip resources"])
+class ZipResources : Runnable {
+
+  @Parameters(index = "0", paramLabel = "outputJar", description = ["Output jar"])
+  lateinit var outputJar: Path
+
+  @Option(
+    names = ["--input_dirs"],
+    split = ",",
+    description = ["Input files directories"],
+    required = true,
+  )
+  val inputDirs = mutableListOf<Path>()
+
+  override fun run() {
+    val filePathToOutputPath = mutableMapOf<Path, Path>()
+    val errors = mutableListOf<String>()
+
+    // inputDirs has filter checking if the dir exists, because some empty dirs generated by blaze
+    // may not exist from Kotlin compiler's side. It turned out to be safer to apply a filter then
+    // to rely that generated directories are always directories, not just path names
+    for (dirPath in inputDirs.filter { curDirPath -> Files.exists(curDirPath) }) {
+      if (!Files.isDirectory(dirPath)) {
+        errors.add("${dirPath} is not a directory")
+      } else {
+        Files.walk(dirPath)
+          .filter { fileOrDir -> !Files.isDirectory(fileOrDir) }
+          .forEach { filePath ->
+            filePathToOutputPath.checkForDuplicatesAndSetFilePathToPathInsideJar(
+              dirPath.relativize(filePath),
+              filePath,
+              errors
+            )
+          }
+      }
+    }
+
+    check(errors.isEmpty()) { errors.joinToString("\n") }
+
+    ZipOutputStream(BufferedOutputStream(Files.newOutputStream(outputJar))).use { zipper ->
+      filePathToOutputPath.writeToStream(zipper)
+    }
+  }
+}