Implement proto_common.declare_generated_files.
Design doc: https://docs.google.com/document/d/1dY_jfRvnH8SjRXGIfg8av-vquyWsvIZydXJOywvaR1A/edit
PiperOrigin-RevId: 441097041
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 c3625b3..d8c0718 100644
--- a/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/proto/proto_common.bzl
@@ -238,12 +238,57 @@
return bool(included)
+def _declare_generated_files(
+ actions,
+ proto_library_target,
+ extension,
+ name_mapper = None):
+ """Declares generated files with a specific extension.
+
+ Use this in lang_proto_library-es when protocol compiler generates files
+ that correspond to .proto file names.
+
+ The function removes ".proto" extension with given one (e.g. ".pb.cc") and
+ declares new output files.
+
+ Args:
+ actions:
+ (ActionFactory) Obtained by ctx.actions, used to declare the files.
+ proto_library_target:
+ (Target) The proto_library to generate the files for.
+ Obtained as the `target` parameter from an aspect's implementation.
+ extension: (str) The extension to use for generated files.
+ name_mapper: (str->str) A function mapped over the base filename without
+ the extension. Used it to replace characters in the name that
+ cause problems in a specific programming language.
+
+ Returns:
+ (list[File]) The list of declared files.
+ """
+ proto_info = proto_library_target[_builtins.toplevel.ProtoInfo]
+ proto_sources = proto_info.direct_sources
+ outputs = []
+
+ for src in proto_sources:
+ basename_no_ext = src.basename[:-(len(src.extension) + 1)]
+
+ if name_mapper:
+ basename_no_ext = name_mapper(basename_no_ext)
+
+ # Note that two proto_library rules can have the same source file, so this is actually a
+ # shared action. NB: This can probably result in action conflicts if the proto_library rules
+ # are not the same.
+ outputs.append(actions.declare_file(basename_no_ext + extension, sibling = src))
+
+ return outputs
+
proto_common = struct(
create_proto_compile_action = _create_proto_compile_action,
)
proto_common_do_not_use = struct(
compile = _compile,
+ declare_generated_files = _declare_generated_files,
experimental_should_generate_code = _experimental_should_generate_code,
experimental_filter_sources = _experimental_filter_sources,
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 6ad4c0f..ec54d98 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
@@ -19,6 +19,7 @@
"//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/analysis:file_provider",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/util:os",
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
index e853d39..0c2b5c1 100644
--- 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
@@ -20,6 +20,7 @@
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.FileProvider;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
@@ -141,6 +142,24 @@
" 'proto_dep': attr.label(),",
" 'toolchain': attr.label(default = '//foo:toolchain'),",
" })");
+
+ scratch.file(
+ "foo/declare_generated_files.bzl",
+ "def _impl(ctx):",
+ " files = proto_common_do_not_use.declare_generated_files(",
+ " ctx.actions,",
+ " ctx.attr.proto_dep,",
+ " ctx.attr.extension,",
+ " (lambda s: s.replace('-','_').replace('.','/')) if ctx.attr.python_names else None)",
+ " for f in files:",
+ " ctx.actions.write(f, '')",
+ " return [DefaultInfo(files = depset(files))]",
+ "declare_generated_files = rule(_impl,",
+ " attrs = {",
+ " 'proto_dep': attr.label(),",
+ " 'extension': attr.string(),",
+ " 'python_names': attr.bool(default = False),",
+ " })");
}
/** Verifies basic usage of <code>proto_common.generate_code</code>. */
@@ -566,4 +585,37 @@
+ " (third_party/x/something.proto).\n"
+ "Separate '//third_party/x:mixed' into 2 proto_library rules.");
}
+
+ /** Verifies <code>proto_common.declare_generated_files</code> call. */
+ @Test
+ public void declareGenerateFiles_basic() throws Exception {
+ scratch.file(
+ "bar/BUILD",
+ TestConstants.LOAD_PROTO_LIBRARY,
+ "load('//foo:declare_generated_files.bzl', 'declare_generated_files')",
+ "proto_library(name = 'proto', srcs = ['A.proto', 'b/B.proto'])",
+ "declare_generated_files(name = 'simple', proto_dep = ':proto', extension = '.cc')");
+
+ ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+ assertThat(prettyArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("bar/A.cc", "bar/b/B.cc");
+ }
+
+ /** Verifies <code>proto_common.declare_generated_files</code> call for Python. */
+ @Test
+ public void declareGenerateFiles_pythonc() throws Exception {
+ scratch.file(
+ "bar/BUILD",
+ TestConstants.LOAD_PROTO_LIBRARY,
+ "load('//foo:declare_generated_files.bzl', 'declare_generated_files')",
+ "proto_library(name = 'proto', srcs = ['my-proto.gen.proto'])",
+ "declare_generated_files(name = 'simple', proto_dep = ':proto', extension = '_pb2.py',",
+ " python_names = True)");
+
+ ConfiguredTarget target = getConfiguredTarget("//bar:simple");
+
+ assertThat(prettyArtifactNames(target.getProvider(FileProvider.class).getFilesToBuild()))
+ .containsExactly("bar/my_proto/gen_pb2.py");
+ }
}