Copy the Bazel java rules out of builtins

PiperOrigin-RevId: 672907957
Change-Id: I8f14924015857a378ded08929b6966047f042d10
diff --git a/java/bazel/rules/bazel_java_binary.bzl b/java/bazel/rules/bazel_java_binary.bzl
new file mode 100644
index 0000000..9317cc2
--- /dev/null
+++ b/java/bazel/rules/bazel_java_binary.bzl
@@ -0,0 +1,576 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Bazel java_binary rule"""
+
+load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain")
+load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/common:java_semantics.bzl", "semantics")
+load(
+    "//java/common/rules:android_lint.bzl",
+    "android_lint_subrule",
+)
+load("//java/common/rules:java_binary.bzl", "BASE_TEST_ATTRIBUTES", "BASIC_JAVA_BINARY_ATTRIBUTES", "basic_java_binary")
+load("//java/common/rules:java_binary_deploy_jar.bzl", "create_deploy_archives")
+load("//java/common/rules:java_helper.bzl", "helper")
+load("//java/common/rules:rule_util.bzl", "merge_attrs")
+load("//third_party/bazel_skylib/lib:paths.bzl", "paths")
+
+visibility("private")
+
+def _bazel_java_binary_impl(ctx):
+    return _bazel_base_binary_impl(ctx, is_test_rule_class = False) + helper.executable_providers(ctx)
+
+def _bazel_java_test_impl(ctx):
+    return _bazel_base_binary_impl(ctx, is_test_rule_class = True) + helper.test_providers(ctx)
+
+def _bazel_base_binary_impl(ctx, is_test_rule_class):
+    deps = _collect_all_targets_as_deps(ctx, classpath_type = "compile_only")
+    runtime_deps = _collect_all_targets_as_deps(ctx)
+
+    main_class = _check_and_get_main_class(ctx)
+    coverage_main_class = main_class
+    coverage_config = helper.get_coverage_config(ctx, _get_coverage_runner(ctx))
+    if coverage_config:
+        main_class = coverage_config.main_class
+
+    launcher_info = _get_launcher_info(ctx)
+
+    executable = _get_executable(ctx)
+
+    feature_config = helper.get_feature_config(ctx)
+    if feature_config:
+        strip_as_default = helper.should_strip_as_default(ctx, feature_config)
+    else:
+        # No C++ toolchain available.
+        strip_as_default = False
+
+    providers, default_info, jvm_flags = basic_java_binary(
+        ctx,
+        deps,
+        runtime_deps,
+        ctx.files.resources,
+        main_class,
+        coverage_main_class,
+        coverage_config,
+        launcher_info,
+        executable,
+        strip_as_default,
+        is_test_rule_class = is_test_rule_class,
+    )
+
+    if ctx.attr.use_testrunner:
+        if semantics.find_java_runtime_toolchain(ctx).version >= 17:
+            jvm_flags.append("-Djava.security.manager=allow")
+        test_class = ctx.attr.test_class if hasattr(ctx.attr, "test_class") else ""
+        if test_class == "":
+            test_class = helper.primary_class(ctx)
+        if test_class == None:
+            fail("cannot determine test class. You might want to rename the " +
+                 " rule or add a 'test_class' attribute.")
+        jvm_flags.extend([
+            "-ea",
+            "-Dbazel.test_suite=" + helper.shell_escape(test_class),
+        ])
+
+    java_attrs = providers["InternalDeployJarInfo"].java_attrs
+
+    if executable:
+        _create_stub(ctx, java_attrs, launcher_info.launcher, executable, jvm_flags, main_class, coverage_main_class)
+
+    runfiles = default_info.runfiles
+
+    if executable:
+        runtime_toolchain = semantics.find_java_runtime_toolchain(ctx)
+        runfiles = runfiles.merge(ctx.runfiles(transitive_files = runtime_toolchain.files))
+
+    test_support = helper.get_test_support(ctx)
+    if test_support:
+        runfiles = runfiles.merge(test_support[DefaultInfo].default_runfiles)
+
+    providers["DefaultInfo"] = DefaultInfo(
+        files = default_info.files,
+        runfiles = runfiles,
+        executable = default_info.executable,
+    )
+
+    info = providers.pop("InternalDeployJarInfo")
+    create_deploy_archives(
+        ctx,
+        info.java_attrs,
+        launcher_info,
+        main_class,
+        coverage_main_class,
+        info.strip_as_default,
+        add_exports = info.add_exports,
+        add_opens = info.add_opens,
+    )
+
+    return providers.values()
+
+def _get_coverage_runner(ctx):
+    if ctx.configuration.coverage_enabled and ctx.attr.create_executable:
+        toolchain = semantics.find_java_toolchain(ctx)
+        runner = toolchain.jacocorunner
+        if not runner:
+            fail("jacocorunner not set in java_toolchain: %s" % toolchain.label)
+        runner_jar = runner.executable
+
+        # wrap the jar in JavaInfo so we can add it to deps for java_common.compile()
+        return JavaInfo(output_jar = runner_jar, compile_jar = runner_jar)
+
+    return None
+
+def _collect_all_targets_as_deps(ctx, classpath_type = "all"):
+    deps = helper.collect_all_targets_as_deps(ctx, classpath_type = classpath_type)
+
+    if classpath_type == "compile_only" and ctx.fragments.java.enforce_explicit_java_test_deps():
+        return deps
+
+    test_support = helper.get_test_support(ctx)
+    if test_support:
+        deps.append(test_support)
+    return deps
+
+def _check_and_get_main_class(ctx):
+    create_executable = ctx.attr.create_executable
+    main_class = _get_main_class(ctx)
+
+    if not create_executable and main_class:
+        fail("main class must not be specified when executable is not created")
+    if create_executable and not main_class:
+        if not ctx.attr.srcs:
+            fail("need at least one of 'main_class' or Java source files")
+        main_class = helper.primary_class(ctx)
+        if main_class == None:
+            fail("main_class was not provided and cannot be inferred: " +
+                 "source path doesn't include a known root (java, javatests, src, testsrc)")
+
+    return _get_main_class(ctx)
+
+def _get_main_class(ctx):
+    if not ctx.attr.create_executable:
+        return None
+
+    main_class = _get_main_class_from_rule(ctx)
+
+    if main_class == "":
+        main_class = helper.primary_class(ctx)
+    return main_class
+
+def _get_main_class_from_rule(ctx):
+    main_class = ctx.attr.main_class
+    if main_class:
+        return main_class
+    if ctx.attr.use_testrunner:
+        return "com.google.testing.junit.runner.BazelTestRunner"
+    return main_class
+
+def _get_launcher_info(ctx):
+    launcher = helper.launcher_artifact_for_target(ctx)
+    return struct(
+        launcher = launcher,
+        unstripped_launcher = launcher,
+        runfiles = [],
+        runtime_jars = [],
+        jvm_flags = [],
+        classpath_resources = [],
+    )
+
+def _get_executable(ctx):
+    if not ctx.attr.create_executable:
+        return None
+    executable_name = ctx.label.name
+    if helper.is_target_platform_windows(ctx):
+        executable_name = executable_name + ".exe"
+
+    return ctx.actions.declare_file(executable_name)
+
+def _create_stub(ctx, java_attrs, launcher, executable, jvm_flags, main_class, coverage_main_class):
+    java_runtime_toolchain = semantics.find_java_runtime_toolchain(ctx)
+    java_executable = helper.get_java_executable(ctx, java_runtime_toolchain, launcher)
+    workspace_name = ctx.workspace_name
+    workspace_prefix = workspace_name + ("/" if workspace_name else "")
+    runfiles_enabled = helper.runfiles_enabled(ctx)
+    coverage_enabled = ctx.configuration.coverage_enabled
+
+    test_support = helper.get_test_support(ctx)
+    test_support_jars = test_support[JavaInfo].transitive_runtime_jars if test_support else depset()
+    classpath = depset(
+        transitive = [
+            java_attrs.runtime_classpath,
+            test_support_jars if ctx.fragments.java.enforce_explicit_java_test_deps() else depset(),
+        ],
+    )
+
+    if helper.is_target_platform_windows(ctx):
+        jvm_flags_for_launcher = []
+        for flag in jvm_flags:
+            jvm_flags_for_launcher.extend(ctx.tokenize(flag))
+        return _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, executable)
+
+    if runfiles_enabled:
+        prefix = "" if helper.is_absolute_target_platform_path(ctx, java_executable) else "${JAVA_RUNFILES}/"
+        java_bin = "JAVABIN=${JAVABIN:-" + prefix + java_executable + "}"
+    else:
+        java_bin = "JAVABIN=${JAVABIN:-$(rlocation " + java_executable + ")}"
+
+    td = ctx.actions.template_dict()
+    td.add_joined(
+        "%classpath%",
+        classpath,
+        map_each = lambda file: _format_classpath_entry(runfiles_enabled, workspace_prefix, file),
+        join_with = ctx.configuration.host_path_separator,
+        format_joined = "\"%s\"",
+        allow_closure = True,
+    )
+
+    ctx.actions.expand_template(
+        template = ctx.file._stub_template,
+        output = executable,
+        substitutions = {
+            "%runfiles_manifest_only%": "" if runfiles_enabled else "1",
+            "%workspace_prefix%": workspace_prefix,
+            "%javabin%": java_bin,
+            "%needs_runfiles%": "0" if helper.is_absolute_target_platform_path(ctx, java_runtime_toolchain.java_executable_exec_path) else "1",
+            "%set_jacoco_metadata%": "",
+            "%set_jacoco_main_class%": "export JACOCO_MAIN_CLASS=" + coverage_main_class if coverage_enabled else "",
+            "%set_jacoco_java_runfiles_root%": "export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + workspace_prefix if coverage_enabled else "",
+            "%java_start_class%": helper.shell_escape(main_class),
+            "%jvm_flags%": " ".join(jvm_flags),
+        },
+        computed_substitutions = td,
+        is_executable = True,
+    )
+    return executable
+
+def _format_classpath_entry(runfiles_enabled, workspace_prefix, file):
+    if runfiles_enabled:
+        return "${RUNPATH}" + file.short_path
+
+    return "$(rlocation " + paths.normalize(workspace_prefix + file.short_path) + ")"
+
+def _create_windows_exe_launcher(ctx, java_executable, classpath, main_class, jvm_flags_for_launcher, runfiles_enabled, executable):
+    launch_info = ctx.actions.args().use_param_file("%s", use_always = True).set_param_file_format("multiline")
+    launch_info.add("binary_type=Java")
+    launch_info.add(ctx.workspace_name, format = "workspace_name=%s")
+    launch_info.add("1" if runfiles_enabled else "0", format = "symlink_runfiles_enabled=%s")
+    launch_info.add(java_executable, format = "java_bin_path=%s")
+    launch_info.add(main_class, format = "java_start_class=%s")
+    launch_info.add_joined(classpath, map_each = _short_path, join_with = ";", format_joined = "classpath=%s", omit_if_empty = False)
+    launch_info.add_joined(jvm_flags_for_launcher, join_with = "\t", format_joined = "jvm_flags=%s", omit_if_empty = False)
+    launch_info.add(semantics.find_java_runtime_toolchain(ctx).java_home_runfiles_path, format = "jar_bin_path=%s/bin/jar.exe")
+
+    # TODO(b/295221112): Change to use the "launcher" attribute (only windows use a fixed _launcher attribute)
+    launcher_artifact = ctx.executable._launcher
+    ctx.actions.run(
+        executable = ctx.executable._windows_launcher_maker,
+        inputs = [launcher_artifact],
+        outputs = [executable],
+        arguments = [launcher_artifact.path, launch_info, executable.path],
+        use_default_shell_env = True,
+    )
+    return executable
+
+def _short_path(file):
+    return file.short_path
+
+def _compute_test_support(use_testrunner):
+    return Label(semantics.JAVA_TEST_RUNNER_LABEL) if use_testrunner else None
+
+def _make_binary_rule(implementation, *, doc, attrs, executable = False, test = False, initializer = None):
+    return rule(
+        implementation = implementation,
+        initializer = initializer,
+        doc = doc,
+        attrs = attrs,
+        executable = executable,
+        test = test,
+        fragments = ["cpp", "java"],
+        provides = [JavaInfo],
+        toolchains = [semantics.JAVA_TOOLCHAIN] + use_cc_toolchain() + (
+            [semantics.JAVA_RUNTIME_TOOLCHAIN] if executable or test else []
+        ),
+        # TODO(hvd): replace with filegroups?
+        outputs = {
+            "classjar": "%{name}.jar",
+            "sourcejar": "%{name}-src.jar",
+            "deploysrcjar": "%{name}_deploy-src.jar",
+            "deployjar": "%{name}_deploy.jar",
+            "unstrippeddeployjar": "%{name}_deploy.jar.unstripped",
+        },
+        exec_groups = {
+            "cpp_link": exec_group(toolchains = use_cc_toolchain()),
+        },
+        subrules = [android_lint_subrule],
+    )
+
+_BASE_BINARY_ATTRS = merge_attrs(
+    BASIC_JAVA_BINARY_ATTRIBUTES,
+    {
+        "resource_strip_prefix": attr.string(
+            doc = """
+The path prefix to strip from Java resources.
+<p>
+If specified, this path prefix is stripped from every file in the <code>resources</code>
+attribute. It is an error for a resource file not to be under this directory. If not
+specified (the default), the path of resource file is determined according to the same
+logic as the Java package of source files. For example, a source file at
+<code>stuff/java/foo/bar/a.txt</code> will be located at <code>foo/bar/a.txt</code>.
+</p>
+            """,
+        ),
+        "_test_support": attr.label(default = _compute_test_support),
+        "_launcher": attr.label(
+            cfg = "exec",
+            executable = True,
+            default = "@bazel_tools//tools/launcher:launcher",
+        ),
+        "_windows_launcher_maker": attr.label(
+            default = "@bazel_tools//tools/launcher:launcher_maker",
+            cfg = "exec",
+            executable = True,
+        ),
+    },
+)
+
+def make_java_binary(executable):
+    return _make_binary_rule(
+        _bazel_java_binary_impl,
+        doc = """
+<p>
+  Builds a Java archive ("jar file"), plus a wrapper shell script with the same name as the rule.
+  The wrapper shell script uses a classpath that includes, among other things, a jar file for each
+  library on which the binary depends. When running the wrapper shell script, any nonempty
+  <code>JAVABIN</code> environment variable will take precedence over the version specified via
+  Bazel's <code>--java_runtime_version</code> flag.
+</p>
+<p>
+  The wrapper script accepts several unique flags. Refer to
+  <code>//src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt</code>
+  for a list of configurable flags and environment variables accepted by the wrapper.
+</p>
+
+<h4 id="java_binary_implicit_outputs">Implicit output targets</h4>
+<ul>
+  <li><code><var>name</var>.jar</code>: A Java archive, containing the class files and other
+    resources corresponding to the binary's direct dependencies.</li>
+  <li><code><var>name</var>-src.jar</code>: An archive containing the sources ("source
+    jar").</li>
+  <li><code><var>name</var>_deploy.jar</code>: A Java archive suitable for deployment (only
+    built if explicitly requested).
+    <p>
+      Building the <code>&lt;<var>name</var>&gt;_deploy.jar</code> target for your rule
+      creates a self-contained jar file with a manifest that allows it to be run with the
+      <code>java -jar</code> command or with the wrapper script's <code>--singlejar</code>
+      option. Using the wrapper script is preferred to <code>java -jar</code> because it
+      also passes the <a href="${link java_binary.jvm_flags}">JVM flags</a> and the options
+      to load native libraries.
+    </p>
+    <p>
+      The deploy jar contains all the classes that would be found by a classloader that
+      searched the classpath from the binary's wrapper script from beginning to end. It also
+      contains the native libraries needed for dependencies. These are automatically loaded
+      into the JVM at runtime.
+    </p>
+    <p>If your target specifies a <a href="#java_binary.launcher">launcher</a>
+      attribute, then instead of being a normal JAR file, the _deploy.jar will be a
+      native binary. This will contain the launcher plus any native (C++) dependencies of
+      your rule, all linked into a static binary. The actual jar file's bytes will be
+      appended to that native binary, creating a single binary blob containing both the
+      executable and the Java code. You can execute the resulting jar file directly
+      like you would execute any native binary.</p>
+  </li>
+  <li><code><var>name</var>_deploy-src.jar</code>: An archive containing the sources
+    collected from the transitive closure of the target. These will match the classes in the
+    <code>deploy.jar</code> except where jars have no matching source jar.</li>
+</ul>
+
+<p>
+It is good practice to use the name of the source file that is the main entry point of the
+application (minus the extension). For example, if your entry point is called
+<code>Main.java</code>, then your name could be <code>Main</code>.
+</p>
+
+<p>
+  A <code>deps</code> attribute is not allowed in a <code>java_binary</code> rule without
+  <a href="${link java_binary.srcs}"><code>srcs</code></a>; such a rule requires a
+  <a href="${link java_binary.main_class}"><code>main_class</code></a> provided by
+  <a href="${link java_binary.runtime_deps}"><code>runtime_deps</code></a>.
+</p>
+
+<p>The following code snippet illustrates a common mistake:</p>
+
+<pre class="code">
+<code class="lang-starlark">
+java_binary(
+    name = "DontDoThis",
+    srcs = [
+        <var>...</var>,
+        <code class="deprecated">"GeneratedJavaFile.java"</code>,  # a generated .java file
+    ],
+    deps = [<code class="deprecated">":generating_rule",</code>],  # rule that generates that file
+)
+</code>
+</pre>
+
+<p>Do this instead:</p>
+
+<pre class="code">
+<code class="lang-starlark">
+java_binary(
+    name = "DoThisInstead",
+    srcs = [
+        <var>...</var>,
+        ":generating_rule",
+    ],
+)
+</code>
+</pre>
+        """,
+        attrs = merge_attrs(
+            _BASE_BINARY_ATTRS,
+            ({} if executable else {
+                "args": attr.string_list(),
+                "output_licenses": attr.string_list(),
+            }),
+        ),
+        executable = executable,
+    )
+
+java_binary = make_java_binary(executable = True)
+
+def _java_test_initializer(**kwargs):
+    if "stamp" in kwargs and type(kwargs["stamp"]) == type(True):
+        kwargs["stamp"] = 1 if kwargs["stamp"] else 0
+    if "use_launcher" in kwargs and not kwargs["use_launcher"]:
+        kwargs["launcher"] = None
+    else:
+        # If launcher is not set or None, set it to config flag
+        if "launcher" not in kwargs or not kwargs["launcher"]:
+            kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL
+    return kwargs
+
+java_test = _make_binary_rule(
+    _bazel_java_test_impl,
+    doc = """
+<p>
+A <code>java_test()</code> rule compiles a Java test. A test is a binary wrapper around your
+test code. The test runner's main method is invoked instead of the main class being compiled.
+</p>
+
+<h4 id="java_test_implicit_outputs">Implicit output targets</h4>
+<ul>
+  <li><code><var>name</var>.jar</code>: A Java archive.</li>
+  <li><code><var>name</var>_deploy.jar</code>: A Java archive suitable
+    for deployment. (Only built if explicitly requested.) See the description of the
+    <code><var>name</var>_deploy.jar</code> output from
+    <a href="#java_binary">java_binary</a> for more details.</li>
+</ul>
+
+<p>
+See the section on <code>java_binary()</code> arguments. This rule also
+supports all <a href="${link common-definitions#common-attributes-tests}">attributes common
+to all test rules (*_test)</a>.
+</p>
+
+<h4 id="java_test_examples">Examples</h4>
+
+<pre class="code">
+<code class="lang-starlark">
+
+java_library(
+    name = "tests",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//java/com/foo/base:testResources",
+        "//java/com/foo/testing/util",
+    ],
+)
+
+java_test(
+    name = "AllTests",
+    size = "small",
+    runtime_deps = [
+        ":tests",
+        "//util/mysql",
+    ],
+)
+</code>
+</pre>
+    """,
+    attrs = merge_attrs(
+        BASE_TEST_ATTRIBUTES,
+        _BASE_BINARY_ATTRS,
+        {
+            "_lcov_merger": attr.label(
+                cfg = "exec",
+                default = configuration_field(
+                    fragment = "coverage",
+                    name = "output_generator",
+                ),
+            ),
+            "_collect_cc_coverage": attr.label(
+                cfg = "exec",
+                allow_single_file = True,
+                default = "@bazel_tools//tools/test:collect_cc_coverage",
+            ),
+        },
+        override_attrs = {
+            "use_testrunner": attr.bool(
+                default = True,
+                doc = semantics.DOCS.for_attribute("use_testrunner") + """
+<br/>
+You can use this to override the default
+behavior, which is to use test runner for
+<code>java_test</code> rules,
+and not use it for <code>java_binary</code> rules.  It is unlikely
+you will want to do this.  One use is for <code>AllTest</code>
+rules that are invoked by another rule (to set up a database
+before running the tests, for example).  The <code>AllTest</code>
+rule must be declared as a <code>java_binary</code>, but should
+still use the test runner as its main entry point.
+
+The name of a test runner class can be overridden with <code>main_class</code> attribute.
+                """,
+            ),
+            "stamp": attr.int(
+                default = 0,
+                values = [-1, 0, 1],
+                doc = """
+Whether to encode build information into the binary. Possible values:
+<ul>
+<li>
+  <code>stamp = 1</code>: Always stamp the build information into the binary, even in
+  <a href="${link user-manual#flag--stamp}"><code>--nostamp</code></a> builds. <b>This
+  setting should be avoided</b>, since it potentially kills remote caching for the
+  binary and any downstream actions that depend on it.
+</li>
+<li>
+  <code>stamp = 0</code>: Always replace build information by constant values. This
+  gives good build result caching.
+</li>
+<li>
+  <code>stamp = -1</code>: Embedding of build information is controlled by the
+  <a href="${link user-manual#flag--stamp}"><code>--[no]stamp</code></a> flag.
+</li>
+</ul>
+<p>Stamped binaries are <em>not</em> rebuilt unless their dependencies change.</p>
+                """,
+            ),
+        },
+        remove_attrs = ["deploy_env"],
+    ),
+    test = True,
+    initializer = _java_test_initializer,
+)
diff --git a/java/bazel/rules/bazel_java_binary_nonexec.bzl b/java/bazel/rules/bazel_java_binary_nonexec.bzl
new file mode 100644
index 0000000..82a7643
--- /dev/null
+++ b/java/bazel/rules/bazel_java_binary_nonexec.bzl
@@ -0,0 +1,28 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Defines a java_binary rule class that is non-executable.
+
+There are three physical rule classes for java_binary and we want all of them
+to have a name string of "java_binary" because various tooling expects that.
+But we also need the rule classes to be defined in separate files. That way the
+hash of their bzl environments will be different. See http://b/226379109,
+specifically #20, for details.
+"""
+
+load(":bazel_java_binary.bzl", "make_java_binary")
+
+visibility("private")
+
+java_binary = make_java_binary(executable = False)
diff --git a/java/bazel/rules/bazel_java_binary_wrapper.bzl b/java/bazel/rules/bazel_java_binary_wrapper.bzl
new file mode 100644
index 0000000..31696bc
--- /dev/null
+++ b/java/bazel/rules/bazel_java_binary_wrapper.bzl
@@ -0,0 +1,45 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Macro encapsulating the java_binary implementation
+
+This is needed since the `executable` nature of the target must be computed from
+the supplied value of the `create_executable` attribute.
+"""
+
+load("//java/common:java_common.bzl", "java_common")
+load(
+    "//java/common/rules:java_binary_wrapper.bzl",
+    "register_java_binary_rules",
+    "register_legacy_java_binary_rules",
+)
+load(":bazel_java_binary.bzl", java_bin_exec = "java_binary")
+load(":bazel_java_binary_nonexec.bzl", java_bin_nonexec = "java_binary")
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+
+visibility("private")
+
+def java_binary(**kwargs):
+    if _java_common_internal.incompatible_disable_non_executable_java_binary():
+        register_java_binary_rules(
+            java_bin_exec,
+            **kwargs
+        )
+    else:
+        register_legacy_java_binary_rules(
+            java_bin_exec,
+            java_bin_nonexec,
+            **kwargs
+        )
diff --git a/java/common/java_semantics.bzl b/java/common/java_semantics.bzl
new file mode 100644
index 0000000..d8bf824
--- /dev/null
+++ b/java/common/java_semantics.bzl
@@ -0,0 +1,107 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Bazel Java Semantics"""
+
+load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper")
+
+visibility(["//java/..."])
+
+def _find_java_toolchain(ctx):
+    return ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"].java
+
+def _find_java_runtime_toolchain(ctx):
+    return ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"].java_runtime
+
+def _get_default_resource_path(path, segment_extractor):
+    # Look for src/.../resources to match Maven repository structure.
+    segments = path.split("/")
+    for idx in range(0, len(segments) - 2):
+        if segments[idx] == "src" and segments[idx + 2] == "resources":
+            return "/".join(segments[idx + 3:])
+    java_segments = segment_extractor(path)
+    return "/".join(java_segments) if java_segments != None else path
+
+def _compatible_javac_options(*_args):
+    return depset()
+
+def _check_java_info_opens_exports():
+    pass
+
+_DOCS = struct(
+    ATTRS = {
+        "resources": """
+<p>
+If resources are specified, they will be bundled in the jar along with the usual
+<code>.class</code> files produced by compilation. The location of the resources inside
+of the jar file is determined by the project structure. Bazel first looks for Maven's
+<a href="https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html">standard directory layout</a>,
+(a "src" directory followed by a "resources" directory grandchild). If that is not
+found, Bazel then looks for the topmost directory named "java" or "javatests" (so, for
+example, if a resource is at <code>&lt;workspace root&gt;/x/java/y/java/z</code>, the
+path of the resource will be <code>y/java/z</code>. This heuristic cannot be overridden,
+however, the <code>resource_strip_prefix</code> attribute can be used to specify a
+specific alternative directory for resource files.
+        """,
+        "use_testrunner": """
+Use the test runner (by default
+<code>com.google.testing.junit.runner.BazelTestRunner</code>) class as the
+main entry point for a Java program, and provide the test class
+to the test runner as a value of <code>bazel.test_suite</code>
+system property.
+        """,
+    },
+)
+
+tokenize_javacopts = cc_helper.tokenize
+
+PLATFORMS_ROOT = "@platforms//"
+
+semantics = struct(
+    JAVA_TOOLCHAIN_LABEL = "@bazel_tools//tools/jdk:current_java_toolchain",
+    JAVA_TOOLCHAIN_TYPE = "@bazel_tools//tools/jdk:toolchain_type",
+    JAVA_TOOLCHAIN = config_common.toolchain_type("@bazel_tools//tools/jdk:toolchain_type", mandatory = True),
+    find_java_toolchain = _find_java_toolchain,
+    JAVA_RUNTIME_TOOLCHAIN_TYPE = "@bazel_tools//tools/jdk:runtime_toolchain_type",
+    JAVA_RUNTIME_TOOLCHAIN = config_common.toolchain_type("@bazel_tools//tools/jdk:runtime_toolchain_type", mandatory = True),
+    find_java_runtime_toolchain = _find_java_runtime_toolchain,
+    JAVA_PLUGINS_FLAG_ALIAS_LABEL = "@bazel_tools//tools/jdk:java_plugins_flag_alias",
+    EXTRA_SRCS_TYPES = [],
+    ALLOWED_RULES_IN_DEPS = [
+        "cc_binary",  # NB: linkshared=1
+        "cc_library",
+        "genrule",
+        "genproto",  # TODO(bazel-team): we should filter using providers instead (starlark rule).
+        "java_import",
+        "java_library",
+        "java_proto_library",
+        "java_lite_proto_library",
+        "proto_library",
+        "sh_binary",
+        "sh_library",
+    ],
+    ALLOWED_RULES_IN_DEPS_WITH_WARNING = [],
+    LINT_PROGRESS_MESSAGE = "Running Android Lint for: %{label}",
+    JAVA_STUB_TEMPLATE_LABEL = "@bazel_tools//tools/jdk:java_stub_template.txt",
+    BUILD_INFO_TRANSLATOR_LABEL = "@bazel_tools//tools/build_defs/build_info:java_build_info",
+    JAVA_TEST_RUNNER_LABEL = "@bazel_tools//tools/jdk:TestRunner",
+    IS_BAZEL = True,
+    get_default_resource_path = _get_default_resource_path,
+    compatible_javac_options = _compatible_javac_options,
+    LAUNCHER_FLAG_LABEL = Label("@bazel_tools//tools/jdk:launcher_flag_alias"),
+    PROGUARD_ALLOWLISTER_LABEL = "@bazel_tools//tools/jdk:proguard_whitelister",
+    check_java_info_opens_exports = _check_java_info_opens_exports,
+    DOCS = struct(
+        for_attribute = lambda name: _DOCS.ATTRS.get(name, ""),
+    ),
+)
diff --git a/java/common/rules/BUILD b/java/common/rules/BUILD
new file mode 100644
index 0000000..ffd0fb0
--- /dev/null
+++ b/java/common/rules/BUILD
@@ -0,0 +1 @@
+package(default_visibility = ["//visibility:public"])
diff --git a/java/common/rules/android_lint.bzl b/java/common/rules/android_lint.bzl
new file mode 100644
index 0000000..9d23681
--- /dev/null
+++ b/java/common/rules/android_lint.bzl
@@ -0,0 +1,145 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Creates the android lint action for java rules"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+load("//java/common/rules:java_helper.bzl", "helper")
+
+visibility(["//java/..."])
+
+def _tokenize_opts(opts_depset):
+    return helper.tokenize_javacopts(ctx = None, opts = opts_depset)
+
+def _android_lint_action(ctx, source_files, source_jars, compilation_info):
+    """
+    Creates an action that runs Android lint against Java source files.
+
+    You need to add `ANDROID_LINT_IMPLICIT_ATTRS` to any rule or aspect using this call.
+
+    To lint generated source jars (java_info.java_outputs.gen_source_jar)
+    add them to the `source_jar` parameter.
+
+    `compilation_info` parameter should supply the classpath and Javac options
+    that were used during Java compilation.
+
+    The Android lint tool is obtained from Java toolchain.
+
+    Args:
+      ctx: (RuleContext) Used to register the action.
+      source_files: (list[File]) A list of .java source files
+      source_jars: (list[File])  A list of .jar or .srcjar files containing
+        source files. It should also include generated source jars.
+      compilation_info: (struct) Information about compilation.
+
+    Returns:
+      (None|File) The Android lint output file or None if no source files were
+      present.
+    """
+
+    # assuming that linting is enabled for all java rules i.e.
+    # --experimental_limit_android_lint_to_android_constrained_java=false
+
+    # --experimental_run_android_lint_on_java_rules= is checked in basic_java_library.bzl
+
+    if not (source_files or source_jars):
+        return None
+
+    toolchain = semantics.find_java_toolchain(ctx)
+    java_runtime = toolchain.java_runtime
+    linter = toolchain._android_linter
+    if not linter:
+        # TODO(hvd): enable after enabling in tests
+        # fail("android linter not set in java_toolchain")
+        return None
+
+    args = ctx.actions.args()
+
+    executable = linter.tool.executable
+    transitive_inputs = []
+    if executable.extension != "jar":
+        tools = [linter.tool]
+        transitive_inputs.append(linter.data)
+        args_list = [args]
+    else:
+        jvm_args = ctx.actions.args()
+        jvm_args.add_all(toolchain.jvm_opt)
+        jvm_args.add_all(linter.jvm_opts)
+        jvm_args.add("-jar", executable)
+        executable = java_runtime.java_executable_exec_path
+        tools = [java_runtime.files, linter.tool.executable]
+        transitive_inputs.append(linter.data)
+        args_list = [jvm_args, args]
+
+    classpath = compilation_info.compilation_classpath
+
+    # TODO(hvd): get from toolchain if we need this - probably android only
+    bootclasspath_aux = []
+    if bootclasspath_aux:
+        classpath = depset(transitive = [classpath, bootclasspath_aux])
+    transitive_inputs.append(classpath)
+
+    bootclasspath = toolchain.bootclasspath
+    transitive_inputs.append(bootclasspath)
+
+    transitive_inputs.append(compilation_info.plugins.processor_jars)
+    transitive_inputs.append(compilation_info.plugins.processor_data)
+    args.add_all("--sources", source_files)
+    args.add_all("--source_jars", source_jars)
+    args.add_all("--bootclasspath", bootclasspath)
+    args.add_all("--classpath", classpath)
+    args.add_all("--lint_rules", compilation_info.plugins.processor_jars)
+    args.add("--target_label", ctx.label)
+
+    javac_opts = compilation_info.javac_options
+    if javac_opts:
+        # wrap in a list so that map_each passes the depset to _tokenize_opts
+        args.add_all("--javacopts", [javac_opts], map_each = _tokenize_opts)
+        args.add("--")
+
+    args.add("--lintopts")
+    args.add_all(linter.lint_opts)
+
+    for package_config in linter.package_config:
+        if package_config.matches(ctx.label):
+            # wrap in a list so that map_each passes the depset to _tokenize_opts
+            package_opts = [package_config.javac_opts]
+            args.add_all(package_opts, map_each = _tokenize_opts)
+            transitive_inputs.append(package_config.data)
+
+    android_lint_out = ctx.actions.declare_file("%s_android_lint_output.xml" % ctx.label.name)
+    args.add("--xml", android_lint_out)
+
+    args.set_param_file_format(format = "multiline")
+    args.use_param_file(param_file_arg = "@%s", use_always = True)
+    ctx.actions.run(
+        mnemonic = "AndroidLint",
+        progress_message = semantics.LINT_PROGRESS_MESSAGE,
+        executable = executable,
+        inputs = depset(
+            # TODO(b/213551463) benchmark using a transitive depset instead
+            source_files + source_jars,
+            transitive = transitive_inputs,
+        ),
+        outputs = [android_lint_out],
+        tools = tools,
+        arguments = args_list,
+        execution_requirements = {"supports-workers": "1"},
+    )
+    return android_lint_out
+
+android_lint_subrule = subrule(
+    implementation = _android_lint_action,
+    toolchains = [semantics.JAVA_TOOLCHAIN_TYPE],
+)
diff --git a/java/common/rules/basic_java_library.bzl b/java/common/rules/basic_java_library.bzl
new file mode 100644
index 0000000..221baca
--- /dev/null
+++ b/java/common/rules/basic_java_library.bzl
@@ -0,0 +1,282 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Common code for reuse across java_* rules
+"""
+
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//java/common:java_common.bzl", "java_common")
+load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/common:java_plugin_info.bzl", "JavaPluginInfo")
+load("//java/common:java_semantics.bzl", "semantics")
+load(":android_lint.bzl", "android_lint_subrule")
+load(":compile_action.bzl", "compile_action")
+load(":proguard_validation.bzl", "validate_proguard_specs")
+load(":rule_util.bzl", "merge_attrs")
+
+visibility([
+    "//java/...",
+])
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+BootClassPathInfo = java_common.BootClassPathInfo
+target_kind = _java_common_internal.target_kind
+
+def _filter_srcs(srcs, ext):
+    return [f for f in srcs if f.extension == ext]
+
+def _filter_provider(provider, *attrs):
+    return [dep[provider] for attr in attrs for dep in attr if provider in dep]
+
+# TODO(b/11285003): disallow jar files in deps, require java_import instead
+def _filter_javainfo_and_legacy_jars(attr):
+    dep_list = []
+
+    # Native code collected data into a NestedSet, using add for legacy jars and
+    # addTransitive for JavaInfo. This resulted in legacy jars being first in the list.
+    for dep in attr:
+        kind = target_kind(dep)
+        if not JavaInfo in dep or kind == "java_binary" or kind == "java_test":
+            for file in dep[DefaultInfo].files.to_list():
+                if file.extension == "jar":
+                    # Native doesn't construct JavaInfo
+                    java_info = JavaInfo(output_jar = file, compile_jar = file)
+                    dep_list.append(java_info)
+
+    for dep in attr:
+        if JavaInfo in dep:
+            dep_list.append(dep[JavaInfo])
+    return dep_list
+
+def basic_java_library(
+        ctx,
+        srcs,
+        deps = [],
+        runtime_deps = [],
+        plugins = [],
+        exports = [],
+        exported_plugins = [],
+        resources = [],
+        resource_jars = [],
+        classpath_resources = [],
+        javacopts = [],
+        neverlink = False,
+        enable_compile_jar_action = True,
+        coverage_config = None,
+        proguard_specs = None,
+        add_exports = [],
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
+    """
+    Creates actions that compile and lint Java sources, sets up coverage and returns JavaInfo, InstrumentedFilesInfo and output groups.
+
+    The call creates actions and providers needed and shared by `java_library`,
+    `java_plugin`,`java_binary`, and `java_test` rules and it is primarily
+    intended to be used in those rules.
+
+    Before compilation coverage.runner is added to the dependencies and if
+    present plugins are extended with the value of `--plugin` flag.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      srcs: (list[File]) The list of source files that are processed to create the target.
+      deps: (list[Target]) The list of other libraries to be linked in to the target.
+      runtime_deps: (list[Target]) Libraries to make available to the final binary or test at runtime only.
+      plugins: (list[Target]) Java compiler plugins to run at compile-time.
+      exports: (list[Target]) Exported libraries.
+      exported_plugins: (list[Target]) The list of `java_plugin`s (e.g. annotation
+        processors) to export to libraries that directly depend on this library.
+      resources: (list[File]) A list of data files to include in a Java jar.
+      resource_jars: (list[File]) A list of jar files to unpack and include in a
+        Java jar.
+      classpath_resources: (list[File])
+      javacopts: (list[str])
+      neverlink: (bool) Whether this library should only be used for compilation and not at runtime.
+      enable_compile_jar_action: (bool) Enables header compilation or ijar creation.
+      coverage_config: (struct{runner:JavaInfo, support_files:list[File]|depset[File], env:dict[str,str]})
+        Coverage configuration. `runner` is added to dependencies during
+        compilation, `support_files` and `env` is returned in InstrumentedFilesInfo.
+      proguard_specs: (list[File]) Files to be used as Proguard specification.
+        Proguard validation is done only when the parameter is set.
+      add_exports: (list[str]) Allow this library to access the given <module>/<package>.
+      add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (Target) The JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
+    Returns:
+      (dict[str, Provider],
+        {files_to_build: list[File],
+         runfiles: list[File],
+         output_groups: dict[str,list[File]]})
+    """
+    source_files = _filter_srcs(srcs, "java")
+    source_jars = _filter_srcs(srcs, "srcjar")
+
+    plugins_javaplugininfo = _collect_plugins(plugins)
+    plugins_javaplugininfo.append(ctx.attr._java_plugins[JavaPluginInfo])
+
+    properties = _filter_srcs(srcs, "properties")
+    if properties:
+        resources = list(resources)
+        resources.extend(properties)
+
+    java_info, compilation_info = compile_action(
+        ctx,
+        ctx.outputs.classjar,
+        ctx.outputs.sourcejar,
+        source_files,
+        source_jars,
+        collect_deps(deps) + ([coverage_config.runner] if coverage_config and coverage_config.runner else []),
+        collect_deps(runtime_deps),
+        plugins_javaplugininfo,
+        collect_deps(exports),
+        _collect_plugins(exported_plugins),
+        resources,
+        resource_jars,
+        classpath_resources,
+        _collect_native_libraries(deps, runtime_deps, exports),
+        javacopts,
+        neverlink,
+        ctx.fragments.java.strict_java_deps,
+        enable_compile_jar_action,
+        add_exports = add_exports,
+        add_opens = add_opens,
+        bootclasspath = bootclasspath[BootClassPathInfo] if bootclasspath else None,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
+    )
+    target = {"JavaInfo": java_info}
+
+    output_groups = dict(
+        compilation_outputs = compilation_info.files_to_build,
+        _source_jars = java_info.transitive_source_jars,
+        _direct_source_jars = java_info.source_jars,
+    )
+
+    if ctx.fragments.java.run_android_lint:
+        generated_source_jars = [
+            output.generated_source_jar
+            for output in java_info.java_outputs
+            if output.generated_source_jar != None
+        ]
+        lint_output = android_lint_subrule(
+            source_files,
+            source_jars + generated_source_jars,
+            compilation_info,
+        )
+        if lint_output:
+            output_groups["_validation"] = [lint_output]
+
+    target["InstrumentedFilesInfo"] = coverage_common.instrumented_files_info(
+        ctx,
+        source_attributes = ["srcs"],
+        dependency_attributes = ["deps", "data", "resources", "resource_jars", "exports", "runtime_deps", "jars"],
+        coverage_support_files = coverage_config.support_files if coverage_config else depset(),
+        coverage_environment = coverage_config.env if coverage_config else {},
+    )
+
+    if proguard_specs != None:
+        target["ProguardSpecProvider"] = validate_proguard_specs(
+            ctx,
+            proguard_specs,
+            [deps, runtime_deps, exports],
+        )
+        output_groups["_hidden_top_level_INTERNAL_"] = target["ProguardSpecProvider"].specs
+
+    return target, struct(
+        files_to_build = compilation_info.files_to_build,
+        runfiles = compilation_info.runfiles,
+        output_groups = output_groups,
+    )
+
+def _collect_plugins(plugins):
+    """Collects plugins from an attribute.
+
+    Use this call to collect plugins from `plugins` or `exported_plugins` attribute.
+
+    The call simply extracts JavaPluginInfo provider.
+
+    Args:
+      plugins: (list[Target]) Attribute to collect plugins from.
+    Returns:
+      (list[JavaPluginInfo]) The plugins.
+    """
+    return _filter_provider(JavaPluginInfo, plugins)
+
+def collect_deps(deps):
+    """Collects dependencies from an attribute.
+
+    Use this call to collect plugins from `deps`, `runtime_deps`, or `exports` attribute.
+
+    The call extracts JavaInfo and additionaly also "legacy jars". "legacy jars"
+    are wrapped into a JavaInfo.
+
+    Args:
+      deps: (list[Target]) Attribute to collect dependencies from.
+    Returns:
+      (list[JavaInfo]) The dependencies.
+    """
+    return _filter_javainfo_and_legacy_jars(deps)
+
+def _collect_native_libraries(*attrs):
+    """Collects native libraries from a list of attributes.
+
+    Use this call to collect native libraries from `deps`, `runtime_deps`, or `exports` attributes.
+
+    The call simply extracts CcInfo provider.
+    Args:
+      *attrs: (*list[Target]) Attribute to collect native libraries from.
+    Returns:
+      (list[CcInfo]) The native library dependencies.
+    """
+    return _filter_provider(CcInfo, *attrs)
+
+def construct_defaultinfo(ctx, files_to_build, files, neverlink, *extra_attrs):
+    """Constructs DefaultInfo for Java library like rule.
+
+    Args:
+      ctx: (RuleContext) Used to construct the runfiles.
+      files_to_build: (list[File]) List of the files built by the rule.
+      files: (list[File]) List of the files include in runfiles.
+      neverlink: (bool) When true empty runfiles are constructed.
+      *extra_attrs: (list[Target]) Extra attributes to merge runfiles from.
+
+    Returns:
+      (DefaultInfo) DefaultInfo provider.
+    """
+    if neverlink:
+        runfiles = None
+    else:
+        runfiles = ctx.runfiles(files = files, collect_default = True)
+        runfiles = runfiles.merge_all([dep[DefaultInfo].default_runfiles for attr in extra_attrs for dep in attr])
+    default_info = DefaultInfo(
+        files = depset(files_to_build),
+        runfiles = runfiles,
+    )
+    return default_info
+
+BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS = merge_attrs(
+    {
+        "_java_plugins": attr.label(
+            default = semantics.JAVA_PLUGINS_FLAG_ALIAS_LABEL,
+            providers = [JavaPluginInfo],
+        ),
+        # TODO(b/245144242): Used by IDE integration, remove when toolchains are used
+        "_java_toolchain": attr.label(
+            default = semantics.JAVA_TOOLCHAIN_LABEL,
+            providers = [java_common.JavaToolchainInfo],
+        ),
+        "_use_auto_exec_groups": attr.bool(default = True),
+    },
+)
diff --git a/java/common/rules/compile_action.bzl b/java/common/rules/compile_action.bzl
new file mode 100644
index 0000000..d1c7f0e
--- /dev/null
+++ b/java/common/rules/compile_action.bzl
@@ -0,0 +1,176 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Java compile action
+"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+
+visibility("private")
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+_compile_private_for_builtins = _java_common_internal.compile
+
+def _filter_strict_deps(mode):
+    return "error" if mode in ["strict", "default"] else mode
+
+def _collect_plugins(deps, plugins):
+    transitive_processor_jars = []
+    transitive_processor_data = []
+    for plugin in plugins:
+        transitive_processor_jars.append(plugin.plugins.processor_jars)
+        transitive_processor_data.append(plugin.plugins.processor_data)
+    for dep in deps:
+        transitive_processor_jars.append(dep.plugins.processor_jars)
+        transitive_processor_data.append(dep.plugins.processor_data)
+    return struct(
+        processor_jars = depset(transitive = transitive_processor_jars),
+        processor_data = depset(transitive = transitive_processor_data),
+    )
+
+def compile_action(
+        ctx,
+        output_class_jar,
+        output_source_jar,
+        source_files = [],
+        source_jars = [],
+        deps = [],
+        runtime_deps = [],
+        plugins = [],
+        exports = [],
+        exported_plugins = [],
+        resources = [],
+        resource_jars = [],
+        classpath_resources = [],
+        native_libraries = [],
+        javacopts = [],
+        neverlink = False,
+        strict_deps = "ERROR",
+        enable_compile_jar_action = True,
+        add_exports = [],
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
+    """
+    Creates actions that compile Java sources, produce source jar, and produce header jar and returns JavaInfo.
+
+    Use this call when you need the most basic and consistent Java compilation.
+
+    Most parameters correspond to attributes on a java_library (srcs, deps,
+    plugins, resources ...) except they are more strict, for example:
+
+    - Where java_library's srcs attribute allows mixing of .java, .srcjar, and
+     .properties files the arguments accepted by this call should be strictly
+     separated into source_files, source_jars, and resources parameter.
+    - deps parameter accepts only JavaInfo providers and plugins parameter only
+     JavaPluginInfo
+
+    The call creates following actions and files:
+    - compiling Java sources to a class jar (output_class_jar parameter)
+    - a source jar (output_source_jar parameter)
+    - optionally a jar containing plugin generated classes when plugins are present
+    - optionally a jar containing plugin generated sources
+    - jdeps file containing dependencies used during compilation
+    - other files used to speed up incremental builds:
+         - a header jar - a jar containing only method signatures without implementation
+         - compile jdeps - dependencies used during header compilation
+
+    The returned JavaInfo provider may be used as a "fully-qualified" dependency
+    to a java_library.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      output_class_jar: (File) Output class .jar file. The file needs to be declared.
+      output_source_jar: (File) Output source .jar file. The file needs to be declared.
+      source_files: (list[File]) A list of .java source files to compile.
+        At least one of source_files or source_jars parameter must be specified.
+      source_jars: (list[File]) A list of .jar or .srcjar files containing
+        source files to compile.
+        At least one of source_files or source_jars parameter must be specified.
+      deps: (list[JavaInfo]) A list of dependencies.
+      runtime_deps: (list[JavaInfo]) A list of runtime dependencies.
+      plugins: (list[JavaPluginInfo]) A list of plugins.
+      exports: (list[JavaInfo]) A list of exports.
+      exported_plugins: (list[JavaInfo]) A list of exported plugins.
+      resources: (list[File]) A list of resources.
+      resource_jars: (list[File]) A list of jars to unpack.
+      classpath_resources: (list[File]) A list of classpath resources.
+      native_libraries: (list[CcInfo]) C++ native library dependencies that are
+        needed for this library.
+      javacopts: (list[str]) A list of the desired javac options. The options
+        may contain `$(location ..)` templates that will be expanded.
+      neverlink: (bool) Whether or not this library should be used only for
+        compilation and not at runtime.
+      strict_deps: (str) A string that specifies how to handle strict deps.
+        Possible values: 'OFF', 'ERROR', 'WARN' and 'DEFAULT'. For more details
+        see https://bazel.build/docs/user-manual#strict-java-deps.
+        By default 'ERROR'.
+      enable_compile_jar_action: (bool) Enables header compilation or ijar
+        creation. If set to False, it forces use of the full class jar in the
+        compilation classpaths of any dependants. Doing so is intended for use
+        by non-library targets such as binaries that do not have dependants.
+      add_exports: (list[str]) Allow this library to access the given <module>/<package>.
+      add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (BootClassPathInfo) The set of JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
+
+    Returns:
+      ((JavaInfo, {files_to_build: list[File],
+                   runfiles: list[File],
+                   compilation_classpath: list[File],
+                   plugins: {processor_jars,
+                             processor_data: depset[File]}}))
+      A tuple with JavaInfo provider and additional compilation info.
+
+      Files_to_build may include an empty .jar file when there are no sources
+      or resources present, whereas runfiles in this case are empty.
+    """
+
+    java_info = _compile_private_for_builtins(
+        ctx,
+        output = output_class_jar,
+        java_toolchain = semantics.find_java_toolchain(ctx),
+        source_files = source_files,
+        source_jars = source_jars,
+        resources = resources,
+        resource_jars = resource_jars,
+        classpath_resources = classpath_resources,
+        plugins = plugins,
+        deps = deps,
+        native_libraries = native_libraries,
+        runtime_deps = runtime_deps,
+        exports = exports,
+        exported_plugins = exported_plugins,
+        javac_opts = [ctx.expand_location(opt) for opt in javacopts],
+        neverlink = neverlink,
+        output_source_jar = output_source_jar,
+        strict_deps = _filter_strict_deps(strict_deps),
+        enable_compile_jar_action = enable_compile_jar_action,
+        add_exports = add_exports,
+        add_opens = add_opens,
+        bootclasspath = bootclasspath,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
+    )
+
+    compilation_info = struct(
+        files_to_build = [output_class_jar],
+        runfiles = [output_class_jar] if source_files or source_jars or resources else [],
+        # TODO(ilist): collect compile_jars from JavaInfo in deps & exports
+        compilation_classpath = java_info.compilation_info.compilation_classpath,
+        javac_options = java_info.compilation_info.javac_options,
+        plugins = _collect_plugins(deps, plugins),
+    )
+
+    return java_info, compilation_info
diff --git a/java/common/rules/import_deps_check.bzl b/java/common/rules/import_deps_check.bzl
new file mode 100644
index 0000000..1981f13
--- /dev/null
+++ b/java/common/rules/import_deps_check.bzl
@@ -0,0 +1,81 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Creates the import deps checker for java rules"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+
+visibility("private")
+
+def import_deps_check(
+        ctx,
+        jars_to_check,
+        declared_deps,
+        transitive_deps,
+        rule_class):
+    """
+    Creates actions that checks import deps for java rules.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      jars_to_check: (list[File])  A list of jars files to check.
+      declared_deps: (list[File]) A list of direct dependencies.
+      transitive_deps: (list[File]) A list of transitive dependencies.
+      rule_class: (String) Rule class.
+
+    Returns:
+      (File) Output file of the created action.
+    """
+    java_toolchain = semantics.find_java_toolchain(ctx)
+    deps_checker = java_toolchain._deps_checker
+    if deps_checker == None:
+        return None
+
+    jdeps_output = ctx.actions.declare_file("_%s/%s/jdeps.proto" % (rule_class, ctx.label.name))
+
+    args = ctx.actions.args()
+    args.add("-jar", deps_checker)
+    args.add_all(jars_to_check, before_each = "--input")
+    args.add_all(declared_deps, before_each = "--directdep")
+    args.add_all(
+        depset(order = "preorder", transitive = [declared_deps, transitive_deps]),
+        before_each = "--classpath_entry",
+    )
+    args.add_all(java_toolchain.bootclasspath, before_each = "--bootclasspath_entry")
+    args.add("--checking_mode=error")
+    args.add("--jdeps_output", jdeps_output)
+    args.add("--rule_label", ctx.label)
+
+    inputs = depset(
+        jars_to_check,
+        transitive = [
+            declared_deps,
+            transitive_deps,
+            java_toolchain.bootclasspath,
+        ],
+    )
+    tools = [deps_checker, java_toolchain.java_runtime.files]
+
+    ctx.actions.run(
+        mnemonic = "ImportDepsChecker",
+        progress_message = "Checking the completeness of the deps for %s" % jars_to_check,
+        executable = java_toolchain.java_runtime.java_executable_exec_path,
+        arguments = [args],
+        inputs = inputs,
+        outputs = [jdeps_output],
+        tools = tools,
+        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
+    )
+
+    return jdeps_output
diff --git a/java/common/rules/java_binary.bzl b/java/common/rules/java_binary.bzl
new file mode 100644
index 0000000..74a5ab9
--- /dev/null
+++ b/java/common/rules/java_binary.bzl
@@ -0,0 +1,821 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Implementation of java_binary for bazel """
+
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//java/common:java_common.bzl", "java_common")
+load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/common:java_plugin_info.bzl", "JavaPluginInfo")
+load("//java/common:java_semantics.bzl", "PLATFORMS_ROOT", "semantics")
+load("//third_party/bazel_skylib/lib:paths.bzl", "paths")
+load("//third_party/protobuf/bazel/common:proto_info.bzl", "ProtoInfo")
+load(":basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS", "basic_java_library", "collect_deps")
+load(":java_binary_deploy_jar.bzl", "create_deploy_archive")
+load(":java_helper.bzl", "helper")
+load(":rule_util.bzl", "merge_attrs")
+
+visibility(["//java/..."])
+
+BootClassPathInfo = java_common.BootClassPathInfo
+CcLauncherInfo = cc_common.launcher_provider
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+JavaCompilationInfo = _java_common_internal.JavaCompilationInfo
+collect_native_deps_dirs = _java_common_internal.collect_native_deps_dirs
+get_runtime_classpath_for_archive = _java_common_internal.get_runtime_classpath_for_archive
+to_java_binary_info = _java_common_internal.to_java_binary_info
+
+InternalDeployJarInfo = provider(
+    "Provider for passing info to deploy jar rule",
+    fields = [
+        "java_attrs",
+        "strip_as_default",
+        "add_exports",
+        "add_opens",
+    ],
+)
+
+def basic_java_binary(
+        ctx,
+        deps,
+        runtime_deps,
+        resources,
+        main_class,
+        coverage_main_class,
+        coverage_config,
+        launcher_info,
+        executable,
+        strip_as_default,
+        extension_registry_provider = None,
+        is_test_rule_class = False):
+    """Creates actions for compiling and linting java sources, coverage support, and sources jar (_deploy-src.jar).
+
+    Args:
+        ctx: (RuleContext) The rule context
+        deps: (list[Target]) The list of other targets to be compiled with
+        runtime_deps: (list[Target]) The list of other targets to be linked in
+        resources: (list[File]) The list of data files to be included in the class jar
+        main_class: (String) FQN of the java main class
+        coverage_main_class: (String) FQN of the actual main class if coverage is enabled
+        coverage_config: (Struct|None) If coverage is enabled, a struct with fields (runner, manifest, env, support_files), None otherwise
+        launcher_info: (Struct) Structure with fields (launcher, unstripped_launcher, runfiles, runtime_jars, jvm_flags, classpath_resources)
+        executable: (File) The executable output of the rule
+        strip_as_default: (bool) Whether this target outputs a stripped launcher and deploy jar
+        extension_registry_provider: (GeneratedExtensionRegistryProvider) internal param, do not use
+        is_test_rule_class: (bool) Whether this rule is a test rule
+
+    Returns:
+        Tuple(
+            dict[str, Provider],    // providers
+            Struct(                 // default info
+                files_to_build: depset(File),
+                runfiles: Runfiles,
+                executable: File
+            ),
+            list[String]            // jvm flags
+          )
+
+    """
+    if not ctx.attr.create_executable and (ctx.attr.launcher and cc_common.launcher_provider in ctx.attr.launcher):
+        fail("launcher specified but create_executable is false")
+    if not ctx.attr.use_launcher and (ctx.attr.launcher and ctx.attr.launcher.label != semantics.LAUNCHER_FLAG_LABEL):
+        fail("launcher specified but use_launcher is false")
+
+    if not ctx.attr.srcs and ctx.attr.deps:
+        fail("deps not allowed without srcs; move to runtime_deps?")
+
+    module_flags = [dep[JavaInfo].module_flags_info for dep in runtime_deps if JavaInfo in dep]
+    add_exports = depset(ctx.attr.add_exports, transitive = [m.add_exports for m in module_flags])
+    add_opens = depset(ctx.attr.add_opens, transitive = [m.add_opens for m in module_flags])
+
+    classpath_resources = []
+    classpath_resources.extend(launcher_info.classpath_resources)
+    if hasattr(ctx.files, "classpath_resources"):
+        classpath_resources.extend(ctx.files.classpath_resources)
+
+    toolchain = semantics.find_java_toolchain(ctx)
+    timezone_data = [toolchain._timezone_data] if toolchain._timezone_data else []
+    target, common_info = basic_java_library(
+        ctx,
+        srcs = ctx.files.srcs,
+        deps = deps,
+        runtime_deps = runtime_deps,
+        plugins = ctx.attr.plugins,
+        resources = resources,
+        resource_jars = timezone_data,
+        classpath_resources = classpath_resources,
+        javacopts = ctx.attr.javacopts,
+        neverlink = ctx.attr.neverlink,
+        enable_compile_jar_action = False,
+        coverage_config = coverage_config,
+        add_exports = ctx.attr.add_exports,
+        add_opens = ctx.attr.add_opens,
+        bootclasspath = ctx.attr.bootclasspath,
+    )
+    java_info = target["JavaInfo"]
+    compilation_info = java_info.compilation_info
+    runtime_classpath = depset(
+        order = "preorder",
+        transitive = [
+            java_info.transitive_runtime_jars
+            for java_info in (
+                collect_deps(ctx.attr.runtime_deps + deps) +
+                ([coverage_config.runner] if coverage_config and coverage_config.runner else [])
+            )
+        ],
+    )
+    if extension_registry_provider:
+        runtime_classpath = depset(order = "preorder", direct = [extension_registry_provider.class_jar], transitive = [runtime_classpath])
+        java_info = java_common.merge(
+            [
+                java_info,
+                JavaInfo(
+                    output_jar = extension_registry_provider.class_jar,
+                    compile_jar = None,
+                    source_jar = extension_registry_provider.src_jar,
+                ),
+            ],
+        )
+        compilation_info = JavaCompilationInfo(
+            compilation_classpath = compilation_info.compilation_classpath,
+            runtime_classpath = runtime_classpath,
+            boot_classpath = compilation_info.boot_classpath,
+            javac_options = compilation_info.javac_options,
+        )
+
+    java_attrs = _collect_attrs(ctx, runtime_classpath, classpath_resources)
+
+    jvm_flags = []
+
+    jvm_flags.extend(launcher_info.jvm_flags)
+
+    native_libs_depsets = []
+    for dep in runtime_deps:
+        if JavaInfo in dep:
+            native_libs_depsets.append(dep[JavaInfo].transitive_native_libraries)
+        if CcInfo in dep:
+            native_libs_depsets.append(dep[CcInfo].transitive_native_libraries())
+    native_libs_dirs = collect_native_deps_dirs(depset(transitive = native_libs_depsets))
+    if native_libs_dirs:
+        prefix = "${JAVA_RUNFILES}/" + ctx.workspace_name + "/"
+        jvm_flags.append("-Djava.library.path=%s" % (
+            ":".join([prefix + d for d in native_libs_dirs])
+        ))
+
+    jvm_flags.extend(ctx.fragments.java.default_jvm_opts)
+    jvm_flags.extend([ctx.expand_make_variables(
+        "jvm_flags",
+        ctx.expand_location(flag, ctx.attr.data, short_paths = True),
+        {},
+    ) for flag in ctx.attr.jvm_flags])
+
+    # TODO(cushon): make string formatting lazier once extend_template support is added
+    # https://github.com/bazelbuild/proposals#:~:text=2022%2D04%2D25,Starlark
+    jvm_flags.extend(["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports.to_list()])
+    jvm_flags.extend(["--add-opens=%s=ALL-UNNAMED" % x for x in add_opens.to_list()])
+
+    files_to_build = []
+
+    if executable:
+        files_to_build.append(executable)
+
+    output_groups = common_info.output_groups
+
+    if coverage_config:
+        _generate_coverage_manifest(ctx, coverage_config.manifest, java_attrs.runtime_classpath)
+        files_to_build.append(coverage_config.manifest)
+
+    if extension_registry_provider:
+        files_to_build.append(extension_registry_provider.class_jar)
+        output_groups["_direct_source_jars"] = (
+            output_groups["_direct_source_jars"] + [extension_registry_provider.src_jar]
+        )
+        output_groups["_source_jars"] = depset(
+            direct = [extension_registry_provider.src_jar],
+            transitive = [output_groups["_source_jars"]],
+        )
+
+    if (ctx.fragments.java.one_version_enforcement_on_java_tests or not is_test_rule_class):
+        one_version_output = _create_one_version_check(ctx, java_attrs.runtime_classpath, is_test_rule_class)
+    else:
+        one_version_output = None
+
+    validation_outputs = [one_version_output] if one_version_output else []
+
+    _create_deploy_sources_jar(ctx, output_groups["_source_jars"])
+
+    files = depset(files_to_build + common_info.files_to_build)
+
+    transitive_runfiles_artifacts = depset(transitive = [
+        files,
+        java_attrs.runtime_classpath,
+        depset(transitive = launcher_info.runfiles),
+    ])
+
+    runfiles = ctx.runfiles(
+        transitive_files = transitive_runfiles_artifacts,
+        collect_default = True,
+    )
+
+    if launcher_info.launcher:
+        default_launcher = helper.filter_launcher_for_target(ctx)
+        default_launcher_artifact = helper.launcher_artifact_for_target(ctx)
+        default_launcher_runfiles = default_launcher[DefaultInfo].default_runfiles
+        if default_launcher_artifact == launcher_info.launcher:
+            runfiles = runfiles.merge(default_launcher_runfiles)
+        else:
+            # N.B. The "default launcher" referred to here is the launcher target specified through
+            # an attribute or flag. We wish to retain the runfiles of the default launcher, *except*
+            # for the original cc_binary artifact, because we've swapped it out with our custom
+            # launcher. Hence, instead of calling builder.addTarget(), or adding an odd method
+            # to Runfiles.Builder, we "unravel" the call and manually add things to the builder.
+            # Because the NestedSet representing each target's launcher runfiles is re-built here,
+            # we may see increased memory consumption for representing the target's runfiles.
+            runfiles = runfiles.merge(
+                ctx.runfiles(
+                    files = [launcher_info.launcher],
+                    transitive_files = depset([
+                        file
+                        for file in default_launcher_runfiles.files.to_list()
+                        if file != default_launcher_artifact
+                    ]),
+                    symlinks = default_launcher_runfiles.symlinks,
+                    root_symlinks = default_launcher_runfiles.root_symlinks,
+                ),
+            )
+
+    runfiles = runfiles.merge_all([
+        dep[DefaultInfo].default_runfiles
+        for dep in ctx.attr.runtime_deps
+        if DefaultInfo in dep
+    ])
+
+    if validation_outputs:
+        output_groups["_validation"] = output_groups.get("_validation", []) + validation_outputs
+
+    _filter_validation_output_group(ctx, output_groups)
+
+    java_binary_info = to_java_binary_info(java_info, compilation_info)
+
+    internal_deploy_jar_info = InternalDeployJarInfo(
+        java_attrs = java_attrs,
+        strip_as_default = strip_as_default,
+        add_exports = add_exports,
+        add_opens = add_opens,
+    )
+
+    # "temporary" workaround for https://github.com/bazelbuild/intellij/issues/5845
+    extra_files = []
+    if is_test_rule_class and ctx.fragments.java.auto_create_java_test_deploy_jars():
+        extra_files.append(_auto_create_deploy_jar(ctx, internal_deploy_jar_info, launcher_info, main_class, coverage_main_class))
+
+    default_info = struct(
+        files = depset(extra_files, transitive = [files]),
+        runfiles = runfiles,
+        executable = executable,
+    )
+
+    return {
+        "OutputGroupInfo": OutputGroupInfo(**output_groups),
+        "JavaInfo": java_binary_info,
+        "InstrumentedFilesInfo": target["InstrumentedFilesInfo"],
+        "JavaRuntimeClasspathInfo": java_common.JavaRuntimeClasspathInfo(runtime_classpath = java_info.transitive_runtime_jars),
+        "InternalDeployJarInfo": internal_deploy_jar_info,
+    }, default_info, jvm_flags
+
+def _collect_attrs(ctx, runtime_classpath, classpath_resources):
+    deploy_env_jars = depset(transitive = [
+        dep[java_common.JavaRuntimeClasspathInfo].runtime_classpath
+        for dep in ctx.attr.deploy_env
+    ]) if hasattr(ctx.attr, "deploy_env") else depset()
+
+    runtime_classpath_for_archive = get_runtime_classpath_for_archive(runtime_classpath, deploy_env_jars)
+    runtime_jars = [ctx.outputs.classjar]
+
+    resources = [p for p in ctx.files.srcs if p.extension == "properties"]
+    transitive_resources = []
+    for r in ctx.attr.resources:
+        transitive_resources.append(
+            r[ProtoInfo].transitive_sources if ProtoInfo in r else r.files,
+        )
+
+    resource_names = dict()
+    for r in classpath_resources:
+        if r.basename in resource_names:
+            fail("entries must have different file names (duplicate: %s)" % r.basename)
+        resource_names[r.basename] = None
+
+    return struct(
+        runtime_jars = depset(runtime_jars),
+        runtime_classpath_for_archive = runtime_classpath_for_archive,
+        classpath_resources = depset(classpath_resources),
+        runtime_classpath = depset(order = "preorder", direct = runtime_jars, transitive = [runtime_classpath]),
+        resources = depset(resources, transitive = transitive_resources),
+    )
+
+def _generate_coverage_manifest(ctx, output, runtime_classpath):
+    ctx.actions.write(
+        output = output,
+        content = "\n".join([file.short_path for file in runtime_classpath.to_list()]),
+    )
+
+def _create_one_version_check(ctx, inputs, is_test_rule_class):
+    one_version_level = ctx.fragments.java.one_version_enforcement_level
+    if one_version_level == "OFF":
+        return None
+    tool = helper.check_and_get_one_version_attribute(ctx, "_one_version_tool")
+
+    if is_test_rule_class:
+        toolchain = semantics.find_java_toolchain(ctx)
+        allowlist = toolchain._one_version_allowlist_for_tests
+    else:
+        allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist")
+
+    if not tool:  # On Mac oneversion tool is not available
+        return None
+
+    output = ctx.actions.declare_file("%s-one-version.txt" % ctx.label.name)
+
+    args = ctx.actions.args()
+    args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
+
+    one_version_inputs = []
+    args.add("--output", output)
+    if allowlist:
+        args.add("--whitelist", allowlist)
+        one_version_inputs.append(allowlist)
+    if one_version_level == "WARNING":
+        args.add("--succeed_on_found_violations")
+    args.add_all(
+        "--inputs",
+        inputs,
+        map_each = helper.jar_and_target_arg_mapper,
+    )
+
+    ctx.actions.run(
+        mnemonic = "JavaOneVersion",
+        progress_message = "Checking for one-version violations in %{label}",
+        executable = tool,
+        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
+        inputs = depset(one_version_inputs, transitive = [inputs]),
+        tools = [tool],
+        outputs = [output],
+        arguments = [args],
+    )
+
+    return output
+
+def _create_deploy_sources_jar(ctx, sources):
+    helper.create_single_jar(
+        ctx.actions,
+        toolchain = semantics.find_java_toolchain(ctx),
+        output = ctx.outputs.deploysrcjar,
+        sources = sources,
+    )
+
+def _filter_validation_output_group(ctx, output_group):
+    to_exclude = depset(transitive = [
+        dep[OutputGroupInfo]._validation
+        for dep in ctx.attr.deploy_env
+        if OutputGroupInfo in dep and hasattr(dep[OutputGroupInfo], "_validation")
+    ]) if hasattr(ctx.attr, "deploy_env") else depset()
+    if to_exclude:
+        transitive_validations = depset(transitive = [
+            _get_validations_from_attr(ctx, attr_name)
+            for attr_name in dir(ctx.attr)
+            # we also exclude implicit, cfg=host/exec and tool attributes
+            if not attr_name.startswith("_") and
+               attr_name not in [
+                   "deploy_env",
+                   "applicable_licenses",
+                   "package_metadata",
+                   "plugins",
+                   "translations",
+                   # special ignored attributes
+                   "compatible_with",
+                   "restricted_to",
+                   "exec_compatible_with",
+                   "target_compatible_with",
+               ]
+        ])
+        if not ctx.attr.create_executable:
+            excluded_set = {x: None for x in to_exclude.to_list()}
+            transitive_validations = [
+                x
+                for x in transitive_validations.to_list()
+                if x not in excluded_set
+            ]
+        output_group["_validation_transitive"] = transitive_validations
+
+def _get_validations_from_attr(ctx, attr_name):
+    attr = getattr(ctx.attr, attr_name)
+    if type(attr) == "list":
+        return depset(transitive = [_get_validations_from_target(t) for t in attr])
+    else:
+        return _get_validations_from_target(attr)
+
+def _get_validations_from_target(target):
+    if (
+        type(target) == "Target" and
+        OutputGroupInfo in target and
+        hasattr(target[OutputGroupInfo], "_validation")
+    ):
+        return target[OutputGroupInfo]._validation
+    else:
+        return depset()
+
+# TODO: bazelbuild/intellij/issues/5845 - remove this once no longer required
+# this need not be completely identical to the regular deploy jar since we only
+# care about packaging the classpath
+def _auto_create_deploy_jar(ctx, info, launcher_info, main_class, coverage_main_class):
+    output = ctx.actions.declare_file(ctx.label.name + "_auto_deploy.jar")
+    java_attrs = info.java_attrs
+    runtime_classpath = depset(
+        direct = launcher_info.runtime_jars,
+        transitive = [
+            java_attrs.runtime_jars,
+            java_attrs.runtime_classpath_for_archive,
+        ],
+        order = "preorder",
+    )
+    create_deploy_archive(
+        ctx,
+        launcher = launcher_info.launcher,
+        main_class = main_class,
+        coverage_main_class = coverage_main_class,
+        resources = java_attrs.resources,
+        classpath_resources = java_attrs.classpath_resources,
+        runtime_classpath = runtime_classpath,
+        manifest_lines = info.manifest_lines,
+        build_info_files = [],
+        build_target = str(ctx.label),
+        output = output,
+        one_version_level = ctx.fragments.java.one_version_enforcement_level,
+        one_version_allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist"),
+        multi_release = ctx.fragments.java.multi_release_deploy_jars,
+        hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic,
+        add_exports = info.add_exports,
+        add_opens = info.add_opens,
+    )
+    return output
+
+BASIC_JAVA_BINARY_ATTRIBUTES = merge_attrs(
+    BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS,
+    # buildifier: disable=attr-licenses
+    {
+        "srcs": attr.label_list(
+            allow_files = [".java", ".srcjar", ".properties"] + semantics.EXTRA_SRCS_TYPES,
+            flags = ["DIRECT_COMPILE_TIME_INPUT", "ORDER_INDEPENDENT"],
+            doc = """
+The list of source files that are processed to create the target.
+This attribute is almost always required; see exceptions below.
+<p>
+Source files of type <code>.java</code> are compiled. In case of generated
+<code>.java</code> files it is generally advisable to put the generating rule's name
+here instead of the name of the file itself. This not only improves readability but
+makes the rule more resilient to future changes: if the generating rule generates
+different files in the future, you only need to fix one place: the <code>outs</code> of
+the generating rule. You should not list the generating rule in <code>deps</code>
+because it is a no-op.
+</p>
+<p>
+Source files of type <code>.srcjar</code> are unpacked and compiled. (This is useful if
+you need to generate a set of <code>.java</code> files with a genrule.)
+</p>
+<p>
+Rules: if the rule (typically <code>genrule</code> or <code>filegroup</code>) generates
+any of the files listed above, they will be used the same way as described for source
+files.
+</p>
+
+<p>
+This argument is almost always required, except if a
+<a href="#java_binary.main_class"><code>main_class</code></a> attribute specifies a
+class on the runtime classpath or you specify the <code>runtime_deps</code> argument.
+</p>
+            """,
+        ),
+        "deps": attr.label_list(
+            allow_files = [".jar"],
+            allow_rules = semantics.ALLOWED_RULES_IN_DEPS + semantics.ALLOWED_RULES_IN_DEPS_WITH_WARNING,
+            providers = [
+                [CcInfo],
+                [JavaInfo],
+            ],
+            flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
+            doc = """
+The list of other libraries to be linked in to the target.
+See general comments about <code>deps</code> at
+<a href="common-definitions.html#typical-attributes">Typical attributes defined by
+most build rules</a>.
+            """,
+        ),
+        "resources": attr.label_list(
+            allow_files = True,
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE", "ORDER_INDEPENDENT"],
+            doc = """
+A list of data files to include in a Java jar.
+
+<p>
+Resources may be source files or generated files.
+</p>
+            """ + semantics.DOCS.for_attribute("resources"),
+        ),
+        "runtime_deps": attr.label_list(
+            allow_files = [".jar"],
+            allow_rules = semantics.ALLOWED_RULES_IN_DEPS,
+            providers = [[CcInfo], [JavaInfo]],
+            flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
+            doc = """
+Libraries to make available to the final binary or test at runtime only.
+Like ordinary <code>deps</code>, these will appear on the runtime classpath, but unlike
+them, not on the compile-time classpath. Dependencies needed only at runtime should be
+listed here. Dependency-analysis tools should ignore targets that appear in both
+<code>runtime_deps</code> and <code>deps</code>.
+            """,
+        ),
+        "data": attr.label_list(
+            allow_files = True,
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+            doc = """
+The list of files needed by this library at runtime.
+See general comments about <code>data</code>
+at <a href="${link common-definitions#typical-attributes}">Typical attributes defined by
+most build rules</a>.
+            """ + semantics.DOCS.for_attribute("data"),
+        ),
+        "plugins": attr.label_list(
+            providers = [JavaPluginInfo],
+            allow_files = True,
+            cfg = "exec",
+            doc = """
+Java compiler plugins to run at compile-time.
+Every <code>java_plugin</code> specified in this attribute will be run whenever this rule
+is built. A library may also inherit plugins from dependencies that use
+<code><a href="#java_library.exported_plugins">exported_plugins</a></code>. Resources
+generated by the plugin will be included in the resulting jar of this rule.
+            """,
+        ),
+        "deploy_env": attr.label_list(
+            providers = [java_common.JavaRuntimeClasspathInfo],
+            allow_files = False,
+            doc = """
+A list of other <code>java_binary</code> targets which represent the deployment
+environment for this binary.
+Set this attribute when building a plugin which will be loaded by another
+<code>java_binary</code>.<br/> Setting this attribute excludes all dependencies from
+the runtime classpath (and the deploy jar) of this binary that are shared between this
+binary and the targets specified in <code>deploy_env</code>.
+            """,
+        ),
+        "launcher": attr.label(
+            # TODO(b/295221112): add back CcLauncherInfo
+            allow_files = False,
+            doc = """
+Specify a binary that will be used to run your Java program instead of the
+normal <code>bin/java</code> program included with the JDK.
+The target must be a <code>cc_binary</code>. Any <code>cc_binary</code> that
+implements the
+<a href="http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html">
+Java Invocation API</a> can be specified as a value for this attribute.
+
+<p>By default, Bazel will use the normal JDK launcher (bin/java or java.exe).</p>
+
+<p>The related <a href="${link user-manual#flag--java_launcher}"><code>
+--java_launcher</code></a> Bazel flag affects only those
+<code>java_binary</code> and <code>java_test</code> targets that have
+<i>not</i> specified a <code>launcher</code> attribute.</p>
+
+<p>Note that your native (C++, SWIG, JNI) dependencies will be built differently
+depending on whether you are using the JDK launcher or another launcher:</p>
+
+<ul>
+<li>If you are using the normal JDK launcher (the default), native dependencies are
+built as a shared library named <code>{name}_nativedeps.so</code>, where
+<code>{name}</code> is the <code>name</code> attribute of this java_binary rule.
+Unused code is <em>not</em> removed by the linker in this configuration.</li>
+
+<li>If you are using any other launcher, native (C++) dependencies are statically
+linked into a binary named <code>{name}_nativedeps</code>, where <code>{name}</code>
+is the <code>name</code> attribute of this java_binary rule. In this case,
+the linker will remove any code it thinks is unused from the resulting binary,
+which means any C++ code accessed only via JNI may not be linked in unless
+that <code>cc_library</code> target specifies <code>alwayslink = 1</code>.</li>
+</ul>
+
+<p>When using any launcher other than the default JDK launcher, the format
+of the <code>*_deploy.jar</code> output changes. See the main
+<a href="#java_binary">java_binary</a> docs for details.</p>
+            """,
+        ),
+        "bootclasspath": attr.label(
+            providers = [BootClassPathInfo],
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+            doc = "Restricted API, do not use!",
+        ),
+        "neverlink": attr.bool(),
+        "javacopts": attr.string_list(
+            doc = """
+Extra compiler options for this binary.
+Subject to <a href="make-variables.html">"Make variable"</a> substitution and
+<a href="common-definitions.html#sh-tokenization">Bourne shell tokenization</a>.
+<p>These compiler options are passed to javac after the global compiler options.</p>
+            """,
+        ),
+        "add_exports": attr.string_list(
+            doc = """
+Allow this library to access the given <code>module</code> or <code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-exports= flags.
+            """,
+        ),
+        "add_opens": attr.string_list(
+            doc = """
+Allow this library to reflectively access the given <code>module</code> or
+<code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-opens= flags.
+            """,
+        ),
+        "main_class": attr.string(
+            doc = """
+Name of class with <code>main()</code> method to use as entry point.
+If a rule uses this option, it does not need a <code>srcs=[...]</code> list.
+Thus, with this attribute one can make an executable from a Java library that already
+contains one or more <code>main()</code> methods.
+<p>
+The value of this attribute is a class name, not a source file. The class must be
+available at runtime: it may be compiled by this rule (from <code>srcs</code>) or
+provided by direct or transitive dependencies (through <code>runtime_deps</code> or
+<code>deps</code>). If the class is unavailable, the binary will fail at runtime; there
+is no build-time check.
+</p>
+            """,
+        ),
+        "jvm_flags": attr.string_list(
+            doc = """
+A list of flags to embed in the wrapper script generated for running this binary.
+Subject to <a href="${link make-variables#location}">$(location)</a> and
+<a href="make-variables.html">"Make variable"</a> substitution, and
+<a href="common-definitions.html#sh-tokenization">Bourne shell tokenization</a>.
+
+<p>The wrapper script for a Java binary includes a CLASSPATH definition
+(to find all the dependent jars) and invokes the right Java interpreter.
+The command line generated by the wrapper script includes the name of
+the main class followed by a <code>"$@"</code> so you can pass along other
+arguments after the classname.  However, arguments intended for parsing
+by the JVM must be specified <i>before</i> the classname on the command
+line.  The contents of <code>jvm_flags</code> are added to the wrapper
+script before the classname is listed.</p>
+
+<p>Note that this attribute has <em>no effect</em> on <code>*_deploy.jar</code>
+outputs.</p>
+            """,
+        ),
+        "deploy_manifest_lines": attr.string_list(
+            doc = """
+A list of lines to add to the <code>META-INF/manifest.mf</code> file generated for the
+<code>*_deploy.jar</code> target. The contents of this attribute are <em>not</em> subject
+to <a href="make-variables.html">"Make variable"</a> substitution.
+            """,
+        ),
+        "stamp": attr.int(
+            default = -1,
+            values = [-1, 0, 1],
+            doc = """
+Whether to encode build information into the binary. Possible values:
+<ul>
+<li>
+  <code>stamp = 1</code>: Always stamp the build information into the binary, even in
+  <a href="${link user-manual#flag--stamp}"><code>--nostamp</code></a> builds. <b>This
+  setting should be avoided</b>, since it potentially kills remote caching for the
+  binary and any downstream actions that depend on it.
+</li>
+<li>
+  <code>stamp = 0</code>: Always replace build information by constant values. This
+  gives good build result caching.
+</li>
+<li>
+  <code>stamp = -1</code>: Embedding of build information is controlled by the
+  <a href="${link user-manual#flag--stamp}"><code>--[no]stamp</code></a> flag.
+</li>
+</ul>
+<p>Stamped binaries are <em>not</em> rebuilt unless their dependencies change.</p>
+            """,
+        ),
+        "use_testrunner": attr.bool(
+            default = False,
+            doc = semantics.DOCS.for_attribute("use_testrunner") + """
+<br/>
+You can use this to override the default
+behavior, which is to use test runner for
+<code>java_test</code> rules,
+and not use it for <code>java_binary</code> rules.  It is unlikely
+you will want to do this.  One use is for <code>AllTest</code>
+rules that are invoked by another rule (to set up a database
+before running the tests, for example).  The <code>AllTest</code>
+rule must be declared as a <code>java_binary</code>, but should
+still use the test runner as its main entry point.
+
+The name of a test runner class can be overridden with <code>main_class</code> attribute.
+            """,
+        ),
+        "use_launcher": attr.bool(
+            default = True,
+            doc = """
+Whether the binary should use a custom launcher.
+
+<p>If this attribute is set to false, the
+<a href="${link java_binary.launcher}">launcher</a> attribute  and the related
+<a href="${link user-manual#flag--java_launcher}"><code>--java_launcher</code></a> flag
+will be ignored for this target.
+            """,
+        ),
+        "env": attr.string_dict(),
+        "classpath_resources": attr.label_list(
+            allow_files = True,
+            doc = """
+<em class="harmful">DO NOT USE THIS OPTION UNLESS THERE IS NO OTHER WAY)</em>
+<p>
+A list of resources that must be located at the root of the java tree. This attribute's
+only purpose is to support third-party libraries that require that their resources be
+found on the classpath as exactly <code>"myconfig.xml"</code>. It is only allowed on
+binaries and not libraries, due to the danger of namespace conflicts.
+</p>
+            """,
+        ),
+        "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+        "_stub_template": attr.label(
+            default = semantics.JAVA_STUB_TEMPLATE_LABEL,
+            allow_single_file = True,
+        ),
+        "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE),
+        "_windows_constraints": attr.label_list(
+            default = [paths.join(PLATFORMS_ROOT, "os:windows")],
+        ),
+        "_build_info_translator": attr.label(default = semantics.BUILD_INFO_TRANSLATOR_LABEL),
+    } | ({} if _java_common_internal.incompatible_disable_non_executable_java_binary() else {"create_executable": attr.bool(default = True, doc = "Deprecated, use <code>java_single_jar</code> instead.")}),
+)
+
+BASE_TEST_ATTRIBUTES = {
+    "test_class": attr.string(
+        doc = """
+The Java class to be loaded by the test runner.<br/>
+<p>
+  By default, if this argument is not defined then the legacy mode is used and the
+  test arguments are used instead. Set the <code>--nolegacy_bazel_java_test</code> flag
+  to not fallback on the first argument.
+</p>
+<p>
+  This attribute specifies the name of a Java class to be run by
+  this test. It is rare to need to set this. If this argument is omitted,
+  it will be inferred using the target's <code>name</code> and its
+  source-root-relative path. If the test is located outside a known
+  source root, Bazel will report an error if <code>test_class</code>
+  is unset.
+</p>
+<p>
+  For JUnit3, the test class needs to either be a subclass of
+  <code>junit.framework.TestCase</code> or it needs to have a public
+  static <code>suite()</code> method that returns a
+  <code>junit.framework.Test</code> (or a subclass of <code>Test</code>).
+  For JUnit4, the class needs to be annotated with
+  <code>org.junit.runner.RunWith</code>.
+</p>
+<p>
+  This attribute allows several <code>java_test</code> rules to
+  share the same <code>Test</code>
+  (<code>TestCase</code>, <code>TestSuite</code>, ...).  Typically
+  additional information is passed to it
+  (e.g. via <code>jvm_flags=['-Dkey=value']</code>) so that its
+  behavior differs in each case, such as running a different
+  subset of the tests.  This attribute also enables the use of
+  Java tests outside the <code>javatests</code> tree.
+</p>
+        """,
+    ),
+    "env_inherit": attr.string_list(),
+    "_apple_constraints": attr.label_list(
+        default = [
+            paths.join(PLATFORMS_ROOT, "os:ios"),
+            paths.join(PLATFORMS_ROOT, "os:macos"),
+            paths.join(PLATFORMS_ROOT, "os:tvos"),
+            paths.join(PLATFORMS_ROOT, "os:visionos"),
+            paths.join(PLATFORMS_ROOT, "os:watchos"),
+        ],
+    ),
+    "_legacy_any_type_attrs": attr.string_list(default = ["stamp"]),
+}
diff --git a/java/common/rules/java_binary_deploy_jar.bzl b/java/common/rules/java_binary_deploy_jar.bzl
new file mode 100644
index 0000000..183be45
--- /dev/null
+++ b/java/common/rules/java_binary_deploy_jar.bzl
@@ -0,0 +1,248 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Auxiliary rule to create the deploy archives for java_binary"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+load(":java_helper.bzl", "helper")
+
+visibility(["//java/..."])
+
+def _get_build_info(ctx, stamp):
+    if helper.is_stamping_enabled(ctx, stamp):
+        # Makes the target depend on BUILD_INFO_KEY, which helps to discover stamped targets
+        # See b/326620485 for more details.
+        ctx.version_file  # buildifier: disable=no-effect
+        return ctx.attr._build_info_translator[OutputGroupInfo].non_redacted_build_info_files.to_list()
+    else:
+        return ctx.attr._build_info_translator[OutputGroupInfo].redacted_build_info_files.to_list()
+
+def create_deploy_archives(
+        ctx,
+        java_attrs,
+        launcher_info,
+        main_class,
+        coverage_main_class,
+        strip_as_default,
+        hermetic = False,
+        add_exports = depset(),
+        add_opens = depset(),
+        shared_archive = None,
+        one_version_level = "OFF",
+        one_version_allowlist = None,
+        extra_args = [],
+        extra_manifest_lines = []):
+    """ Registers actions for _deploy.jar and _deploy.jar.unstripped
+
+    Args:
+        ctx: (RuleContext) The rule context
+        java_attrs: (Struct) Struct of (classpath_resources, runtime_jars, runtime_classpath_for_archive, resources)
+        launcher_info: (Struct) Struct of (runtime_jars, launcher, unstripped_launcher)
+        main_class: (String) FQN of the entry point for execution
+        coverage_main_class: (String) FQN of the entry point for coverage collection
+        strip_as_default: (bool) Whether to create unstripped deploy jar
+        hermetic: (bool)
+        add_exports: (depset)
+        add_opens: (depset)
+        shared_archive: (File) Optional .jsa artifact
+        one_version_level: (String) Optional one version check level, default OFF
+        one_version_allowlist: (File) Optional allowlist for one version check
+        extra_args: (list[Args]) Optional arguments for the deploy jar action
+        extra_manifest_lines: (list[String]) Optional lines added to the jar manifest
+    """
+    classpath_resources = java_attrs.classpath_resources
+
+    runtime_classpath = depset(
+        direct = launcher_info.runtime_jars,
+        transitive = [
+            java_attrs.runtime_jars,
+            java_attrs.runtime_classpath_for_archive,
+        ],
+        order = "preorder",
+    )
+    multi_release = ctx.fragments.java.multi_release_deploy_jars
+    build_info_files = _get_build_info(ctx, ctx.attr.stamp)
+    build_target = str(ctx.label)
+    manifest_lines = ctx.attr.deploy_manifest_lines + extra_manifest_lines
+    create_deploy_archive(
+        ctx,
+        launcher_info.launcher,
+        main_class,
+        coverage_main_class,
+        java_attrs.resources,
+        classpath_resources,
+        runtime_classpath,
+        manifest_lines,
+        build_info_files,
+        build_target,
+        output = ctx.outputs.deployjar,
+        shared_archive = shared_archive,
+        one_version_level = one_version_level,
+        one_version_allowlist = one_version_allowlist,
+        multi_release = multi_release,
+        hermetic = hermetic,
+        add_exports = add_exports,
+        add_opens = add_opens,
+        extra_args = extra_args,
+    )
+
+    if strip_as_default:
+        create_deploy_archive(
+            ctx,
+            launcher_info.unstripped_launcher,
+            main_class,
+            coverage_main_class,
+            java_attrs.resources,
+            classpath_resources,
+            runtime_classpath,
+            manifest_lines,
+            build_info_files,
+            build_target,
+            output = ctx.outputs.unstrippeddeployjar,
+            multi_release = multi_release,
+            hermetic = hermetic,
+            add_exports = add_exports,
+            add_opens = add_opens,
+            extra_args = extra_args,
+        )
+    else:
+        ctx.actions.write(ctx.outputs.unstrippeddeployjar, "")
+
+def create_deploy_archive(
+        ctx,
+        launcher,
+        main_class,
+        coverage_main_class,
+        resources,
+        classpath_resources,
+        runtime_classpath,
+        manifest_lines,
+        build_info_files,
+        build_target,
+        output,
+        shared_archive = None,
+        one_version_level = "OFF",
+        one_version_allowlist = None,
+        multi_release = False,
+        hermetic = False,
+        add_exports = [],
+        add_opens = [],
+        extra_args = []):
+    """ Creates a deploy jar
+
+    Requires a Java runtime toolchain if and only if hermetic is True.
+
+    Args:
+        ctx: (RuleContext) The rule context
+        launcher: (File) the launcher artifact
+        main_class: (String) FQN of the entry point for execution
+        coverage_main_class: (String) FQN of the entry point for coverage collection
+        resources: (Depset) resource inputs
+        classpath_resources: (Depset) classpath resource inputs
+        runtime_classpath: (Depset) source files to add to the jar
+        build_target: (String) Name of the build target for stamping
+        manifest_lines: (list[String]) Optional lines added to the jar manifest
+        build_info_files: (list[File]) build info files for stamping
+        build_target: (String) the owner build target label name string
+        output: (File) the output jar artifact
+        shared_archive: (File) Optional .jsa artifact
+        one_version_level: (String) Optional one version check level, default OFF
+        one_version_allowlist: (File) Optional allowlist for one version check
+        multi_release: (bool)
+        hermetic: (bool)
+        add_exports: (depset)
+        add_opens: (depset)
+        extra_args: (list[Args]) Optional arguments for the deploy jar action
+    """
+    input_files = []
+    input_files.extend(build_info_files)
+
+    transitive_input_files = [
+        resources,
+        classpath_resources,
+        runtime_classpath,
+    ]
+
+    single_jar = semantics.find_java_toolchain(ctx).single_jar
+
+    manifest_lines = list(manifest_lines)
+    if ctx.configuration.coverage_enabled:
+        manifest_lines.append("Coverage-Main-Class: %s" % coverage_main_class)
+
+    args = ctx.actions.args()
+    args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
+
+    args.add("--output", output)
+    args.add("--build_target", build_target)
+    args.add("--normalize")
+    args.add("--compression")
+    if main_class:
+        args.add("--main_class", main_class)
+    args.add_all("--deploy_manifest_lines", manifest_lines)
+    args.add_all(build_info_files, before_each = "--build_info_file")
+    if launcher:
+        input_files.append(launcher)
+        args.add("--java_launcher", launcher)
+    args.add_all("--classpath_resources", classpath_resources)
+    args.add_all(
+        "--sources",
+        runtime_classpath,
+        map_each = helper.jar_and_target_arg_mapper,
+    )
+
+    if one_version_level != "OFF" and one_version_allowlist:
+        input_files.append(one_version_allowlist)
+        args.add("--enforce_one_version")
+        args.add("--one_version_whitelist", one_version_allowlist)
+        if one_version_level == "WARNING":
+            args.add("--succeed_on_found_violations")
+
+    if multi_release:
+        args.add("--multi_release")
+
+    if hermetic:
+        runtime = ctx.toolchains["@//tools/jdk/hermetic:hermetic_runtime_toolchain_type"].java_runtime
+        if runtime.lib_modules != None:
+            java_home = runtime.java_home
+            lib_modules = runtime.lib_modules
+            hermetic_files = runtime.hermetic_files
+            args.add("--hermetic_java_home", java_home)
+            args.add("--jdk_lib_modules", lib_modules)
+            args.add_all("--resources", hermetic_files)
+            input_files.append(lib_modules)
+            transitive_input_files.append(hermetic_files)
+
+            if shared_archive == None:
+                shared_archive = runtime.default_cds
+
+    if shared_archive:
+        input_files.append(shared_archive)
+        args.add("--cds_archive", shared_archive)
+
+    args.add_all("--add_exports", add_exports)
+    args.add_all("--add_opens", add_opens)
+
+    inputs = depset(input_files, transitive = transitive_input_files)
+
+    ctx.actions.run(
+        mnemonic = "JavaDeployJar",
+        progress_message = "Building deploy jar %s" % output.short_path,
+        executable = single_jar,
+        inputs = inputs,
+        tools = [single_jar],
+        outputs = [output],
+        arguments = [args] + extra_args,
+        use_default_shell_env = True,
+        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
+    )
diff --git a/java/common/rules/java_binary_wrapper.bzl b/java/common/rules/java_binary_wrapper.bzl
new file mode 100644
index 0000000..3d94dcd
--- /dev/null
+++ b/java/common/rules/java_binary_wrapper.bzl
@@ -0,0 +1,73 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Macro encapsulating the java_binary implementation
+
+This is needed since the `executable` nature of the target must be computed from
+the supplied value of the `create_executable` attribute.
+"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+
+visibility(["//java/..."])
+
+def register_legacy_java_binary_rules(
+        rule_exec,
+        rule_nonexec,
+        **kwargs):
+    """Registers the correct java_binary rule and deploy jar rule
+
+    Args:
+        rule_exec: (Rule) The executable java_binary rule
+        rule_nonexec: (Rule) The non-executable java_binary rule
+        **kwargs: Actual args to instantiate the rule
+    """
+
+    create_executable = "create_executable" not in kwargs or kwargs["create_executable"]
+
+    # TODO(hvd): migrate depot to integers / maybe use decompose_select_list()
+    if "stamp" in kwargs and type(kwargs["stamp"]) == type(True):
+        kwargs["stamp"] = 1 if kwargs["stamp"] else 0
+    if not create_executable:
+        rule_nonexec(**kwargs)
+    else:
+        if "use_launcher" in kwargs and not kwargs["use_launcher"]:
+            kwargs["launcher"] = None
+        else:
+            # If launcher is not set or None, set it to config flag
+            if "launcher" not in kwargs or not kwargs["launcher"]:
+                kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL
+        rule_exec(**kwargs)
+
+def register_java_binary_rules(
+        java_binary,
+        **kwargs):
+    """Creates a java_binary rule and a deploy jar rule
+
+    Args:
+        java_binary: (Rule) The executable java_binary rule
+        **kwargs: Actual args to instantiate the rule
+    """
+
+    # TODO(hvd): migrate depot to integers / maybe use decompose_select_list()
+    if "stamp" in kwargs and type(kwargs["stamp"]) == type(True):
+        kwargs["stamp"] = 1 if kwargs["stamp"] else 0
+
+    if "use_launcher" in kwargs and not kwargs["use_launcher"]:
+        kwargs["launcher"] = None
+    else:
+        # If launcher is not set or None, set it to config flag
+        if "launcher" not in kwargs or not kwargs["launcher"]:
+            kwargs["launcher"] = semantics.LAUNCHER_FLAG_LABEL
+    java_binary(**kwargs)
diff --git a/java/common/rules/java_helper.bzl b/java/common/rules/java_helper.bzl
new file mode 100644
index 0000000..c0aa231
--- /dev/null
+++ b/java/common/rules/java_helper.bzl
@@ -0,0 +1,507 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Common util functions for java_* rules"""
+
+load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper")
+load(
+    "//java/common:java_semantics.bzl",
+    "semantics",
+    _semantics_tokenize_javacopts = "tokenize_javacopts",
+)
+load("//third_party/bazel_skylib/lib:paths.bzl", "paths")
+
+visibility(["//java/..."])
+
+def _collect_all_targets_as_deps(ctx, classpath_type = "all"):
+    deps = []
+    if not classpath_type == "compile_only":
+        if hasattr(ctx.attr, "runtime_deps"):
+            deps.extend(ctx.attr.runtime_deps)
+        if hasattr(ctx.attr, "exports"):
+            deps.extend(ctx.attr.exports)
+
+    deps.extend(ctx.attr.deps or [])
+
+    launcher = _filter_launcher_for_target(ctx)
+    if launcher:
+        deps.append(launcher)
+
+    return deps
+
+def _filter_launcher_for_target(ctx):
+    # create_executable=0 disables the launcher
+    if hasattr(ctx.attr, "create_executable") and not ctx.attr.create_executable:
+        return None
+
+    # use_launcher=False disables the launcher
+    if hasattr(ctx.attr, "use_launcher") and not ctx.attr.use_launcher:
+        return None
+
+    # BUILD rule "launcher" attribute
+    if ctx.attr.launcher and cc_common.launcher_provider in ctx.attr.launcher:
+        return ctx.attr.launcher
+
+    return None
+
+def _launcher_artifact_for_target(ctx):
+    launcher = _filter_launcher_for_target(ctx)
+    if not launcher:
+        return None
+    files = launcher[DefaultInfo].files.to_list()
+    if len(files) != 1:
+        fail("%s expected a single artifact in %s" % (ctx.label, launcher))
+    return files[0]
+
+def _check_and_get_main_class(ctx):
+    create_executable = ctx.attr.create_executable
+    use_testrunner = ctx.attr.use_testrunner
+    main_class = ctx.attr.main_class
+
+    if not create_executable and use_testrunner:
+        fail("cannot have use_testrunner without creating an executable")
+    if not create_executable and main_class:
+        fail("main class must not be specified when executable is not created")
+    if create_executable and not use_testrunner:
+        if not main_class:
+            if not ctx.attr.srcs:
+                fail("need at least one of 'main_class', 'use_testrunner' or Java source files")
+            main_class = _primary_class(ctx)
+            if main_class == None:
+                fail("main_class was not provided and cannot be inferred: " +
+                     "source path doesn't include a known root (java, javatests, src, testsrc)")
+    if not create_executable:
+        return None
+    if not main_class:
+        if use_testrunner:
+            main_class = "com.google.testing.junit.runner.GoogleTestRunner"
+        else:
+            main_class = _primary_class(ctx)
+    return main_class
+
+def _primary_class(ctx):
+    if ctx.attr.srcs:
+        main = ctx.label.name + ".java"
+        for src in ctx.files.srcs:
+            if src.basename == main:
+                return _full_classname(_strip_extension(src))
+    return _full_classname(paths.get_relative(ctx.label.package, ctx.label.name))
+
+def _strip_extension(file):
+    return file.dirname + "/" + (
+        file.basename[:-(1 + len(file.extension))] if file.extension else file.basename
+    )
+
+# TODO(b/193629418): once out of builtins, create a canonical implementation and remove duplicates in depot
+def _full_classname(path):
+    java_segments = _java_segments(path)
+    return ".".join(java_segments) if java_segments != None else None
+
+def _java_segments(path):
+    if path.startswith("/"):
+        fail("path must not be absolute: '%s'" % path)
+    segments = path.split("/")
+    root_idx = -1
+    for idx, segment in enumerate(segments):
+        if segment in ["java", "javatests", "src", "testsrc"]:
+            root_idx = idx
+            break
+    if root_idx < 0:
+        return None
+    is_src = "src" == segments[root_idx]
+    check_mvn_idx = root_idx if is_src else -1
+    if (root_idx == 0 or is_src):
+        for i in range(root_idx + 1, len(segments) - 1):
+            segment = segments[i]
+            if "src" == segment or (is_src and (segment in ["java", "javatests"])):
+                next = segments[i + 1]
+                if next in ["com", "org", "net"]:
+                    root_idx = i
+                elif "src" == segment:
+                    check_mvn_idx = i
+                break
+
+    if check_mvn_idx >= 0 and check_mvn_idx < len(segments) - 2:
+        next = segments[check_mvn_idx + 1]
+        if next in ["main", "test"]:
+            next = segments[check_mvn_idx + 2]
+            if next in ["java", "resources"]:
+                root_idx = check_mvn_idx + 2
+    return segments[(root_idx + 1):]
+
+def _concat(*lists):
+    result = []
+    for list in lists:
+        result.extend(list)
+    return result
+
+def _get_shared_native_deps_path(
+        linker_inputs,
+        link_opts,
+        linkstamps,
+        build_info_artifacts,
+        features,
+        is_test_target_partially_disabled_thin_lto):
+    """
+    Returns the path of the shared native library.
+
+    The name must be generated based on the rule-specific inputs to the link actions. At this point
+    this includes order-sensitive list of linker inputs and options collected from the transitive
+    closure and linkstamp-related artifacts that are compiled during linking. All those inputs can
+    be affected by modifying target attributes (srcs/deps/stamp/etc). However, target build
+    configuration can be ignored since it will either change output directory (in case of different
+    configuration instances) or will not affect anything (if two targets use same configuration).
+    Final goal is for all native libraries that use identical linker command to use same output
+    name.
+
+    <p>TODO(bazel-team): (2010) Currently process of identifying parameters that can affect native
+    library name is manual and should be kept in sync with the code in the
+    CppLinkAction.Builder/CppLinkAction/Link classes which are responsible for generating linker
+    command line. Ideally we should reuse generated command line for both purposes - selecting a
+    name of the native library and using it as link action payload. For now, correctness of the
+    method below is only ensured by validations in the CppLinkAction.Builder.build() method.
+    """
+
+    fp = ""
+    for artifact in linker_inputs:
+        fp += artifact.short_path
+    fp += str(len(link_opts))
+    for opt in link_opts:
+        fp += opt
+    for artifact in linkstamps:
+        fp += artifact.short_path
+    for artifact in build_info_artifacts:
+        fp += artifact.short_path
+    for feature in features:
+        fp += feature
+
+    # Sharing of native dependencies may cause an ActionConflictException when ThinLTO is
+    # disabled for test and test-only targets that are statically linked, but enabled for other
+    # statically linked targets. This happens in case the artifacts for the shared native
+    # dependency are output by actions owned by the non-test and test targets both. To fix
+    # this, we allow creation of multiple artifacts for the shared native library - one shared
+    # among the test and test-only targets where ThinLTO is disabled, and the other shared among
+    # other targets where ThinLTO is enabled.
+    fp += "1" if is_test_target_partially_disabled_thin_lto else "0"
+
+    fingerprint = "%x" % hash(fp)
+    return "_nativedeps/" + fingerprint
+
+def _check_and_get_one_version_attribute(ctx, attr):
+    value = getattr(semantics.find_java_toolchain(ctx), attr)
+    return value
+
+def _jar_and_target_arg_mapper(jar):
+    # Emit pretty labels for targets in the main repository.
+    label = str(jar.owner)
+    if label.startswith("@@//"):
+        label = label.lstrip("@")
+    return jar.path + "," + label
+
+def _get_feature_config(ctx):
+    cc_toolchain = find_cc_toolchain(ctx, mandatory = False)
+    if not cc_toolchain:
+        return None
+    feature_config = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features + ["java_launcher_link", "static_linking_mode"],
+        unsupported_features = ctx.disabled_features,
+    )
+    return feature_config
+
+def _should_strip_as_default(ctx, feature_config):
+    fission_is_active = ctx.fragments.cpp.fission_active_for_current_compilation_mode()
+    create_per_obj_debug_info = fission_is_active and cc_common.is_enabled(
+        feature_name = "per_object_debug_info",
+        feature_configuration = feature_config,
+    )
+    compilation_mode = ctx.var["COMPILATION_MODE"]
+    strip_as_default = create_per_obj_debug_info and compilation_mode == "opt"
+
+    return strip_as_default
+
+def _get_coverage_config(ctx, runner):
+    toolchain = semantics.find_java_toolchain(ctx)
+    if not ctx.configuration.coverage_enabled:
+        return None
+    runner = runner if ctx.attr.create_executable else None
+    manifest = ctx.actions.declare_file("runtime_classpath_for_coverage/%s/runtime_classpath.txt" % ctx.label.name)
+    singlejar = toolchain.single_jar
+    return struct(
+        runner = runner,
+        main_class = "com.google.testing.coverage.JacocoCoverageRunner",
+        manifest = manifest,
+        env = {
+            "JAVA_RUNTIME_CLASSPATH_FOR_COVERAGE": manifest.path,
+            "SINGLE_JAR_TOOL": singlejar.executable.path,
+        },
+        support_files = [manifest, singlejar.executable],
+    )
+
+def _get_java_executable(ctx, java_runtime_toolchain, launcher):
+    java_executable = launcher.short_path if launcher else java_runtime_toolchain.java_executable_runfiles_path
+    if not _is_absolute_target_platform_path(ctx, java_executable):
+        java_executable = ctx.workspace_name + "/" + java_executable
+    return paths.normalize(java_executable)
+
+def _has_target_constraints(ctx, constraints):
+    # Constraints is a label_list.
+    for constraint in constraints:
+        constraint_value = constraint[platform_common.ConstraintValueInfo]
+        if ctx.target_platform_has_constraint(constraint_value):
+            return True
+    return False
+
+def _is_target_platform_windows(ctx):
+    return _has_target_constraints(ctx, ctx.attr._windows_constraints)
+
+def _is_absolute_target_platform_path(ctx, path):
+    if _is_target_platform_windows(ctx):
+        return len(path) > 2 and path[1] == ":"
+    return path.startswith("/")
+
+def _runfiles_enabled(ctx):
+    return ctx.configuration.runfiles_enabled()
+
+def _get_test_support(ctx):
+    if ctx.attr.create_executable and ctx.attr.use_testrunner:
+        return ctx.attr._test_support
+    return None
+
+def _test_providers(ctx):
+    test_providers = []
+    if _has_target_constraints(ctx, ctx.attr._apple_constraints):
+        test_providers.append(testing.ExecutionInfo({"requires-darwin": ""}))
+
+    test_env = {}
+    test_env.update(cc_helper.get_expanded_env(ctx, {}))
+
+    coverage_config = _get_coverage_config(
+        ctx,
+        runner = None,  # we only need the environment
+    )
+    if coverage_config:
+        test_env.update(coverage_config.env)
+    test_providers.append(testing.TestEnvironment(
+        environment = test_env,
+        inherited_environment = ctx.attr.env_inherit,
+    ))
+
+    return test_providers
+
+def _executable_providers(ctx):
+    if ctx.attr.create_executable:
+        return [RunEnvironmentInfo(cc_helper.get_expanded_env(ctx, {}))]
+    return []
+
+def _resource_mapper(file):
+    root_relative_path = paths.relativize(
+        path = file.path,
+        start = paths.join(file.root.path, file.owner.workspace_root),
+    )
+    return "%s:%s" % (
+        file.path,
+        semantics.get_default_resource_path(root_relative_path, segment_extractor = _java_segments),
+    )
+
+def _create_single_jar(
+        actions,
+        toolchain,
+        output,
+        sources = depset(),
+        resources = depset(),
+        mnemonic = "JavaSingleJar",
+        progress_message = "Building singlejar jar %{output}",
+        build_target = None,
+        output_creator = None):
+    """Register singlejar action for the output jar.
+
+    Args:
+      actions: (actions) ctx.actions
+      toolchain: (JavaToolchainInfo) The java toolchain
+      output: (File) Output file of the action.
+      sources: (depset[File]) The jar files to merge into the output jar.
+      resources: (depset[File]) The files to add to the output jar.
+      mnemonic: (str) The action identifier
+      progress_message: (str) The action progress message
+      build_target: (Label) The target label to stamp in the manifest. Optional.
+      output_creator: (str) The name of the tool to stamp in the manifest. Optional,
+          defaults to 'singlejar'
+    Returns:
+      (File) Output file which was used for registering the action.
+    """
+    args = actions.args()
+    args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
+    args.add("--output", output)
+    args.add_all(
+        [
+            "--compression",
+            "--normalize",
+            "--exclude_build_data",
+            "--warn_duplicate_resources",
+        ],
+    )
+    args.add_all("--sources", sources)
+    args.add_all("--resources", resources, map_each = _resource_mapper)
+
+    args.add("--build_target", build_target)
+    args.add("--output_jar_creator", output_creator)
+
+    actions.run(
+        mnemonic = mnemonic,
+        progress_message = progress_message,
+        executable = toolchain.single_jar,
+        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
+        inputs = depset(transitive = [resources, sources]),
+        tools = [toolchain.single_jar],
+        outputs = [output],
+        arguments = [args],
+    )
+    return output
+
+# TODO(hvd): use skylib shell.quote()
+def _shell_escape(s):
+    """Shell-escape a string
+
+    Quotes a word so that it can be used, without further quoting, as an argument
+    (or part of an argument) in a shell command.
+
+    Args:
+        s: (str) the string to escape
+
+    Returns:
+        (str) the shell-escaped string
+    """
+    if not s:
+        # Empty string is a special case: needs to be quoted to ensure that it
+        # gets treated as a separate argument.
+        return "''"
+    for c in s.elems():
+        # We do this positively so as to be sure we don't inadvertently forget
+        # any unsafe characters.
+        if not c.isalnum() and c not in "@%-_+:,./":
+            return "'" + s.replace("'", "'\\''") + "'"
+    return s
+
+def _tokenize_javacopts(ctx = None, opts = []):
+    """Tokenizes a list or depset of options to a list.
+
+    Iff opts is a depset, we reverse the flattened list to ensure right-most
+    duplicates are preserved in their correct position.
+
+    If the ctx parameter is omitted, a slow, but pure Starlark, implementation
+    of shell tokenization is used. Otherwise, tokenization is performed using
+    ctx.tokenize() which has significantly better performance (up to 100x for
+    large options lists).
+
+    Args:
+        ctx: (RuleContext|None) the rule context
+        opts: (depset[str]|[str]) the javac options to tokenize
+    Returns:
+        [str] list of tokenized options
+    """
+    if hasattr(opts, "to_list"):
+        opts = reversed(opts.to_list())
+    if ctx:
+        return [
+            token
+            for opt in opts
+            for token in ctx.tokenize(opt)
+        ]
+    else:
+        return _semantics_tokenize_javacopts(opts)
+
+def _detokenize_javacopts(opts):
+    """Detokenizes a list of options to a depset.
+
+    Args:
+        opts: ([str]) the javac options to detokenize
+
+    Returns:
+        (depset[str]) depset of detokenized options
+    """
+    return depset(
+        [" ".join([_shell_escape(opt) for opt in opts])],
+        order = "preorder",
+    )
+
+def _derive_output_file(ctx, base_file, *, name_suffix = "", extension = None, extension_suffix = ""):
+    """Declares a new file whose name is derived from the given file
+
+    This method allows appending a suffix to the name (before extension), changing
+    the extension or appending a suffix after the extension. The new file is declared
+    as a sibling of the given base file. At least one of the three options must be
+    specified. It is an error to specify both `extension` and `extension_suffix`.
+
+    Args:
+        ctx: (RuleContext) the rule context.
+        base_file: (File) the file from which to derive the resultant file.
+        name_suffix: (str) Optional. The suffix to append to the name before the
+        extension.
+        extension: (str) Optional. The new extension to use (without '.'). By default,
+        the base_file's extension is used.
+        extension_suffix: (str) Optional. The suffix to append to the base_file's extension
+
+    Returns:
+        (File) the derived file
+    """
+    if not name_suffix and not extension_suffix and not extension:
+        fail("At least one of name_suffix, extension or extension_suffix is required")
+    if extension and extension_suffix:
+        fail("only one of extension or extension_suffix can be specified")
+    if extension == None:
+        extension = base_file.extension
+    new_basename = paths.replace_extension(base_file.basename, name_suffix + "." + extension + extension_suffix)
+    return ctx.actions.declare_file(new_basename, sibling = base_file)
+
+def _is_stamping_enabled(ctx, stamp):
+    if ctx.configuration.is_tool_configuration():
+        return 0
+    if stamp == 1 or stamp == 0:
+        return stamp
+
+    # stamp == -1 / auto
+    return int(ctx.configuration.stamp_binaries())
+
+helper = struct(
+    collect_all_targets_as_deps = _collect_all_targets_as_deps,
+    filter_launcher_for_target = _filter_launcher_for_target,
+    launcher_artifact_for_target = _launcher_artifact_for_target,
+    check_and_get_main_class = _check_and_get_main_class,
+    primary_class = _primary_class,
+    strip_extension = _strip_extension,
+    concat = _concat,
+    get_shared_native_deps_path = _get_shared_native_deps_path,
+    check_and_get_one_version_attribute = _check_and_get_one_version_attribute,
+    jar_and_target_arg_mapper = _jar_and_target_arg_mapper,
+    get_feature_config = _get_feature_config,
+    should_strip_as_default = _should_strip_as_default,
+    get_coverage_config = _get_coverage_config,
+    get_java_executable = _get_java_executable,
+    is_absolute_target_platform_path = _is_absolute_target_platform_path,
+    is_target_platform_windows = _is_target_platform_windows,
+    runfiles_enabled = _runfiles_enabled,
+    get_test_support = _get_test_support,
+    test_providers = _test_providers,
+    executable_providers = _executable_providers,
+    create_single_jar = _create_single_jar,
+    shell_escape = _shell_escape,
+    tokenize_javacopts = _tokenize_javacopts,
+    detokenize_javacopts = _detokenize_javacopts,
+    derive_output_file = _derive_output_file,
+    is_stamping_enabled = _is_stamping_enabled,
+)
diff --git a/java/common/rules/java_import.bzl b/java/common/rules/java_import.bzl
new file mode 100644
index 0000000..7ba8065
--- /dev/null
+++ b/java/common/rules/java_import.bzl
@@ -0,0 +1,356 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Definition of java_import rule.
+"""
+
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//java/common:java_common.bzl", "java_common")
+load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/common:java_semantics.bzl", "semantics")
+load(":basic_java_library.bzl", "construct_defaultinfo")
+load(":import_deps_check.bzl", "import_deps_check")
+load(":proguard_validation.bzl", "validate_proguard_specs")
+
+visibility(["//java/..."])
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+_run_ijar_private_for_builtins = _java_common_internal.run_ijar_private_for_builtins
+
+def _filter_provider(provider, *attrs):
+    return [dep[provider] for attr in attrs for dep in attr if provider in dep]
+
+def _collect_jars(ctx, jars):
+    jars_dict = {}
+    for info in jars:
+        if JavaInfo in info:
+            fail("'jars' attribute cannot contain labels of Java targets")
+        for jar in info.files.to_list():
+            jar_path = jar.dirname + jar.basename
+            if jars_dict.get(jar_path) != None:
+                fail("in jars attribute of java_import rule //" + ctx.label.package + ":" + ctx.attr.name + ": " + jar.basename + " is a duplicate")
+            jars_dict[jar_path] = jar
+    return [jar_tuple[1] for jar_tuple in jars_dict.items()] if len(jars_dict.items()) > 0 else []
+
+def _process_with_ijars_if_needed(jars, ctx):
+    file_dict = {}
+    use_ijars = ctx.fragments.java.use_ijars()
+    for jar in jars:
+        interface_jar = jar
+        if use_ijars:
+            ijar_basename = jar.short_path.removeprefix("../").removesuffix("." + jar.extension) + "-ijar.jar"
+            interface_jar_directory = "_ijar/" + ctx.label.name + "/" + ijar_basename
+
+            interface_jar = ctx.actions.declare_file(interface_jar_directory)
+            _run_ijar_private_for_builtins(
+                ctx.actions,
+                target_label = ctx.label,
+                jar = jar,
+                output = interface_jar,
+                java_toolchain = semantics.find_java_toolchain(ctx),
+            )
+        file_dict[jar] = interface_jar
+
+    return file_dict
+
+def _check_export_error(ctx, exports):
+    not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_exports") and not getattr(ctx.attr, "_allowlist_java_import_exports")[PackageSpecificationInfo].contains(ctx.label)
+    disallow_java_import_exports = ctx.fragments.java.disallow_java_import_exports()
+
+    if len(exports) != 0 and (disallow_java_import_exports or not_in_allowlist):
+        fail("java_import.exports is no longer supported; use java_import.deps instead")
+
+def _check_empty_jars_error(ctx, jars):
+    # TODO(kotlaja): Remove temporary incompatible flag [disallow_java_import_empty_jars] once migration is done.
+    not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_empty_jars") and not getattr(ctx.attr, "_allowlist_java_import_empty_jars")[PackageSpecificationInfo].contains(ctx.label)
+    disallow_java_import_empty_jars = ctx.fragments.java.disallow_java_import_empty_jars()
+
+    if len(jars) == 0 and disallow_java_import_empty_jars and not_in_allowlist:
+        fail("empty java_import.jars is no longer supported " + ctx.label.package)
+
+def _create_java_info_with_dummy_output_file(ctx, srcjar, all_deps, exports, runtime_deps_list, neverlink, cc_info_list, add_exports, add_opens):
+    dummy_jar = ctx.actions.declare_file(ctx.label.name + "_dummy.jar")
+    dummy_src_jar = srcjar
+    if dummy_src_jar == None:
+        dummy_src_jar = ctx.actions.declare_file(ctx.label.name + "_src_dummy.java")
+        ctx.actions.write(dummy_src_jar, "")
+    return java_common.compile(
+        ctx,
+        output = dummy_jar,
+        java_toolchain = semantics.find_java_toolchain(ctx),
+        source_files = [dummy_src_jar],
+        deps = all_deps,
+        runtime_deps = runtime_deps_list,
+        neverlink = neverlink,
+        exports = [export[JavaInfo] for export in exports if JavaInfo in export],  # Watchout, maybe you need to add them there manually.
+        native_libraries = cc_info_list,
+        add_exports = add_exports,
+        add_opens = add_opens,
+    )
+
+def bazel_java_import_rule(
+        ctx,
+        jars = [],
+        srcjar = None,
+        deps = [],
+        runtime_deps = [],
+        exports = [],
+        neverlink = False,
+        proguard_specs = [],
+        add_exports = [],
+        add_opens = []):
+    """Implements java_import.
+
+    This rule allows the use of precompiled .jar files as libraries in other Java rules.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      jars: (list[Artifact]) List of output jars.
+      srcjar: (Artifact) The jar containing the sources.
+      deps: (list[Target]) The list of dependent libraries.
+      runtime_deps: (list[Target]) Runtime dependencies to attach to the rule.
+      exports: (list[Target])  The list of exported libraries.
+      neverlink: (bool) Whether this rule should only be used for compilation and not at runtime.
+      proguard_specs: (list[File]) Files to be used as Proguard specification.
+      add_exports: (list[str]) Allow this library to access the given <module>/<package>.
+      add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+
+    Returns:
+      (list[provider]) A list containing DefaultInfo, JavaInfo,
+      OutputGroupsInfo, ProguardSpecProvider providers.
+    """
+
+    _check_empty_jars_error(ctx, jars)
+    _check_export_error(ctx, exports)
+
+    collected_jars = _collect_jars(ctx, jars)
+    all_deps = _filter_provider(JavaInfo, deps, exports)
+
+    jdeps_artifact = None
+    merged_java_info = java_common.merge(all_deps)
+    not_in_allowlist = hasattr(ctx.attr, "_allowlist_java_import_deps_checking") and not ctx.attr._allowlist_java_import_deps_checking[PackageSpecificationInfo].contains(ctx.label)
+    if len(collected_jars) > 0 and not_in_allowlist and "incomplete-deps" not in ctx.attr.tags:
+        jdeps_artifact = import_deps_check(
+            ctx,
+            collected_jars,
+            merged_java_info.compile_jars,
+            merged_java_info.transitive_compile_time_jars,
+            "java_import",
+        )
+
+    compilation_to_runtime_jar_map = _process_with_ijars_if_needed(collected_jars, ctx)
+    runtime_deps_list = [runtime_dep[JavaInfo] for runtime_dep in runtime_deps if JavaInfo in runtime_dep]
+    cc_info_list = [dep[CcInfo] for dep in deps if CcInfo in dep]
+    java_info = None
+    if len(collected_jars) > 0:
+        java_infos = []
+        for jar in collected_jars:
+            java_infos.append(JavaInfo(
+                output_jar = jar,
+                compile_jar = compilation_to_runtime_jar_map[jar],
+                deps = all_deps,
+                runtime_deps = runtime_deps_list,
+                neverlink = neverlink,
+                source_jar = srcjar,
+                exports = [export[JavaInfo] for export in exports if JavaInfo in export],  # Watchout, maybe you need to add them there manually.
+                native_libraries = cc_info_list,
+                add_exports = add_exports,
+                add_opens = add_opens,
+            ))
+        java_info = java_common.merge(java_infos)
+    else:
+        # TODO(kotlaja): Remove next line once all java_import targets with empty jars attribute are cleaned from depot (b/246559727).
+        java_info = _create_java_info_with_dummy_output_file(ctx, srcjar, all_deps, exports, runtime_deps_list, neverlink, cc_info_list, add_exports, add_opens)
+
+    target = {"JavaInfo": java_info}
+
+    target["ProguardSpecProvider"] = validate_proguard_specs(
+        ctx,
+        proguard_specs,
+        [deps, runtime_deps, exports],
+    )
+
+    # TODO(kotlaja): Revise if collected_runtimes can be added into construct_defaultinfo directly.
+    collected_runtimes = []
+    for runtime_dep in ctx.attr.runtime_deps:
+        collected_runtimes.extend(runtime_dep.files.to_list())
+
+    target["DefaultInfo"] = construct_defaultinfo(
+        ctx,
+        collected_jars,
+        collected_jars + collected_runtimes,
+        neverlink,
+        exports,
+    )
+
+    output_group_src_jars = depset() if srcjar == None else depset([srcjar])
+    target["OutputGroupInfo"] = OutputGroupInfo(
+        **{
+            "_source_jars": output_group_src_jars,
+            "_direct_source_jars": output_group_src_jars,
+            "_validation": depset() if jdeps_artifact == None else depset([jdeps_artifact]),
+            "_hidden_top_level_INTERNAL_": target["ProguardSpecProvider"].specs,
+        }
+    )
+    return target
+
+def _proxy(ctx):
+    return bazel_java_import_rule(
+        ctx,
+        ctx.attr.jars,
+        ctx.file.srcjar,
+        ctx.attr.deps,
+        ctx.attr.runtime_deps,
+        ctx.attr.exports,
+        ctx.attr.neverlink,
+        ctx.files.proguard_specs,
+        ctx.attr.add_exports,
+        ctx.attr.add_opens,
+    ).values()
+
+_ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT = [
+    "java_library",
+    "java_import",
+    "cc_library",
+    "cc_binary",
+]
+
+# buildifier: disable=attr-licenses
+JAVA_IMPORT_ATTRS = {
+    "data": attr.label_list(
+        allow_files = True,
+        flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+        doc = """
+The list of files needed by this rule at runtime.
+        """,
+    ),
+    "deps": attr.label_list(
+        providers = [JavaInfo],
+        allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
+        doc = """
+The list of other libraries to be linked in to the target.
+See <a href="${link java_library.deps}">java_library.deps</a>.
+        """,
+    ),
+    "exports": attr.label_list(
+        providers = [JavaInfo],
+        allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
+        doc = """
+Targets to make available to users of this rule.
+See <a href="${link java_library.exports}">java_library.exports</a>.
+        """,
+    ),
+    "runtime_deps": attr.label_list(
+        allow_files = [".jar"],
+        allow_rules = _ALLOWED_RULES_IN_DEPS_FOR_JAVA_IMPORT,
+        providers = [[CcInfo], [JavaInfo]],
+        flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
+        doc = """
+Libraries to make available to the final binary or test at runtime only.
+See <a href="${link java_library.runtime_deps}">java_library.runtime_deps</a>.
+        """,
+    ),
+    # JavaImportBazeRule attr
+    "jars": attr.label_list(
+        allow_files = [".jar"],
+        mandatory = True,
+        doc = """
+The list of JAR files provided to Java targets that depend on this target.
+        """,
+    ),
+    "srcjar": attr.label(
+        allow_single_file = [".srcjar", ".jar"],
+        flags = ["DIRECT_COMPILE_TIME_INPUT"],
+        doc = """
+A JAR file that contains source code for the compiled JAR files.
+        """,
+    ),
+    "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. Examples of libraries like this are IDE APIs
+for IDE plug-ins or <code>tools.jar</code> for anything running on
+a standard JDK.
+        """,
+    ),
+    "constraints": attr.string_list(
+        doc = """
+Extra constraints imposed on this rule as a Java library.
+        """,
+    ),
+    # ProguardLibraryRule attr
+    "proguard_specs": attr.label_list(
+        allow_files = True,
+        doc = """
+Files to be used as Proguard specification.
+These will describe the set of specifications to be used by Proguard. If specified,
+they will be added to any <code>android_binary</code> target depending on this library.
+
+The files included here must only have idempotent rules, namely -dontnote, -dontwarn,
+assumenosideeffects, and rules that start with -keep. Other options can only appear in
+<code>android_binary</code>'s proguard_specs, to ensure non-tautological merges.
+        """,
+    ),
+    # Additional attrs
+    "add_exports": attr.string_list(
+        doc = """
+Allow this library to access the given <code>module</code> or <code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-exports= flags.
+        """,
+    ),
+    "add_opens": attr.string_list(
+        doc = """
+Allow this library to reflectively access the given <code>module</code> or
+<code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-opens= flags.
+        """,
+    ),
+    "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+    "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE),
+}
+
+java_import = rule(
+    _proxy,
+    doc = """
+<p>
+  This rule allows the use of precompiled <code>.jar</code> files as
+  libraries for <code><a href="${link java_library}">java_library</a></code> and
+  <code>java_binary</code> rules.
+</p>
+
+<h4 id="java_import_examples">Examples</h4>
+
+<pre class="code">
+<code class="lang-starlark">
+    java_import(
+        name = "maven_model",
+        jars = [
+            "maven_model/maven-aether-provider-3.2.3.jar",
+            "maven_model/maven-model-3.2.3.jar",
+            "maven_model/maven-model-builder-3.2.3.jar",
+        ],
+    )
+</code>
+</pre>
+    """,
+    attrs = JAVA_IMPORT_ATTRS,
+    provides = [JavaInfo],
+    fragments = ["java", "cpp"],
+    toolchains = [semantics.JAVA_TOOLCHAIN],
+)
diff --git a/java/common/rules/java_library.bzl b/java/common/rules/java_library.bzl
new file mode 100644
index 0000000..1ca788d
--- /dev/null
+++ b/java/common/rules/java_library.bzl
@@ -0,0 +1,392 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Definition of java_library rule.
+"""
+
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//java/common:java_info.bzl", "JavaInfo")
+load("//java/common:java_plugin_info.bzl", "JavaPluginInfo")
+load("//java/common:java_semantics.bzl", "semantics")
+load(":android_lint.bzl", "android_lint_subrule")
+load(":basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS", "basic_java_library", "construct_defaultinfo")
+load(":rule_util.bzl", "merge_attrs")
+
+visibility(["//java/..."])
+
+BootClassPathInfo = java_common.BootClassPathInfo
+
+def bazel_java_library_rule(
+        ctx,
+        srcs = [],
+        deps = [],
+        runtime_deps = [],
+        plugins = [],
+        exports = [],
+        exported_plugins = [],
+        resources = [],
+        javacopts = [],
+        neverlink = False,
+        proguard_specs = [],
+        add_exports = [],
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
+    """Implements java_library.
+
+    Use this call when you need to produce a fully fledged java_library from
+    another rule's implementation.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      srcs: (list[File]) The list of source files that are processed to create the target.
+      deps: (list[Target]) The list of other libraries to be linked in to the target.
+      runtime_deps: (list[Target]) Libraries to make available to the final binary or test at runtime only.
+      plugins: (list[Target]) Java compiler plugins to run at compile-time.
+      exports: (list[Target]) Exported libraries.
+      exported_plugins: (list[Target]) The list of `java_plugin`s (e.g. annotation
+        processors) to export to libraries that directly depend on this library.
+      resources: (list[File]) A list of data files to include in a Java jar.
+      javacopts: (list[str]) Extra compiler options for this library.
+      neverlink: (bool) Whether this library should only be used for compilation and not at runtime.
+      proguard_specs: (list[File]) Files to be used as Proguard specification.
+      add_exports: (list[str]) Allow this library to access the given <module>/<package>.
+      add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (Target) The JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
+    Returns:
+      (dict[str, provider]) A list containing DefaultInfo, JavaInfo,
+        InstrumentedFilesInfo, OutputGroupsInfo, ProguardSpecProvider providers.
+    """
+    if not srcs and deps:
+        fail("deps not allowed without srcs; move to runtime_deps?")
+
+    target, base_info = basic_java_library(
+        ctx,
+        srcs,
+        deps,
+        runtime_deps,
+        plugins,
+        exports,
+        exported_plugins,
+        resources,
+        [],  # resource_jars
+        [],  # class_pathresources
+        javacopts,
+        neverlink,
+        proguard_specs = proguard_specs,
+        add_exports = add_exports,
+        add_opens = add_opens,
+        bootclasspath = bootclasspath,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
+    )
+
+    target["DefaultInfo"] = construct_defaultinfo(
+        ctx,
+        base_info.files_to_build,
+        base_info.runfiles,
+        neverlink,
+        exports,
+        runtime_deps,
+    )
+    target["OutputGroupInfo"] = OutputGroupInfo(**base_info.output_groups)
+
+    return target
+
+def _proxy(ctx):
+    return bazel_java_library_rule(
+        ctx,
+        ctx.files.srcs,
+        ctx.attr.deps,
+        ctx.attr.runtime_deps,
+        ctx.attr.plugins,
+        ctx.attr.exports,
+        ctx.attr.exported_plugins,
+        ctx.files.resources,
+        ctx.attr.javacopts,
+        ctx.attr.neverlink,
+        ctx.files.proguard_specs,
+        ctx.attr.add_exports,
+        ctx.attr.add_opens,
+        ctx.attr.bootclasspath,
+        ctx.attr.javabuilder_jvm_flags,
+    ).values()
+
+JAVA_LIBRARY_IMPLICIT_ATTRS = BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS
+
+JAVA_LIBRARY_ATTRS = merge_attrs(
+    JAVA_LIBRARY_IMPLICIT_ATTRS,
+    # buildifier: disable=attr-licenses
+    {
+        "srcs": attr.label_list(
+            allow_files = [".java", ".srcjar", ".properties"] + semantics.EXTRA_SRCS_TYPES,
+            flags = ["DIRECT_COMPILE_TIME_INPUT", "ORDER_INDEPENDENT"],
+            doc = """
+The list of source files that are processed to create the target.
+This attribute is almost always required; see exceptions below.
+<p>
+Source files of type <code>.java</code> are compiled. In case of generated
+<code>.java</code> files it is generally advisable to put the generating rule's name
+here instead of the name of the file itself. This not only improves readability but
+makes the rule more resilient to future changes: if the generating rule generates
+different files in the future, you only need to fix one place: the <code>outs</code> of
+the generating rule. You should not list the generating rule in <code>deps</code>
+because it is a no-op.
+</p>
+<p>
+Source files of type <code>.srcjar</code> are unpacked and compiled. (This is useful if
+you need to generate a set of <code>.java</code> files with a genrule.)
+</p>
+<p>
+Rules: if the rule (typically <code>genrule</code> or <code>filegroup</code>) generates
+any of the files listed above, they will be used the same way as described for source
+files.
+</p>
+<p>
+Source files of type <code>.properties</code> are treated as resources.
+</p>
+
+<p>All other files are ignored, as long as there is at least one file of a
+file type described above. Otherwise an error is raised.</p>
+
+<p>
+This argument is almost always required, except if you specify the <code>runtime_deps</code> argument.
+</p>
+            """,
+        ),
+        "data": attr.label_list(
+            allow_files = True,
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+            doc = """
+The list of files needed by this library at runtime.
+See general comments about <code>data</code> at
+<a href="${link common-definitions#typical-attributes}">Typical attributes defined by
+most build rules</a>.
+<p>
+  When building a <code>java_library</code>, Bazel doesn't put these files anywhere; if the
+  <code>data</code> files are generated files then Bazel generates them. When building a
+  test that depends on this <code>java_library</code> Bazel copies or links the
+  <code>data</code> files into the runfiles area.
+</p>
+            """ + semantics.DOCS.for_attribute("data"),
+        ),
+        "resources": attr.label_list(
+            allow_files = True,
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE", "ORDER_INDEPENDENT"],
+            doc = """
+A list of data files to include in a Java jar.
+<p>
+Resources may be source files or generated files.
+</p>
+            """ + semantics.DOCS.for_attribute("resources"),
+        ),
+        "plugins": attr.label_list(
+            providers = [JavaPluginInfo],
+            allow_files = True,
+            cfg = "exec",
+            doc = """
+Java compiler plugins to run at compile-time.
+Every <code>java_plugin</code> specified in this attribute will be run whenever this rule
+is built. A library may also inherit plugins from dependencies that use
+<code><a href="#java_library.exported_plugins">exported_plugins</a></code>. Resources
+generated by the plugin will be included in the resulting jar of this rule.
+            """,
+        ),
+        "deps": attr.label_list(
+            allow_files = [".jar"],
+            allow_rules = semantics.ALLOWED_RULES_IN_DEPS + semantics.ALLOWED_RULES_IN_DEPS_WITH_WARNING,
+            providers = [
+                [CcInfo],
+                [JavaInfo],
+            ],
+            flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
+            doc = """
+The list of libraries to link into this library.
+See general comments about <code>deps</code> at
+<a href="${link common-definitions#typical-attributes}">Typical attributes defined by
+most build rules</a>.
+<p>
+  The jars built by <code>java_library</code> rules listed in <code>deps</code> will be on
+  the compile-time classpath of this rule. Furthermore the transitive closure of their
+  <code>deps</code>, <code>runtime_deps</code> and <code>exports</code> will be on the
+  runtime classpath.
+</p>
+<p>
+  By contrast, targets in the <code>data</code> attribute are included in the runfiles but
+  on neither the compile-time nor runtime classpath.
+</p>
+            """,
+        ),
+        "runtime_deps": attr.label_list(
+            allow_files = [".jar"],
+            allow_rules = semantics.ALLOWED_RULES_IN_DEPS,
+            providers = [[CcInfo], [JavaInfo]],
+            flags = ["SKIP_ANALYSIS_TIME_FILETYPE_CHECK"],
+            doc = """
+Libraries to make available to the final binary or test at runtime only.
+Like ordinary <code>deps</code>, these will appear on the runtime classpath, but unlike
+them, not on the compile-time classpath. Dependencies needed only at runtime should be
+listed here. Dependency-analysis tools should ignore targets that appear in both
+<code>runtime_deps</code> and <code>deps</code>.
+            """,
+        ),
+        "exports": attr.label_list(
+            allow_rules = semantics.ALLOWED_RULES_IN_DEPS,
+            providers = [[JavaInfo], [CcInfo]],
+            doc = """
+Exported libraries.
+<p>
+  Listing rules here will make them available to parent rules, as if the parents explicitly
+  depended on these rules. This is not true for regular (non-exported) <code>deps</code>.
+</p>
+<p>
+  Summary: a rule <i>X</i> can access the code in <i>Y</i> if there exists a dependency
+  path between them that begins with a <code>deps</code> edge followed by zero or more
+  <code>exports</code> edges. Let's see some examples to illustrate this.
+</p>
+<p>
+  Assume <i>A</i> depends on <i>B</i> and <i>B</i> depends on <i>C</i>. In this case
+  C is a <em>transitive</em> dependency of A, so changing C's sources and rebuilding A will
+  correctly rebuild everything. However A will not be able to use classes in C. To allow
+  that, either A has to declare C in its <code>deps</code>, or B can make it easier for A
+  (and anything that may depend on A) by declaring C in its (B's) <code>exports</code>
+  attribute.
+</p>
+<p>
+  The closure of exported libraries is available to all direct parent rules. Take a slightly
+  different example: A depends on B, B depends on C and D, and also exports C but not D.
+  Now A has access to C but not to D. Now, if C and D exported some libraries, C' and D'
+  respectively, A could only access C' but not D'.
+</p>
+<p>
+  Important: an exported rule is not a regular dependency. Sticking to the previous example,
+  if B exports C and wants to also use C, it has to also list it in its own
+  <code>deps</code>.
+</p>
+            """,
+        ),
+        "exported_plugins": attr.label_list(
+            providers = [JavaPluginInfo],
+            cfg = "exec",
+            doc = """
+The list of <code><a href="#${link java_plugin}">java_plugin</a></code>s (e.g. annotation
+processors) to export to libraries that directly depend on this library.
+<p>
+  The specified list of <code>java_plugin</code>s will be applied to any library which
+  directly depends on this library, just as if that library had explicitly declared these
+  labels in <code><a href="${link java_library.plugins}">plugins</a></code>.
+</p>
+            """,
+        ),
+        "bootclasspath": attr.label(
+            providers = [BootClassPathInfo],
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+            doc = """Restricted API, do not use!""",
+        ),
+        "javabuilder_jvm_flags": attr.string_list(doc = """Restricted API, do not use!"""),
+        "javacopts": attr.string_list(
+            doc = """
+Extra compiler options for this library.
+Subject to <a href="make-variables.html">"Make variable"</a> substitution and
+<a href="common-definitions.html#sh-tokenization">Bourne shell tokenization</a>.
+<p>These compiler options are passed to javac after the global compiler options.</p>
+            """,
+        ),
+        "neverlink": attr.bool(
+            doc = """
+Whether this library should only be used for compilation and not at runtime.
+Useful if the library will be provided by the runtime environment during execution. Examples
+of such libraries are the IDE APIs for IDE plug-ins or <code>tools.jar</code> for anything
+running on a standard JDK.
+<p>
+  Note that <code>neverlink = 1</code> does not prevent the compiler from inlining material
+  from this library into compilation targets that depend on it, as permitted by the Java
+  Language Specification (e.g., <code>static final</code> constants of <code>String</code>
+  or of primitive types). The preferred use case is therefore when the runtime library is
+  identical to the compilation library.
+</p>
+<p>
+  If the runtime library differs from the compilation library then you must ensure that it
+  differs only in places that the JLS forbids compilers to inline (and that must hold for
+  all future versions of the JLS).
+</p>
+            """,
+        ),
+        "resource_strip_prefix": attr.string(
+            doc = """
+The path prefix to strip from Java resources.
+<p>
+If specified, this path prefix is stripped from every file in the <code>resources</code>
+attribute. It is an error for a resource file not to be under this directory. If not
+specified (the default), the path of resource file is determined according to the same
+logic as the Java package of source files. For example, a source file at
+<code>stuff/java/foo/bar/a.txt</code> will be located at <code>foo/bar/a.txt</code>.
+</p>
+            """,
+        ),
+        "proguard_specs": attr.label_list(
+            allow_files = True,
+            doc = """
+Files to be used as Proguard specification.
+These will describe the set of specifications to be used by Proguard. If specified,
+they will be added to any <code>android_binary</code> target depending on this library.
+
+The files included here must only have idempotent rules, namely -dontnote, -dontwarn,
+assumenosideeffects, and rules that start with -keep. Other options can only appear in
+<code>android_binary</code>'s proguard_specs, to ensure non-tautological merges.
+            """,
+        ),
+        "add_exports": attr.string_list(
+            doc = """
+Allow this library to access the given <code>module</code> or <code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-exports= flags.
+            """,
+        ),
+        "add_opens": attr.string_list(
+            doc = """
+Allow this library to reflectively access the given <code>module</code> or
+<code>package</code>.
+<p>
+This corresponds to the javac and JVM --add-opens= flags.
+            """,
+        ),
+        "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+        "_java_toolchain_type": attr.label(default = semantics.JAVA_TOOLCHAIN_TYPE),
+    },
+)
+
+java_library = rule(
+    _proxy,
+    doc = """
+<p>This rule compiles and links sources into a <code>.jar</code> file.</p>
+
+<h4>Implicit outputs</h4>
+<ul>
+  <li><code>lib<var>name</var>.jar</code>: A Java archive containing the class files.</li>
+  <li><code>lib<var>name</var>-src.jar</code>: An archive containing the sources ("source
+    jar").</li>
+</ul>
+    """,
+    attrs = JAVA_LIBRARY_ATTRS,
+    provides = [JavaInfo],
+    outputs = {
+        "classjar": "lib%{name}.jar",
+        "sourcejar": "lib%{name}-src.jar",
+    },
+    fragments = ["java", "cpp"],
+    toolchains = [semantics.JAVA_TOOLCHAIN],
+    subrules = [android_lint_subrule],
+)
diff --git a/java/common/rules/java_package_configuration.bzl b/java/common/rules/java_package_configuration.bzl
new file mode 100644
index 0000000..32e3abd
--- /dev/null
+++ b/java/common/rules/java_package_configuration.bzl
@@ -0,0 +1,115 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Implementation for the java_package_configuration rule"""
+
+load("//java/common:java_common.bzl", "java_common")
+load(":java_helper.bzl", "helper")
+
+visibility(["//java/..."])
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+
+JavaPackageConfigurationInfo = provider(
+    "A provider for Java per-package configuration",
+    fields = [
+        "data",
+        "javac_opts",
+        "matches",
+        "package_specs",
+    ],
+)
+
+def _matches(package_specs, label):
+    for spec in package_specs:
+        if spec.contains(label):
+            return True
+    return False
+
+def _rule_impl(ctx):
+    javacopts = _java_common_internal.expand_java_opts(ctx, "javacopts", tokenize = True)
+    javacopts_depset = helper.detokenize_javacopts(javacopts)
+    package_specs = [package[PackageSpecificationInfo] for package in ctx.attr.packages]
+    return [
+        DefaultInfo(),
+        JavaPackageConfigurationInfo(
+            data = depset(ctx.files.data),
+            javac_opts = javacopts_depset,
+            matches = lambda label: _matches(package_specs, label),
+            package_specs = package_specs,
+        ),
+    ]
+
+java_package_configuration = rule(
+    implementation = _rule_impl,
+    doc = """
+<p>
+Configuration to apply to a set of packages.
+Configurations can be added to
+<code><a href="${link java_toolchain.javacopts}">java_toolchain.javacopts</a></code>s.
+</p>
+
+<h4 id="java_package_configuration_example">Example:</h4>
+
+<pre class="code">
+<code class="lang-starlark">
+
+java_package_configuration(
+    name = "my_configuration",
+    packages = [":my_packages"],
+    javacopts = ["-Werror"],
+)
+
+package_group(
+    name = "my_packages",
+    packages = [
+        "//com/my/project/...",
+        "-//com/my/project/testing/...",
+    ],
+)
+
+java_toolchain(
+    ...,
+    package_configuration = [
+        ":my_configuration",
+    ]
+)
+
+</code>
+</pre>
+    """,
+    attrs = {
+        "packages": attr.label_list(
+            cfg = "exec",
+            providers = [PackageSpecificationInfo],
+            doc = """
+The set of <code><a href="${link package_group}">package_group</a></code>s
+the configuration should be applied to.
+            """,
+        ),
+        "javacopts": attr.string_list(
+            doc = """
+Java compiler flags.
+            """,
+        ),
+        "data": attr.label_list(
+            allow_files = True,
+            doc = """
+The list of files needed by this configuration at runtime.
+            """,
+        ),
+        # buildifier: disable=attr-licenses
+        "output_licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+    },
+)
diff --git a/java/common/rules/java_plugin.bzl b/java/common/rules/java_plugin.bzl
new file mode 100644
index 0000000..4dbf4e9
--- /dev/null
+++ b/java/common/rules/java_plugin.bzl
@@ -0,0 +1,185 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Definition of java_plugin rule.
+"""
+
+load("//java/common:java_plugin_info.bzl", "JavaPluginInfo")
+load("//java/common:java_semantics.bzl", "semantics")
+load(":android_lint.bzl", "android_lint_subrule")
+load(":basic_java_library.bzl", "basic_java_library", "construct_defaultinfo")
+load(":java_library.bzl", "JAVA_LIBRARY_ATTRS", "JAVA_LIBRARY_IMPLICIT_ATTRS")
+load(":rule_util.bzl", "merge_attrs")
+
+visibility(["//java/..."])
+
+def bazel_java_plugin_rule(
+        ctx,
+        srcs = [],
+        data = [],
+        generates_api = False,
+        processor_class = "",
+        deps = [],
+        plugins = [],
+        resources = [],
+        javacopts = [],
+        neverlink = False,
+        proguard_specs = [],
+        add_exports = [],
+        add_opens = []):
+    """Implements java_plugin rule.
+
+    Use this call when you need to produce a fully fledged java_plugin from
+    another rule's implementation.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      srcs: (list[File]) The list of source files that are processed to create the target.
+      data: (list[File]) The list of files needed by this plugin at runtime.
+      generates_api: (bool) This attribute marks annotation processors that generate API code.
+      processor_class: (str) The processor class is the fully qualified type of
+        the class that the Java compiler should use as entry point to the annotation processor.
+      deps: (list[Target]) The list of other libraries to be linked in to the target.
+      plugins: (list[Target]) Java compiler plugins to run at compile-time.
+      resources: (list[File]) A list of data files to include in a Java jar.
+      javacopts: (list[str]) Extra compiler options for this library.
+      neverlink: (bool) Whether this library should only be used for compilation and not at runtime.
+      proguard_specs: (list[File]) Files to be used as Proguard specification.
+      add_exports: (list[str]) Allow this library to access the given <module>/<package>.
+      add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+    Returns:
+      (list[provider]) A list containing DefaultInfo, JavaInfo,
+        InstrumentedFilesInfo, OutputGroupsInfo, ProguardSpecProvider providers.
+    """
+    target, base_info = basic_java_library(
+        ctx,
+        srcs,
+        deps,
+        [],  # runtime_deps
+        plugins,
+        [],  # exports
+        [],  # exported_plugins
+        resources,
+        [],  # resource_jars
+        [],  # classpath_resources
+        javacopts,
+        neverlink,
+        proguard_specs = proguard_specs,
+        add_exports = add_exports,
+        add_opens = add_opens,
+    )
+    java_info = target.pop("JavaInfo")
+
+    # Replace JavaInfo with JavaPluginInfo
+    target["JavaPluginInfo"] = JavaPluginInfo(
+        runtime_deps = [java_info],
+        processor_class = processor_class if processor_class else None,  # ignore empty string (default)
+        data = data,
+        generates_api = generates_api,
+    )
+    target["DefaultInfo"] = construct_defaultinfo(
+        ctx,
+        base_info.files_to_build,
+        base_info.runfiles,
+        neverlink,
+    )
+    target["OutputGroupInfo"] = OutputGroupInfo(**base_info.output_groups)
+
+    return target
+
+def _proxy(ctx):
+    return bazel_java_plugin_rule(
+        ctx,
+        ctx.files.srcs,
+        ctx.files.data,
+        ctx.attr.generates_api,
+        ctx.attr.processor_class,
+        ctx.attr.deps,
+        ctx.attr.plugins,
+        ctx.files.resources,
+        ctx.attr.javacopts,
+        ctx.attr.neverlink,
+        ctx.files.proguard_specs,
+        ctx.attr.add_exports,
+        ctx.attr.add_opens,
+    ).values()
+
+JAVA_PLUGIN_ATTRS = merge_attrs(
+    JAVA_LIBRARY_ATTRS,
+    {
+        "generates_api": attr.bool(doc = """
+This attribute marks annotation processors that generate API code.
+<p>If a rule uses an API-generating annotation processor, other rules
+depending on it can refer to the generated code only if their
+compilation actions are scheduled after the generating rule. This
+attribute instructs Bazel to introduce scheduling constraints when
+--java_header_compilation is enabled.
+<p><em class="harmful">WARNING: This attribute affects build
+performance, use it only if necessary.</em></p>
+        """),
+        "processor_class": attr.string(doc = """
+The processor class is the fully qualified type of the class that the Java compiler should
+use as entry point to the annotation processor. If not specified, this rule will not
+contribute an annotation processor to the Java compiler's annotation processing, but its
+runtime classpath will still be included on the compiler's annotation processor path. (This
+is primarily intended for use by
+<a href="https://errorprone.info/docs/plugins">Error Prone plugins</a>, which are loaded
+from the annotation processor path using
+<a href="https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html">
+java.util.ServiceLoader</a>.)
+       """),
+        # buildifier: disable=attr-licenses
+        "output_licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+    },
+    remove_attrs = ["runtime_deps", "exports", "exported_plugins"],
+)
+
+JAVA_PLUGIN_IMPLICIT_ATTRS = JAVA_LIBRARY_IMPLICIT_ATTRS
+
+java_plugin = rule(
+    _proxy,
+    doc = """
+<p>
+  <code>java_plugin</code> defines plugins for the Java compiler run by Bazel. The
+  only supported kind of plugins are annotation processors. A <code>java_library</code> or
+  <code>java_binary</code> rule can run plugins by depending on them via the <code>plugins</code>
+  attribute. A <code>java_library</code> can also automatically export plugins to libraries that
+  directly depend on it using
+  <code><a href="${link java_library.exported_plugins}">exported_plugins</a></code>.
+</p>
+
+<h4 id="java_plugin_implicit_outputs">Implicit output targets</h4>
+    <ul>
+      <li><code><var>libname</var>.jar</code>: A Java archive.</li>
+    </ul>
+
+<p>
+  Arguments are identical to <a href="${link java_library}"><code>java_library</code></a>, except
+  for the addition of the <code>processor_class</code> argument.
+</p>
+    """,
+    attrs = merge_attrs(
+        JAVA_PLUGIN_ATTRS,
+        JAVA_PLUGIN_IMPLICIT_ATTRS,
+    ),
+    provides = [JavaPluginInfo],
+    outputs = {
+        "classjar": "lib%{name}.jar",
+        "sourcejar": "lib%{name}-src.jar",
+    },
+    fragments = ["java", "cpp"],
+    toolchains = [semantics.JAVA_TOOLCHAIN],
+    subrules = [android_lint_subrule],
+)
diff --git a/java/common/rules/java_runtime.bzl b/java/common/rules/java_runtime.bzl
new file mode 100644
index 0000000..af76f49
--- /dev/null
+++ b/java/common/rules/java_runtime.bzl
@@ -0,0 +1,260 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Definition of java_runtime rule and JavaRuntimeInfo provider.
+"""
+
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//java/common:java_semantics.bzl", "PLATFORMS_ROOT")
+load("//third_party/bazel_skylib/lib:paths.bzl", "paths")
+load(":java_helper.bzl", "helper")
+
+visibility(["//java/..."])
+
+ToolchainInfo = platform_common.ToolchainInfo
+
+def _init_java_runtime_info(**_kwargs):
+    fail("instantiating JavaRuntimeInfo is a private API")
+
+JavaRuntimeInfo, _new_javaruntimeinfo = provider(
+    doc = "Information about the Java runtime used by the java rules.",
+    fields = {
+        "default_cds": "Returns the JDK default CDS archive.",
+        "files": "Returns the files in the Java runtime.",
+        "hermetic_files": "Returns the files in the Java runtime needed for hermetic deployments.",
+        "hermetic_static_libs": "Returns the JDK static libraries.",
+        "java_executable_exec_path": "Returns the execpath of the Java executable.",
+        "java_executable_runfiles_path": """Returns the path of the Java executable in
+                runfiles trees. This should only be used when one needs to access the
+                JVM during the execution of a binary or a test built by Bazel. In particular,
+                when one needs to invoke the JVM during an action, java_executable_exec_path
+                should be used instead.""",
+        "java_home": "Returns the execpath of the root of the Java installation.",
+        "java_home_runfiles_path": """Returns the path of the Java installation in runfiles trees.
+                This should only be used when one needs to access the JDK during the execution
+                of a binary or a test built by Bazel. In particular, when one needs the JDK
+                during an action, java_home should be used instead.""",
+        "lib_ct_sym": "Returns the lib/ct.sym file.",
+        "lib_modules": "Returns the lib/modules file.",
+        "version": "The Java feature version of the runtime. This is 0 if the version is unknown.",
+    },
+    init = _init_java_runtime_info,
+)
+
+def _is_main_repo(label):
+    return label.workspace_name == ""
+
+def _default_java_home(label):
+    if _is_main_repo(label):
+        return label.package
+    else:
+        return paths.get_relative(label.workspace_root, label.package)
+
+def _get_bin_java(ctx):
+    is_windows = helper.is_target_platform_windows(ctx)
+    return "bin/java.exe" if is_windows else "bin/java"
+
+def _get_runfiles_java_executable(ctx, java_home, label):
+    if paths.is_absolute(java_home) or _is_main_repo(label):
+        return paths.get_relative(java_home, _get_bin_java(ctx))
+    else:
+        repo_runfiles_path = "" if _is_main_repo(label) else paths.get_relative("..", label.workspace_name)
+        return paths.get_relative(repo_runfiles_path, _get_bin_java(ctx))
+
+def _is_java_binary(path):
+    return path.endswith("bin/java") or path.endswith("bin/java.exe")
+
+def _get_lib_ct_sym(srcs, explicit_lib_ct_sym):
+    if explicit_lib_ct_sym:
+        return explicit_lib_ct_sym
+    candidates = [src for src in srcs if src.path.endswith("/lib/ct.sym")]
+    if len(candidates) == 1:
+        return candidates[0]
+    else:
+        return None
+
+def _java_runtime_rule_impl(ctx):
+    all_files = []  # [depset[File]]
+    all_files.append(depset(ctx.files.srcs))
+
+    java_home = _default_java_home(ctx.label)
+    if ctx.attr.java_home:
+        java_home_attr = ctx.expand_make_variables("java_home", ctx.attr.java_home, {})
+        if ctx.files.srcs and paths.is_absolute(java_home_attr):
+            fail("'java_home' with an absolute path requires 'srcs' to be empty.")
+        java_home = paths.get_relative(java_home, java_home_attr)
+
+    java_binary_exec_path = paths.get_relative(java_home, _get_bin_java(ctx))
+    java_binary_runfiles_path = _get_runfiles_java_executable(ctx, java_home, ctx.label)
+
+    java = ctx.file.java
+    if java:
+        if paths.is_absolute(java_home):
+            fail("'java_home' with an absolute path requires 'java' to be empty.")
+        java_binary_exec_path = java.path
+        java_binary_runfiles_path = java.short_path
+        if not _is_java_binary(java_binary_exec_path):
+            fail("the path to 'java' must end in 'bin/java'.")
+        java_home = paths.dirname(paths.dirname(java_binary_exec_path))
+        all_files.append(depset([java]))
+
+    java_home_runfiles_path = paths.dirname(paths.dirname(java_binary_runfiles_path))
+
+    hermetic_inputs = depset(ctx.files.hermetic_srcs)
+    all_files.append(hermetic_inputs)
+
+    lib_ct_sym = _get_lib_ct_sym(ctx.files.srcs, ctx.file.lib_ct_sym)
+    lib_modules = ctx.file.lib_modules
+    hermetic_static_libs = [dep[CcInfo] for dep in ctx.attr.hermetic_static_libs]
+
+    # If a runtime does not set default_cds in hermetic mode, it is not fatal.
+    # We can skip the default CDS in the check below.
+    default_cds = ctx.file.default_cds
+
+    if (hermetic_inputs or lib_modules or hermetic_static_libs) and (
+        not hermetic_inputs or not lib_modules or not hermetic_static_libs
+    ):
+        fail("hermetic specified, all of java_runtime.lib_modules, java_runtime.hermetic_srcs and java_runtime.hermetic_static_libs must be specified")
+
+    files = depset(transitive = all_files)
+
+    java_runtime_info = _new_javaruntimeinfo(
+        default_cds = default_cds,
+        files = files,
+        hermetic_files = hermetic_inputs,
+        hermetic_static_libs = hermetic_static_libs,
+        java_executable_exec_path = java_binary_exec_path,
+        java_executable_runfiles_path = java_binary_runfiles_path,
+        java_home = java_home,
+        java_home_runfiles_path = java_home_runfiles_path,
+        lib_ct_sym = lib_ct_sym,
+        lib_modules = lib_modules,
+        version = ctx.attr.version,
+    )
+    return [
+        DefaultInfo(
+            files = files,
+            runfiles = ctx.runfiles(transitive_files = files),
+        ),
+        java_runtime_info,
+        platform_common.TemplateVariableInfo({
+            "JAVA": java_binary_exec_path,
+            "JAVABASE": java_home,
+        }),
+        ToolchainInfo(java_runtime = java_runtime_info),
+    ]
+
+java_runtime = rule(
+    implementation = _java_runtime_rule_impl,
+    doc = """
+<p>
+Specifies the configuration for a Java runtime.
+</p>
+
+<h4 id="java_runtime_example">Example:</h4>
+
+<pre class="code">
+<code class="lang-starlark">
+
+java_runtime(
+    name = "jdk-9-ea+153",
+    srcs = glob(["jdk9-ea+153/**"]),
+    java_home = "jdk9-ea+153",
+)
+
+</code>
+</pre>
+    """,
+    attrs = {
+        "default_cds": attr.label(
+            allow_single_file = True,
+            executable = True,
+            cfg = "target",
+            doc = """
+Default CDS archive for hermetic <code>java_runtime</code>. When hermetic
+is enabled for a <code>java_binary</code> target and if the target does not
+provide its own CDS archive by specifying the
+<a href="${link java_binary.classlist}"><code>classlist</code></a> attribute,
+the <code>java_runtime</code> default CDS is packaged in the hermetic deploy JAR.
+            """,
+        ),
+        "hermetic_srcs": attr.label_list(
+            allow_files = True,
+            doc = """
+Files in the runtime needed for hermetic deployments.
+            """,
+        ),
+        "hermetic_static_libs": attr.label_list(
+            providers = [CcInfo],
+            doc = """
+The libraries that are statically linked with the launcher for hermetic deployments
+            """,
+        ),
+        "java": attr.label(
+            allow_single_file = True,
+            executable = True,
+            cfg = "target",
+            doc = """
+The path to the java executable.
+            """,
+        ),
+        "java_home": attr.string(
+            doc = """
+The path to the root of the runtime.
+Subject to <a href="${link make-variables}">"Make" variable</a> substitution.
+If this path is absolute, the rule denotes a non-hermetic Java runtime with a well-known
+path. In that case, the <code>srcs</code> and <code>java</code> attributes must be empty.
+            """,
+        ),
+        "lib_ct_sym": attr.label(
+            allow_single_file = True,
+            doc = """
+The lib/ct.sym file needed for compilation with <code>--release</code>. If not specified and
+there is exactly one file in <code>srcs</code> whose path ends with
+<code>/lib/ct.sym</code>, that file is used.
+            """,
+        ),
+        "lib_modules": attr.label(
+            allow_single_file = True,
+            executable = True,
+            cfg = "target",
+            doc = """
+The lib/modules file needed for hermetic deployments.
+            """,
+        ),
+        "srcs": attr.label_list(
+            allow_files = True,
+            doc = """
+All files in the runtime.
+            """,
+        ),
+        "version": attr.int(
+            doc = """
+The feature version of the Java runtime. I.e., the integer returned by
+<code>Runtime.version().feature()</code>.
+            """,
+        ),
+        # buildifier: disable=attr-licenses
+        "output_licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+        "_windows_constraints": attr.label_list(
+            default = [paths.join(PLATFORMS_ROOT, "os:windows")],
+        ),
+    },
+    fragments = ["java"],
+    provides = [
+        JavaRuntimeInfo,
+        platform_common.TemplateVariableInfo,
+    ],
+)
diff --git a/java/common/rules/java_toolchain.bzl b/java/common/rules/java_toolchain.bzl
new file mode 100644
index 0000000..8e0f583
--- /dev/null
+++ b/java/common/rules/java_toolchain.bzl
@@ -0,0 +1,604 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Definition of java_toolchain rule and JavaToolchainInfo provider.
+"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+load(":java_helper.bzl", "helper")
+load(":java_package_configuration.bzl", "JavaPackageConfigurationInfo")
+load(":java_runtime.bzl", "JavaRuntimeInfo")
+
+visibility(["//java/..."])
+
+_java_common_internal = java_common.internal_DO_NOT_USE()
+ToolchainInfo = platform_common.ToolchainInfo
+BootClassPathInfo = java_common.BootClassPathInfo
+JavaPluginDataInfo = _java_common_internal.JavaPluginDataInfo
+
+def _java_toolchain_info_init(**_kwargs):
+    fail("JavaToolchainInfo instantiation is a private API")
+
+_PRIVATE_API_DOC_STRING = "internal API, DO NOT USE!"
+
+JavaToolchainInfo, _new_javatoolchaininfo = provider(
+    doc = "Information about the JDK used by the <code>java_*</code> rules.",
+    fields = {
+        "bootclasspath": "(depset[File]) The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.",
+        "ijar": "(FilesToRunProvider) The ijar executable.",
+        "jacocorunner": "(FilesToRunProvider) The jacocorunner used by the toolchain.",
+        "java_runtime": "(JavaRuntimeInfo) The java runtime information.",
+        "jvm_opt": "(depset[str]) The default options for the JVM running the java compiler and associated tools.",
+        "label": "(label) The toolchain label.",
+        "proguard_allowlister": "(FilesToRunProvider) The binary to validate proguard configuration.",
+        "single_jar": "(FilesToRunProvider) The SingleJar deploy jar.",
+        "source_version": "(str) The java source version.",
+        "target_version": "(str) The java target version.",
+        "tools": "(depset[File]) The compilation tools.",
+        # private
+        "_android_linter": _PRIVATE_API_DOC_STRING,
+        "_bootclasspath_info": _PRIVATE_API_DOC_STRING,
+        "_bytecode_optimizer": _PRIVATE_API_DOC_STRING,
+        "_compatible_javacopts": _PRIVATE_API_DOC_STRING,
+        "_deps_checker": _PRIVATE_API_DOC_STRING,
+        "_forcibly_disable_header_compilation": _PRIVATE_API_DOC_STRING,
+        "_gen_class": _PRIVATE_API_DOC_STRING,
+        "_header_compiler": _PRIVATE_API_DOC_STRING,
+        "_header_compiler_builtin_processors": _PRIVATE_API_DOC_STRING,
+        "_header_compiler_direct": _PRIVATE_API_DOC_STRING,
+        "_javabuilder": _PRIVATE_API_DOC_STRING,
+        "_javacopts": _PRIVATE_API_DOC_STRING,
+        "_javacopts_list": _PRIVATE_API_DOC_STRING,
+        "_javac_supports_workers": _PRIVATE_API_DOC_STRING,
+        "_javac_supports_multiplex_workers": _PRIVATE_API_DOC_STRING,
+        "_javac_supports_worker_cancellation": _PRIVATE_API_DOC_STRING,
+        "_javac_supports_worker_multiplex_sandboxing": _PRIVATE_API_DOC_STRING,
+        "_jspecify_info": _PRIVATE_API_DOC_STRING,
+        "_local_java_optimization_config": _PRIVATE_API_DOC_STRING,
+        "_one_version_tool": _PRIVATE_API_DOC_STRING,
+        "_one_version_allowlist": _PRIVATE_API_DOC_STRING,
+        "_one_version_allowlist_for_tests": _PRIVATE_API_DOC_STRING,
+        "_package_configuration": _PRIVATE_API_DOC_STRING,
+        "_reduced_classpath_incompatible_processors": _PRIVATE_API_DOC_STRING,
+        "_timezone_data": _PRIVATE_API_DOC_STRING,
+    },
+    init = _java_toolchain_info_init,
+)
+
+def _java_toolchain_impl(ctx):
+    javac_opts_list = _get_javac_opts(ctx)
+    bootclasspath_info = _get_bootclasspath_info(ctx)
+    java_runtime = _get_java_runtime(ctx)
+    if java_runtime and java_runtime.lib_ct_sym:
+        header_compiler_direct_data = [java_runtime.lib_ct_sym]
+        header_compiler_direct_jvm_opts = ["-Dturbine.ctSymPath=" + java_runtime.lib_ct_sym.path]
+    elif java_runtime and java_runtime.java_home:
+        # Turbine finds ct.sym relative to java.home.
+        header_compiler_direct_data = []
+        header_compiler_direct_jvm_opts = ["-Djava.home=" + java_runtime.java_home]
+    else:
+        header_compiler_direct_data = []
+        header_compiler_direct_jvm_opts = []
+    java_toolchain_info = _new_javatoolchaininfo(
+        bootclasspath = bootclasspath_info.bootclasspath,
+        ijar = ctx.attr.ijar.files_to_run if ctx.attr.ijar else None,
+        jacocorunner = ctx.attr.jacocorunner.files_to_run if ctx.attr.jacocorunner else None,
+        java_runtime = java_runtime,
+        jvm_opt = depset(_java_common_internal.expand_java_opts(ctx, "jvm_opts", tokenize = False, exec_paths = True)),
+        label = ctx.label,
+        proguard_allowlister = ctx.attr.proguard_allowlister.files_to_run if ctx.attr.proguard_allowlister else None,
+        single_jar = ctx.attr.singlejar.files_to_run,
+        source_version = ctx.attr.source_version,
+        target_version = ctx.attr.target_version,
+        tools = depset(ctx.files.tools),
+        # private
+        _android_linter = _get_android_lint_tool(ctx),
+        _bootclasspath_info = bootclasspath_info,
+        _bytecode_optimizer = _get_tool_from_executable(ctx, "_bytecode_optimizer"),
+        _compatible_javacopts = _get_compatible_javacopts(ctx),
+        _deps_checker = ctx.file.deps_checker,
+        _forcibly_disable_header_compilation = ctx.attr.forcibly_disable_header_compilation,
+        _gen_class = ctx.file.genclass,
+        _header_compiler = _get_tool_from_ctx(ctx, "header_compiler", "turbine_data", "turbine_jvm_opts"),
+        _header_compiler_builtin_processors = depset(ctx.attr.header_compiler_builtin_processors),
+        _header_compiler_direct = _get_tool_from_executable(
+            ctx,
+            "header_compiler_direct",
+            data = header_compiler_direct_data,
+            jvm_opts = header_compiler_direct_jvm_opts,
+        ),
+        _javabuilder = _get_tool_from_ctx(ctx, "javabuilder", "javabuilder_data", "javabuilder_jvm_opts"),
+        _javacopts = helper.detokenize_javacopts(javac_opts_list),
+        _javacopts_list = javac_opts_list,
+        _javac_supports_workers = ctx.attr.javac_supports_workers,
+        _javac_supports_multiplex_workers = ctx.attr.javac_supports_multiplex_workers,
+        _javac_supports_worker_cancellation = ctx.attr.javac_supports_worker_cancellation,
+        _javac_supports_worker_multiplex_sandboxing = ctx.attr.javac_supports_worker_multiplex_sandboxing,
+        _jspecify_info = _get_jspecify_info(ctx),
+        _local_java_optimization_config = ctx.files._local_java_optimization_configuration,
+        _one_version_tool = ctx.attr.oneversion.files_to_run if ctx.attr.oneversion else None,
+        _one_version_allowlist = ctx.file.oneversion_whitelist,
+        _one_version_allowlist_for_tests = ctx.file.oneversion_allowlist_for_tests,
+        _package_configuration = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.package_configuration],
+        _reduced_classpath_incompatible_processors = depset(ctx.attr.reduced_classpath_incompatible_processors, order = "preorder"),
+        _timezone_data = ctx.file.timezone_data,
+    )
+    toolchain_info = ToolchainInfo(java = java_toolchain_info)
+    return [java_toolchain_info, toolchain_info, DefaultInfo()]
+
+def _get_bootclasspath_info(ctx):
+    bootclasspath_infos = [dep[BootClassPathInfo] for dep in ctx.attr.bootclasspath if BootClassPathInfo in dep]
+    if bootclasspath_infos:
+        if len(bootclasspath_infos) != 1:
+            fail("in attribute 'bootclasspath': expected exactly one entry with a BootClassPathInfo provider")
+        else:
+            return bootclasspath_infos[0]
+    else:
+        return BootClassPathInfo(bootclasspath = ctx.files.bootclasspath)
+
+def _get_java_runtime(ctx):
+    if not ctx.attr.java_runtime:
+        return None
+    return ctx.attr.java_runtime[ToolchainInfo].java_runtime
+
+def _get_javac_opts(ctx):
+    opts = []
+    if ctx.attr.source_version:
+        opts.extend(["-source", ctx.attr.source_version])
+    if ctx.attr.target_version:
+        opts.extend(["-target", ctx.attr.target_version])
+    if ctx.attr.xlint:
+        opts.append("-Xlint:" + ",".join(ctx.attr.xlint))
+    opts.extend(_java_common_internal.expand_java_opts(ctx, "misc", tokenize = True))
+    opts.extend(_java_common_internal.expand_java_opts(ctx, "javacopts", tokenize = True))
+    return opts
+
+def _get_android_lint_tool(ctx):
+    if not ctx.attr.android_lint_runner:
+        return None
+    files_to_run = ctx.attr.android_lint_runner.files_to_run
+    if not files_to_run or not files_to_run.executable:
+        fail(ctx.attr.android_lint_runner.label, "does not refer to a valid executable target")
+    return struct(
+        tool = files_to_run,
+        data = depset(ctx.files.android_lint_data),
+        jvm_opts = depset([ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_jvm_opts]),
+        lint_opts = [ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_opts],
+        package_config = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.android_lint_package_configuration],
+    )
+
+def _get_tool_from_ctx(ctx, tool_attr, data_attr, opts_attr):
+    dep = getattr(ctx.attr, tool_attr)
+    if not dep:
+        return None
+    files_to_run = dep.files_to_run
+    if not files_to_run or not files_to_run.executable:
+        fail(dep.label, "does not refer to a valid executable target")
+    data = getattr(ctx.attr, data_attr)
+    return struct(
+        tool = files_to_run,
+        data = depset(getattr(ctx.files, data_attr)),
+        jvm_opts = depset([ctx.expand_location(opt, data) for opt in getattr(ctx.attr, opts_attr)]),
+    )
+
+def _get_tool_from_executable(ctx, attr_name, data = [], jvm_opts = []):
+    dep = getattr(ctx.attr, attr_name)
+    if not dep:
+        return None
+    files_to_run = dep.files_to_run
+    if not files_to_run or not files_to_run.executable:
+        fail(dep.label, "does not refer to a valid executable target")
+    return struct(tool = files_to_run, data = depset(data), jvm_opts = depset(jvm_opts))
+
+def _get_compatible_javacopts(ctx):
+    result = {}
+    for key, opt_list in ctx.attr.compatible_javacopts.items():
+        result[key] = helper.detokenize_javacopts([token for opt in opt_list for token in ctx.tokenize(opt)])
+    return result
+
+def _get_jspecify_info(ctx):
+    if not ctx.attr.jspecify_processor_class:
+        return None
+    stubs = ctx.files.jspecify_stubs
+    javacopts = []
+    javacopts.extend(ctx.attr.jspecify_javacopts)
+    if stubs:
+        javacopts.append("-Astubs=" + ":".join([file.path for file in stubs]))
+    return struct(
+        processor = JavaPluginDataInfo(
+            processor_classes = depset([ctx.attr.jspecify_processor_class]),
+            processor_jars = depset([ctx.file.jspecify_processor]),
+            processor_data = depset(stubs),
+        ),
+        implicit_deps = depset([ctx.file.jspecify_implicit_deps]),
+        javacopts = javacopts,
+        packages = [target[PackageSpecificationInfo] for target in ctx.attr.jspecify_packages],
+    )
+
+def _extract_singleton_list_value(dict, key):
+    if key in dict and type(dict[key]) == type([]):
+        list = dict[key]
+        if len(list) > 1:
+            fail("expected a single value for:", key, "got: ", list)
+        elif len(list) == 1:
+            dict[key] = dict[key][0]
+        else:
+            dict[key] = None
+
+_LEGACY_ANY_TYPE_ATTRS = [
+    "genclass",
+    "deps_checker",
+    "header_compiler",
+    "header_compiler_direct",
+    "ijar",
+    "javabuilder",
+    "singlejar",
+]
+
+def _java_toolchain_initializer(**kwargs):
+    # these attributes are defined as executable `label_list`s in native but are
+    # expected to be singleton values. Since this is not supported in Starlark,
+    # we just inline the value from the list (if present) before invoking the
+    # rule.
+    for attr in _LEGACY_ANY_TYPE_ATTRS:
+        _extract_singleton_list_value(kwargs, attr)
+
+    return kwargs
+
+java_toolchain = rule(
+    implementation = _java_toolchain_impl,
+    initializer = _java_toolchain_initializer,
+    doc = """
+<p>
+Specifies the configuration for the Java compiler. Which toolchain to be used can be changed through
+the --java_toolchain argument. Normally you should not write those kind of rules unless you want to
+tune your Java compiler.
+</p>
+
+<h4>Examples</h4>
+
+<p>A simple example would be:
+</p>
+
+<pre class="code">
+<code class="lang-starlark">
+
+java_toolchain(
+    name = "toolchain",
+    source_version = "7",
+    target_version = "7",
+    bootclasspath = ["//tools/jdk:bootclasspath"],
+    xlint = [ "classfile", "divzero", "empty", "options", "path" ],
+    javacopts = [ "-g" ],
+    javabuilder = ":JavaBuilder_deploy.jar",
+)
+</code>
+</pre>
+    """,
+    # buildifier: disable=attr-licenses
+    attrs = {
+        "android_lint_data": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            doc = """
+Labels of tools available for label-expansion in android_lint_jvm_opts.
+            """,
+        ),
+        "android_lint_opts": attr.string_list(
+            default = [],
+            doc = """
+The list of Android Lint arguments.
+            """,
+        ),
+        "android_lint_jvm_opts": attr.string_list(
+            default = [],
+            doc = """
+The list of arguments for the JVM when invoking Android Lint.
+            """,
+        ),
+        "android_lint_package_configuration": attr.label_list(
+            cfg = "exec",
+            providers = [JavaPackageConfigurationInfo],
+            allow_files = True,
+            doc = """
+Android Lint Configuration that should be applied to the specified package groups.
+            """,
+        ),
+        "android_lint_runner": attr.label(
+            cfg = "exec",
+            executable = True,
+            allow_single_file = True,
+            doc = """
+Label of the Android Lint runner, if any.
+            """,
+        ),
+        "bootclasspath": attr.label_list(
+            default = [],
+            allow_files = True,
+            doc = """
+The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.
+            """,
+        ),
+        "compatible_javacopts": attr.string_list_dict(
+            doc = """Internal API, do not use!""",
+        ),
+        "deps_checker": attr.label(
+            allow_single_file = True,
+            cfg = "exec",
+            executable = True,
+            doc = """
+Label of the ImportDepsChecker deploy jar.
+            """,
+        ),
+        "forcibly_disable_header_compilation": attr.bool(
+            default = False,
+            doc = """
+Overrides --java_header_compilation to disable header compilation on platforms that do not
+support it, e.g. JDK 7 Bazel.
+            """,
+        ),
+        "genclass": attr.label(
+            allow_single_file = True,
+            cfg = "exec",
+            executable = True,
+            doc = """
+Label of the GenClass deploy jar.
+            """,
+        ),
+        "header_compiler": attr.label(
+            allow_single_file = True,
+            cfg = "exec",
+            executable = True,
+            doc = """
+Label of the header compiler. Required if --java_header_compilation is enabled.
+            """,
+        ),
+        "header_compiler_direct": attr.label(
+            allow_single_file = True,
+            cfg = "exec",
+            executable = True,
+            doc = """
+Optional label of the header compiler to use for direct classpath actions that do not
+include any API-generating annotation processors.
+
+<p>This tool does not support annotation processing.
+            """,
+        ),
+        "header_compiler_builtin_processors": attr.string_list(
+            doc = """Internal API, do not use!""",
+        ),
+        "ijar": attr.label(
+            cfg = "exec",
+            allow_files = True,
+            executable = True,
+            doc = """
+Label of the ijar executable.
+            """,
+        ),
+        "jacocorunner": attr.label(
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+            doc = """
+Label of the JacocoCoverageRunner deploy jar.
+            """,
+        ),
+        "javabuilder": attr.label(
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+            doc = """
+Label of the JavaBuilder deploy jar.
+            """,
+        ),
+        "javabuilder_data": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            doc = """
+Labels of data available for label-expansion in javabuilder_jvm_opts.
+            """,
+        ),
+        "javabuilder_jvm_opts": attr.string_list(
+            doc = """
+The list of arguments for the JVM when invoking JavaBuilder.
+            """,
+        ),
+        "java_runtime": attr.label(
+            cfg = "exec",
+            providers = [JavaRuntimeInfo],
+            doc = """
+The java_runtime to use with this toolchain. It defaults to java_runtime
+in execution configuration.
+            """,
+        ),
+        "javac_supports_workers": attr.bool(
+            default = True,
+            doc = """
+True if JavaBuilder supports running as a persistent worker, false if it doesn't.
+            """,
+        ),
+        "javac_supports_multiplex_workers": attr.bool(
+            default = True,
+            doc = """
+True if JavaBuilder supports running as a multiplex persistent worker, false if it doesn't.
+            """,
+        ),
+        "javac_supports_worker_cancellation": attr.bool(
+            default = True,
+            doc = """
+True if JavaBuilder supports cancellation of persistent workers, false if it doesn't.
+            """,
+        ),
+        "javac_supports_worker_multiplex_sandboxing": attr.bool(
+            default = False,
+            doc = """
+True if JavaBuilder supports running as a multiplex persistent worker with sandboxing, false if it doesn't.
+            """,
+        ),
+        "javacopts": attr.string_list(
+            default = [],
+            doc = """
+The list of extra arguments for the Java compiler. Please refer to the Java compiler
+documentation for the extensive list of possible Java compiler flags.
+            """,
+        ),
+        "jspecify_implicit_deps": attr.label(
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+            doc = """Experimental, do not use!""",
+        ),
+        "jspecify_javacopts": attr.string_list(
+            doc = """Experimental, do not use!""",
+        ),
+        "jspecify_packages": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            providers = [PackageSpecificationInfo],
+            doc = """Experimental, do not use!""",
+        ),
+        "jspecify_processor": attr.label(
+            cfg = "exec",
+            allow_single_file = True,
+            executable = True,
+            doc = """Experimental, do not use!""",
+        ),
+        "jspecify_processor_class": attr.string(
+            doc = """Experimental, do not use!""",
+        ),
+        "jspecify_stubs": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            doc = """Experimental, do not use!""",
+        ),
+        "jvm_opts": attr.string_list(
+            default = [],
+            doc = """
+The list of arguments for the JVM when invoking the Java compiler. Please refer to the Java
+virtual machine documentation for the extensive list of possible flags for this option.
+            """,
+        ),
+        "misc": attr.string_list(
+            default = [],
+            doc = """Deprecated: use javacopts instead""",
+        ),
+        "oneversion": attr.label(
+            cfg = "exec",
+            allow_files = True,
+            executable = True,
+            doc = """
+Label of the one-version enforcement binary.
+            """,
+        ),
+        "oneversion_whitelist": attr.label(
+            allow_single_file = True,
+            doc = """
+Label of the one-version allowlist.
+            """,
+        ),
+        "oneversion_allowlist_for_tests": attr.label(
+            allow_single_file = True,
+            doc = """
+Label of the one-version allowlist for tests.
+            """,
+        ),
+        "package_configuration": attr.label_list(
+            cfg = "exec",
+            providers = [JavaPackageConfigurationInfo],
+            doc = """
+Configuration that should be applied to the specified package groups.
+            """,
+        ),
+        "proguard_allowlister": attr.label(
+            cfg = "exec",
+            executable = True,
+            allow_files = True,
+            default = semantics.PROGUARD_ALLOWLISTER_LABEL,
+            doc = """
+Label of the Proguard allowlister.
+            """,
+        ),
+        "reduced_classpath_incompatible_processors": attr.string_list(
+            doc = """Internal API, do not use!""",
+        ),
+        "singlejar": attr.label(
+            cfg = "exec",
+            allow_files = True,
+            executable = True,
+            doc = """
+Label of the SingleJar deploy jar.
+            """,
+        ),
+        "source_version": attr.string(
+            doc = """
+The Java source version (e.g., '6' or '7'). It specifies which set of code structures
+are allowed in the Java source code.
+            """,
+        ),
+        "target_version": attr.string(
+            doc = """
+The Java target version (e.g., '6' or '7'). It specifies for which Java runtime the class
+should be build.
+            """,
+        ),
+        "timezone_data": attr.label(
+            cfg = "exec",
+            allow_single_file = True,
+            doc = """
+Label of a resource jar containing timezone data. If set, the timezone data is added as an
+implicitly runtime dependency of all java_binary rules.
+            """,
+        ),
+        "tools": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            doc = """
+Labels of tools available for label-expansion in jvm_opts.
+            """,
+        ),
+        "turbine_data": attr.label_list(
+            cfg = "exec",
+            allow_files = True,
+            doc = """
+Labels of data available for label-expansion in turbine_jvm_opts.
+            """,
+        ),
+        "turbine_jvm_opts": attr.string_list(
+            doc = """
+The list of arguments for the JVM when invoking turbine.
+            """,
+        ),
+        "xlint": attr.string_list(
+            default = [],
+            doc = """
+The list of warning to add or removes from default list. Precedes it with a dash to
+removes it. Please see the Javac documentation on the -Xlint options for more information.
+            """,
+        ),
+        "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+        "_bytecode_optimizer": attr.label(
+            cfg = "exec",
+            executable = True,
+            default = configuration_field(fragment = "java", name = "java_toolchain_bytecode_optimizer"),
+        ),
+        "_local_java_optimization_configuration": attr.label(
+            cfg = "exec",
+            default = configuration_field(fragment = "java", name = "local_java_optimization_configuration"),
+            allow_files = True,
+        ),
+        "_legacy_any_type_attrs": attr.string_list(default = _LEGACY_ANY_TYPE_ATTRS),
+    },
+    fragments = ["java"],
+)
diff --git a/java/common/rules/proguard_validation.bzl b/java/common/rules/proguard_validation.bzl
new file mode 100644
index 0000000..18142b3
--- /dev/null
+++ b/java/common/rules/proguard_validation.bzl
@@ -0,0 +1,71 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Proguard
+"""
+
+load("//java/common:java_semantics.bzl", "semantics")
+load("//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
+
+visibility("private")
+
+def _filter_provider(provider, *attrs):
+    return [dep[provider] for attr in attrs for dep in attr if provider in dep]
+
+def _validate_spec(ctx, spec_file):
+    validated_proguard_spec = ctx.actions.declare_file(
+        "validated_proguard/%s/%s_valid" % (ctx.label.name, spec_file.path),
+    )
+
+    toolchain = semantics.find_java_toolchain(ctx)
+
+    args = ctx.actions.args()
+    args.add("--path", spec_file)
+    args.add("--output", validated_proguard_spec)
+
+    ctx.actions.run(
+        mnemonic = "ValidateProguard",
+        progress_message = "Validating proguard configuration %{input}",
+        executable = toolchain.proguard_allowlister,
+        arguments = [args],
+        inputs = [spec_file],
+        outputs = [validated_proguard_spec],
+        toolchain = Label(semantics.JAVA_TOOLCHAIN_TYPE),
+    )
+
+    return validated_proguard_spec
+
+def validate_proguard_specs(ctx, proguard_specs = [], transitive_attrs = []):
+    """
+    Creates actions that validate Proguard specification and returns ProguardSpecProvider.
+
+    Use transtive_attrs parameter to collect Proguard validations from `deps`,
+    `runtime_deps`, `exports`, `plugins`, and `exported_plugins` attributes.
+
+    Args:
+      ctx: (RuleContext) Used to register the actions.
+      proguard_specs: (list[File]) List of Proguard specs files.
+      transitive_attrs: (list[list[Target]])  Attributes to collect transitive
+        proguard validations from.
+    Returns:
+      (ProguardSpecProvider) A ProguardSpecProvider.
+    """
+    proguard_validations = _filter_provider(ProguardSpecInfo, *transitive_attrs)
+    return ProguardSpecInfo(
+        depset(
+            [_validate_spec(ctx, spec_file) for spec_file in proguard_specs],
+            transitive = [validation.specs for validation in proguard_validations],
+        ),
+    )
diff --git a/java/common/rules/rule_util.bzl b/java/common/rules/rule_util.bzl
new file mode 100644
index 0000000..2b1423a
--- /dev/null
+++ b/java/common/rules/rule_util.bzl
@@ -0,0 +1,50 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Defines rule utilities."""
+
+visibility(["//java/..."])
+
+def merge_attrs(*attribute_dicts, override_attrs = {}, remove_attrs = []):
+    """Merges attributes together.
+
+    Attributes are first merged, then overridden and removed.
+
+    If there are duplicate definitions of an attribute, the last one is used.
+    (Current API doesn't let us compare)
+
+    Overridden and removed attributes need to be present.
+
+    Args:
+      *attribute_dicts: (*dict[str,Attribute]) A list of attribute dictionaries
+        to merge together.
+      override_attrs: (dict[str,Attribute]) A dictionary of attributes to override
+      remove_attrs: (list[str]) A list of attributes to remove.
+    Returns:
+      (dict[str,Attribute]) The merged attributes dictionary.
+    """
+    all_attributes = {}
+    for attribute_dict in attribute_dicts:
+        for key, attr in attribute_dict.items():
+            all_attributes.setdefault(key, attr)
+    for key, attr in override_attrs.items():
+        if all_attributes.get(key) == None:
+            fail("Trying to override attribute %s where there is none." % key)
+        all_attributes[key] = attr
+    for key in remove_attrs:
+        if key in override_attrs:
+            fail("Trying to remove overridden attribute %s." % key)
+        if key not in all_attributes:
+            fail("Trying to remove non-existent attribute %s." % key)
+        all_attributes.pop(key)
+    return all_attributes