xref: /aosp_15_r20/external/bazelbuild-rules_rust/proto/protobuf/proto.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1# 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
17load("@rules_proto//proto:defs.bzl", "ProtoInfo")
18load(
19    "//proto/protobuf:toolchain.bzl",
20    _generate_proto = "rust_generate_proto",
21    _generated_file_stem = "generated_file_stem",
22)
23
24# buildifier: disable=bzl-visibility
25load("//rust/private:rustc.bzl", "rustc_compile_action")
26
27# buildifier: disable=bzl-visibility
28load("//rust/private:utils.bzl", "can_build_metadata", "compute_crate_name", "determine_output_hash", "find_toolchain", "transform_deps")
29
30RustProtoInfo = provider(
31    doc = "Rust protobuf provider info",
32    fields = {
33        "proto_sources": "List[string]: list of source paths of protos",
34        "transitive_proto_sources": "depset[string]",
35    },
36)
37
38def _compute_proto_source_path(file, source_root_attr):
39    """Take the short path of file and make it suitable for protoc.
40
41    Args:
42        file (File): The target source file.
43        source_root_attr (str): The directory relative to which the `.proto` \
44            files defined in the proto_library are defined.
45
46    Returns:
47        str: The protoc suitible path of `file`
48    """
49
50    # Bazel creates symlinks to the .proto files under a directory called
51    # "_virtual_imports/<rule name>" if we do any sort of munging of import
52    # paths (e.g. using strip_import_prefix / import_prefix attributes)
53    virtual_imports = "/_virtual_imports/"
54    if virtual_imports in file.path:
55        return file.path.split(virtual_imports)[1].split("/", 1)[1]
56
57    # For proto, they need to be requested with their absolute name to be
58    # compatible with the descriptor_set passed by proto_library.
59    # I.e. if you compile a protobuf at @repo1//package:file.proto, the proto
60    # compiler would generate a file descriptor with the path
61    # `package/file.proto`. Since we compile from the proto descriptor, we need
62    # to pass the list of descriptors and the list of path to compile.
63    # For the precedent example, the file (noted `f`) would have
64    # `f.short_path` returns `external/repo1/package/file.proto`.
65    # In addition, proto_library can provide a proto_source_path to change the base
66    # path, which should a be a prefix.
67    path = file.short_path
68
69    # Strip external prefix.
70    path = path.split("/", 2)[2] if path.startswith("../") else path
71
72    # Strip source_root.
73    if path.startswith(source_root_attr):
74        return path[len(source_root_attr):]
75    else:
76        return path
77
78def _rust_proto_aspect_impl(target, ctx):
79    """The implementation of the `rust_proto_aspect` aspect
80
81    Args:
82        target (Target): The target to which the aspect is applied
83        ctx (ctx): The rule context which the targetis created from
84
85    Returns:
86        list: A list containg a `RustProtoInfo` provider
87    """
88    if ProtoInfo not in target:
89        return None
90
91    if hasattr(ctx.rule.attr, "proto_source_root"):
92        source_root = ctx.rule.attr.proto_source_root
93    else:
94        source_root = ""
95
96    if source_root and source_root[-1] != "/":
97        source_root += "/"
98
99    sources = [
100        _compute_proto_source_path(f, source_root)
101        for f in target[ProtoInfo].direct_sources
102    ]
103    transitive_sources = [
104        f[RustProtoInfo].transitive_proto_sources
105        for f in ctx.rule.attr.deps
106        if RustProtoInfo in f
107    ]
108    return [RustProtoInfo(
109        proto_sources = sources,
110        transitive_proto_sources = depset(transitive = transitive_sources, direct = sources),
111    )]
112
113_rust_proto_aspect = aspect(
114    doc = "An aspect that gathers rust proto direct and transitive sources",
115    implementation = _rust_proto_aspect_impl,
116    attr_aspects = ["deps"],
117)
118
119def _gen_lib(ctx, grpc, srcs, lib):
120    """Generate a lib.rs file for the crates.
121
122    Args:
123        ctx (ctx): The current rule's context object
124        grpc (bool): True if the current rule is a `gRPC` rule.
125        srcs (list): A list of protoc suitible file paths (str).
126        lib (File): The File object where the rust source file should be written
127    """
128    content = ["extern crate protobuf;"]
129    if grpc:
130        content.append("extern crate grpc;")
131        content.append("extern crate tls_api;")
132    for f in srcs.to_list():
133        content.append("pub mod %s;" % _generated_file_stem(f))
134        content.append("pub use %s::*;" % _generated_file_stem(f))
135        if grpc:
136            content.append("pub mod %s_grpc;" % _generated_file_stem(f))
137            content.append("pub use %s_grpc::*;" % _generated_file_stem(f))
138    ctx.actions.write(lib, "\n".join(content))
139
140def _expand_provider(lst, provider):
141    """Gathers a list of a specific provider from a list of targets.
142
143    Args:
144        lst (list): A list of Targets
145        provider (Provider): The target provider type to extract `lst`
146
147    Returns:
148        list: A list of providers of the type from `provider`.
149    """
150    return [el[provider] for el in lst if provider in el]
151
152def _rust_proto_compile(protos, descriptor_sets, imports, crate_name, ctx, is_grpc, compile_deps, toolchain):
153    """Create and run a rustc compile action based on the current rule's attributes
154
155    Args:
156        protos (depset): Paths of protos to compile.
157        descriptor_sets (depset): A set of transitive protobuf `FileDescriptorSet`s
158        imports (depset): A set of transitive protobuf Imports.
159        crate_name (str): The name of the Crate for the current target
160        ctx (ctx): The current rule's context object
161        is_grpc (bool): True if the current rule is a `gRPC` rule.
162        compile_deps (list): A list of Rust dependencies (`List[Target]`)
163        toolchain (rust_toolchain): the current `rust_toolchain`.
164
165    Returns:
166        list: A list of providers, see `rustc_compile_action`
167    """
168
169    # Create all the source in a specific folder
170    proto_toolchain = ctx.toolchains[Label("//proto/protobuf:toolchain_type")]
171    output_dir = "%s.%s.rust" % (crate_name, "grpc" if is_grpc else "proto")
172
173    # Generate the proto stubs
174    srcs = _generate_proto(
175        ctx,
176        descriptor_sets,
177        protos = protos,
178        imports = imports,
179        output_dir = output_dir,
180        proto_toolchain = proto_toolchain,
181        is_grpc = is_grpc,
182    )
183
184    # and lib.rs
185    lib_rs = ctx.actions.declare_file("%s/lib.rs" % output_dir)
186    _gen_lib(ctx, is_grpc, protos, lib_rs)
187    srcs.append(lib_rs)
188
189    # And simulate rust_library behavior
190    output_hash = determine_output_hash(lib_rs, ctx.label)
191    rust_lib = ctx.actions.declare_file("%s/lib%s-%s.rlib" % (
192        output_dir,
193        crate_name,
194        output_hash,
195    ))
196    rust_metadata = None
197    if can_build_metadata(toolchain, ctx, "rlib"):
198        rust_metadata = ctx.actions.declare_file("%s/lib%s-%s.rmeta" % (
199            output_dir,
200            crate_name,
201            output_hash,
202        ))
203
204    # Gather all dependencies for compilation
205    compile_action_deps = depset(
206        transform_deps(
207            compile_deps +
208            proto_toolchain.grpc_compile_deps if is_grpc else proto_toolchain.proto_compile_deps,
209        ),
210    )
211
212    providers = rustc_compile_action(
213        ctx = ctx,
214        attr = ctx.attr,
215        toolchain = toolchain,
216        crate_info_dict = dict(
217            name = crate_name,
218            type = "rlib",
219            root = lib_rs,
220            srcs = depset(srcs),
221            deps = compile_action_deps,
222            proc_macro_deps = depset([]),
223            aliases = {},
224            output = rust_lib,
225            metadata = rust_metadata,
226            edition = proto_toolchain.edition,
227            rustc_env = {},
228            is_test = False,
229            compile_data = depset([target.files for target in getattr(ctx.attr, "compile_data", [])]),
230            compile_data_targets = depset(getattr(ctx.attr, "compile_data", [])),
231            wrapped_crate_type = None,
232            owner = ctx.label,
233        ),
234        output_hash = output_hash,
235    )
236    providers.append(OutputGroupInfo(rust_generated_srcs = srcs))
237    return providers
238
239def _rust_protogrpc_library_impl(ctx, is_grpc):
240    """Implementation of the rust_(proto|grpc)_library.
241
242    Args:
243        ctx (ctx): The current rule's context object
244        is_grpc (bool): True if the current rule is a `gRPC` rule.
245
246    Returns:
247        list: A list of providers, see `_rust_proto_compile`
248    """
249    proto = _expand_provider(ctx.attr.deps, ProtoInfo)
250    transitive_sources = [
251        f[RustProtoInfo].transitive_proto_sources
252        for f in ctx.attr.deps
253        if RustProtoInfo in f
254    ]
255
256    toolchain = find_toolchain(ctx)
257    crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
258
259    return _rust_proto_compile(
260        protos = depset(transitive = transitive_sources),
261        descriptor_sets = depset(transitive = [p.transitive_descriptor_sets for p in proto]),
262        imports = depset(transitive = [p.transitive_imports for p in proto]),
263        crate_name = crate_name,
264        ctx = ctx,
265        is_grpc = is_grpc,
266        compile_deps = ctx.attr.rust_deps,
267        toolchain = toolchain,
268    )
269
270def _rust_proto_library_impl(ctx):
271    """The implementation of the `rust_proto_library` rule
272
273    Args:
274        ctx (ctx): The rule's context object.
275
276    Returns:
277        list: A list of providers, see `_rust_protogrpc_library_impl`
278    """
279    return _rust_protogrpc_library_impl(ctx, False)
280
281rust_proto_library = rule(
282    implementation = _rust_proto_library_impl,
283    attrs = {
284        "crate_name": attr.string(
285            doc = """\
286                Crate name to use for this target.
287
288                This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
289                Defaults to the target name, with any hyphens replaced by underscores.
290            """,
291        ),
292        "deps": attr.label_list(
293            doc = (
294                "List of proto_library dependencies that will be built. " +
295                "One crate for each proto_library will be created with the corresponding stubs."
296            ),
297            mandatory = True,
298            providers = [ProtoInfo],
299            aspects = [_rust_proto_aspect],
300        ),
301        "rust_deps": attr.label_list(
302            doc = "The crates the generated library depends on.",
303        ),
304        "rustc_flags": attr.string_list(
305            doc = """\
306                List of compiler flags passed to `rustc`.
307
308                These strings are subject to Make variable expansion for predefined
309                source/output path variables like `$location`, `$execpath`, and
310                `$rootpath`. This expansion is useful if you wish to pass a generated
311                file of arguments to rustc: `@$(location //package:target)`.
312            """,
313        ),
314        "_cc_toolchain": attr.label(
315            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
316        ),
317        "_optional_output_wrapper": attr.label(
318            executable = True,
319            cfg = "exec",
320            default = Label("//proto/protobuf:optional_output_wrapper"),
321        ),
322        "_process_wrapper": attr.label(
323            default = Label("//util/process_wrapper"),
324            executable = True,
325            allow_single_file = True,
326            cfg = "exec",
327        ),
328    },
329    fragments = ["cpp"],
330    toolchains = [
331        str(Label("//proto/protobuf:toolchain_type")),
332        str(Label("//rust:toolchain_type")),
333        "@bazel_tools//tools/cpp:toolchain_type",
334    ],
335    doc = """\
336Builds a Rust library crate from a set of `proto_library`s.
337
338Example:
339
340```python
341load("@rules_rust//proto/protobuf:defs.bzl", "rust_proto_library")
342
343proto_library(
344    name = "my_proto",
345    srcs = ["my.proto"]
346)
347
348rust_proto_library(
349    name = "rust",
350    deps = [":my_proto"],
351)
352
353rust_binary(
354    name = "my_proto_binary",
355    srcs = ["my_proto_binary.rs"],
356    deps = [":rust"],
357)
358```
359""",
360)
361
362def _rust_grpc_library_impl(ctx):
363    """The implementation of the `rust_grpc_library` rule
364
365    Args:
366        ctx (ctx): The rule's context object
367
368    Returns:
369        list: A list of providers. See `_rust_protogrpc_library_impl`
370    """
371    return _rust_protogrpc_library_impl(ctx, True)
372
373rust_grpc_library = rule(
374    implementation = _rust_grpc_library_impl,
375    attrs = {
376        "crate_name": attr.string(
377            doc = """\
378                Crate name to use for this target.
379
380                This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
381                Defaults to the target name, with any hyphens replaced by underscores.
382            """,
383        ),
384        "deps": attr.label_list(
385            doc = (
386                "List of proto_library dependencies that will be built. " +
387                "One crate for each proto_library will be created with the corresponding gRPC stubs."
388            ),
389            mandatory = True,
390            providers = [ProtoInfo],
391            aspects = [_rust_proto_aspect],
392        ),
393        "rust_deps": attr.label_list(
394            doc = "The crates the generated library depends on.",
395        ),
396        "rustc_flags": attr.string_list(
397            doc = """\
398                List of compiler flags passed to `rustc`.
399
400                These strings are subject to Make variable expansion for predefined
401                source/output path variables like `$location`, `$execpath`, and
402                `$rootpath`. This expansion is useful if you wish to pass a generated
403                file of arguments to rustc: `@$(location //package:target)`.
404            """,
405        ),
406        "_cc_toolchain": attr.label(
407            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
408        ),
409        "_optional_output_wrapper": attr.label(
410            executable = True,
411            cfg = "exec",
412            default = Label("//proto/protobuf:optional_output_wrapper"),
413        ),
414        "_process_wrapper": attr.label(
415            default = Label("//util/process_wrapper"),
416            executable = True,
417            allow_single_file = True,
418            cfg = "exec",
419        ),
420    },
421    fragments = ["cpp"],
422    toolchains = [
423        str(Label("//proto/protobuf:toolchain_type")),
424        str(Label("//rust:toolchain_type")),
425        "@bazel_tools//tools/cpp:toolchain_type",
426    ],
427    doc = """\
428Builds a Rust library crate from a set of `proto_library`s suitable for gRPC.
429
430Example:
431
432```python
433load("@rules_rust//proto/protobuf:defs.bzl", "rust_grpc_library")
434
435proto_library(
436    name = "my_proto",
437    srcs = ["my.proto"]
438)
439
440rust_grpc_library(
441    name = "rust",
442    deps = [":my_proto"],
443)
444
445rust_binary(
446    name = "my_service",
447    srcs = ["my_service.rs"],
448    deps = [":rust"],
449)
450```
451""",
452)
453