From 982bbce2cfe12942eb47de214a2393e2cfd56c04 Mon Sep 17 00:00:00 2001 From: ilist Date: Thu, 7 Apr 2022 07:15:29 -0700 Subject: [PATCH] Implement and expose proto_common.compile call. Design doc: https://docs.google.com/document/d/1dY_jfRvnH8SjRXGIfg8av-vquyWsvIZydXJOywvaR1A/edit PiperOrigin-RevId: 440098122 --- .../bazel/rules/BazelRuleClassProvider.java | 3 + .../proto/ProtoBootstrap.java | 3 + .../starlark/builtins_bzl/common/exports.bzl | 3 +- .../common/proto/proto_common.bzl | 81 +++ .../devtools/build/lib/rules/proto/BUILD | 18 + .../lib/rules/proto/BazelProtoCommonTest.java | 464 ++++++++++++++++++ 6 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/google/devtools/build/lib/rules/proto/BazelProtoCommonTest.java 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 8aa2ed8a3e767a..1fbf128f6a23b7 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 @@ public void init(ConfiguredRuleClassProvider.Builder builder) { 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 0a2eb50a80f1de..3cc888d5dfbf08 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 @@ public class ProtoBootstrap implements Bootstrap { /** 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 ProtoBootstrap( public void addBindingsToBuilder(ImmutableMap.Builder 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 e949e90b40ab33..0ce3ac4092841c 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/objc_library.bzl", "objc_library") 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 @@ exported_toplevels = { # 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 49bfb91f1b59e3..f34905e18d74e0 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 _proto_path_flag(path): 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 40cd6dfdf0a30a..0a34bf6e3e8938 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 @@ -12,6 +12,24 @@ filegroup( visibility = ["//src:__subpackages__"], ) +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"], 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 00000000000000..c1312f637cc0b9 --- /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 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 proto_common.generate_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 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 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 proto_common.generate_code with plugin_output + * 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 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 proto_common.generate_code with additional_args + * 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 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 proto_common.generate_code with additional_tools + * 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 proto_common.generate_code with additional_tools + * 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 proto_common.generate_code with additional_inputs + * 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 proto_common.generate_code with resource_set + * 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 --protocopts 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 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 proto_common.generate_code correctly handles direct generated + * .proto 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 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 proto_common.generate_code correctly handles in-direct generated + * .proto 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 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 proto_common.generate_code correctly handles external proto_library + * -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 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 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(); + } +}