Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 1 | """Generated an open-api spec for a grpc api spec. |
| 2 | |
| 3 | Reads the the api spec in protobuf format and generate an open-api spec. |
| 4 | Optionally applies settings from the grpc-service configuration. |
| 5 | """ |
| 6 | |
| 7 | def _collect_includes(gen_dir, srcs): |
| 8 | """Build an include path mapping. |
| 9 | |
| 10 | It is important to not just collect unique dirnames, to also support |
| 11 | proto files of the same name from different packages. |
| 12 | |
| 13 | The implementation below is similar to what bazel does in its |
| 14 | ProtoCompileActionBuilder.java |
| 15 | """ |
| 16 | includes = [] |
| 17 | for src in srcs: |
| 18 | ref_path = src.path |
| 19 | |
| 20 | if ref_path.startswith(gen_dir): |
| 21 | ref_path = ref_path[len(gen_dir):].lstrip("/") |
| 22 | |
| 23 | if src.owner.workspace_root: |
| 24 | workspace_root = src.owner.workspace_root |
| 25 | ref_path = ref_path[len(workspace_root):].lstrip("/") |
| 26 | |
| 27 | include = ref_path + "=" + src.path |
| 28 | if include not in includes: |
| 29 | includes.append(include) |
| 30 | |
| 31 | return includes |
| 32 | |
| 33 | def _run_proto_gen_swagger(ctx, direct_proto_srcs, transitive_proto_srcs, actions, protoc, protoc_gen_swagger, grpc_api_configuration, single_output): |
| 34 | swagger_files = [] |
| 35 | |
| 36 | inputs = direct_proto_srcs + transitive_proto_srcs |
| 37 | tools = [protoc_gen_swagger] |
| 38 | |
| 39 | options = ["logtostderr=true", "allow_repeated_fields_in_body=true"] |
| 40 | if grpc_api_configuration: |
| 41 | options.append("grpc_api_configuration=%s" % grpc_api_configuration.path) |
| 42 | inputs.append(grpc_api_configuration) |
| 43 | |
| 44 | includes = _collect_includes(ctx.genfiles_dir.path, direct_proto_srcs + transitive_proto_srcs) |
| 45 | |
| 46 | if single_output: |
| 47 | swagger_file = actions.declare_file( |
| 48 | "%s.swagger.json" % ctx.attr.name, |
| 49 | sibling = direct_proto_srcs[0], |
| 50 | ) |
| 51 | output_dir = ctx.bin_dir.path |
| 52 | if direct_proto_srcs[0].owner.workspace_root: |
| 53 | output_dir = "/".join([output_dir, direct_proto_srcs[0].owner.workspace_root]) |
| 54 | |
| 55 | output_dir = "/".join([output_dir, direct_proto_srcs[0].dirname]) |
| 56 | |
| 57 | options.append("allow_merge=true") |
| 58 | options.append("merge_file_name=%s" % ctx.attr.name) |
| 59 | |
| 60 | args = actions.args() |
| 61 | args.add("--plugin=%s" % protoc_gen_swagger.path) |
| 62 | args.add("--swagger_out=%s:%s" % (",".join(options), output_dir)) |
| 63 | args.add_all(["-I%s" % include for include in includes]) |
| 64 | args.add_all([src.path for src in direct_proto_srcs]) |
| 65 | |
| 66 | actions.run( |
| 67 | executable = protoc, |
| 68 | inputs = inputs, |
| 69 | tools = tools, |
| 70 | outputs = [swagger_file], |
| 71 | arguments = [args], |
| 72 | ) |
| 73 | |
| 74 | swagger_files.append(swagger_file) |
| 75 | else: |
| 76 | for proto in direct_proto_srcs: |
| 77 | swagger_file = actions.declare_file( |
| 78 | "%s.swagger.json" % proto.basename[:-len(".proto")], |
| 79 | sibling = proto, |
| 80 | ) |
| 81 | |
| 82 | output_dir = ctx.bin_dir.path |
| 83 | if proto.owner.workspace_root: |
| 84 | output_dir = "/".join([output_dir, proto.owner.workspace_root]) |
| 85 | |
| 86 | args = actions.args() |
| 87 | args.add("--plugin=%s" % protoc_gen_swagger.path) |
| 88 | args.add("--swagger_out=%s:%s" % (",".join(options), output_dir)) |
| 89 | args.add_all(["-I%s" % include for include in includes]) |
| 90 | args.add(proto.path) |
| 91 | |
| 92 | actions.run( |
| 93 | executable = protoc, |
| 94 | inputs = inputs, |
| 95 | tools = tools, |
| 96 | outputs = [swagger_file], |
| 97 | arguments = [args], |
| 98 | ) |
| 99 | |
| 100 | swagger_files.append(swagger_file) |
| 101 | |
| 102 | return swagger_files |
| 103 | |
| 104 | def _proto_gen_swagger_impl(ctx): |
| 105 | proto = ctx.attr.proto[ProtoInfo] |
| 106 | grpc_api_configuration = ctx.file.grpc_api_configuration |
| 107 | |
| 108 | return [DefaultInfo( |
| 109 | files = depset( |
| 110 | _run_proto_gen_swagger( |
| 111 | ctx, |
| 112 | direct_proto_srcs = proto.direct_sources, |
| 113 | transitive_proto_srcs = ctx.files._well_known_protos + proto.transitive_sources.to_list(), |
| 114 | actions = ctx.actions, |
| 115 | protoc = ctx.executable._protoc, |
| 116 | protoc_gen_swagger = ctx.executable._protoc_gen_swagger, |
| 117 | grpc_api_configuration = grpc_api_configuration, |
| 118 | single_output = ctx.attr.single_output, |
| 119 | ), |
| 120 | ), |
| 121 | )] |
| 122 | |
| 123 | protoc_gen_swagger = rule( |
| 124 | attrs = { |
| 125 | "proto": attr.label( |
| 126 | allow_rules = ["proto_library"], |
| 127 | mandatory = True, |
| 128 | providers = ["proto"], |
| 129 | ), |
| 130 | "grpc_api_configuration": attr.label( |
| 131 | allow_single_file = True, |
| 132 | mandatory = False, |
| 133 | ), |
| 134 | "single_output": attr.bool( |
| 135 | default = False, |
| 136 | mandatory = False, |
| 137 | ), |
| 138 | "_protoc": attr.label( |
| 139 | default = "@com_google_protobuf//:protoc", |
| 140 | executable = True, |
| 141 | cfg = "host", |
| 142 | ), |
| 143 | "_well_known_protos": attr.label( |
| 144 | default = "@com_google_protobuf//:well_known_protos", |
| 145 | allow_files = True, |
| 146 | ), |
| 147 | "_protoc_gen_swagger": attr.label( |
| 148 | default = Label("//protoc-gen-swagger:protoc-gen-swagger"), |
| 149 | executable = True, |
| 150 | cfg = "host", |
| 151 | ), |
| 152 | }, |
| 153 | implementation = _proto_gen_swagger_impl, |
| 154 | ) |