9

I'm using Bazel and Google's protocol buffers. I want to add a Bazel rule so I can generate the C++ API from the .proto files. In GNU make, I would do (simplified example):

%.h: %.cc
%.cc: %.proto
    protoc --cpp_out=. $<

How can I accomplish the same (i.e. generate the API whenever mymessage.proto changes) using Bazel?

morxa
  • 3,221
  • 3
  • 27
  • 43
  • 1
    Native support for cc_proto_library has recently landed in Bazel: https://bazel.build/blog/2017/02/27/protocol-buffers.html. If that helps you, I'll turn this comment into an answer. – user1071136 Mar 29 '17 at 19:46
  • @user1071136 that would be good! – morxa Mar 30 '17 at 10:56

4 Answers4

8

I tried the above and it didn't seem to work, I got an error from protoc for trying to create two directories, then that the my-proto.h directory didn't exist. Instead, I did

genrule(
    name = "my-proto-gen",
    outs = ["my-proto.pb.h my-proto.pb.cc"],
    cmd = "$(location //third_party/protobuf:protoc) --cpp_out=$(GENDIR) $<",
    srcs = ["my-proto.proto"],
    tools = ["//third_party/protobuf:protoc"],
)

cc_library(
    name = "my-proto",
    srcs = ["my-proto.pb.cc"],
    hdrs = ["my-proto.pb.h"]
)

Which actually just checks to see that the header file is created and creates it bazel-genfiles directory.

You can then include the proto build in your cc_library as :my-proto.

UPDATE: To get this entire thing to work, do the following:

  1. Add the following to your WORKSPACE file. This downloads the protobuf library:

    http_archive(
        name = "protobuf",
        url = "https://github.com/google/protobuf/releases/download/v3.0.0/protobuf-cpp-3.0.0.zip",
        strip_prefix = "protobuf-3.0.0",
    )
    
  2. Create a .bzl file (let's say protobuf.bzl) and put in the following:

    def cpp_proto(name, src):
      native.genrule(
          name = "%s-gen" % name,
          outs = ["%s.pb.cc" % name, "%s.pb.h" % name],
          cmd = "$(location @protobuf//:protoc) --cpp_out=$(GENDIR) $<",
          srcs = [src],
          tools = ["@protobuf//:protoc"],
      )
    
      native.cc_library(
          name = name,
          srcs = ["%s.pb.cc" % name],
          hdrs = ["%s.pb.h" % name],
      )
    
  3. In your build files, add in load(':protobuf.bzl', 'cpp_proto')

  4. You can now use the macro as such:

    cpp_proto(
        name = "my-proto",
        src = "my-proto.proto"
    )
    
    cc_library(
        name = "my-program",
        srcs = ["my-program.cc"],
        deps = [
            ":my-proto",
        ],
    )
    
Sam
  • 333
  • 1
  • 3
  • 7
  • This only works if the proto file is in the workspace. If it's in a subfolder, the generated files are not in the folder where bazel expects them to be and fails. I would need to add `--proto_path` to the `protoc` call. – morxa Aug 25 '16 at 12:36
5

Native support for cc_proto_library has recently landed in Bazel: http://bazel.build/blog/2017/02/27/protocol-buffers.html.

tl;dr, after setting your WORKSPACE file once,

cc_proto_library(
    name = "person_cc_proto",
    deps = [":person_proto"],
)

proto_library(
    name = "person_proto",
    srcs = ["person.proto"],
    deps = [":address_proto"],
)

...

Then,

$ bazel build :person_cc_proto

There's an example at https://github.com/cgrushko/proto_library.

The gist is that you define a proto_library to "import" you .proto file into Bazel, and cc_proto_library to compile it to C++. The protocol buffer compiler and runtimes are taken by default from @com_google_protobuf//:protoc and @com_google_protobuf_cc//:cc_toolchain, respectively.

The reason for this separation is to enable large proto graphs that need to be compiled to multiple languages.

user1071136
  • 15,636
  • 4
  • 42
  • 61
3

There are several ways. As a one-off, you can just create a genrule to execute the command over certain inputs:

genrule(
    name = "my-proto-gen",
    outs = ["my-proto.cc", "my-proto.h"],
    cmd = "$(location //path/to:protoc) --cpp_out=$@ $<",
    srcs = ["my-proto.proto"],
    tools = ["//path/to:protoc"],
)

cc_library(
    name = "my-proto",
    srcs = ["my-proto.cc"],
    hdrs = ["my-proto.h"],
)

Based on your make rule, I'd assume you want to do this multiple times. In that case, you can define a macro in a .bzl file. Macros are basically functions that call build rules:

# In, say, foo/bar.bzl.
def cpp_proto(name, src):
  native.genrule(
      name = "%s-gen" % name,
      outs = ["%s.cc" % name, "%s.h" % name],
      cmd = "$(location //path/to:protoc) --cpp_out=$@ $<",
      srcs = [src],
      tools = ["//path/to:protoc"],
  )

  native.cc_library(
      name = name,
      srcs = ["%s.cc" % name],
      hdrs = ["%s.h" % name],
  )

Then in, say, foo/BUILD, you can import & use your macro to concisely call the rules:

load('//foo:bar.bzl', 'cpp_proto')
cpp_proto('my-proto', 'my_proto.proto')

Then you can depend on //foo:my-proto from cc_librarys, cc_binarys, and cc_tests.

Finally, you might want to follow https://github.com/bazelbuild/bazel/issues/52 (and just use mzhaom's macro).

kris
  • 23,024
  • 10
  • 70
  • 79
  • Thanks to the pointer to mzhaom's macro, I somehow missed it. However, I didn't really understand how to use it. There were also two other alternatives suggested at the end of the thread, but I couldn't get either of them running. The native protobuf support seems to lack the cpp part, and the pubref rules always threw an exception. – morxa Aug 25 '16 at 12:40
2

I put together a set of protobuf generating rules at https://github.com/pubref/rules_protobuf. It supports C++ in additional to a number of other languages. Hope you find it useful.

Paul
  • 595
  • 6
  • 6