|
| 1 | + |
| 2 | +## Build Rules |
| 3 | + |
| 4 | +The core of `stackb/rules_proto` contains two build rules: |
| 5 | + |
| 6 | +| Rule | Description | |
| 7 | +|-----------------|---------------------------------------------------------| |
| 8 | +| `proto_compile` | Executes the `protoc` tool. | |
| 9 | +| `proto_plugin` | Provides static `protoc` plugin-specific configuration. | |
| 10 | + |
| 11 | +### proto_compile |
| 12 | + |
| 13 | +Example: |
| 14 | + |
| 15 | +```python |
| 16 | +load("@rules_proto//proto:defs.bzl", "proto_library") |
| 17 | +load("@build_stack_rules_proto//rules:proto_compile.bzl", "proto_compile") |
| 18 | + |
| 19 | +proto_library( |
| 20 | + name = "thing_proto", |
| 21 | + srcs = ["thing.proto"], |
| 22 | + deps = ["@com_google_protobuf//:timestamp_proto"], |
| 23 | +) |
| 24 | + |
| 25 | +proto_plugin(name = "cpp") |
| 26 | + |
| 27 | +proto_compile( |
| 28 | + name = "person_cpp_compile", |
| 29 | + outputs = [ |
| 30 | + "person.pb.cc", |
| 31 | + "person.pb.h", |
| 32 | + ], |
| 33 | + plugins = [":cpp"], |
| 34 | + proto = "person_proto", |
| 35 | +) |
| 36 | +``` |
| 37 | + |
| 38 | +Takeaways: |
| 39 | + |
| 40 | +- A `proto_library` rule forms the basis for other language-specific derived |
| 41 | + rules. |
| 42 | +- `proto_library` is provided by |
| 43 | + [bazelbuild/rules_proto](https://github.com/bazelbuild/rules_proto). |
| 44 | +- A `proto_compile` rule references a single `proto_library` target. |
| 45 | +- The `plugins` attribute is a list of labels to `proto_plugin` targets. |
| 46 | +- The `outputs` attribute names the files that will be generated by the protoc |
| 47 | + invocation. |
| 48 | +- The |
| 49 | + [proto](https://github.com/bazelbuild/bazel-gazelle/blob/master/language/proto/lang.go) |
| 50 | + extension provided by [bazel-gazelle] is responsible for generating |
| 51 | + `proto_library`. |
| 52 | + |
| 53 | +### proto_plugin |
| 54 | + |
| 55 | +`proto_plugin` primarily provides the plugin tool executable. The example seen |
| 56 | +above is the simplest case where the plugin is builtin to `protoc` itself; no |
| 57 | +separate plugin tool is required. In this case the `proto_plugin` rule |
| 58 | +degenerates into just a `name`. |
| 59 | + |
| 60 | +It is possible to add additional plugin-specific |
| 61 | +`name = "foo", options = ["bar"]` on the `proto_plugin` rule, but the use-case |
| 62 | +for this is narrow. Generally it is preferred to say |
| 63 | +`# gazelle:proto_plugin foo option bar` such that the option can be interpreted |
| 64 | +during a gazelle run. |
| 65 | + |
| 66 | +### proto_compiled_sources |
| 67 | + |
| 68 | +`proto_compiled_sources` is used when you prefer to check the generated files |
| 69 | +into source control. This may be necessary for legacy reasons, during an initial |
| 70 | +Bazel migration, or to support better IDE integration. |
| 71 | + |
| 72 | +The shape of a `proto_compiled_sources` rule is essentially identical to |
| 73 | +`proto_compile` with one exception: generated source are named in the `srcs` |
| 74 | +attribute rather than `outputs`. |
| 75 | + |
| 76 | +For example, a `proto_compiled_sources` named `//example/thing:proto_go_sources` |
| 77 | +is a macro that generates three rules: |
| 78 | + |
| 79 | +1. `bazel build //example/thing:proto_go_sources` emits the generated files. |
| 80 | +2. `bazel run //example/thing:proto_go_sources.update` copies the generated |
| 81 | + files back into the source package. |
| 82 | +3. `bazel test //example/thing:proto_go_sources_test` asserts the source files |
| 83 | + are identical to generated files. |
| 84 | + |
| 85 | +In this scenario, `2.` is used to build the generated files (in the `bazel-bin/` |
| 86 | +output tree) and copy the `example/thing/thing.pb.go` back into place where it |
| 87 | +will be committed under source control. `3.` is used to prevent drift: if a |
| 88 | +developer modifies `thing.proto` and neglects to run the `.update` the test will |
| 89 | +fail in CI. |
| 90 | + |
| 91 | +### proto_compile_assets |
| 92 | + |
| 93 | +The macro `proto_compile_assets` aggregates a list of dependencies (which |
| 94 | +provide `ProtoCompileInfo`) into a single runnable target that copies files in |
| 95 | +bulk. |
| 96 | + |
| 97 | +For example, `bazel run //proto:assets` will copy all the generated `.pb.go` |
| 98 | +files back into the source tree: |
| 99 | + |
| 100 | +```py |
| 101 | +load("@build_stack_rules_proto//rules:proto_compile_assets.bzl", "proto_compile_assets") |
| 102 | + |
| 103 | +proto_compile_assets( |
| 104 | + name = "assets", |
| 105 | + deps = [, |
| 106 | + "//proto/api/v1:proto_go_compile", |
| 107 | + "//proto/api/v2:proto_go_compile", |
| 108 | + "//proto/api/v3:proto_go_compile", |
| 109 | + ], |
| 110 | +) |
| 111 | +``` |
| 112 | + |
| 113 | +### The `output_mappings` attribute |
| 114 | + |
| 115 | +Consider the following rule within the package `example/thing`: |
| 116 | + |
| 117 | +```python |
| 118 | +proto_compile( |
| 119 | + name = "thing_go_compile", |
| 120 | + output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/example/thing/thing.pb.go"], |
| 121 | + outputs = ["thing.pb.go"], |
| 122 | + plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"], |
| 123 | + proto = "thing_proto", |
| 124 | +) |
| 125 | +``` |
| 126 | + |
| 127 | +This rule is declaring that a file `bazel-bin/example/thing/thing.pb.go` will be |
| 128 | +output when the action is run. When we |
| 129 | +`bazel build //example/thing:thing_go_compile`, the file is indeed created. |
| 130 | + |
| 131 | +Let's temporarily comment out the `output_mappings` attribute and rebuild: |
| 132 | + |
| 133 | +```python |
| 134 | +proto_compile( |
| 135 | + name = "thing_go_compile", |
| 136 | + # output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/example/thing/thing.pb.go"], |
| 137 | + outputs = ["thing.pb.go"], |
| 138 | + plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"], |
| 139 | + proto = "thing_proto", |
| 140 | +) |
| 141 | +``` |
| 142 | + |
| 143 | +```sh |
| 144 | +$ bazel build //example/thing:thing_go_compile |
| 145 | +ERROR: /github.com/stackb/rules_proto/example/thing/BUILD.bazel:54:14: output 'example/thing/thing.pb.go' was not created |
| 146 | +``` |
| 147 | + |
| 148 | +What happened? Let's add a debugging attribute `verbose = True` on the rule: |
| 149 | +this will print debugging information and show the bazel sandbox before and |
| 150 | +after the `protoc` tool is invoked: |
| 151 | + |
| 152 | +```python |
| 153 | +proto_compile( |
| 154 | + name = "thing_go_compile", |
| 155 | + # output_mappings = ["thing.pb.go=github.com/stackb/rules_proto/example/thing/thing.pb.go"], |
| 156 | + outputs = ["thing.pb.go"], |
| 157 | + plugins = ["@build_stack_rules_proto//plugin/golang/protobuf:protoc-gen-go"], |
| 158 | + proto = "thing_proto", |
| 159 | + verbose = True, |
| 160 | +) |
| 161 | +``` |
| 162 | + |
| 163 | +```sh |
| 164 | +$ bazel build //example/thing:thing_go_compile |
| 165 | +##### SANDBOX BEFORE RUNNING PROTOC |
| 166 | +./bazel-out/host/bin/external/com_google_protobuf/protoc |
| 167 | +./bazel-out/darwin-opt-exec-2B5CBBC6/bin/external/com_github_golang_protobuf/protoc-gen-go/protoc-gen-go_/protoc-gen-go |
| 168 | +./bazel-out/darwin-fastbuild/bin/example/thing/thing_proto-descriptor-set.proto.bin |
| 169 | +./bazel-out/darwin-fastbuild/bin/external/com_google_protobuf/timestamp_proto-descriptor-set.proto.bin |
| 170 | + |
| 171 | +##### SANDBOX AFTER RUNNING PROTOC |
| 172 | +./bazel-out/darwin-fastbuild/bin/github.com/stackb/rules_proto/example/thing/thing.pb.go |
| 173 | +``` |
| 174 | + |
| 175 | +So, the file was created, but not in the location we wanted. In this case the |
| 176 | +`protoc-gen-go` plugin is not "playing nice" with Bazel. Because this |
| 177 | +`thing.proto` has |
| 178 | +`option go_package = "github.com/stackb/rules_proto/example/thing;thing";`, the |
| 179 | +output location is no longer based on the `package`. This is a problem, because |
| 180 | +Bazel semantics disallow declaring a File outside its package boundary. As a |
| 181 | +result, we need to do a |
| 182 | +`mv ./bazel-out/darwin-fastbuild/bin/github.com/stackb/rules_proto/example/thing/thing.pb.go ./bazel-out/darwin-fastbuild/bin/example/thing/thing.pb.go` |
| 183 | +to relocate the file into its expected location before the action terminates. |
| 184 | + |
| 185 | +Therefore, the `output_mappings` attribute is a list of entries that map file |
| 186 | +locations `want=got` relative to the action execution root. It is required when |
| 187 | +the actual output location does not match the desired location. This can occur |
| 188 | +if the proto `package` statement does not match the Bazel package path, or in |
| 189 | +special circumstances specific to the plugin itself (like `go_package`). |
| 190 | + |
| 191 | + |
| 192 | +## proto_gazelle |
| 193 | + |
| 194 | +`proto_gazelle` is not a repository rule: it's just like the typical `gazelle` |
| 195 | +rule, but with extra deps resolution superpowers. But, we discuss it here since |
| 196 | +it works in conjunction with `proto_repository`: |
| 197 | + |
| 198 | +```python |
| 199 | +load("@build_stack_rules_proto//rules:proto_gazelle.bzl", "DEFAULT_LANGUAGES", "proto_gazelle") |
| 200 | + |
| 201 | +proto_gazelle( |
| 202 | + name = "gazelle", |
| 203 | + cfgs = ["//proto:config.yaml"], |
| 204 | + command = "update", |
| 205 | + gazelle = ":gazelle-protobuf", |
| 206 | + imports = [ |
| 207 | + "@bazelapis//:imports.csv", |
| 208 | + "@googleapis//:imports.csv", |
| 209 | + "@protobufapis//:imports.csv", |
| 210 | + "@remoteapis//:imports.csv", |
| 211 | + ], |
| 212 | +) |
| 213 | +``` |
| 214 | + |
| 215 | +In this example, we are again setting the base gazelle config using the YAML |
| 216 | +file (the same one used in for the `proto_repository` rules). We are also now |
| 217 | +importing resolve information from four external sources. |
| 218 | + |
| 219 | +With this setup, we can simply place an import statement like |
| 220 | +`import "src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto";` |
| 221 | +in a `foo.proto` file in the default workspace, and gazelle will automagically |
| 222 | +figure out the import dependency tree spanning `@bazelapis`, `@remoteapis`, |
| 223 | +`@googleapis`, and the well-known types from `@protobufapis`. |
| 224 | + |
| 225 | +This works for any `proto_language`, with any set of custom protoc plugins. |
| 226 | + |
| 227 | +## golden_filegroup |
| 228 | + |
| 229 | +`golden_filegroup` is a utility macro for golden file testing. It works like a |
| 230 | +native filegroup, but adds `.update` and `.test` targets. Example: |
| 231 | + |
| 232 | +```py |
| 233 | +load("@build_stack_rules_proto//rules:golden_filegroup.bzl", "golden_filegroup") |
| 234 | + |
| 235 | +# golden_filegroup asserts that generated files named in 'srcs' are |
| 236 | +# identical to the ones checked into source control. |
| 237 | +# |
| 238 | +# Usage: |
| 239 | +# |
| 240 | +# $ bazel build :golden # not particularly useful, just a regular filegroup |
| 241 | +# |
| 242 | +# $ bazel test :golden.test # checks that generated files are identical to |
| 243 | +# ones in git (for CI) |
| 244 | +# |
| 245 | +# $ bazel run :golden.update # copies the generated files into source tree |
| 246 | +# (then 'git add' to your PR if it looks good) |
| 247 | +golden_filegroup( |
| 248 | + name = "golden", |
| 249 | + srcs = [ |
| 250 | + ":some_generated_file1.json", |
| 251 | + ":some_generated_file2.json", |
| 252 | + ], |
| 253 | +) |
| 254 | +``` |
0 commit comments