Implement and expose proto_common.compile call.

Design doc: https://docs.google.com/document/d/1dY_jfRvnH8SjRXGIfg8av-vquyWsvIZydXJOywvaR1A/edit

PiperOrigin-RevId: 440098122
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index 8aa2ed8..1fbf128 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -112,6 +112,7 @@
 import com.google.devtools.build.lib.rules.proto.BazelProtoLibraryRule;
 import com.google.devtools.build.lib.rules.proto.ProtoConfiguration;
 import com.google.devtools.build.lib.rules.proto.ProtoInfo;
+import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainProvider;
 import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainRule;
 import com.google.devtools.build.lib.rules.python.PyInfo;
 import com.google.devtools.build.lib.rules.python.PyRuleClasses.PySymlink;
@@ -291,6 +292,8 @@
                   new StarlarkAspectStub(),
                   new ProviderStub());
           builder.addStarlarkBootstrap(bootstrap);
+          builder.addStarlarkBuiltinsInternal(
+              "ProtoLangToolchainInfo", ProtoLangToolchainProvider.PROVIDER);
         }
 
         @Override
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/proto/ProtoBootstrap.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/proto/ProtoBootstrap.java
index 0a2eb50..3cc888d 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/proto/ProtoBootstrap.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/proto/ProtoBootstrap.java
@@ -34,6 +34,8 @@
   /** The name of the proto namespace in Starlark. */
   public static final String PROTO_COMMON_NAME = "proto_common";
 
+  public static final String PROTO_COMMON_SECOND_NAME = "proto_common_do_not_use";
+
   private final ProtoInfoProviderApi protoInfoApiProvider;
   private final Object protoCommon;
   private final StarlarkAspectApi protoRegistryAspect;
@@ -54,6 +56,7 @@
   public void addBindingsToBuilder(ImmutableMap.Builder<String, Object> builder) {
     builder.put(PROTO_INFO_STARLARK_NAME, protoInfoApiProvider);
     builder.put(PROTO_COMMON_NAME, protoCommon);
+    builder.put(PROTO_COMMON_SECOND_NAME, protoCommon);
     builder.put(
         "ProtoRegistryAspect",
         FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
diff --git a/src/main/starlark/builtins_bzl/common/exports.bzl b/src/main/starlark/builtins_bzl/common/exports.bzl
index e949e90..0ce3ac4 100755
--- a/src/main/starlark/builtins_bzl/common/exports.bzl
+++ b/src/main/starlark/builtins_bzl/common/exports.bzl
@@ -23,7 +23,7 @@
 load("@_builtins//:common/objc/apple_static_library.bzl", "apple_static_library")
 load("@_builtins//:common/objc/compilation_support.bzl", "compilation_support")
 load("@_builtins//:common/objc/linking_support.bzl", "linking_support")
-load("@_builtins//:common/proto/proto_common.bzl", "proto_common")
+load("@_builtins//:common/proto/proto_common.bzl", "proto_common", "proto_common_do_not_use")
 load("@_builtins//:common/proto/proto_library.bzl", "proto_library")
 load("@_builtins//:common/java/proto/java_lite_proto_library.bzl", "java_lite_proto_library")
 load("@_builtins//:common/cc/cc_library.bzl", "cc_library")
@@ -33,6 +33,7 @@
     # that builtins injection is working properly. Its built-in value is
     # "original value".
     "_builtins_dummy": "overridden value",
+    "proto_common_do_not_use": proto_common_do_not_use,
 }
 
 # A list of Starlarkified native rules.
diff --git a/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl b/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl
index 49bfb91..f34905e 100644
--- a/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl
@@ -88,6 +88,87 @@
 def _Iimport_path_equals_fullpath(proto_source):
     return "-I%s=%s" % (proto_source.import_path(), proto_source.source_file().path)
 
+def _compile(
+        actions,
+        proto_library_target,
+        proto_lang_toolchain_info,
+        generated_files,
+        plugin_output = None,
+        additional_args = None,
+        additional_tools = [],
+        additional_inputs = depset(),
+        resource_set = None):
+    """Creates proto compile action for compiling *.proto files to language specific sources.
+
+    Args:
+      actions: (ActionFactory)  Obtained by ctx.actions, used to register the actions.
+      proto_library_target:  (Target) The proto_library to generate the sources for.
+        Obtained as the `target` parameter from an aspect's implementation.
+      proto_lang_toolchain_info: (ProtoLangToolchainInfo) The proto lang toolchain info.
+        Obtained from a `proto_lang_toolchain` target or constructed ad-hoc..
+      generated_files: (list[File]) The output files generated by the proto compiler.
+        Callee needs to declare files using `ctx.actions.declare_file`.
+        See also: `proto_common.declare_generated_files`.
+      plugin_output: (File|str) The file or directory passed to the plugin.
+        Formatted with `proto_lang_toolchain.out_replacement_format_flag`
+      additional_args: (Args) Additional arguments to add to the action.
+        Accepts an ctx.actions.args() object that is added at the beginning
+        of the command line.
+      additional_tools: (list[File]) Additional tools to add to the action.
+      additional_inputs: (Depset[File]) Additional input files to add to the action.
+      resource_set:
+        (func) A callback function that is passed to the created action.
+        See `ctx.actions.run`, `resource_set` parameter for full definition of
+        the callback.
+    """
+    proto_info = proto_library_target[_builtins.toplevel.ProtoInfo]
+
+    args = actions.args()
+    args.use_param_file(param_file_arg = "@%s")
+    args.set_param_file_format("multiline")
+    tools = list(additional_tools)
+
+    if plugin_output:
+        args.add(plugin_output, format = proto_lang_toolchain_info.out_replacement_format_flag)
+    if proto_lang_toolchain_info.plugin:
+        tools.append(proto_lang_toolchain_info.plugin)
+        args.add(proto_lang_toolchain_info.plugin.executable, format = proto_lang_toolchain_info.plugin_format_flag)
+
+    args.add_all(proto_info.transitive_proto_path, map_each = _proto_path_flag)
+    # Example: `--proto_path=--proto_path=bazel-bin/target/third_party/pkg/_virtual_imports/subpkg`
+
+    args.add_all(proto_lang_toolchain_info.protoc_opts)
+
+    # Include maps
+    # For each import, include both the import as well as the import relativized against its
+    # protoSourceRoot. This ensures that protos can reference either the full path or the short
+    # path when including other protos.
+    args.add_all(proto_info.transitive_proto_sources(), map_each = _Iimport_path_equals_fullpath)
+    # Example: `-Ia.proto=bazel-bin/target/third_party/pkg/_virtual_imports/subpkg/a.proto`
+
+    args.add_all(proto_info.direct_sources)
+
+    if additional_args:
+        additional_args.use_param_file(param_file_arg = "@%s")
+        additional_args.set_param_file_format("multiline")
+
+    actions.run(
+        mnemonic = proto_lang_toolchain_info.mnemonic,
+        progress_message = proto_lang_toolchain_info.progress_message,
+        executable = proto_lang_toolchain_info.proto_compiler,
+        arguments = [additional_args, args] if additional_args else [args],
+        inputs = depset(transitive = [proto_info.transitive_sources, additional_inputs]),
+        outputs = generated_files,
+        tools = tools,
+        use_default_shell_env = True,
+        resource_set = resource_set,
+    )
+
 proto_common = struct(
     create_proto_compile_action = _create_proto_compile_action,
 )
+
+proto_common_do_not_use = struct(
+    compile = _compile,
+    ProtoLangToolchainInfo = _builtins.internal.ProtoLangToolchainInfo,
+)
diff --git a/src/test/java/com/google/devtools/build/lib/rules/proto/BUILD b/src/test/java/com/google/devtools/build/lib/rules/proto/BUILD
index 40cd6df..0a34bf6 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/proto/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/proto/BUILD
@@ -13,6 +13,24 @@
 )
 
 java_test(
+    name = "BazelProtoCommonTest",
+    srcs = ["BazelProtoCommonTest.java"],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
+        "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
+        "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
+        "//src/main/java/com/google/devtools/build/lib/util:os",
+        "//src/test/java/com/google/devtools/build/lib/actions/util",
+        "//src/test/java/com/google/devtools/build/lib/analysis/util",
+        "//src/test/java/com/google/devtools/build/lib/packages:testutil",
+        "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants",
+        "//third_party:junit4",
+        "//third_party:truth",
+        "@com_google_testparameterinjector//:testparameterinjector",
+    ],
+)
+
+java_test(
     name = "ProtoCompileActionBuilderTest",
     srcs = ["ProtoCompileActionBuilderTest.java"],
     deps = [
diff --git a/src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoCommonTest.java
new file mode 100644
index 0000000..c1312f6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoCommonTest.java
@@ -0,0 +1,464 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.rules.proto;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames;
+
+import com.google.common.truth.Correspondence;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.actions.SpawnAction;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.util.MockProtoSupport;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.util.OS;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+import com.google.testing.junit.testparameterinjector.TestParameters;
+import java.util.List;
+import java.util.regex.Pattern;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for proto_common module. */
+@RunWith(TestParameterInjector.class)
+public class BazelProtoCommonTest extends BuildViewTestCase {
+  private static final Correspondence<String, String> MATCHES_REGEX =
+      Correspondence.from((a, b) -> Pattern.matches(b, a), "matches");
+
+  @Before
+  public final void setup() throws Exception {
+    MockProtoSupport.setupWorkspace(scratch);
+    invalidatePackages();
+
+    MockProtoSupport.setup(mockToolsConfig);
+
+    scratch.file(
+        "third_party/x/BUILD",
+        "licenses(['unencumbered'])",
+        "cc_binary(name = 'plugin', srcs = ['plugin.cc'])",
+        "cc_library(name = 'runtime', srcs = ['runtime.cc'])",
+        "filegroup(name = 'descriptors', srcs = ['metadata.proto', 'descriptor.proto'])",
+        "filegroup(name = 'any', srcs = ['any.proto'])",
+        "proto_library(name = 'denied', srcs = [':descriptors', ':any'])");
+    scratch.file(
+        "foo/BUILD",
+        TestConstants.LOAD_PROTO_LANG_TOOLCHAIN,
+        "proto_lang_toolchain(",
+        "    name = 'toolchain',",
+        "    command_line = '--java_out=param1,param2:$(OUT)',",
+        "    plugin_format_flag = '--plugin=%s',",
+        "    plugin = '//third_party/x:plugin',",
+        "    runtime = '//third_party/x:runtime',",
+        "    blacklisted_protos = ['//third_party/x:denied'],",
+        "    progress_message = 'Progress Message %{label}',",
+        "    mnemonic = 'MyMnemonic',",
+        ")",
+        "proto_lang_toolchain(",
+        "    name = 'toolchain_noplugin',",
+        "    command_line = '--java_out=param1,param2:$(OUT)',",
+        "    runtime = '//third_party/x:runtime',",
+        "    blacklisted_protos = ['//third_party/x:denied'],",
+        "    progress_message = 'Progress Message %{label}',",
+        "    mnemonic = 'MyMnemonic',",
+        ")");
+
+    scratch.file(
+        "foo/generate.bzl",
+        "def _resource_set_callback(os, inputs_size):",
+        "   return {'memory': 25 + 0.15 * inputs_size, 'cpu': 1}",
+        "def _impl(ctx):",
+        "  outfile = ctx.actions.declare_file('out')",
+        "  kwargs = {}",
+        "  if ctx.attr.plugin_output:",
+        "    kwargs['plugin_output'] = ctx.attr.plugin_output",
+        "  if ctx.attr.additional_args:",
+        "    additional_args = ctx.actions.args()",
+        "    additional_args.add_all(ctx.attr.additional_args)",
+        "    kwargs['additional_args'] = additional_args",
+        "  if ctx.files.additional_tools:",
+        "    kwargs['additional_tools'] = ctx.files.additional_tools",
+        "  if ctx.files.additional_inputs:",
+        "    kwargs['additional_inputs'] = depset(ctx.files.additional_inputs)",
+        "  if ctx.attr.use_resource_set:",
+        "    kwargs['resource_set'] = _resource_set_callback",
+        "  proto_common_do_not_use.compile(",
+        "    ctx.actions,",
+        "    ctx.attr.proto_dep,",
+        "    ctx.attr.toolchain[proto_common_do_not_use.ProtoLangToolchainInfo],",
+        "    [outfile],",
+        "    **kwargs)",
+        "  return [DefaultInfo(files = depset([outfile]))]",
+        "generate_rule = rule(_impl,",
+        "  attrs = {",
+        "     'proto_dep': attr.label(),",
+        "     'plugin_output': attr.string(),",
+        "     'toolchain': attr.label(default = '//foo:toolchain'),",
+        "     'additional_args': attr.string_list(),",
+        "     'additional_tools': attr.label_list(cfg = 'exec'),",
+        "     'additional_inputs': attr.label_list(allow_files = True),",
+        "     'use_resource_set': attr.bool(),",
+        "  })");
+  }
+
+  /** Verifies basic usage of <code>proto_common.generate_code</code>. */
+  @Test
+  public void generateCode_basic() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    List<String> cmdLine = spawnAction.getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "-Ibar/A.proto=bar/A.proto",
+            "bar/A.proto")
+        .inOrder();
+    assertThat(spawnAction.getMnemonic()).isEqualTo("MyMnemonic");
+    assertThat(spawnAction.getProgressMessage()).isEqualTo("Progress Message //bar:simple");
+  }
+
+  /** Verifies usage of proto_common.generate_code with no plugin specified by toolchain. */
+  @Test
+  public void generateCode_noPlugin() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto',",
+        "  toolchain = '//foo:toolchain_noplugin')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    List<String> cmdLine =
+        getGeneratingSpawnAction(getBinArtifact("out", target)).getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly("-Ibar/A.proto=bar/A.proto", "bar/A.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>plugin_output</code>
+   * parameter.
+   */
+  @Test
+  public void generateCode_withPluginOutput() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto', plugin_output = 'foo.srcjar')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    List<String> cmdLine =
+        getGeneratingSpawnAction(getBinArtifact("out", target)).getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--java_out=param1,param2:foo.srcjar",
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "-Ibar/A.proto=bar/A.proto",
+            "bar/A.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>additional_args</code>
+   * parameter.
+   */
+  @Test
+  public void generateCode_additionalArgs() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto', additional_args = ['--a', '--b'])");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    List<String> cmdLine =
+        getGeneratingSpawnAction(getBinArtifact("out", target)).getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--a",
+            "--b",
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "-Ibar/A.proto=bar/A.proto",
+            "bar/A.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>additional_tools</code>
+   * parameter.
+   */
+  @Test
+  public void generateCode_additionalTools() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "cc_binary(name = 'tool1', srcs = ['tool1.cc'])",
+        "cc_binary(name = 'tool2', srcs = ['tool2.cc'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto',",
+        "  additional_tools = [':tool1', ':tool2'])");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    assertThat(prettyArtifactNames(spawnAction.getTools()))
+        .containsAtLeast("bar/tool1", "bar/tool2", "third_party/x/plugin");
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>additional_tools</code>
+   * parameter and no plugin on the toolchain.
+   */
+  @Test
+  public void generateCode_additionalToolsNoPlugin() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "cc_binary(name = 'tool1', srcs = ['tool1.cc'])",
+        "cc_binary(name = 'tool2', srcs = ['tool2.cc'])",
+        "generate_rule(name = 'simple',",
+        "  proto_dep = ':proto',",
+        "  additional_tools = [':tool1', ':tool2'],",
+        "  toolchain = '//foo:toolchain_noplugin',",
+        ")");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    assertThat(prettyArtifactNames(spawnAction.getTools()))
+        .containsAtLeast("bar/tool1", "bar/tool2");
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>additional_inputs</code>
+   * parameter.
+   */
+  @Test
+  public void generateCode_additionalInputs() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto',",
+        "  additional_inputs = [':input1.txt', ':input2.txt'])");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    assertThat(prettyArtifactNames(spawnAction.getInputs()))
+        .containsAtLeast("bar/input1.txt", "bar/input2.txt");
+  }
+
+  /**
+   * Verifies usage of <code>proto_common.generate_code</code> with <code>resource_set</code>
+   * parameter.
+   */
+  @Test
+  public void generateCode_resourceSet() throws Exception {
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto', use_resource_set = True)");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    assertThat(spawnAction.getResourceSetOrBuilder().buildResourceSet(OS.DARWIN, 0))
+        .isEqualTo(ResourceSet.createWithRamCpu(25, 1));
+    assertThat(spawnAction.getResourceSetOrBuilder().buildResourceSet(OS.LINUX, 2))
+        .isEqualTo(ResourceSet.createWithRamCpu(25.3, 1));
+  }
+
+  /** Verifies <code>--protocopts</code> are passed to command line. */
+  @Test
+  public void generateCode_protocOpts() throws Exception {
+    useConfiguration("--protocopt=--foo", "--protocopt=--bar");
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    List<String> cmdLine = spawnAction.getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "--foo",
+            "--bar",
+            "-Ibar/A.proto=bar/A.proto",
+            "bar/A.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies <code>proto_common.generate_code</code> correctly handles direct generated <code>
+   * .proto</code> files.
+   */
+  @Test
+  public void generateCode_directGeneratedProtos() throws Exception {
+    useConfiguration("--noincompatible_generated_protos_in_virtual_imports");
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "genrule(name = 'generate', srcs = ['A.txt'], cmd = '', outs = ['G.proto'])",
+        "proto_library(name = 'proto', srcs = ['A.proto', 'G.proto'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    List<String> cmdLine = spawnAction.getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "-Ibar/A.proto=bar/A.proto",
+            "-Ibar/G.proto=bl?azel?-out/k8-fastbuild/bin/bar/G.proto",
+            "bar/A.proto",
+            "bl?azel?-out/k8-fastbuild/bin/bar/G.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies <code>proto_common.generate_code</code> correctly handles in-direct generated <code>
+   * .proto</code> files.
+   */
+  @Test
+  public void generateCode_inDirectGeneratedProtos() throws Exception {
+    useConfiguration("--noincompatible_generated_protos_in_virtual_imports");
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "genrule(name = 'generate', srcs = ['A.txt'], cmd = '', outs = ['G.proto'])",
+        "proto_library(name = 'generated', srcs = ['G.proto'])",
+        "proto_library(name = 'proto', srcs = ['A.proto'], deps = [':generated'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    List<String> cmdLine = spawnAction.getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            "-Ibar/A.proto=bar/A.proto",
+            "-Ibar/G.proto=bl?azel?-out/k8-fastbuild/bin/bar/G.proto",
+            "bar/A.proto")
+        .inOrder();
+  }
+
+  /**
+   * Verifies <code>proto_common.generate_code</code> correctly handles external <code>proto_library
+   * </code>-es.
+   */
+  @Test
+  @TestParameters({
+    "{virtual: false, sibling: false, generated: false, expectedFlags:"
+        + " ['--proto_path=external/foo','-Ie/E.proto=external/foo/e/E.proto']}",
+    "{virtual: false, sibling: false, generated: true, expectedFlags:"
+        + " ['--proto_path=external/foo',"
+        + " '-Ie/E.proto=bl?azel?-out/k8-fastbuild/bin/external/foo/e/E.proto']}",
+    "{virtual: true, sibling: false, generated: false,expectedFlags:"
+        + " ['--proto_path=external/foo','-Ie/E.proto=external/foo/e/E.proto']}",
+    "{virtual: true, sibling: false, generated: true, expectedFlags:"
+        + " ['--proto_path=bl?azel?-out/k8-fastbuild/bin/external/foo/e/_virtual_imports/e',"
+        + " '-Ie/E.proto=bl?azel?-out/k8-fastbuild/bin/external/foo/e/_virtual_imports/e/e/E.proto']}",
+    "{virtual: true, sibling: true, generated: false,expectedFlags:"
+        + " ['--proto_path=../foo','-I../foo/e/E.proto=../foo/e/E.proto']}",
+    "{virtual: true, sibling: true, generated: true, expectedFlags:"
+        + " ['--proto_path=bl?azel?-out/foo/k8-fastbuild/bin/e/_virtual_imports/e',"
+        + " '-Ie/E.proto=bl?azel?-out/foo/k8-fastbuild/bin/e/_virtual_imports/e/e/E.proto']}",
+    "{virtual: false, sibling: true, generated: false,expectedFlags:"
+        + " ['--proto_path=../foo','-I../foo/e/E.proto=../foo/e/E.proto']}",
+    "{virtual: false, sibling: true, generated: true, expectedFlags:"
+        + " ['--proto_path=../foo','-Ie/E.proto=bl?azel?-out/foo/k8-fastbuild/bin/e/E.proto']}",
+  })
+  public void generateCode_externalProtoLibrary(
+      boolean virtual, boolean sibling, boolean generated, List<String> expectedFlags)
+      throws Exception {
+    if (virtual) {
+      useConfiguration("--incompatible_generated_protos_in_virtual_imports");
+    } else {
+      useConfiguration("--noincompatible_generated_protos_in_virtual_imports");
+    }
+    if (sibling) {
+      setBuildLanguageOptions("--experimental_sibling_repository_layout");
+    }
+    scratch.appendFile("WORKSPACE", "local_repository(name = 'foo', path = '/foo')");
+    invalidatePackages();
+    scratch.file("/foo/WORKSPACE");
+    scratch.file(
+        "/foo/e/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "proto_library(name='e', srcs=['E.proto'])",
+        generated
+            ? "genrule(name = 'generate', srcs = ['A.txt'], cmd = '', outs = ['E.proto'])"
+            : "");
+    scratch.file(
+        "bar/BUILD",
+        TestConstants.LOAD_PROTO_LIBRARY,
+        "load('//foo:generate.bzl', 'generate_rule')",
+        "proto_library(name = 'proto', srcs = ['A.proto'], deps = ['@foo//e:e'])",
+        "generate_rule(name = 'simple', proto_dep = ':proto')");
+
+    ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+    SpawnAction spawnAction = getGeneratingSpawnAction(getBinArtifact("out", target));
+    List<String> cmdLine = spawnAction.getRemainingArguments();
+    assertThat(cmdLine)
+        .comparingElementsUsing(MATCHES_REGEX)
+        .containsExactly(
+            "--plugin=bl?azel?-out/[^/]*-exec-[^/]*/bin/third_party/x/plugin",
+            expectedFlags.get(0),
+            "-Ibar/A.proto=bar/A.proto",
+            expectedFlags.get(1),
+            "bar/A.proto")
+        .inOrder();
+  }
+}