blob: b5e65a3392f101883cd0dee4f2ab348720d92a87 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Rust Protobuf Rules
16
17These build rules are used for building [protobufs][protobuf]/[gRPC][grpc] in [Rust][rust] with Bazel.
18
19[rust]: http://www.rust-lang.org/
20[protobuf]: https://developers.google.com/protocol-buffers/
21[grpc]: https://grpc.io
22
23### Setup
24
25To use the Rust proto rules, add the following to your `WORKSPACE` file to add the
26external repositories for the Rust proto toolchain (in addition to the [rust rules setup](..)):
27
28```python
29load("//proto:repositories.bzl", "rust_proto_repositories")
30
31rust_proto_repositories()
32```
33"""
34
35load("@rules_proto//proto:defs.bzl", "ProtoInfo")
36load(
37 "//proto:toolchain.bzl",
38 _generate_proto = "rust_generate_proto",
39 _generated_file_stem = "generated_file_stem",
40)
41load("//rust:defs.bzl", "rust_common")
42
43# buildifier: disable=bzl-visibility
44load("//rust/private:rustc.bzl", "rustc_compile_action")
45
46# buildifier: disable=bzl-visibility
Brian Silverman5f6f2762022-08-13 19:30:05 -070047load("//rust/private:utils.bzl", "can_build_metadata", "compute_crate_name", "determine_output_hash", "find_toolchain", "transform_deps")
Brian Silvermancc09f182022-03-09 15:40:20 -080048
49RustProtoInfo = provider(
50 doc = "Rust protobuf provider info",
51 fields = {
52 "proto_sources": "List[string]: list of source paths of protos",
53 "transitive_proto_sources": "depset[string]",
54 },
55)
56
57def _compute_proto_source_path(file, source_root_attr):
58 """Take the short path of file and make it suitable for protoc.
59
60 Args:
61 file (File): The target source file.
62 source_root_attr (str): The directory relative to which the `.proto` \
63 files defined in the proto_library are defined.
64
65 Returns:
66 str: The protoc suitible path of `file`
67 """
68
69 # Bazel creates symlinks to the .proto files under a directory called
70 # "_virtual_imports/<rule name>" if we do any sort of munging of import
71 # paths (e.g. using strip_import_prefix / import_prefix attributes)
72 virtual_imports = "/_virtual_imports/"
73 if virtual_imports in file.path:
74 return file.path.split(virtual_imports)[1].split("/", 1)[1]
75
76 # For proto, they need to be requested with their absolute name to be
77 # compatible with the descriptor_set passed by proto_library.
78 # I.e. if you compile a protobuf at @repo1//package:file.proto, the proto
79 # compiler would generate a file descriptor with the path
80 # `package/file.proto`. Since we compile from the proto descriptor, we need
81 # to pass the list of descriptors and the list of path to compile.
82 # For the precedent example, the file (noted `f`) would have
83 # `f.short_path` returns `external/repo1/package/file.proto`.
84 # In addition, proto_library can provide a proto_source_path to change the base
85 # path, which should a be a prefix.
86 path = file.short_path
87
88 # Strip external prefix.
89 path = path.split("/", 2)[2] if path.startswith("../") else path
90
91 # Strip source_root.
92 if path.startswith(source_root_attr):
93 return path[len(source_root_attr):]
94 else:
95 return path
96
97def _rust_proto_aspect_impl(target, ctx):
98 """The implementation of the `rust_proto_aspect` aspect
99
100 Args:
101 target (Target): The target to which the aspect is applied
102 ctx (ctx): The rule context which the targetis created from
103
104 Returns:
105 list: A list containg a `RustProtoInfo` provider
106 """
107 if ProtoInfo not in target:
108 return None
109
110 if hasattr(ctx.rule.attr, "proto_source_root"):
111 source_root = ctx.rule.attr.proto_source_root
112 else:
113 source_root = ""
114
115 if source_root and source_root[-1] != "/":
116 source_root += "/"
117
118 sources = [
119 _compute_proto_source_path(f, source_root)
120 for f in target[ProtoInfo].direct_sources
121 ]
122 transitive_sources = [
123 f[RustProtoInfo].transitive_proto_sources
124 for f in ctx.rule.attr.deps
125 if RustProtoInfo in f
126 ]
127 return [RustProtoInfo(
128 proto_sources = sources,
129 transitive_proto_sources = depset(transitive = transitive_sources, direct = sources),
130 )]
131
132_rust_proto_aspect = aspect(
133 doc = "An aspect that gathers rust proto direct and transitive sources",
134 implementation = _rust_proto_aspect_impl,
135 attr_aspects = ["deps"],
136)
137
138def _gen_lib(ctx, grpc, srcs, lib):
139 """Generate a lib.rs file for the crates.
140
141 Args:
142 ctx (ctx): The current rule's context object
143 grpc (bool): True if the current rule is a `gRPC` rule.
144 srcs (list): A list of protoc suitible file paths (str).
145 lib (File): The File object where the rust source file should be written
146 """
147 content = ["extern crate protobuf;"]
148 if grpc:
149 content.append("extern crate grpc;")
150 content.append("extern crate tls_api;")
151 for f in srcs.to_list():
152 content.append("pub mod %s;" % _generated_file_stem(f))
153 content.append("pub use %s::*;" % _generated_file_stem(f))
154 if grpc:
155 content.append("pub mod %s_grpc;" % _generated_file_stem(f))
156 content.append("pub use %s_grpc::*;" % _generated_file_stem(f))
157 ctx.actions.write(lib, "\n".join(content))
158
159def _expand_provider(lst, provider):
160 """Gathers a list of a specific provider from a list of targets.
161
162 Args:
163 lst (list): A list of Targets
164 provider (Provider): The target provider type to extract `lst`
165
166 Returns:
167 list: A list of providers of the type from `provider`.
168 """
169 return [el[provider] for el in lst if provider in el]
170
171def _rust_proto_compile(protos, descriptor_sets, imports, crate_name, ctx, is_grpc, compile_deps, toolchain):
172 """Create and run a rustc compile action based on the current rule's attributes
173
174 Args:
175 protos (depset): Paths of protos to compile.
176 descriptor_sets (depset): A set of transitive protobuf `FileDescriptorSet`s
177 imports (depset): A set of transitive protobuf Imports.
178 crate_name (str): The name of the Crate for the current target
179 ctx (ctx): The current rule's context object
180 is_grpc (bool): True if the current rule is a `gRPC` rule.
181 compile_deps (list): A list of Rust dependencies (`List[Target]`)
182 toolchain (rust_toolchain): the current `rust_toolchain`.
183
184 Returns:
185 list: A list of providers, see `rustc_compile_action`
186 """
187
188 # Create all the source in a specific folder
Brian Silverman5f6f2762022-08-13 19:30:05 -0700189 proto_toolchain = ctx.toolchains[Label("//proto:toolchain_type")]
Brian Silvermancc09f182022-03-09 15:40:20 -0800190 output_dir = "%s.%s.rust" % (crate_name, "grpc" if is_grpc else "proto")
191
192 # Generate the proto stubs
193 srcs = _generate_proto(
194 ctx,
195 descriptor_sets,
196 protos = protos,
197 imports = imports,
198 output_dir = output_dir,
199 proto_toolchain = proto_toolchain,
200 is_grpc = is_grpc,
201 )
202
203 # and lib.rs
204 lib_rs = ctx.actions.declare_file("%s/lib.rs" % output_dir)
205 _gen_lib(ctx, is_grpc, protos, lib_rs)
206 srcs.append(lib_rs)
207
208 # And simulate rust_library behavior
209 output_hash = determine_output_hash(lib_rs, ctx.label)
210 rust_lib = ctx.actions.declare_file("%s/lib%s-%s.rlib" % (
211 output_dir,
212 crate_name,
213 output_hash,
214 ))
Brian Silverman5f6f2762022-08-13 19:30:05 -0700215 rust_metadata = None
216 if can_build_metadata(toolchain, ctx, "rlib"):
217 rust_metadata = ctx.actions.declare_file("%s/lib%s-%s.rmeta" % (
218 output_dir,
219 crate_name,
220 output_hash,
221 ))
Brian Silvermancc09f182022-03-09 15:40:20 -0800222
223 # Gather all dependencies for compilation
224 compile_action_deps = depset(
225 transform_deps(
226 compile_deps +
227 proto_toolchain.grpc_compile_deps if is_grpc else proto_toolchain.proto_compile_deps,
228 ),
229 )
230
231 return rustc_compile_action(
232 ctx = ctx,
233 attr = ctx.attr,
234 toolchain = toolchain,
235 crate_info = rust_common.create_crate_info(
236 name = crate_name,
237 type = "rlib",
238 root = lib_rs,
239 srcs = depset(srcs),
240 deps = compile_action_deps,
241 proc_macro_deps = depset([]),
242 aliases = {},
243 output = rust_lib,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700244 metadata = rust_metadata,
Brian Silvermancc09f182022-03-09 15:40:20 -0800245 edition = proto_toolchain.edition,
246 rustc_env = {},
247 is_test = False,
248 compile_data = depset([target.files for target in getattr(ctx.attr, "compile_data", [])]),
249 wrapped_crate_type = None,
250 owner = ctx.label,
251 ),
252 output_hash = output_hash,
253 )
254
255def _rust_protogrpc_library_impl(ctx, is_grpc):
256 """Implementation of the rust_(proto|grpc)_library.
257
258 Args:
259 ctx (ctx): The current rule's context object
260 is_grpc (bool): True if the current rule is a `gRPC` rule.
261
262 Returns:
263 list: A list of providers, see `_rust_proto_compile`
264 """
265 proto = _expand_provider(ctx.attr.deps, ProtoInfo)
266 transitive_sources = [
267 f[RustProtoInfo].transitive_proto_sources
268 for f in ctx.attr.deps
269 if RustProtoInfo in f
270 ]
271
272 toolchain = find_toolchain(ctx)
273 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain)
274
275 return _rust_proto_compile(
276 protos = depset(transitive = transitive_sources),
277 descriptor_sets = depset(transitive = [p.transitive_descriptor_sets for p in proto]),
278 imports = depset(transitive = [p.transitive_imports for p in proto]),
279 crate_name = crate_name,
280 ctx = ctx,
281 is_grpc = is_grpc,
282 compile_deps = ctx.attr.rust_deps,
283 toolchain = toolchain,
284 )
285
286def _rust_proto_library_impl(ctx):
287 """The implementation of the `rust_proto_library` rule
288
289 Args:
290 ctx (ctx): The rule's context object.
291
292 Returns:
293 list: A list of providers, see `_rust_protogrpc_library_impl`
294 """
295 return _rust_protogrpc_library_impl(ctx, False)
296
297rust_proto_library = rule(
298 implementation = _rust_proto_library_impl,
299 attrs = {
300 "deps": attr.label_list(
301 doc = (
302 "List of proto_library dependencies that will be built. " +
303 "One crate for each proto_library will be created with the corresponding stubs."
304 ),
305 mandatory = True,
306 providers = [ProtoInfo],
307 aspects = [_rust_proto_aspect],
308 ),
309 "rust_deps": attr.label_list(
310 doc = "The crates the generated library depends on.",
311 ),
312 "_cc_toolchain": attr.label(
313 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
314 ),
315 "_optional_output_wrapper": attr.label(
316 executable = True,
317 cfg = "exec",
318 default = Label("//proto:optional_output_wrapper"),
319 ),
320 "_process_wrapper": attr.label(
321 default = Label("//util/process_wrapper"),
322 executable = True,
323 allow_single_file = True,
324 cfg = "exec",
325 ),
326 },
327 fragments = ["cpp"],
328 host_fragments = ["cpp"],
329 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700330 str(Label("//proto:toolchain_type")),
331 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800332 "@bazel_tools//tools/cpp:toolchain_type",
333 ],
334 # TODO: Remove once (bazelbuild/bazel#11584) is closed and the rules use
335 # the version of Bazel that issue was closed on as the min supported version
336 incompatible_use_toolchain_transition = True,
337 doc = """\
338Builds a Rust library crate from a set of `proto_library`s.
339
340Example:
341
342```python
343load("@rules_rust//proto:proto.bzl", "rust_proto_library")
344
345proto_library(
346 name = "my_proto",
347 srcs = ["my.proto"]
348)
349
350rust_proto_library(
351 name = "rust",
352 deps = [":my_proto"],
353)
354
355rust_binary(
356 name = "my_proto_binary",
357 srcs = ["my_proto_binary.rs"],
358 deps = [":rust"],
359)
360```
361""",
362)
363
364def _rust_grpc_library_impl(ctx):
365 """The implementation of the `rust_grpc_library` rule
366
367 Args:
368 ctx (ctx): The rule's context object
369
370 Returns:
371 list: A list of providers. See `_rust_protogrpc_library_impl`
372 """
373 return _rust_protogrpc_library_impl(ctx, True)
374
375rust_grpc_library = rule(
376 implementation = _rust_grpc_library_impl,
377 attrs = {
378 "deps": attr.label_list(
379 doc = (
380 "List of proto_library dependencies that will be built. " +
381 "One crate for each proto_library will be created with the corresponding gRPC stubs."
382 ),
383 mandatory = True,
384 providers = [ProtoInfo],
385 aspects = [_rust_proto_aspect],
386 ),
387 "rust_deps": attr.label_list(
388 doc = "The crates the generated library depends on.",
389 ),
390 "_cc_toolchain": attr.label(
391 default = "@bazel_tools//tools/cpp:current_cc_toolchain",
392 ),
393 "_optional_output_wrapper": attr.label(
394 executable = True,
395 cfg = "exec",
396 default = Label("//proto:optional_output_wrapper"),
397 ),
398 "_process_wrapper": attr.label(
399 default = Label("//util/process_wrapper"),
400 executable = True,
401 allow_single_file = True,
402 cfg = "exec",
403 ),
404 },
405 fragments = ["cpp"],
406 host_fragments = ["cpp"],
407 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700408 str(Label("//proto:toolchain_type")),
409 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800410 "@bazel_tools//tools/cpp:toolchain_type",
411 ],
412 # TODO: Remove once (bazelbuild/bazel#11584) is closed and the rules use
413 # the version of Bazel that issue was closed on as the min supported version
414 incompatible_use_toolchain_transition = True,
415 doc = """\
416Builds a Rust library crate from a set of `proto_library`s suitable for gRPC.
417
418Example:
419
420```python
421load("//proto:proto.bzl", "rust_grpc_library")
422
423proto_library(
424 name = "my_proto",
425 srcs = ["my.proto"]
426)
427
428rust_grpc_library(
429 name = "rust",
430 deps = [":my_proto"],
431)
432
433rust_binary(
434 name = "my_service",
435 srcs = ["my_service.rs"],
436 deps = [":rust"],
437)
438```
439""",
440)