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 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 7 | load("@rules_proto//proto:defs.bzl", "ProtoInfo") |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 8 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 9 | # TODO(yannic): Replace with |proto_common.direct_source_infos| when |
| 10 | # https://github.com/bazelbuild/rules_proto/pull/22 lands. |
| 11 | def _direct_source_infos(proto_info, provided_sources = []): |
| 12 | """Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources. |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 13 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 14 | Files that are both in `proto_info`'s direct sources and in |
| 15 | `provided_sources` are skipped. This is useful, e.g., for well-known |
| 16 | protos that are already provided by the Protobuf runtime. |
| 17 | |
| 18 | Args: |
| 19 | proto_info: An instance of `ProtoInfo`. |
| 20 | provided_sources: Optional. A sequence of files to ignore. |
| 21 | Usually, these files are already provided by the |
| 22 | Protocol Buffer runtime (e.g. Well-Known protos). |
| 23 | |
| 24 | Returns: A sequence of `ProtoFileInfo` containing information about |
| 25 | `proto_info`'s direct sources. |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 26 | """ |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 27 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 28 | source_root = proto_info.proto_source_root |
| 29 | if "." == source_root: |
| 30 | return [struct(file = src, import_path = src.path) for src in proto_info.direct_sources] |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 31 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 32 | offset = len(source_root) + 1 # + '/'. |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 33 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 34 | infos = [] |
| 35 | for src in proto_info.direct_sources: |
| 36 | # TODO(yannic): Remove this hack when we drop support for Bazel < 1.0. |
| 37 | local_offset = offset |
| 38 | if src.root.path and not source_root.startswith(src.root.path): |
| 39 | # Before Bazel 1.0, `proto_source_root` wasn't guaranteed to be a |
| 40 | # prefix of `src.path`. This could happend, e.g., if `file` was |
| 41 | # generated (https://github.com/bazelbuild/bazel/issues/9215). |
| 42 | local_offset += len(src.root.path) + 1 # + '/'. |
| 43 | infos.append(struct(file = src, import_path = src.path[local_offset:])) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 44 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 45 | return infos |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 46 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 47 | def _run_proto_gen_swagger( |
| 48 | actions, |
| 49 | proto_info, |
| 50 | target_name, |
| 51 | transitive_proto_srcs, |
| 52 | protoc, |
| 53 | protoc_gen_swagger, |
| 54 | grpc_api_configuration, |
| 55 | single_output, |
| 56 | json_names_for_fields): |
| 57 | args = actions.args() |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 58 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 59 | args.add("--plugin", "protoc-gen-swagger=%s" % protoc_gen_swagger.path) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 60 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 61 | args.add("--swagger_opt", "logtostderr=true") |
| 62 | args.add("--swagger_opt", "allow_repeated_fields_in_body=true") |
| 63 | |
| 64 | extra_inputs = [] |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 65 | if grpc_api_configuration: |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 66 | extra_inputs.append(grpc_api_configuration) |
| 67 | args.add("--swagger_opt", "grpc_api_configuration=%s" % grpc_api_configuration.path) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 68 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 69 | if json_names_for_fields: |
| 70 | args.add("--swagger_opt", "json_names_for_fields=true") |
| 71 | |
| 72 | proto_file_infos = _direct_source_infos(proto_info) |
| 73 | |
| 74 | # TODO(yannic): Use |proto_info.transitive_descriptor_sets| when |
| 75 | # https://github.com/bazelbuild/bazel/issues/9337 is fixed. |
| 76 | args.add_all(proto_info.transitive_proto_path, format_each = "--proto_path=%s") |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 77 | |
| 78 | if single_output: |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 79 | args.add("--swagger_opt", "allow_merge=true") |
| 80 | args.add("--swagger_opt", "merge_file_name=%s" % target_name) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 81 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 82 | swagger_file = actions.declare_file("%s.swagger.json" % target_name) |
| 83 | args.add("--swagger_out", swagger_file.dirname) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 84 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 85 | args.add_all([f.import_path for f in proto_file_infos]) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 86 | |
| 87 | actions.run( |
| 88 | executable = protoc, |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 89 | tools = [protoc_gen_swagger], |
| 90 | inputs = depset( |
| 91 | direct = extra_inputs, |
| 92 | transitive = [transitive_proto_srcs], |
| 93 | ), |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 94 | outputs = [swagger_file], |
| 95 | arguments = [args], |
| 96 | ) |
| 97 | |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 98 | return [swagger_file] |
| 99 | |
| 100 | # TODO(yannic): We may be able to generate all files in a single action, |
| 101 | # but that will change at least the semantics of `use_go_template.proto`. |
| 102 | swagger_files = [] |
| 103 | for proto_file_info in proto_file_infos: |
| 104 | # TODO(yannic): This probably doesn't work as expected: we only add this |
| 105 | # option after we have seen it, so `.proto` sources that happen to be |
| 106 | # in the list of `.proto` files before `use_go_template.proto` will be |
| 107 | # compiled without this option, and all sources that get compiled after |
| 108 | # `use_go_template.proto` will have this option on. |
| 109 | if proto_file_info.file.basename == "use_go_template.proto": |
| 110 | args.add("--swagger_opt", "use_go_templates=true") |
| 111 | |
| 112 | file_name = "%s.swagger.json" % proto_file_info.import_path[:-len(".proto")] |
| 113 | swagger_file = actions.declare_file( |
| 114 | "_virtual_imports/%s/%s" % (target_name, file_name), |
| 115 | ) |
| 116 | |
| 117 | file_args = actions.args() |
| 118 | |
| 119 | offset = len(file_name) + 1 # + '/'. |
| 120 | file_args.add("--swagger_out", swagger_file.path[:-offset]) |
| 121 | |
| 122 | file_args.add(proto_file_info.import_path) |
| 123 | |
| 124 | actions.run( |
| 125 | executable = protoc, |
| 126 | tools = [protoc_gen_swagger], |
| 127 | inputs = depset( |
| 128 | direct = extra_inputs, |
| 129 | transitive = [transitive_proto_srcs], |
| 130 | ), |
| 131 | outputs = [swagger_file], |
| 132 | arguments = [args, file_args], |
| 133 | ) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 134 | swagger_files.append(swagger_file) |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 135 | |
| 136 | return swagger_files |
| 137 | |
| 138 | def _proto_gen_swagger_impl(ctx): |
| 139 | proto = ctx.attr.proto[ProtoInfo] |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 140 | return [ |
| 141 | DefaultInfo( |
| 142 | files = depset( |
| 143 | _run_proto_gen_swagger( |
| 144 | actions = ctx.actions, |
| 145 | proto_info = proto, |
| 146 | target_name = ctx.attr.name, |
| 147 | transitive_proto_srcs = depset( |
| 148 | direct = ctx.files._well_known_protos, |
| 149 | transitive = [proto.transitive_sources], |
| 150 | ), |
| 151 | protoc = ctx.executable._protoc, |
| 152 | protoc_gen_swagger = ctx.executable._protoc_gen_swagger, |
| 153 | grpc_api_configuration = ctx.file.grpc_api_configuration, |
| 154 | single_output = ctx.attr.single_output, |
| 155 | json_names_for_fields = ctx.attr.json_names_for_fields, |
| 156 | ), |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 157 | ), |
| 158 | ), |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 159 | ] |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 160 | |
| 161 | protoc_gen_swagger = rule( |
| 162 | attrs = { |
| 163 | "proto": attr.label( |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 164 | mandatory = True, |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 165 | providers = [ProtoInfo], |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 166 | ), |
| 167 | "grpc_api_configuration": attr.label( |
| 168 | allow_single_file = True, |
| 169 | mandatory = False, |
| 170 | ), |
| 171 | "single_output": attr.bool( |
| 172 | default = False, |
| 173 | mandatory = False, |
| 174 | ), |
Arjun E K | 57a7fcb | 2020-01-30 06:44:45 +0000 | [diff] [blame^] | 175 | "json_names_for_fields": attr.bool( |
| 176 | default = False, |
| 177 | mandatory = False, |
| 178 | ), |
Matteo Scandolo | a6a3aee | 2019-11-26 13:30:14 -0700 | [diff] [blame] | 179 | "_protoc": attr.label( |
| 180 | default = "@com_google_protobuf//:protoc", |
| 181 | executable = True, |
| 182 | cfg = "host", |
| 183 | ), |
| 184 | "_well_known_protos": attr.label( |
| 185 | default = "@com_google_protobuf//:well_known_protos", |
| 186 | allow_files = True, |
| 187 | ), |
| 188 | "_protoc_gen_swagger": attr.label( |
| 189 | default = Label("//protoc-gen-swagger:protoc-gen-swagger"), |
| 190 | executable = True, |
| 191 | cfg = "host", |
| 192 | ), |
| 193 | }, |
| 194 | implementation = _proto_gen_swagger_impl, |
| 195 | ) |