docs: skeleton for RST documentation generation plugin. (#205)
Mostly Bazel hacking to get a protoc plugin running against the proto_library graph. The Python plugin doesn't actually do any RST generation yet, it just runs against each file and dumps the input proto. The tool can be run with: bazel build //api --aspects \ tools/protodoc/protodoc.bzl%proto_doc_aspect --output_groups=rst There's a snafu with unsandboxed runs in CI, where I can only get it to work on direct leaf invocations, will fix this in a followup PR. Signed-off-by: Harvey Tuch <htuch@google.com>pull/207/head
parent
5b29f002b4
commit
0d30f54a20
8 changed files with 117 additions and 2 deletions
@ -0,0 +1,6 @@ |
|||||||
|
py_binary( |
||||||
|
name = "protodoc", |
||||||
|
srcs = ["protodoc.py"], |
||||||
|
deps = ["@com_google_protobuf//:protobuf_python"], |
||||||
|
visibility = ["//visibility:public"], |
||||||
|
) |
@ -0,0 +1,73 @@ |
|||||||
|
# Borrowed from |
||||||
|
# https://github.com/bazelbuild/rules_go/blob/master/proto/toolchain.bzl. This |
||||||
|
# does some magic munging to remove workspace prefixes from output paths to |
||||||
|
# convert path as understood by Bazel into paths as understood by protoc. |
||||||
|
def _proto_path(proto): |
||||||
|
""" |
||||||
|
The proto path is not really a file path |
||||||
|
It's the path to the proto that was seen when the descriptor file was generated. |
||||||
|
""" |
||||||
|
path = proto.path |
||||||
|
root = proto.root.path |
||||||
|
ws = proto.owner.workspace_root |
||||||
|
if path.startswith(root): path = path[len(root):] |
||||||
|
if path.startswith("/"): path = path[1:] |
||||||
|
if path.startswith(ws): path = path[len(ws):] |
||||||
|
if path.startswith("/"): path = path[1:] |
||||||
|
return path |
||||||
|
|
||||||
|
# Bazel aspect (https://docs.bazel.build/versions/master/skylark/aspects.html) |
||||||
|
# that can be invoked from the CLI to produce docs via //tools/protodoc for |
||||||
|
# proto_library targets. Example use: |
||||||
|
# |
||||||
|
# bazel build //api --aspects tools/protodoc/protodoc.bzl%proto_doc_aspect \ |
||||||
|
# --output_groups=rst |
||||||
|
# |
||||||
|
# The aspect builds the transitive docs, so any .proto in the dependency graph |
||||||
|
# get docs created. |
||||||
|
def _proto_doc_aspect_impl(target, ctx): |
||||||
|
# Compute RST files from the current proto_library node's dependencies. |
||||||
|
transitive_outputs = depset() |
||||||
|
for dep in ctx.rule.attr.deps: |
||||||
|
transitive_outputs = transitive_outputs | dep.output_groups["rst"] |
||||||
|
proto_sources = target.proto.direct_sources |
||||||
|
# If this proto_library doesn't actually name any sources, e.g. //api:api, |
||||||
|
# but just glues together other libs, we just need to follow the graph. |
||||||
|
if not proto_sources: |
||||||
|
return [OutputGroupInfo(rst=transitive_outputs)] |
||||||
|
# The outputs live in the ctx.label's package root. We add some additional |
||||||
|
# path information to match with protoc's notion of path relative locations. |
||||||
|
outputs = [ctx.actions.declare_file(ctx.label.name + "/" + _proto_path(f) + |
||||||
|
".rst") for f in proto_sources] |
||||||
|
# Create the protoc command-line args. |
||||||
|
ctx_path = ctx.label.package + "/" + ctx.label.name |
||||||
|
output_path = outputs[0].root.path + "/" + outputs[0].owner.workspace_root + "/" + ctx_path |
||||||
|
# proto_library will be generating the descriptor sets for all the .proto deps of the |
||||||
|
# current node, we can feed them into protoc instead of setting up elaborate -I path |
||||||
|
# expressions. |
||||||
|
descriptor_set_in = ":".join([s.path for s in target.proto.transitive_descriptor_sets]) |
||||||
|
args = ["--descriptor_set_in", descriptor_set_in] |
||||||
|
args += ["--plugin=protoc-gen-protodoc=" + ctx.executable._protodoc.path, "--protodoc_out=" + output_path] |
||||||
|
args += [_proto_path(src) for src in target.proto.direct_sources] |
||||||
|
ctx.action(executable=ctx.executable._protoc, |
||||||
|
arguments=args, |
||||||
|
inputs=[ctx.executable._protodoc] + |
||||||
|
target.proto.transitive_descriptor_sets.to_list() + |
||||||
|
proto_sources, |
||||||
|
outputs=outputs, |
||||||
|
mnemonic="ProtoDoc", |
||||||
|
use_default_shell_env=True) |
||||||
|
transitive_outputs = depset(outputs) | transitive_outputs |
||||||
|
return [OutputGroupInfo(rst=transitive_outputs)] |
||||||
|
|
||||||
|
proto_doc_aspect = aspect(implementation = _proto_doc_aspect_impl, |
||||||
|
attr_aspects = ["deps"], |
||||||
|
attrs = { |
||||||
|
"_protoc": attr.label(default=Label("@com_google_protobuf//:protoc"), |
||||||
|
executable=True, |
||||||
|
cfg="host"), |
||||||
|
"_protodoc": attr.label(default=Label("//tools/protodoc"), |
||||||
|
executable=True, |
||||||
|
cfg="host"), |
||||||
|
} |
||||||
|
) |
@ -0,0 +1,18 @@ |
|||||||
|
import sys |
||||||
|
|
||||||
|
from google.protobuf.compiler import plugin_pb2 |
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
# http://www.expobrain.net/2015/09/13/create-a-plugin-for-google-protocol-buffer/ |
||||||
|
request = plugin_pb2.CodeGeneratorRequest() |
||||||
|
request.ParseFromString(sys.stdin.read()) |
||||||
|
response = plugin_pb2.CodeGeneratorResponse() |
||||||
|
|
||||||
|
for proto_file in request.proto_file: |
||||||
|
f = response.file.add() |
||||||
|
f.name = proto_file.name + '.rst' |
||||||
|
# We don't actually generate any RST right now, we just string dump the |
||||||
|
# input proto file descriptor into the output file. |
||||||
|
f.content = str(proto_file) |
||||||
|
|
||||||
|
sys.stdout.write(response.SerializeToString()) |
Loading…
Reference in new issue