blob: 2cb1f22fddaf4fd358baa2d0632ba0a5b2d931f9 [file] [log] [blame]
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -07001"""Generated an open-api spec for a grpc api spec.
2
3Reads the the api spec in protobuf format and generate an open-api spec.
4Optionally applies settings from the grpc-service configuration.
5"""
6
Arjun E K57a7fcb2020-01-30 06:44:45 +00007load("@rules_proto//proto:defs.bzl", "ProtoInfo")
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -07008
Arjun E K57a7fcb2020-01-30 06:44:45 +00009# TODO(yannic): Replace with |proto_common.direct_source_infos| when
10# https://github.com/bazelbuild/rules_proto/pull/22 lands.
11def _direct_source_infos(proto_info, provided_sources = []):
12 """Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources.
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070013
Arjun E K57a7fcb2020-01-30 06:44:45 +000014 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 Scandoloa6a3aee2019-11-26 13:30:14 -070026 """
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070027
Arjun E K57a7fcb2020-01-30 06:44:45 +000028 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 Scandoloa6a3aee2019-11-26 13:30:14 -070031
Arjun E K57a7fcb2020-01-30 06:44:45 +000032 offset = len(source_root) + 1 # + '/'.
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070033
Arjun E K57a7fcb2020-01-30 06:44:45 +000034 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 Scandoloa6a3aee2019-11-26 13:30:14 -070044
Arjun E K57a7fcb2020-01-30 06:44:45 +000045 return infos
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070046
Arjun E K57a7fcb2020-01-30 06:44:45 +000047def _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 Scandoloa6a3aee2019-11-26 13:30:14 -070058
Arjun E K57a7fcb2020-01-30 06:44:45 +000059 args.add("--plugin", "protoc-gen-swagger=%s" % protoc_gen_swagger.path)
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070060
Arjun E K57a7fcb2020-01-30 06:44:45 +000061 args.add("--swagger_opt", "logtostderr=true")
62 args.add("--swagger_opt", "allow_repeated_fields_in_body=true")
63
64 extra_inputs = []
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070065 if grpc_api_configuration:
Arjun E K57a7fcb2020-01-30 06:44:45 +000066 extra_inputs.append(grpc_api_configuration)
67 args.add("--swagger_opt", "grpc_api_configuration=%s" % grpc_api_configuration.path)
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070068
Arjun E K57a7fcb2020-01-30 06:44:45 +000069 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 Scandoloa6a3aee2019-11-26 13:30:14 -070077
78 if single_output:
Arjun E K57a7fcb2020-01-30 06:44:45 +000079 args.add("--swagger_opt", "allow_merge=true")
80 args.add("--swagger_opt", "merge_file_name=%s" % target_name)
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070081
Arjun E K57a7fcb2020-01-30 06:44:45 +000082 swagger_file = actions.declare_file("%s.swagger.json" % target_name)
83 args.add("--swagger_out", swagger_file.dirname)
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070084
Arjun E K57a7fcb2020-01-30 06:44:45 +000085 args.add_all([f.import_path for f in proto_file_infos])
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070086
87 actions.run(
88 executable = protoc,
Arjun E K57a7fcb2020-01-30 06:44:45 +000089 tools = [protoc_gen_swagger],
90 inputs = depset(
91 direct = extra_inputs,
92 transitive = [transitive_proto_srcs],
93 ),
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -070094 outputs = [swagger_file],
95 arguments = [args],
96 )
97
Arjun E K57a7fcb2020-01-30 06:44:45 +000098 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 Scandoloa6a3aee2019-11-26 13:30:14 -0700134 swagger_files.append(swagger_file)
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -0700135
136 return swagger_files
137
138def _proto_gen_swagger_impl(ctx):
139 proto = ctx.attr.proto[ProtoInfo]
Arjun E K57a7fcb2020-01-30 06:44:45 +0000140 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 Scandoloa6a3aee2019-11-26 13:30:14 -0700157 ),
158 ),
Arjun E K57a7fcb2020-01-30 06:44:45 +0000159 ]
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -0700160
161protoc_gen_swagger = rule(
162 attrs = {
163 "proto": attr.label(
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -0700164 mandatory = True,
Arjun E K57a7fcb2020-01-30 06:44:45 +0000165 providers = [ProtoInfo],
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -0700166 ),
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 K57a7fcb2020-01-30 06:44:45 +0000175 "json_names_for_fields": attr.bool(
176 default = False,
177 mandatory = False,
178 ),
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -0700179 "_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)