The core of stackb/rules_proto contains two build rules:
| Rule | Description |
|---|---|
proto_compile |
Executes the protoc tool. |
proto_plugin |
Provides static protoc plugin-specific configuration. |
Example:
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@build_stack_rules_proto//rules:proto_compile.bzl", "proto_compile")
proto_library(
name = "thing_proto",
srcs = ["thing.proto"],
deps = ["@com_google_protobuf//:timestamp_proto"],
)
proto_plugin(name = "cpp")
proto_compile(
name = "person_cpp_compile",
outputs = [
"person.pb.cc",
"person.pb.h",
],
plugins = [":cpp"],
proto = "person_proto",
)Takeaways:
- A
proto_libraryrule forms the basis for other language-specific derived rules. proto_libraryis provided by bazelbuild/rules_proto.- A
proto_compilerule references a singleproto_librarytarget. - The
pluginsattribute is a list of labels toproto_plugintargets. - The
outputsattribute names the files that will be generated by the protoc invocation. - The
proto
extension provided by [bazel-gazelle] is responsible for generating
proto_library.
proto_plugin primarily provides the plugin tool executable. The example seen
above is the simplest case where the plugin is builtin to protoc itself; no
separate plugin tool is required. In this case the proto_plugin rule
degenerates into just a name.
It is possible to add additional plugin-specific
name = "foo", options = ["bar"] on the proto_plugin rule, but the use-case
for this is narrow. Generally it is preferred to say
# gazelle:proto_plugin foo option bar such that the option can be interpreted
during a gazelle run.
proto_compiled_sources is used when you prefer to check the generated files
into source control. This may be necessary for legacy reasons, during an initial
Bazel migration, or to support better IDE integration.
The shape of a proto_compiled_sources rule is essentially identical to
proto_compile with one exception: generated source are named in the srcs
attribute rather than outputs.
For example, a proto_compiled_sources named //example/thing:proto_go_sources
is a macro that generates three rules:
bazel build //example/thing:proto_go_sourcesemits the generated files.bazel run //example/thing:proto_go_sources.updatecopies the generated files back into the source package.bazel test //example/thing:proto_go_sources_testasserts the source files are identical to generated files.
In this scenario, 2. is used to build the generated files (in the bazel-bin/
output tree) and copy the example/thing/thing.pb.go back into place where it
will be committed under source control. 3. is used to prevent drift: if a
developer modifies thing.proto and neglects to run the .update the test will
fail in CI.
The macro proto_compile_assets aggregates a list of dependencies (which
provide ProtoCompileInfo) into a single runnable target that copies files in
bulk.
For example, bazel run //proto:assets will copy all the generated .pb.go
files back into the source tree:
load("@build_stack_rules_proto//rules:proto_compile_assets.bzl", "proto_compile_assets")
proto_compile_assets(
name = "assets",
deps = [,
"//proto/api/v1:proto_go_compile",
"//proto/api/v2:proto_go_compile",
"//proto/api/v3:proto_go_compile",
],
)Consider the following rule within the package example/thing:
proto_compile(
name = "thing_go_compile",
output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/v4/example/thing/thing.pb.go"],
outputs = ["thing.pb.go"],
plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"],
proto = "thing_proto",
)This rule is declaring that a file bazel-bin/example/thing/thing.pb.go will be
output when the action is run. When we
bazel build //example/thing:thing_go_compile, the file is indeed created.
Let's temporarily comment out the output_mappings attribute and rebuild:
proto_compile(
name = "thing_go_compile",
# output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/v4/example/thing/thing.pb.go"],
outputs = ["thing.pb.go"],
plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"],
proto = "thing_proto",
)$ bazel build //example/thing:thing_go_compile
ERROR: /github.com/stackb/rules_proto/v4/example/thing/BUILD.bazel:54:14: output 'example/thing/thing.pb.go' was not createdWhat happened? Let's add a debugging attribute verbose = True on the rule:
this will print debugging information and show the bazel sandbox before and
after the protoc tool is invoked:
proto_compile(
name = "thing_go_compile",
# output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/v4/example/thing/thing.pb.go"],
outputs = ["thing.pb.go"],
plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"],
proto = "thing_proto",
verbose = True,
)$ bazel build //example/thing:thing_go_compile
##### SANDBOX BEFORE RUNNING PROTOC
./bazel-out/host/bin/external/com_google_protobuf/protoc
./bazel-out/darwin-opt-exec-2B5CBBC6/bin/external/com_github_golang_protobuf/protoc-gen-go/protoc-gen-go_/protoc-gen-go
./bazel-out/darwin-fastbuild/bin/example/thing/thing_proto-descriptor-set.proto.bin
./bazel-out/darwin-fastbuild/bin/external/com_google_protobuf/timestamp_proto-descriptor-set.proto.bin
##### SANDBOX AFTER RUNNING PROTOC
./bazel-out/darwin-fastbuild/bin/github.com/stackb/rules_proto/v4/example/thing/thing.pb.goSo, the file was created, but not in the location we wanted. In this case the
protoc-gen-go plugin is not "playing nice" with Bazel. Because this
thing.proto has
option go_package = "github.com/stackb/rules_proto/v4/example/thing;thing";, the
output location is no longer based on the package. This is a problem, because
Bazel semantics disallow declaring a File outside its package boundary. As a
result, we need to do a
mv ./bazel-out/darwin-fastbuild/bin/github.com/stackb/rules_proto/v4/example/thing/thing.pb.go ./bazel-out/darwin-fastbuild/bin/example/thing/thing.pb.go
to relocate the file into its expected location before the action terminates.
Therefore, the output_mappings attribute is a list of entries that map file
locations want=got relative to the action execution root. It is required when
the actual output location does not match the desired location. This can occur
if the proto package statement does not match the Bazel package path, or in
special circumstances specific to the plugin itself (like go_package).
proto_gazelle is not a repository rule: it's just like the typical gazelle
rule, but with extra deps resolution superpowers. But, we discuss it here since
it works in conjunction with proto_repository:
load("@build_stack_rules_proto//rules:proto_gazelle.bzl", "DEFAULT_LANGUAGES", "proto_gazelle")
proto_gazelle(
name = "gazelle",
cfgs = ["//proto:config.yaml"],
command = "update",
gazelle = ":gazelle-protobuf",
imports = [
"@bazelapis//:imports.csv",
"@googleapis//:imports.csv",
"@protobufapis//:imports.csv",
"@remoteapis//:imports.csv",
],
)In this example, we are again setting the base gazelle config using the YAML
file (the same one used in for the proto_repository rules). We are also now
importing resolve information from four external sources.
With this setup, we can simply place an import statement like
import "src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto";
in a foo.proto file in the default workspace, and gazelle will automagically
figure out the import dependency tree spanning @bazelapis, @remoteapis,
@googleapis, and the well-known types from @protobufapis.
This works for any proto_language, with any set of custom protoc plugins.
golden_filegroup is a utility macro for golden file testing. It works like a
native filegroup, but adds .update and .test targets. Example:
load("@build_stack_rules_proto//rules:golden_filegroup.bzl", "golden_filegroup")
# golden_filegroup asserts that generated files named in 'srcs' are
# identical to the ones checked into source control.
#
# Usage:
#
# $ bazel build :golden # not particularly useful, just a regular filegroup
#
# $ bazel test :golden.test # checks that generated files are identical to
# ones in git (for CI)
#
# $ bazel run :golden.update # copies the generated files into source tree
# (then 'git add' to your PR if it looks good)
golden_filegroup(
name = "golden",
srcs = [
":some_generated_file1.json",
":some_generated_file2.json",
],
)